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

22 KiB

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.tscompany_id FK column on every agent
  • packages/db/src/schema/approvals.tscompany_id column
  • packages/db/src/schema/company_memberships.ts — table name + company_id
  • packages/db/src/schema/goals.tscompany_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.tscompanyId field on Agent, AgentInstructionsBundle, AgentAccessState
  • packages/shared/src/constants.tsCOMPANY_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.tsAPI.companies = "/api/companies" constant drives all frontend API calls
  • ui/src/api/companies.ts — all company API calls
  • ui/src/context/CompanyContext.tsxCompanyContext, 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.tscompanyService()
  • 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.tstaskAssignSource 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.tsresolveDefaultAgentInstructionsBundleRole() 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.tsboardAuthService(), 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.tsONBOARDING_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.tsAGENT_ROLE_LABELS maps roles to display strings; changing ceo: "CEO" here propagates to wherever the label map is used
  • packages/shared/src/api.tsAPI.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.tsxDEFAULT_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.tsPLUGIN_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.tsPLUGIN_EVENT_TYPES includes "company.created" and "company.updated" — changing these breaks plugin event subscriptions
  • packages/shared/src/constants.tsPLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS includes "company" and "companies" — changing this affects URL routing validation
  • packages/shared/src/constants.tsPLUGIN_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