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>
391 lines
20 KiB
Markdown
391 lines
20 KiB
Markdown
# 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)
|