nexus/.planning/codebase/CONCERNS.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

342 lines
22 KiB
Markdown

# Codebase Concerns — Paperclip Fork (Nexus)
**Analysis Date:** 2026-03-30
**Source repo:** `/Volumes/UsbNvme/repos/nexus`
**Fork goal:** Rename company→project, CEO→Project Manager, Board→Owner. UI overhaul, onboarding redesign, directory restructure.
---
## Terminology Embedding Depth
### "company" — Pervasive at All Layers
The word `company` is not a UI display string. It is a core identifier embedded at every layer of the stack.
**Database schema — DO NOT rename columns:**
- `packages/db/src/schema/companies.ts` — table `companies`, all columns use `company_id`
- `packages/db/src/schema/agents.ts``company_id` FK column on every agent
- `packages/db/src/schema/approvals.ts``company_id` column
- `packages/db/src/schema/company_memberships.ts` — table name + `company_id`
- `packages/db/src/schema/goals.ts``company_id` column; `level` field has value `"company"` as a constant
- `packages/db/src/schema/company_logos.ts`, `company_secrets.ts`, `company_skills.ts` — table names with `company_` prefix
- 47 migration SQL files in `packages/db/src/migrations/` reference `company_id` columns — renaming is impossible without new migrations and data migration
**TypeScript types — must be renamed carefully:**
- `packages/shared/src/types/company.ts` — exports `interface Company`
- `packages/shared/src/types/company-portability.ts` — ~15 exported interfaces all named `CompanyPortability*`
- `packages/shared/src/types/agent.ts``companyId` field on `Agent`, `AgentInstructionsBundle`, `AgentAccessState`
- `packages/shared/src/constants.ts``COMPANY_STATUSES`, `BUDGET_SCOPE_TYPES` contains `"company"` string, `GOAL_LEVELS` contains `"company"` string, `PLUGIN_CAPABILITIES` contains `"companies.read"`, `PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS` contains `"company"` and `"companies"`, plugin event types include `"company.created"` and `"company.updated"`
**API routes — affect URL shape:**
- `server/src/routes/companies.ts``/api/companies/:companyId` prefix for all company operations
- `server/src/routes/*.ts` — almost every route file takes `/:companyId` in the path
- `packages/shared/src/api.ts``API.companies = "/api/companies"` constant drives all frontend API calls
- `ui/src/api/companies.ts` — all company API calls
- `ui/src/context/CompanyContext.tsx``CompanyContext`, `CompanyProvider`, `useCompany`, `createCompany`; also uses `localStorage.setItem("paperclip.selectedCompanyId", ...)` as a persisted key
**UI components:**
- `ui/src/components/CompanyRail.tsx`, `CompanySwitcher.tsx`, `CompanyPatternIcon.tsx`
- `ui/src/pages/Companies.tsx`, `CompanySettings.tsx`, `CompanySkills.tsx`, `CompanyExport.tsx`, `CompanyImport.tsx`
- `ui/src/hooks/useCompanyPageMemory.ts`
- `ui/src/lib/company-routes.ts` — routing helpers named `BOARD_ROUTE_ROOTS`, `extractCompanyPrefixFromPath`, `normalizeCompanyPrefix`
- `ui/src/lib/company-export-selection.ts`, `company-portability-sidebar.ts`, `company-page-memory.ts`
**CLI — user-visible command names:**
- `cli/src/commands/client/company.ts` — CLI commands named `company import`, `company export`, etc.
- `cli/src/__tests__/company.test.ts`, `company-delete.test.ts`, `company-import-*.test.ts`
- `cli/src/index.ts` — registers company sub-commands on the CLI tree
**Server services:**
- `server/src/services/companies.ts``companyService()`
- `server/src/services/company-portability.ts`, `company-skills.ts`, `company-export-readme.ts`
**Impact:** Renaming `company` to `project` in code will conflict with the existing `projects` concept (there is already a `Project` entity distinct from `Company`). This is the single highest-risk rename.
---
### "CEO" — In Code, Not Just UI
**Constants (breaking if changed):**
- `packages/shared/src/constants.ts` line 38: `AGENT_ROLES` array contains `"ceo"` as a string literal
- `packages/shared/src/constants.ts` line 53: `AGENT_ROLE_LABELS` maps `ceo: "CEO"`
- `packages/shared/src/constants.ts` line 332: `INVITE_TYPES` contains `"bootstrap_ceo"`
- `packages/shared/src/constants.ts` line 187: `APPROVAL_TYPES` contains `"approve_ceo_strategy"`
- `packages/shared/src/types/agent.ts``taskAssignSource` field has literal value `"ceo_role"`
**CLI commands:**
- `cli/src/commands/auth-bootstrap-ceo.ts` — exported function `bootstrapCeoInvite`
- `cli/src/index.ts` line 146: `.command("bootstrap-ceo")` — this is a user-typed command
- `cli/src/commands/onboard.ts` — calls `bootstrapCeoInvite`, displays "Generating bootstrap CEO invite" in terminal output, generates invite URL with path `/invite/...` and message "Created bootstrap CEO invite"
- `cli/src/commands/run.ts` — imports `bootstrapCeoInvite`
**Server services:**
- `server/src/services/default-agent-instructions.ts``resolveDefaultAgentInstructionsBundleRole()` returns `"ceo"` for the ceo role; `DEFAULT_AGENT_BUNDLE_FILES` has `ceo:` key; reads from `onboarding-assets/ceo/` directory
- `server/src/services/approvals.ts` lines 112, 179 — checks `type === "hire_agent"` (not CEO specifically but linked)
**Onboarding assets — must be rewritten:**
- `server/src/onboarding-assets/ceo/SOUL.md` — "You are the CEO." throughout; contains `"board"`, `"hire"`, `"fire"` language extensively
- `server/src/onboarding-assets/ceo/AGENTS.md` — "You are the CEO. Your job is to lead the company..."; references board, hire, fire, CTO, CMO, delegates using `paperclip-create-agent` skill
- `server/src/onboarding-assets/ceo/HEARTBEAT.md`, `TOOLS.md` — same corpus
- `server/src/onboarding-assets/default/AGENTS.md` — likely also contains company/board references
**UI:**
- `ui/src/components/OnboardingWizard.tsx` line 114: `agentName` default is `"CEO"`
- `ui/src/components/OnboardingWizard.tsx` line 71: `DEFAULT_TASK_DESCRIPTION` starts with `"You are the CEO. You set the direction for the company."` and contains `"hire a founding engineer"`
- `ui/src/components/ApprovalPayload.tsx` lines 5-6: `approve_ceo_strategy: "CEO Strategy"` in display map; line 21: `approve_ceo_strategy: Lightbulb` in icon map
- `ui/src/pages/App.tsx` line 62: shows `pnpm paperclipai auth bootstrap-ceo` as UI copy
- `ui/src/pages/InviteLanding.tsx` — checks `invite.inviteType === "bootstrap_ceo"` in 5 places; displays "Bootstrap your Paperclip instance"
**Database values (stored in rows):**
- The `role` column on the `agents` table can contain `"ceo"`. Any existing databases have `"ceo"` stored as a role value. A rename would require a data migration.
- The `invites.invite_type` column stores `"bootstrap_ceo"` as a string value.
- The `approvals.type` column stores `"approve_ceo_strategy"` as a string value.
---
### "Board" — Auth System Identity
**Board = the human operator role.** This is deeply baked into the auth and API key system.
**Service layer:**
- `server/src/services/board-auth.ts``boardAuthService()`, `createBoardApiToken()` returns tokens prefixed `pcp_board_...`, `touchBoardApiKey()`, `revokeBoardApiKey()`, `resolveBoardAccess()`, `resolveBoardActivityCompanyIds()`
- `server/src/middleware/board-mutation-guard.ts` — middleware that guards write operations by board users
- `server/src/board-claim.ts` — dedicated board claim flow
**Database schema:**
- `packages/db/src/schema/board_api_keys.ts` — table named `board_api_keys`
- `packages/db/src/schema/cli_auth_challenges.ts` — column `requested_access` stores `"board"` as a value
**Token prefixes (stored in DB, shared with CLI):**
- `server/src/services/board-auth.ts` line 30: `pcp_board_${...}` — tokens with this prefix are stored in the DB and used by the CLI
- `cli/src/client/board-auth.ts` — CLI-side board authentication
**Constants:**
- `packages/db/src/schema/companies.ts` line 16: `requireBoardApprovalForNewAgents` column
- `packages/shared/src/types/company.ts`: `requireBoardApprovalForNewAgents` field on `Company`
- `packages/shared/src/types/company-portability.ts`: `requireBoardApprovalForNewAgents` in the manifest type
**UI:**
- `ui/src/pages/BoardClaim.tsx` — page for claiming board access
- `ui/src/lib/company-routes.ts` line 1: `BOARD_ROUTE_ROOTS` set used for routing logic; the name encodes the concept
**Impact:** Renaming `board` to `owner` requires changing token prefixes (`pcp_board_``pcp_owner_`), the `board_api_keys` DB table name (requires migration), and all auth middleware. Token prefix changes break existing issued tokens — any users with `pcp_board_*` tokens will be logged out.
---
### "hire" / "fire" — In Approval Types and Agent Instructions
**Code-level occurrences:**
- `packages/shared/src/constants.ts` line 187: `APPROVAL_TYPES` contains `"hire_agent"` — stored in DB `approvals.type` column
- `server/src/services/hire-hook.ts` — entire file named after hire; exports `notifyHireApproved`, uses `HireApprovedPayload` type from `packages/adapter-utils/src/types.ts`
- `packages/adapter-utils/src/types.ts` line 213: `HireApprovedPayload` interface; line 277: `onHireApproved` lifecycle hook on `ServerAdapterModule`
- `server/src/routes/agents.ts` line 1228: creates `type: "hire_agent"` approval
- `server/src/routes/approvals.ts` line 66, 281: checks `type === "hire_agent"`
- `cli/src/commands/client/approval.ts` line 114: CLI option says `hire_agent|approve_ceo_strategy`
- `ui/src/components/ApprovalPayload.tsx` line 5: `hire_agent: "Hire Agent"` in label map
- `ui/src/components/ApprovalPayload.tsx` line 131: branching on `type === "hire_agent"`
**Agent instruction content:**
- `server/src/onboarding-assets/ceo/SOUL.md` line 15: "Hire slow, fire fast"
- `server/src/onboarding-assets/ceo/AGENTS.md` line 6: "Hire new agents when the team needs capacity"
- `server/src/onboarding-assets/ceo/AGENTS.md` line 16: delegates via `paperclip-create-agent` skill with instruction to hire
- `skills/paperclip-create-agent/` skill is entirely named around hiring
**Stored DB values:** `hire_agent` appears in the `approvals.type` column. Existing rows cannot be silently changed. A rename requires either a data migration or keeping the stored value while changing the label only.
---
## Data Directory Concerns
### `~/.paperclip` — The Home Directory
**All path roots use the name "paperclip":**
- `server/src/home-paths.ts` line 18: `path.resolve(os.homedir(), ".paperclip")` — default home dir
- `server/src/home-paths.ts` — exported functions: `resolvePaperclipHomeDir()`, `resolvePaperclipInstanceId()`, `resolvePaperclipInstanceRoot()`
- `cli/src/config/home.ts` line 10: same `.paperclip` default
- `server/src/paths.ts` line 13: looks for `.paperclip/config.json` when searching ancestor directories for config
- `cli/src/config/store.ts` line 16: same ancestor search for `.paperclip/config.json`
- `cli/src/client/context.ts` line 25: looks for `.paperclip/context.json`
**Environment variable names:**
- `PAPERCLIP_HOME` — overrides the home directory
- `PAPERCLIP_INSTANCE_ID` — overrides instance ID
- `PAPERCLIP_CONFIG` — overrides config path
- `PAPERCLIP_AGENT_JWT_SECRET` — agent auth secret
- `PAPERCLIP_PUBLIC_URL`, `PAPERCLIP_DEPLOYMENT_MODE`, `PAPERCLIP_DEPLOYMENT_EXPOSURE`, `PAPERCLIP_ALLOWED_HOSTNAMES`, `PAPERCLIP_AUTH_*`, `PAPERCLIP_STORAGE_*`, `PAPERCLIP_SECRETS_*`
- These appear in ~25+ places across `cli/src/commands/onboard.ts`, `server/src/home-paths.ts`, `server/src/startup-banner.ts`, `server/src/ui-branding.ts`, Docker files
**Impact:** Renaming `~/.paperclip` to `~/.nexus` requires changing:
1. The default string in `server/src/home-paths.ts` and `cli/src/config/home.ts`
2. All `PAPERCLIP_*` env var names (breaking change for existing deployments)
3. Docker config `PAPERCLIP_HOME: "/home/reviewer/.paperclip-review"` in `docker-compose.untrusted-review.yml`
4. Shell scripts `scripts/provision-worktree.sh` and `scripts/backup-db.sh`
5. Docs in `doc/DATABASE.md`, `doc/DOCKER.md`, `doc/SPEC-implementation.md`
6. The `.paperclip/config.json` ancestor-search path logic
---
### "companies" Subdirectory in Instance Root
- `server/src/services/agent-instructions.ts` line 135: agent instructions are stored at `~/.paperclip/instances/<id>/companies/<companyId>/agents/<agentId>/instructions/`
- `server/src/services/company-skills.ts` line 1282: skills stored at `~/.paperclip/instances/<id>/skills/<companyId>`
- `server/src/home-paths.ts` line 85: managed project workspaces stored at `~/.paperclip/instances/<id>/projects/<companyId>/<projectId>/<repoName>/`
These paths are embedded in the filesystem on any existing deployment. Renaming `companies` in the path would break existing agent instruction directories unless a migration script is provided.
---
### `.paperclip.yaml` Portability File
- `server/src/services/company-portability.ts` — the export format emits `.paperclip.yaml` as a sidecar file
- `cli/src/commands/client/company.ts` lines 183, 189-190: CLI detects and processes `.paperclip.yaml` and `.paperclip.yml`
- `ui/src/pages/CompanyExport.tsx` lines 75, 778-785: UI references `.paperclip.yaml`
- The file format is documented in `docs/companies/companies-spec.md` under the `schema: paperclip/v1` header
**Impact:** Any exported company bundles from the upstream will have `.paperclip.yaml` files. If Nexus renames to `.nexus.yaml`, these files become incompatible. Either keep reading `.paperclip.yaml` (and emit `.nexus.yaml`) or accept breaking import compatibility.
---
## "paperclip" Brand in Package Names and Token Prefixes
**npm package names:**
- `cli/package.json`: `"name": "paperclipai"` — the CLI binary is named `paperclipai`
- `packages/shared/package.json`: `"name": "@paperclipai/shared"`
- `packages/db/package.json`: `"name": "@paperclipai/db"`
- `packages/adapter-utils/package.json`: `"name": "@paperclipai/adapter-utils"`
- All internal imports use `@paperclipai/*` throughout the entire monorepo
**Impact:** Renaming packages requires updating every `import` statement in the codebase (thousands of occurrences). The `pnpm-workspace.yaml` and all `package.json` dependency declarations must also change. This is purely mechanical but extremely high volume.
**API token prefixes:**
- `pcp_board_*` — board API keys stored in DB
- `pcp_bootstrap_*` — CEO bootstrap invite tokens stored in DB
- `pcp_cli_auth_*` — CLI auth challenge tokens stored in DB
These are stored as values in the database. If renamed, existing tokens become invalid unless the server accepts both prefixes.
**CLI binary name:**
- `package.json` script: `"paperclipai": "node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts"`
- CLI displays `paperclipai onboard`, `paperclipai run`, `paperclipai auth bootstrap-ceo`
- `ui/src/App.tsx` renders `pnpm paperclipai auth bootstrap-ceo` as literal user-facing instruction
- `server/src/startup-banner.ts` line 96: `run \`pnpm paperclipai onboard\`` as warning text
---
## Onboarding Flow — High Complexity Change
### UI Onboarding Wizard
The onboarding wizard (`ui/src/components/OnboardingWizard.tsx`) is the primary first-run experience. It is deeply coupled to the corporate metaphor:
- Step 1 prompts for "company name" and "company goal" — variables named `companyName`, `companyGoal`
- Step 2 creates the first agent with default name `"CEO"`, default description: *"You are the CEO. You set the direction for the company. - hire a founding engineer - write a hiring plan..."*
- Step 3 creates the first task with `taskTitle` defaulting to `"Hire your first engineer and create a hiring plan"`
- The entire flow uses `companiesApi.create()` which calls `/api/companies`
- `ui/src/lib/onboarding-launch.ts``ONBOARDING_PROJECT_NAME = "Onboarding"` and `selectDefaultCompanyGoalId()` filters `goal.level === "company"`
**The onboarding wizard must be substantially rewritten** to replace company creation with project creation (given company maps to project in Nexus). However, since the DB entity is still `company`, this is a UI-layer rename only — the API calls still go to `/api/companies`.
### CLI Onboarding
`cli/src/commands/onboard.ts` is the other half of onboarding:
- Displays banner: `p.intro(pc.bgCyan(pc.black(" paperclipai onboard ")))`
- Calls `bootstrapCeoInvite` on completion
- Displays "Start Paperclip now?" prompt
- Generates next-steps text: `paperclipai run`, `paperclipai configure`, `paperclipai doctor`
All terminal strings that reference `paperclipai` and `Paperclip` are hardcoded — there is no i18n or constant layer for branding strings.
---
## Schema References That Should NOT Change
Per the fork PRD, the database schema must stay compatible with upstream. The following identifiers should be left as-is in the database layer, treating them as opaque internal keys:
- Table names: `companies`, `company_memberships`, `company_secrets`, `company_skills`, `company_logos`, `board_api_keys`
- Column names: all `company_id` foreign keys
- Stored enum values: `"ceo"` in `agents.role`, `"hire_agent"` and `"approve_ceo_strategy"` in `approvals.type`, `"bootstrap_ceo"` in `invites.invite_type`, `"company"` in `goals.level`, `"board"` in `cli_auth_challenges.requested_access`
These values exist in the running DB and in migration history. Any rename here requires new migration SQL + data migration.
---
## Hardcoded Strings vs Constants
There is NO i18n or centralized string table for user-facing copy. All UI labels are inline JSX strings or TypeScript constants.
**Somewhat centralised (easier to change):**
- `packages/shared/src/constants.ts``AGENT_ROLE_LABELS` maps roles to display strings; changing `ceo: "CEO"` here propagates to wherever the label map is used
- `packages/shared/src/api.ts``API.companies` constant drives all frontend API path construction
- `ui/src/components/ApprovalPayload.tsx` lines 5-6 — `hire_agent: "Hire Agent"`, `approve_ceo_strategy: "CEO Strategy"` are display-only labels
**Fully hardcoded (harder to change):**
- All terminal output in `cli/src/commands/onboard.ts` — every `p.log.*` call is a literal string
- `server/src/startup-banner.ts` — the ASCII art says "PAPERCLIP", `resolveAgentJwtSecretStatus` message references `pnpm paperclipai onboard`
- `ui/src/components/OnboardingWizard.tsx``DEFAULT_TASK_DESCRIPTION`, `taskTitle` default, `agentName` default are all hardcoded literals
- `server/src/onboarding-assets/ceo/SOUL.md` and `AGENTS.md` — plain Markdown prose
---
## Plugin System Concerns
The plugin API surface exposes company-centric types to third-party plugins:
- `packages/shared/src/constants.ts``PLUGIN_CAPABILITIES` includes `"companies.read"` — this is a capability string that plugins declare in their manifests; changing it breaks all plugins that declare this capability
- `packages/shared/src/constants.ts``PLUGIN_EVENT_TYPES` includes `"company.created"` and `"company.updated"` — changing these breaks plugin event subscriptions
- `packages/shared/src/constants.ts``PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS` includes `"company"` and `"companies"` — changing this affects URL routing validation
- `packages/shared/src/constants.ts``PLUGIN_STATE_SCOPE_KINDS` includes `"company"` — plugin state scoped to a company would break
Any fork that changes these strings is breaking the plugin API contract. If Nexus wants to maintain upstream plugin compatibility, these must remain unchanged.
---
## Upstream Sync Risk
If this fork intends to periodically merge upstream Paperclip changes:
- Any rename of package names (`@paperclipai/*``@nexusai/*`) will cause merge conflicts on every upstream file that imports those packages — this is nearly every file
- Renames of `company` to `project` in service/route files will conflict heavily with upstream changes to the `companies.ts` service
- Renamed function names (`bootstrapCeoInvite`, `companyService`, `boardAuthService`) will not patch-merge cleanly
- The `server/src/onboarding-assets/ceo/` directory name, if renamed, creates a merge conflict on any upstream changes to those files
**Recommendation:** If upstream sync is a requirement, keep all code-level identifiers unchanged and do only display-layer (UI string) renames. Make a clear boundary between "DB/code identity" (unchanged) and "display vocabulary" (renamed).
---
## Test Coverage Gaps for Fork Changes
**Untested areas relevant to the fork:**
- The `DEFAULT_TASK_DESCRIPTION` and default `agentName = "CEO"` in `OnboardingWizard.tsx` have no unit tests — changing them is safe but unverified
- `server/src/onboarding-assets/ceo/` content is loaded at runtime via `fs.readFile` in `default-agent-instructions.ts` — no test validates the file content structure, only the loading mechanism
- CLI terminal output strings (`p.log.*` calls in `onboard.ts`) are not covered by automated tests — manual smoke tests in `tests/release-smoke/` cover the auth flow but not every string
**Covered by tests (risky to change):**
- `cli/src/__tests__/board-auth.test.ts` — tests the board auth flow including token prefix behavior
- `server/src/__tests__/hire-hook.test.ts` — tests the hire approval hook
- `server/src/__tests__/invite-onboarding-text.test.ts` — likely tests invite text containing "CEO"; check before renaming
- `server/src/__tests__/company-branding-route.test.ts`, `company-portability.test.ts`, `company-portability-routes.test.ts` — all test company-named routes; renaming these routes breaks these tests
---
## Summary Risk Table
| Area | Risk | Breaking Change |
|------|------|----------------|
| DB table/column names (`companies`, `company_id`) | **Critical** | Yes — requires migration |
| Stored enum values (`"ceo"`, `"hire_agent"`, `"bootstrap_ceo"`) | **Critical** | Yes — data migration |
| `pcp_board_*` token prefix | **High** | Yes — existing tokens invalidated |
| `@paperclipai/*` package names | **High** | Yes — breaks every import |
| `PAPERCLIP_*` env var names | **High** | Yes — breaks all existing deployments |
| `~/.paperclip` home dir | **High** | Yes — breaks existing data paths |
| `companies/` subdir in instance root | **High** | Yes — breaks existing instruction files |
| CLI binary name `paperclipai` | **Medium** | Yes — users must relearn commands |
| `bootstrap-ceo` CLI subcommand | **Medium** | Yes — changes user-facing command |
| `company.created` plugin event types | **Medium** | Yes — breaks third-party plugins |
| `.paperclip.yaml` export format | **Medium** | Yes — breaks import of upstream bundles |
| UI display strings ("company", "CEO", "board") | **Low** | No — display only |
| `OnboardingWizard` default task/agent text | **Low** | No — display only |
| Onboarding asset prose (SOUL.md, AGENTS.md) | **Low** | No — content only |
---
*Concerns audit: 2026-03-30*