Skip to content

Project taxonomy

The taxonomy lives in codika-os-app/seeds/projects.yaml. The seeder reads it, walks the workspace, mints projects + path prefixes, and lints the result. The YAML is the source of truth. Don’t edit the projects or project_path_prefixes tables by hand.

If you’re new to the model, read Concepts → Projects first.

Most new projects don’t need any taxonomy change at all — they appear automatically.

The common case: a new direct-child folder

Section titled “The common case: a new direct-child folder”

Most discovery rules use leaf: direct-child and walk a workspace folder. Adding a project in those categories is just create the folder and re-run the seeder:

ActionWhat appears
mkdir agency/projects/<new-client-slug>New client:product project. Lints against existing client:knowledge entries.
mkdir products/<new-product>New product: project.
mkdir agency/tools/<new-tool>New tool: project.
mkdir core/app/<new-app>New platform:app project.

Then, from codika-os-app:

Terminal window
npm run db:sync:projects # dev
DB_TARGET=prod npm run db:sync:projects # prod

That’s it. No YAML edit. The seeder picks the bare folder name as the slug, generates a display name, assigns a least-used color, and inserts the row plus its path prefixes (canonical + legacy).

The less common case: a new flat_path or prompt_only project

Section titled “The less common case: a new flat_path or prompt_only project”

If the project doesn’t live under a direct-child root — it’s a fixed path, or it has no filesystem footprint at all — add a discovery entry to the right category in projects.yaml:

- id: growth
display_name: Growth
discovery:
# … existing rules …
- flat_path: workspace/content/podcasts
slug: podcasts
subcategory: content
display_name: Podcasts

Run npm run db:sync:projects after.

After seeding, open /tracking. The new project shows up in the per-project table (zero values until a session attributes to it). Drill into a known matching session and confirm the Attribution panel shows method = 'path' with the new project id.

Rare. Existing subcategories cover the work the team does today; the right defaults are:

  • A new client → no taxonomy change. The two subcategories (knowledge / product) cover every client.
  • A new internal product → product (no subcategory).
  • A new internal tool → tool (no subcategory).
  • A new platform repo → fits one of app / cloud-run / infra / libs / template / marketplace.

Reach for a new subcategory only when the existing ones genuinely don’t fit AND the rollup distinction is worth keeping on every chart from that point on.

A subcategory and a top-level category solve different problems:

You want…Add a…
A finer breakdown within an existing rollup (e.g. split growth into content/strategy/outreach)Subcategory
A new rollup axis that should appear next to client / platform / tool on the headline donutTop-level category

Adding a top-level category is a heavier change: it shows up everywhere, every chart legend grows, every code path that enumerates categories needs an update (category-colors.ts, KPI tiles, the classifier schema check). Subcategories live inside an existing category and surface only on drill-down.

If you’re unsure, default to a subcategory. Promoting a subcategory to a top-level category later is just a YAML edit + reseed; the inverse (demoting a category) drops history.

When the agent classifier (the /codika-os:classify-sessions skill) hits work that doesn’t fit any existing project, it returns projectSlug: null plus a suggestedSlug, suggestedCategory, and optional suggestedSubcategory. These rows are persisted in project_attribution_attempts for clustering.

At the end of the skill loop, suggestions are aggregated and surfaced to you. The workflow:

  1. Agent suggests — the skill collects per-cluster slugs with sample reasonings.
  2. Team triages — you review, decide whether each cluster is a real recurring project or a one-off. Reject one-offs.
  3. Seed change — for each accepted cluster, add a discovery rule to seeds/projects.yaml (or create the folder, for direct-child cases).
  4. Re-classify — run npm run db:sync:projects (dev + prod), then codika-os sync --full to re-attribute existing sessions whose paths now match. Sessions still unattributed can be re-run through /codika-os:classify-sessions.

The skill does not edit seeds/projects.yaml itself. The taxonomy change is always a human decision committed to the repo.

A client appears under two folders: agency/crm/codika-clients/clients/<slug>/ for the CRM side, agency/projects/<slug>/ for any product work. The seeder enforces that the slug matches on both sides — otherwise the same client would mint two project rows (creafid vs creafid-app) and rollups would split.

The seeder fails loudly with a line like:

LINT: client slug parity violation
agency/crm/codika-clients/clients/igs-syndic exists but no matching
folder under agency/projects/igs-syndic.
Either rename one side, or remove the orphan.

Pick the canonical slug (usually the public-facing name), then rename the off-side folder:

Terminal window
cd agency/crm/codika-clients
git mv clients/igs-syndic clients/syndicable
git commit -m "rename: igs-syndic → syndicable"

Then grep the workspace for any code or doc still referencing the old slug:

Terminal window
rg -l 'igs-syndic' --type-not lock

Update those references, then re-run npm run db:sync:projects. The lint passes; existing sessions that touched the old path attribute via the legacy prefix (the seeder keeps clients/igs-syndic as is_legacy = true if you add it explicitly, otherwise sessions on the old path won’t match and are re-classified as unattributed — usually fine after a folder rename since the new path picks up subsequent activity).

Two projects are reached only by the agent classifier:

ProjectWhen it lands
growth:outreachPrompt evidence describes outbound (Lemlist, Apollo, LinkedIn) and no path matches a more specific project.
knowledge:generalWorkspace-rooted reading/exploration that doesn’t fit anywhere more specific. (Also matched at low priority by path against workspace/.)

When the path classifier runs at sync time, it cannot pick a prompt-only project — there are no path prefixes for it to match. The session is uploaded with project_id = NULL and surfaces in codika-os classifications pending. The agent classifier then picks it from prompt evidence.

If you find yourself wanting to add a third prompt-only project, ask whether the work really has no filesystem footprint. Most “feels like outreach” sessions actually touch workspace/openclaw/docs/campaigns/ or agency/crm/codika-clients/ and land via path matching. prompt_only is the exception, not the default.

  • Don’t change a project slug after it has sessions attributed. URL routes (/tracking/projects/<slug>) and the CLI classifier output both depend on it. Use alias to rename what users see.
  • Don’t add .codika/ as a path prefix. It’s the workspace root and would catch everything.
  • Don’t edit projects or project_path_prefixes directly in SQL. Re-running db:sync:projects will overwrite your change with the YAML’s view of the world. If you need a one-off attribution fix, update sessions.project_id + attribution_method = 'manual' instead.
  • Don’t bypass the parity lint. If the seeder complains about client slugs, fix the folders. A drift between the two sides means broken rollups for that client until it’s resolved.