# 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:**
```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)
