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>
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— tablecompanies, all columns usecompany_idpackages/db/src/schema/agents.ts—company_idFK column on every agentpackages/db/src/schema/approvals.ts—company_idcolumnpackages/db/src/schema/company_memberships.ts— table name +company_idpackages/db/src/schema/goals.ts—company_idcolumn;levelfield has value"company"as a constantpackages/db/src/schema/company_logos.ts,company_secrets.ts,company_skills.ts— table names withcompany_prefix- 47 migration SQL files in
packages/db/src/migrations/referencecompany_idcolumns — renaming is impossible without new migrations and data migration
TypeScript types — must be renamed carefully:
packages/shared/src/types/company.ts— exportsinterface Companypackages/shared/src/types/company-portability.ts— ~15 exported interfaces all namedCompanyPortability*packages/shared/src/types/agent.ts—companyIdfield onAgent,AgentInstructionsBundle,AgentAccessStatepackages/shared/src/constants.ts—COMPANY_STATUSES,BUDGET_SCOPE_TYPEScontains"company"string,GOAL_LEVELScontains"company"string,PLUGIN_CAPABILITIEScontains"companies.read",PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTScontains"company"and"companies", plugin event types include"company.created"and"company.updated"
API routes — affect URL shape:
server/src/routes/companies.ts—/api/companies/:companyIdprefix for all company operationsserver/src/routes/*.ts— almost every route file takes/:companyIdin the pathpackages/shared/src/api.ts—API.companies = "/api/companies"constant drives all frontend API callsui/src/api/companies.ts— all company API callsui/src/context/CompanyContext.tsx—CompanyContext,CompanyProvider,useCompany,createCompany; also useslocalStorage.setItem("paperclip.selectedCompanyId", ...)as a persisted key
UI components:
ui/src/components/CompanyRail.tsx,CompanySwitcher.tsx,CompanyPatternIcon.tsxui/src/pages/Companies.tsx,CompanySettings.tsx,CompanySkills.tsx,CompanyExport.tsx,CompanyImport.tsxui/src/hooks/useCompanyPageMemory.tsui/src/lib/company-routes.ts— routing helpers namedBOARD_ROUTE_ROOTS,extractCompanyPrefixFromPath,normalizeCompanyPrefixui/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 namedcompany import,company export, etc.cli/src/__tests__/company.test.ts,company-delete.test.ts,company-import-*.test.tscli/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.tsline 38:AGENT_ROLESarray contains"ceo"as a string literalpackages/shared/src/constants.tsline 53:AGENT_ROLE_LABELSmapsceo: "CEO"packages/shared/src/constants.tsline 332:INVITE_TYPEScontains"bootstrap_ceo"packages/shared/src/constants.tsline 187:APPROVAL_TYPEScontains"approve_ceo_strategy"packages/shared/src/types/agent.ts—taskAssignSourcefield has literal value"ceo_role"
CLI commands:
cli/src/commands/auth-bootstrap-ceo.ts— exported functionbootstrapCeoInvitecli/src/index.tsline 146:.command("bootstrap-ceo")— this is a user-typed commandcli/src/commands/onboard.ts— callsbootstrapCeoInvite, 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— importsbootstrapCeoInvite
Server services:
server/src/services/default-agent-instructions.ts—resolveDefaultAgentInstructionsBundleRole()returns"ceo"for the ceo role;DEFAULT_AGENT_BUNDLE_FILEShasceo:key; reads fromonboarding-assets/ceo/directoryserver/src/services/approvals.tslines 112, 179 — checkstype === "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 extensivelyserver/src/onboarding-assets/ceo/AGENTS.md— "You are the CEO. Your job is to lead the company..."; references board, hire, fire, CTO, CMO, delegates usingpaperclip-create-agentskillserver/src/onboarding-assets/ceo/HEARTBEAT.md,TOOLS.md— same corpusserver/src/onboarding-assets/default/AGENTS.md— likely also contains company/board references
UI:
ui/src/components/OnboardingWizard.tsxline 114:agentNamedefault is"CEO"ui/src/components/OnboardingWizard.tsxline 71:DEFAULT_TASK_DESCRIPTIONstarts with"You are the CEO. You set the direction for the company."and contains"hire a founding engineer"ui/src/components/ApprovalPayload.tsxlines 5-6:approve_ceo_strategy: "CEO Strategy"in display map; line 21:approve_ceo_strategy: Lightbulbin icon mapui/src/pages/App.tsxline 62: showspnpm paperclipai auth bootstrap-ceoas UI copyui/src/pages/InviteLanding.tsx— checksinvite.inviteType === "bootstrap_ceo"in 5 places; displays "Bootstrap your Paperclip instance"
Database values (stored in rows):
- The
rolecolumn on theagentstable can contain"ceo". Any existing databases have"ceo"stored as a role value. A rename would require a data migration. - The
invites.invite_typecolumn stores"bootstrap_ceo"as a string value. - The
approvals.typecolumn 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 prefixedpcp_board_...,touchBoardApiKey(),revokeBoardApiKey(),resolveBoardAccess(),resolveBoardActivityCompanyIds()server/src/middleware/board-mutation-guard.ts— middleware that guards write operations by board usersserver/src/board-claim.ts— dedicated board claim flow
Database schema:
packages/db/src/schema/board_api_keys.ts— table namedboard_api_keyspackages/db/src/schema/cli_auth_challenges.ts— columnrequested_accessstores"board"as a value
Token prefixes (stored in DB, shared with CLI):
server/src/services/board-auth.tsline 30:pcp_board_${...}— tokens with this prefix are stored in the DB and used by the CLIcli/src/client/board-auth.ts— CLI-side board authentication
Constants:
packages/db/src/schema/companies.tsline 16:requireBoardApprovalForNewAgentscolumnpackages/shared/src/types/company.ts:requireBoardApprovalForNewAgentsfield onCompanypackages/shared/src/types/company-portability.ts:requireBoardApprovalForNewAgentsin the manifest type
UI:
ui/src/pages/BoardClaim.tsx— page for claiming board accessui/src/lib/company-routes.tsline 1:BOARD_ROUTE_ROOTSset 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.tsline 187:APPROVAL_TYPEScontains"hire_agent"— stored in DBapprovals.typecolumnserver/src/services/hire-hook.ts— entire file named after hire; exportsnotifyHireApproved, usesHireApprovedPayloadtype frompackages/adapter-utils/src/types.tspackages/adapter-utils/src/types.tsline 213:HireApprovedPayloadinterface; line 277:onHireApprovedlifecycle hook onServerAdapterModuleserver/src/routes/agents.tsline 1228: createstype: "hire_agent"approvalserver/src/routes/approvals.tsline 66, 281: checkstype === "hire_agent"cli/src/commands/client/approval.tsline 114: CLI option sayshire_agent|approve_ceo_strategyui/src/components/ApprovalPayload.tsxline 5:hire_agent: "Hire Agent"in label mapui/src/components/ApprovalPayload.tsxline 131: branching ontype === "hire_agent"
Agent instruction content:
server/src/onboarding-assets/ceo/SOUL.mdline 15: "Hire slow, fire fast"server/src/onboarding-assets/ceo/AGENTS.mdline 6: "Hire new agents when the team needs capacity"server/src/onboarding-assets/ceo/AGENTS.mdline 16: delegates viapaperclip-create-agentskill with instruction to hireskills/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.tsline 18:path.resolve(os.homedir(), ".paperclip")— default home dirserver/src/home-paths.ts— exported functions:resolvePaperclipHomeDir(),resolvePaperclipInstanceId(),resolvePaperclipInstanceRoot()cli/src/config/home.tsline 10: same.paperclipdefaultserver/src/paths.tsline 13: looks for.paperclip/config.jsonwhen searching ancestor directories for configcli/src/config/store.tsline 16: same ancestor search for.paperclip/config.jsoncli/src/client/context.tsline 25: looks for.paperclip/context.json
Environment variable names:
PAPERCLIP_HOME— overrides the home directoryPAPERCLIP_INSTANCE_ID— overrides instance IDPAPERCLIP_CONFIG— overrides config pathPAPERCLIP_AGENT_JWT_SECRET— agent auth secretPAPERCLIP_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:
- The default string in
server/src/home-paths.tsandcli/src/config/home.ts - All
PAPERCLIP_*env var names (breaking change for existing deployments) - Docker config
PAPERCLIP_HOME: "/home/reviewer/.paperclip-review"indocker-compose.untrusted-review.yml - Shell scripts
scripts/provision-worktree.shandscripts/backup-db.sh - Docs in
doc/DATABASE.md,doc/DOCKER.md,doc/SPEC-implementation.md - The
.paperclip/config.jsonancestor-search path logic
"companies" Subdirectory in Instance Root
server/src/services/agent-instructions.tsline 135: agent instructions are stored at~/.paperclip/instances/<id>/companies/<companyId>/agents/<agentId>/instructions/server/src/services/company-skills.tsline 1282: skills stored at~/.paperclip/instances/<id>/skills/<companyId>server/src/home-paths.tsline 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.yamlas a sidecar filecli/src/commands/client/company.tslines 183, 189-190: CLI detects and processes.paperclip.yamland.paperclip.ymlui/src/pages/CompanyExport.tsxlines 75, 778-785: UI references.paperclip.yaml- The file format is documented in
docs/companies/companies-spec.mdunder theschema: paperclip/v1header
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 namedpaperclipaipackages/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 DBpcp_bootstrap_*— CEO bootstrap invite tokens stored in DBpcp_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.jsonscript:"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.tsxrenderspnpm paperclipai auth bootstrap-ceoas literal user-facing instructionserver/src/startup-banner.tsline 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
taskTitledefaulting 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"andselectDefaultCompanyGoalId()filtersgoal.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
bootstrapCeoInviteon 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_idforeign keys - Stored enum values:
"ceo"inagents.role,"hire_agent"and"approve_ceo_strategy"inapprovals.type,"bootstrap_ceo"ininvites.invite_type,"company"ingoals.level,"board"incli_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_LABELSmaps roles to display strings; changingceo: "CEO"here propagates to wherever the label map is usedpackages/shared/src/api.ts—API.companiesconstant drives all frontend API path constructionui/src/components/ApprovalPayload.tsxlines 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— everyp.log.*call is a literal string server/src/startup-banner.ts— the ASCII art says "PAPERCLIP",resolveAgentJwtSecretStatusmessage referencespnpm paperclipai onboardui/src/components/OnboardingWizard.tsx—DEFAULT_TASK_DESCRIPTION,taskTitledefault,agentNamedefault are all hardcoded literalsserver/src/onboarding-assets/ceo/SOUL.mdandAGENTS.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_CAPABILITIESincludes"companies.read"— this is a capability string that plugins declare in their manifests; changing it breaks all plugins that declare this capabilitypackages/shared/src/constants.ts—PLUGIN_EVENT_TYPESincludes"company.created"and"company.updated"— changing these breaks plugin event subscriptionspackages/shared/src/constants.ts—PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTSincludes"company"and"companies"— changing this affects URL routing validationpackages/shared/src/constants.ts—PLUGIN_STATE_SCOPE_KINDSincludes"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
companytoprojectin service/route files will conflict heavily with upstream changes to thecompanies.tsservice - 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_DESCRIPTIONand defaultagentName = "CEO"inOnboardingWizard.tsxhave no unit tests — changing them is safe but unverified server/src/onboarding-assets/ceo/content is loaded at runtime viafs.readFileindefault-agent-instructions.ts— no test validates the file content structure, only the loading mechanism- CLI terminal output strings (
p.log.*calls inonboard.ts) are not covered by automated tests — manual smoke tests intests/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 behaviorserver/src/__tests__/hire-hook.test.ts— tests the hire approval hookserver/src/__tests__/invite-onboarding-text.test.ts— likely tests invite text containing "CEO"; check before renamingserver/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