# 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//companies//agents//instructions/` - `server/src/services/company-skills.ts` line 1282: skills stored at `~/.paperclip/instances//skills/` - `server/src/home-paths.ts` line 85: managed project workspaces stored at `~/.paperclip/instances//projects////` 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*