nexus/.planning/phases/01-foundation/01-RESEARCH.md
Mikkel Georgsen 6c4272ce85 [nexus] chore: migrate .planning/ from agent repo to nexus repo
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>
2026-04-04 03:55:42 +00:00

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

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/: 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]"`
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.yamlpackages/* 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)