# 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 (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. ## 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) | ## 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:** ```json { "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:** ```typescript // 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:** ```sh #!/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:** ```bash git config rerere.enabled true # Optionally persist rr-cache across clones: git config rerere.autoupdate true ``` **git range-diff usage for rebase verification:** ```bash # 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/`**: `shared` is an upstream package. Nexus strings in `shared` create merge conflicts every upstream rebase. Keep Nexus vocabulary isolated in `packages/branding/`. - **Using `prepare-commit-msg` hook instead of `commit-msg`**: `prepare-commit-msg` runs before the editor opens and would silently prepend the prefix. `commit-msg` runs 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 skip `Merge branch/pull request/remote-tracking` first lines. - **Writing the zone taxonomy without distinguishing stored values from code identifiers**: The critical insight is that `"ceo"` appears in both `AGENT_ROLES` (a stored enum) AND in `AGENT_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\]"` | Single-concern check; complexity invites bugs | | 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) ```json { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "rootDir": "src" }, "include": ["src"] } ``` ### pnpm workspace:* dependency reference (used in packages/db) ```json { "dependencies": { "@paperclipai/branding": "workspace:*" } } ``` This resolves to the local `packages/branding/` package during development. ### git rerere configuration ```bash 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 ```bash # 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 `.sample` files present; no active hooks - `git range-diff --help` confirmed available on git 2.39.5 ### Secondary (MEDIUM confidence) - `git config rerere.enabled` confirmed 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)