Planning artifacts (milestones v1.0-v1.2.1, v1.3 queue, PROJECT.md, STATE.md, config) now live alongside the code they describe. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
20 KiB
Phase 1: Foundation - Research
Researched: 2026-03-30 Domain: pnpm monorepo package scaffolding, git hooks, git rerere, zone taxonomy design Confidence: HIGH
Summary
Phase 1 creates the scaffolding for the entire Nexus fork — no upstream files are touched. The work is four bounded tasks: (1) add a new packages/branding/ workspace package exporting a VOCAB constant; (2) write a zone taxonomy document classifying all rename targets; (3) install a commit-msg git hook enforcing the [nexus] prefix; (4) enable git rerere and write a rebase runbook.
All four tasks are new-file creation or git configuration — zero changes to upstream source. The package structure pattern is well established in this monorepo (see packages/shared/ and packages/adapter-utils/). The commit-msg hook is a simple shell script that already has a sample at .git/hooks/commit-msg.sample. Git rerere is a single config flag.
Primary recommendation: Follow the packages/shared/ package pattern exactly for the branding package. It is the simplest non-dependency package in the monorepo and is already consumed by all layers (server, ui, cli).
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
All implementation choices are at Claude's discretion — discuss phase was skipped per user setting. Use ROADMAP phase goal, success criteria, and codebase conventions to guide decisions.
Claude's Discretion
All implementation choices are at Claude's discretion.
Deferred Ideas (OUT OF SCOPE)
None — discuss phase skipped. </user_constraints>
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| FOUND-01 | Branding package (packages/branding/) exists with all fork-specific display strings centralized |
New package follows packages/shared/ pattern; packages/* glob already in pnpm-workspace.yaml |
| FOUND-02 | Zone taxonomy document classifies every rename target as display (safe), code (don't touch), or stored (don't touch) | Full audit of rename targets documented below under Rename Target Inventory |
| FOUND-03 | All fork commits use [nexus] prefix for upstream rebase visibility |
commit-msg hook in .git/hooks/ — no external tooling needed |
| FOUND-04 | git rerere enabled and git range-diff documented for rebase workflow |
Single git config rerere.enabled true call; git range-diff is available (git 2.39.5) |
| </phase_requirements> |
Standard Stack
Core
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| TypeScript | 5.7.3 | Package source language | All packages in the monorepo use TypeScript |
| pnpm workspaces | 9.15.4 | Monorepo package management | Pinned in root package.json packageManager field |
| vitest | ^3.0.5 | Test runner | Already configured at root and per-package |
Supporting
| Library | Version | Purpose | When to Use |
|---|---|---|---|
| (none) | — | Branding package has no runtime dependencies | Package only exports plain TypeScript objects |
Alternatives Considered
| Instead of | Could Use | Tradeoff |
|---|---|---|
Manual .git/hooks/ script |
husky / lefthook | Husky/lefthook add npm dependencies and pnpm install step; manual hook is zero-dependency and immediately installs |
packages/branding/ new package |
Adding to packages/shared/ |
Separate package keeps Nexus-specific strings isolated from upstream shared — clean rebase surface |
Installation: No new npm packages. The branding package is a zero-dependency workspace member.
Architecture Patterns
Recommended Project Structure — branding package
packages/branding/
├── package.json # @paperclipai/branding (workspace:*)
├── tsconfig.json # extends ../../tsconfig.base.json
└── src/
├── index.ts # re-exports VOCAB
└── vocab.ts # defines VOCAB constant
Pattern 1: Minimal Workspace Package (matches packages/shared and packages/adapter-utils)
What: A package.json with "type": "module", "exports": { ".": "./src/index.ts" } for dev-time resolution, and publishConfig with compiled dist paths for production. TypeScript source in src/, tsconfig.json extending ../../tsconfig.base.json.
When to use: Any new zero-dependency utility package in the monorepo.
Example:
{
"name": "@paperclipai/branding",
"version": "0.1.0",
"license": "MIT",
"type": "module",
"exports": {
".": "./src/index.ts",
"./*": "./src/*.ts"
},
"publishConfig": {
"access": "public",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"files": ["dist"],
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.7.3"
}
}
Key detail: The pnpm-workspace.yaml already includes packages/* as a glob — no changes to the workspace manifest are needed when adding packages/branding/.
Pattern 2: VOCAB Constant Shape
What: A typed record mapping semantic vocabulary keys to Nexus display strings. Consumed as import { VOCAB } from "@paperclipai/branding".
When to use: Any time a downstream component needs a display string that differs from the upstream Paperclip value.
Example:
// packages/branding/src/vocab.ts
export const VOCAB = {
// Entity renames (display only)
company: "Workspace",
companies: "Workspaces",
ceo: "Project Manager",
board: "Owner",
hire: "Add",
fire: "Remove",
// Brand name
appName: "Nexus",
tagline: "Open-source orchestration for your agents",
} as const;
export type VocabKey = keyof typeof VOCAB;
Why as const: Provides literal type inference — downstream callers get typeof VOCAB.company as "Workspace" not string, enabling type-safe display string consumption.
Pattern 3: commit-msg Git Hook
What: A shell script at .git/hooks/commit-msg that reads the commit message file and rejects commits whose first line does not start with [nexus].
When to use: Every commit to the nexus fork repo.
Example:
#!/bin/sh
# Nexus fork: enforce [nexus] prefix on all fork commits
# Allows upstream merge commits (no prefix check needed for those)
MSG_FILE="$1"
FIRST_LINE=$(head -1 "$MSG_FILE")
# Skip merge commits (git generates these automatically)
if echo "$FIRST_LINE" | grep -qE "^Merge (branch|pull request|remote-tracking)"; then
exit 0
fi
if ! echo "$FIRST_LINE" | grep -qE "^\[nexus\]"; then
echo "ERROR: Commit message must start with [nexus]"
echo " Got: $FIRST_LINE"
echo " Example: [nexus] feat: add branding package"
exit 1
fi
Installation: chmod +x .git/hooks/commit-msg
Important: .git/hooks/ is not tracked by git. The hook must be (re-)installed after a fresh clone. Document this in the runbook.
Pattern 4: git rerere + range-diff Rebase Workflow
What: git rerere (reuse recorded resolution) records conflict resolutions so that when the same conflict recurs after a rebase, git applies the saved resolution automatically.
Configuration:
git config rerere.enabled true
# Optionally persist rr-cache across clones:
git config rerere.autoupdate true
git range-diff usage for rebase verification:
# After rebasing nexus commits on top of new upstream/master:
# Verify the rebase did not silently mangle any nexus commit
git range-diff upstream/master nexus-before-rebase nexus-after-rebase
# One-liner for routine check after each rebase:
# OLD_TIP = SHA before rebase, recorded manually or via ORIG_HEAD
git range-diff upstream/master ORIG_HEAD HEAD
Important: git range-diff is available in git 2.39.5 (confirmed on this machine). No installation needed.
Anti-Patterns to Avoid
- Putting VOCAB into
packages/shared/:sharedis an upstream package. Nexus strings insharedcreate merge conflicts every upstream rebase. Keep Nexus vocabulary isolated inpackages/branding/. - Using
prepare-commit-msghook instead ofcommit-msg:prepare-commit-msgruns before the editor opens and would silently prepend the prefix.commit-msgruns after — it validates the author's actual intent and rejects rather than auto-modifying. - Enforcing
[nexus]on merge commits: Upstream rebase produces merge commits without the prefix. The hook must skipMerge branch/pull request/remote-trackingfirst lines. - Writing the zone taxonomy without distinguishing stored values from code identifiers: The critical insight is that
"ceo"appears in bothAGENT_ROLES(a stored enum) AND inAGENT_ROLE_LABELS(a display label). The zone taxonomy must classify each occurrence independently.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| Commit prefix enforcement | Complex regex validator | Simple `head -1 | grep -qE "^[nexus]"` |
| Conflict re-resolution | Manual merge resolution on every rebase | git rerere |
Git records and replays resolutions automatically |
| Package linking | Symlinks or path aliases | pnpm workspace:* protocol |
Already used by all packages in the monorepo; packages/* glob already active |
Key insight: All tooling for this phase (git hooks, rerere, workspace packages) is built into git and pnpm. Zero new dependencies.
Rename Target Inventory
This section is the research input for FOUND-02 (zone taxonomy document). It audits every term that needs reclassification so the taxonomy document can be written with verified data.
Terms Requiring Classification
| Term | Occurrences | Zone | Rationale |
|---|---|---|---|
company (TypeScript identifier, variable, function name) |
companyService, companyId, selectedCompanyId, route handlers, etc. |
CODE — do not touch | Renaming would cause merge conflicts across hundreds of files |
company (DB column/table name) |
companies table, company_id FK columns |
STORED — do not touch | Changing requires a DB migration; upstream sync breaks |
"Company" (UI display string in JSX) |
~16 UI files (based on grep audit) | DISPLAY — safe to change | Pure string, no code coupling |
company (API route segment /api/companies) |
Route paths | CODE — do not touch | Upstream sync priority; client and server must match |
COMPANY_STATUSES / CompanyStatus (TypeScript type) |
packages/shared/src/constants.ts |
CODE — do not touch | Upstream shared type; changing breaks plugin API contract |
AGENT_ROLES enum value "ceo" |
packages/shared/src/constants.ts |
STORED — do not touch | Stored in DB agent_role column; changing invalidates existing rows |
AGENT_ROLE_LABELS.ceo value "CEO" |
packages/shared/src/constants.ts |
DISPLAY — safe to change | String value only; key ceo stays unchanged |
"CEO" in UI text |
ui/src/components/agent-config-primitives.tsx, AgentProperties.tsx, etc. |
DISPLAY — safe to change | String literals consumed via AGENT_ROLE_LABELS |
"Board" in UI text |
Various UI files | DISPLAY — safe to change | Display string; board identifier in code stays |
board_api_keys table / board actor type |
DB schema, auth code | CODE/STORED — do not touch | Auth token format and DB schema |
"Hire" / "Fire" in UI button text |
UI dialogs | DISPLAY — safe to change | Button label strings |
hire_agent approval type (stored enum) |
packages/shared/src/constants.ts APPROVAL_TYPES |
STORED — do not touch | Stored in DB; changing invalidates existing approvals |
"PAPERCLIP" ASCII art in startup banner |
server/src/startup-banner.ts, cli/src/utils/banner.ts |
DISPLAY — safe to change | String constants in display functions |
PAPERCLIP_* env vars (PAPERCLIP_HOME, etc.) |
Throughout server/cli config | CODE — do not touch | Changing breaks existing deployments; upstream sync |
@paperclipai/* package names |
All package.json files |
CODE — do not touch | Import statements throughout monorepo; nuclear merge surface |
paperclip.ing URL references |
ui/src/pages/CompanyExport.tsx |
DISPLAY — safe to change | User-facing documentation URL |
"n" placeholder strings |
Various UI files (ui/src/pages/RoutineDetail.tsx, etc.) |
DISPLAY — safe to change | Already partially replaced; these are display messages |
approve_ceo_strategy approval type |
packages/shared/src/constants.ts |
STORED — do not touch | DB-stored enum value |
"bootstrap_ceo" invite type |
packages/shared/src/constants.ts |
STORED — do not touch | DB-stored enum value |
Zone Summary
| Zone | Count | Description |
|---|---|---|
| DISPLAY — safe | ~40 surface points | UI strings, banner text, button labels, doc URLs, tooltip text |
| CODE — do not touch | Many hundreds | TypeScript identifiers, function names, import paths, route segments, env var names |
| STORED — do not touch | ~8 enum values | DB-stored enum values (ceo, board, company_id, approval types, invite types) |
Common Pitfalls
Pitfall 1: Hook Not Executable
What goes wrong: The commit-msg hook file exists but has no execute permission — git silently ignores non-executable hooks.
Why it happens: Creating a file via Write or echo does not set +x.
How to avoid: Always chmod +x .git/hooks/commit-msg immediately after writing the file.
Warning signs: Commits without [nexus] prefix succeed without error.
Pitfall 2: Hook Not Re-installed After Fresh Clone
What goes wrong: .git/hooks/ is not tracked by git. After a fresh clone of the nexus repo, the hook is absent.
Why it happens: Git intentionally excludes hooks from tracking.
How to avoid: Document the hook installation command in the rebase runbook. Optionally add a scripts/install-hooks.sh that the runbook references.
Pitfall 3: rerere Cache Not Shared
What goes wrong: rerere cache lives in .git/rr-cache/ which is not committed. Resolutions are lost after a re-clone.
Why it happens: Git design — .git/ is local state.
How to avoid: For a solo-developer fork this is acceptable. Document in the runbook that rerere cache is machine-local. If re-cloning, conflict resolutions must be redone once.
Pitfall 4: VOCAB in packages/shared Causes Rebase Conflicts
What goes wrong: Adding Nexus-specific strings to packages/shared/src/constants.ts causes conflicts on every upstream rebase of that file.
Why it happens: constants.ts is actively maintained upstream with new constants added regularly.
How to avoid: All Nexus strings live exclusively in packages/branding/. Never modify packages/shared/ for Nexus vocabulary.
Pitfall 5: Zone Taxonomy Not Granular Enough
What goes wrong: Classifying "company" as DISPLAY-safe causes Phase 3 to accidentally rename TypeScript identifiers, which breaks type checking and causes hundreds of import errors.
Why it happens: The same word appears at all three layers (display, code, stored) with different meanings.
How to avoid: The taxonomy must classify at the occurrence level, not just the term level. Example: AGENT_ROLES[0] (stored enum value "ceo") is STORED-do-not-touch, while AGENT_ROLE_LABELS.ceo (display value "CEO") is DISPLAY-safe.
Code Examples
Verified patterns from official sources:
Workspace Package tsconfig.json (matches adapter-utils pattern)
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
pnpm workspace:* dependency reference (used in packages/db)
{
"dependencies": {
"@paperclipai/branding": "workspace:*"
}
}
This resolves to the local packages/branding/ package during development.
git rerere configuration
git config rerere.enabled true
git config rerere.autoupdate true
After enabling, rerere automatically records conflict resolutions. On subsequent conflicts at the same location (e.g., after another upstream rebase), git replays the saved resolution.
git range-diff rebase verification
# Record upstream branch state before rebase
UPSTREAM_BASE=$(git merge-base HEAD upstream/master)
# Perform rebase
git rebase upstream/master
# Verify no nexus commits were mangled
# ORIG_HEAD is set by git to the tip before the rebase
git range-diff upstream/master ORIG_HEAD HEAD
Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|---|---|---|---|---|
| git | FOUND-03 (hook), FOUND-04 (rerere, range-diff) | Yes | 2.39.5 | — |
| pnpm | FOUND-01 (workspace package) | Yes | 9.15.4 | — |
| Node.js | FOUND-01 (TypeScript compilation) | Yes | 25.8.2 | — |
| git range-diff | FOUND-04 | Yes | built into git 2.39.5 | — |
Missing dependencies with no fallback: None.
Missing dependencies with fallback: None.
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | vitest ^3.0.5 |
| Config file | packages/branding/vitest.config.ts (Wave 0 — does not yet exist) |
| Quick run command | pnpm vitest run --project packages/branding |
| Full suite command | pnpm test:run (root, runs all projects) |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| FOUND-01 | VOCAB exports all required keys with correct string values |
unit | pnpm vitest run --project packages/branding |
No — Wave 0 |
| FOUND-02 | Zone taxonomy doc exists at .planning/ZONE-TAXONOMY.md |
smoke (file check) | test -f .planning/ZONE-TAXONOMY.md && echo OK |
No |
| FOUND-03 | commit-msg hook rejects messages without [nexus] prefix |
manual (git hook invocation) | echo "bad message" | .git/hooks/commit-msg /dev/stdin; echo exit=$? |
No |
| FOUND-04 | git config rerere.enabled returns true |
smoke (git config check) | git -C /Volumes/UsbNvme/repos/nexus config --get rerere.enabled |
No |
Sampling Rate
- Per task commit: Run FOUND-01 unit test (
pnpm vitest run --project packages/branding) - Per wave merge:
pnpm test:run(full suite) - Phase gate: All four FOUND checks pass before
/gsd:verify-work
Wave 0 Gaps
packages/branding/src/vocab.test.ts— covers FOUND-01 (VOCAB constant shape and all key presence)packages/branding/vitest.config.ts— vitest project config- Hook script at
.git/hooks/commit-msg— covers FOUND-03 (manual test only)
Sources
Primary (HIGH confidence)
- Direct codebase inspection:
/Volumes/UsbNvme/repos/nexus/packages/shared/package.json— package pattern verified - Direct codebase inspection:
/Volumes/UsbNvme/repos/nexus/packages/adapter-utils/package.json— minimal package pattern verified - Direct codebase inspection:
/Volumes/UsbNvme/repos/nexus/pnpm-workspace.yaml—packages/*glob confirmed active - Direct codebase inspection:
/Volumes/UsbNvme/repos/nexus/packages/shared/src/constants.ts— all AGENT_ROLES, AGENT_ROLE_LABELS, APPROVAL_TYPES confirmed - Direct codebase inspection:
/Volumes/UsbNvme/repos/nexus/.git/hooks/— only.samplefiles present; no active hooks git range-diff --helpconfirmed available on git 2.39.5
Secondary (MEDIUM confidence)
git config rerere.enabledconfirmed not set; needs enabling
Tertiary (LOW confidence)
- None
Metadata
Confidence breakdown:
- Standard stack: HIGH — all packages verified against live codebase
- Architecture: HIGH — branding package follows verified monorepo pattern exactly
- Pitfalls: HIGH — derived from direct codebase audit and git documentation
Research date: 2026-03-30 Valid until: 2026-04-30 (stable codebase; upstream changes could affect rename targets)