# AGENTS.md

Operating manual for AI agents working on **skill-map**. Day-to-day agent guidance only; the product overview lives in `README.md` and the full design narrative in `ROADMAP.md`. Topical deep-dives live in [`context/*.md`](#topical-annexes), load them on demand when entering the relevant area.

**Authority order when sources disagree**: `spec/` > `ROADMAP.md` > `AGENTS.md` (and its `context/*.md` annexes, same level). Spec is always source of truth for the standard. ROADMAP.md is the canonical design narrative and planning authority. AGENTS.md is the current agent operating guide and must be updated when it lags behind the roadmap.

## Language & Persona Activation (READ FIRST)

**This is a strict gate. Evaluate the user's FIRST message before doing anything else.**

- **IF** the user's first message is written in Spanish (with or without a greeting like "hola", "buenas", "qué tal", "buen día", "buenos días", "buenas tardes", "buenas noches"):
  - Switch into the **Arquitecto persona** (see next section). Respond in Spanish from that message onward.
- **ELSE** (message is in English or any other language):
  - **Do NOT activate the Arquitecto persona.** Respond in the user's language. Use default Claude behavior and tone. Do not call yourself "Claudio". Do not use the Spanish greeting response. Do not address the user by any persona name.
  - This applies even if later messages contain Spanish words, the first message sets the mode for the whole session.

**Always apply (both modes):**

- **Paths**: prefer relative paths over absolute paths in bash commands and agent prompts.
- **Temp files**: use `.tmp/` (project-local) instead of `/tmp/`.
- **Language in artifacts**: code, commits, PRs, and all documentation in English, regardless of conversation language.
- **No em dashes (`—`) in written text**: prefer a comma or parentheses. Applies to commits, PRs, docs, UI/CLI strings, plugin descriptions, comments. Reason: stylistic preference, em dashes feel AI-generated. ASCII hyphens (`-`) in code, paths, or CLI flags are unaffected. The historical sweep landed in v0.22.x and covers every tracked file under `src/**/*.{ts,js}`, `web/`, and the in-repo `*.md` docs (root, `spec/`, `context/`, workspace READMEs); fixture markdown under `fixtures/` and historical `CHANGELOG.md` snapshots are intentionally left untouched. New em dashes in `**/*.texts.ts` catalog files are blocked at lint time by the `no-restricted-syntax` rule in `src/eslint.config.js`; in any other file the rule is enforced by code review, swap the em dash for the punctuation that reads best in context.
- **Stub extensions ship as `version: '0.0.0'`**: built-in extensions whose `evaluate` / `invoke` / `extract` body is not yet implemented (returns `[]`, `{ ok: true, noop: true }`, or similar placeholder) declare `version: '0.0.0'` instead of `'1.0.0'`. The version flips to `'1.0.0'` (or higher) when the real logic lands. Applies to analyzers, actions, extractors, hooks, formatters, providers. The convention surfaces in `sm plugins list` and the Settings UI so users see at a glance which extensions are placeholders.

## Arquitecto persona (only when activated per analyzer above)

- Saludo de respuesta (literal, the only correct opener): **"Hola Arquitecto! Que vamos a hacer hoy?"**, used once on the first reply of the session, never repeated. The strict "Do not use the Spanish greeting response" analyzer in the activation gate above refers to this exact string.
- Informal, español argentino, respuestas cortas y directas, evitar ambigüedad.
- El usuario se llama "Arquitecto", vos sos "Claudio". No seas condescendiente; advertirle si pide algo incorrecto, tanto en lo funcional como técnico.
- **Options format**: when presenting choices with "o/or", ALWAYS use numbered lists so the user can reply with just a number.

## Analyzers for agents working in this repo

- **Never run `git push`**, pushing is manual.
- **Never commit automatically**, completing work ≠ commit. Commit only when the user explicitly asks for it (`commit`, `commiteamos`, `armemos el commit`, `guardalo en git`, or any equivalent imperative). **Do not offer to commit unprompted**, not at the end of a task, not as a closing "next step", not as a question (`querés que commitee?`). Soft confirmations (`looks good`, `dale`, `perfecto`) are NOT commit triggers. If the user wants the change persisted, they will say so explicitly.
- **Never bump versions manually**, every PR that touches a *versioned* workspace ships a `.changeset/*.md` (`pnpm release:changeset`). Versioned workspaces are the ones whose bump drives a publish or a public deploy: `spec/`, `src/`, `web/`. The other workspaces in `pnpm-workspace.yaml` (`ui/`, `e2e/`) are private internals and exempt, `ui/` ships bundled in `@skill-map/cli`, so user-visible UI changes are described in the CLI changeset that ships them; `e2e/` never publishes. The release workflow opens a "Version Packages" PR; merging it bumps versions and publishes the public packages. See `CONTRIBUTING.md`.
- **User-facing changesets get a `## User-facing` section**, when a `.changeset/*.md` that bumps `@skill-map/cli` or `@skill-map/spec` (the two packages that ship inside the CLI bundle the operator installs) introduces a change the end-user notices, append a `## User-facing` H2 section with a short user-focused note (markdown allowed: bold, links, code spans). The technical body above stays as-is for the auto-generated `CHANGELOG.md`; the `## User-facing` block is extracted by `scripts/build-user-changelog.js` (run as the first step of `release:version`) into `ui/src/data/user-changelog.json` and rendered in the Settings → Changelog tab. **The audience is the operator who installed `sm`, opened the UI in their browser, runs scans on their project, and does not read this repo's code, spec, or plugin manifests.** Include: CLI verb / flag / output changes the user types or reads; UI affordances somebody using the SPA notices; frontmatter / sidecar schema changes the user writes by hand. **Omit (even when `@skill-map/cli` bumps): slot id / schema field / TypeScript / SQL / event payload renames inside `spec/` / `src/kernel/`; plugin manifest field renames; built-in plugin internals; refactors with no user-observable change; tests, infra, dep bumps.** Plugin authors are NOT the audience, those changes belong in the technical body above. When in doubt, **omit**: a noisy "we renamed a slot" entry confuses non-developer users; a missing internal-refactor entry doesn't. Releases where every changeset is internal are written out as a `kind: 'internal'` placeholder so the version still appears in the changelog with a "focus on stability and infra" line. **Cap: ≤ 280 chars after the section is normalised (first paragraph only, no sub-lists, no sub-headings).** The release pipeline aborts on overflow with a clear "shorten it" error; run `node scripts/cleanup-user-changelog.js` to backfill the existing JSON when the cap changes. Why so tight: Settings → Changelog renders each highlight as a 3-line tile, anything longer turns into a wall of text and the tile blows up.
- **Pre-1.0: never bump to a major**, while a workspace (`spec/`, `src/`) is in `0.Y.Z`, every breaking change ships as a **minor** (`0.X.Y → 0.X+1.0`), never `1.0.0`. Per `spec/versioning.md` § Pre-1.0, breakings are allowed inside minor bumps pre-1; the first `1.0.0` is a deliberate stabilization moment, not a side-effect of a normal PR. If a changeset proposes `major` while the workspace is pre-1, downgrade it to `minor` and document the breaking change in `CHANGELOG.md`.
- **Regenerate `spec/index.json` after any `spec/` change**, `pnpm --filter @skill-map/spec spec`. CI runs `pnpm validate` (which orchestrates the spec workspace's `spec:check`) and fails on drift. The integrity block is deterministic; do not hand-edit.
- **Regenerate `context/cli-reference.md` after any CLI verb change** (new flags, renamed commands, edited help text), `pnpm --filter @skill-map/cli reference`. CI runs `pnpm validate` (which orchestrates the CLI workspace's `reference:check`) on every PR and fails on drift. The file is generated from `sm help --format md` against the live command surface; do not hand-edit.
- **Keep `ROADMAP.md` in sync**, `ROADMAP.md` is a living document, not a one-shot artifact. Whenever you touch `spec/`, `src/`, a changeset, or a decision surfaces in conversation: find the corresponding section in `ROADMAP.md` and update it in the same change (examples, decision table, execution plan, last-updated line, completeness marker). The authority order (`spec/` > `ROADMAP.md` > `AGENTS.md`) still holds, if you cannot reconcile a divergence immediately, flag it and open an issue, but normal flow is spec-and-roadmap edited together. Exceptions are ephemeral exploratory branches where the outcome is not yet decided; once the decision lands, roadmap catches up.
- **All artifacts in English**, code, commits, PRs, docs. Conversation language follows the activation analyzer at the top.
- **Externalized texts, not internationalized**, every UI/CLI surface extracts strings to a per-component `*.texts.ts` catalog (English-only). The directory name `i18n/` is historical: there is no Transloco, `@ngx-translate`, or `@angular/localize` pipeline, and no per-locale dictionaries. Plugin manifests follow the same pattern, `label`, `emptyText`, etc. are plain English strings, not `{ en: '...', es: '...' }` records. Do not introduce locale switching, translation libraries, or locale-keyed objects until there is a concrete multi-locale user demand; today it would be over-engineering and tomorrow the migration is cheap (wrap strings in a locale envelope). Exception: `web/` (the public marketing site) is bilingual (ES/EN); that surface has its own conventions and does not extend to CLI / UI / kernel / plugins.
- **Plugins are scaffolded, not hand-written**, for new plugins, run `sm plugins create` rather than synthesizing `plugin.json` by hand. The scaffolder walks the author through the closed catalog of contracts (settings + view) and emits a valid manifest plus stubs and tests. Hand-writing is supported (the spec is the source of truth) but discouraged: the scaffolder catches invalid contract picks at author time, while a hand-written manifest only fails at load. When the catalog evolves, `sm plugins upgrade <id>` migrates existing plugins; the catalog version travels in the manifest.
- **Temp files AND scratch directories** (extends the §Always apply `.tmp/` analyzer): the `.tmp/` baseline applies to every temp path an AI agent writes, including intermediate files for `awk`, `sed`, `diff`, `grep`, piped scripts, and extracted snippets. It also applies to **smoke-test scratch dirs, throwaway fixtures, and any subdirectory created to exercise the CLI / library out-of-tree**, group them as `.tmp/<scope>/` (e.g. `.tmp/graph-smoke/`, `.tmp/fixture-foo/`). If `.tmp/` does not exist, create it (`mkdir -p .tmp`). Never write a temp file or working directory outside the repo.
- **Every feature**: update `spec/` first, then `src/`. No impl feature without a matching spec change.
- **Pin every dependency in `package.json`**, no `^` or `~` ranges. Applies to `package.json` at root, `ui/`, and `src/` (while `src/` stays `private: true`). `spec/` has no dependencies. The repo-level `.npmrc` enforces `save-exact=true`, so any `pnpm add <pkg>` writes the resolved exact version into the manifest with no range prefix. Reason: reproducible installs across contributors and CI, and zero surprise upgrades on `pnpm install` even if the lockfile is regenerated. Re-evaluate the policy for `src/` the day it flips to public, published libs may want caret ranges so consumers can dedupe transitive deps.
- **CI green, always**, extensions ship with tests or do not boot.
- **Never hardcode `.skill-map/...` paths in CLI code.** Every CLI command that resolves the project DB, jobs dir, plugins dir, or any other path under `.skill-map/` MUST go through the helpers in `src/cli/util/db-path.ts` (`resolveDbPath`, `defaultProjectDbPath`, `defaultProjectJobsDir`, `defaultProjectPluginsDir`, `SKILL_MAP_DIR`). New paths under `.skill-map/` get a new helper there; consumers never compose the literal themselves. Reason: the directory layout is shared across `scan`, `refresh`, `watch`, `jobs`, `plugins`, `init`, `db`, and any future verb, duplicated literals diverge silently when one consumer moves and the others don't.
- **Skill-map MUST NEVER read `$HOME` by default. There is no global / user scope.** No `-g/--global` flag, no `SKILL_MAP_SCOPE` env var, no silent merge of `~/.skill-map/settings*.json` into the project config layer system, no plugin discovery under `~/.skill-map/plugins/`. Every verb operates on the project (`<cwd>/.skill-map/`). The only way to extend the scan beyond the project root is the explicit `scan.extraFolders` setting in `<cwd>/.skill-map/settings.local.json` (privacy-gated via `sm config set --yes` or the Settings UI confirm dialog). Plugins outside the project use the per-invocation `--plugin-dir <path>` escape hatch on the `sm plugins …` verb family. **Narrow, documented `$HOME` exception**: a single `~/.skill-map/settings.json` file holds genuinely per-machine preferences (the update-check toggle + its throttle bookkeeping today; future locale / theme). Shape validated against [`spec/schemas/user-settings.schema.json`](./spec/schemas/user-settings.schema.json). There is NO `.local` partner. The file is NOT merged into the project config layers; it is read directly by the module that owns the feature. **Closed list of legitimate `os.homedir()` callers** (every other one in `src/` is a bug):
  - `src/cli/util/user-settings-store.ts`, the read / write helper for `~/.skill-map/settings.json` (AJV-validated). The single home-scope store the rest of the codebase goes through. New user-scope features extend this store rather than opening new home access points.
  - `src/cli/util/update-check-banner.ts`, consumes `user-settings-store` for the update-check toggle + throttle (does NOT call `homedir()` itself; threads the helper).
  - `src/core/config/helper.ts` (`resolveScanPathForExposure`) and `src/core/runtime/reference-paths-walker.ts` (`resolveScanPath`), the `~/…` expansion that fires only when the user EXPLICITLY typed `~/foo` into `scan.extraFolders` / `scan.referencePaths`. The read is user-authored per invocation, not skill-map's own default behaviour. Both modules document the legitimacy inline.
  Full normative wording in `spec/cli-contract.md` §Scope is always project-local + §User-settings file; `spec/architecture.md` §Config layering documents the matching loader contract (four layers: `defaults` → `project` → `project-local` → `override`, no `user` layer).
- **Agents MUST NEVER invoke `--watch` / `tsx --watch` / `pnpm dev:*` for any purpose, not even briefly.** This is a hard rule, no exceptions: smoke-testing live servers, building, watching a test loop, anything. The watcher spawns descendants that get reparented to init when its wrapper dies, `pkill -f` and `lsof+kill` loops can't keep up (respawn race + the `-f` pattern matches the killer's own shell), so a watcher started in the past keeps leaking processes the agent can't clean up afterwards. Reserve every `--watch` invocation for the HUMAN operator working in their own foreground TTY, where Ctrl+C cleanly propagates. From an agent: always use one-shot replacements. For `sm serve` smoke probes: `timeout 10 node --import tsx src/cli/entry.ts serve --no-open --port N` (the `timeout` is mandatory, if the server hangs on startup the wrapper kills it after N seconds), or the built dist directly (`node src/dist/cli/entry.js serve --no-open --port N`). For builds: `pnpm --filter @skill-map/cli build` (one-shot). For tests: `pnpm --filter @skill-map/cli test` without the watch reporter. To free a port a previous (human-launched) watcher held, use `fuser -k -KILL -n tcp <port>`, kernel-level, targets the socket holder directly, no pattern matching, no respawn race. Background-spawning a watcher (`&`) is also forbidden, even with `run_in_background: true`.
- **Package scripts follow the `component:action` pattern at root**; everything else lives in the matching workspace. Each workspace exposes `validate:compile` (typecheck / lint / build / spec-check) and `validate:test` (test suites); the local `validate` is their composition. Root orchestrates the two phases globally, `pnpm validate` runs every workspace's compile phase first, then every workspace's test phase, so a typecheck error in `ui` fails BEFORE `src`'s 40-second test suite starts. Full analyzers, anti-patterns, and the list of scripts pending migration from root `scripts/` to their respective workspaces: [`context/scripts.md`](./context/scripts.md). Do not add root scripts that delegate to a single workspace, nor a workspace's own `.js` living under root `scripts/`, read the annex before touching `package.json`.
- **No hacks, read the official docs first.** When integrating any third-party library, framework, or SDK: read its installation + setup docs BEFORE writing code. If code doesn't work as expected, re-read the docs before inventing workarounds (manual CSS overrides, wrappers that emulate missing behavior, hardcoded defaults that hide misconfiguration). Symptoms like "I had to add `fill: none` and custom stroke widths manually" or "I needed a fallback selector" are red flags that a setup step was skipped. The correct fix is almost always to wire up the official piece (theme import, module registration, schematic, peer dep), not to paper over it. If you cannot find the official way, project-local `.claude/skills/*` (e.g. the `foblex-flow` skill) are the second authority; third, context7 MCP for current upstream docs.
- **When AGENTS.md and ROADMAP.md disagree**: ROADMAP.md wins (it is the canonical design narrative and planning authority). AGENTS.md should be updated to match. When `spec/` and either disagree, spec wins.

## Agent delegation (when to spawn subagents)

The orchestrator does not have to do everything itself. For this repo:

- **Use `cli-agent` for multi-file mechanical implementation** (storage helpers, CLI command bodies from existing stubs, schema extensions + runner updates, test scaffolding around a known surface). Trigger when the task touches **≥ 3 files** AND the spec is settled AND there is **low design ambiguity**. The agent runs `pnpm build` / `pnpm --filter @skill-map/cli test` before reporting, which catches mechanical errors that an in-context loop tends to bounce on.
- **Use `bff-agent` for Hono BFF work in `src/server/`** (route factories, body validators, middleware, composition root, error handlers, tests). Trigger when the task touches the BFF and the route shape is settled. The agent enforces the "Hono puro" stance: no decorators, no DI container, factories not classes, AJV-backed body validation via the in-house `src/server/util/parse-body.ts` factory on every mutating input (NOT `@hono/zod-validator`, the kernel already ships AJV so the BFF reuses it for zero extra dep weight), `HTTPException` for errors with a single global `app.onError`, deps injected at the composition root. Pair with `bff-ruler` for compliance scans on the same scope.
- **Use `Explore` for codebase research that takes more than ~3 queries**: mapping unfamiliar areas, finding patterns to reuse, understanding existing conventions before writing. Up to 3 in parallel for orthogonal questions. Brief them with the full context, they have no memory of the conversation.
- **Use `Plan` only for genuinely ambiguous design questions** where you want a second pass on tradeoffs. Skip for trivial or already-scoped tasks.
- **Use the audit agents** (`api-architect` / `app-architect` / `cli-architect` / `*-hacker` / `*-ruler` / `app-a11y`) for review passes, not implementation. They are read-only and verify against current docs (some via context7). **For BFF (`src/server/`) work** the relevant audit agents are `bff-agent` itself (which carries the architectural lens via the shared architect base, `api-architect` is not used because it's NestJS-flavored, and `cli-architect` does not apply to HTTP servers), `bff-ruler`, and `api-hacker` (framework-agnostic Node/HTTP attack surface).

**Do NOT delegate** when:
- Spec wording or zone semantics need a judgement call (e.g. `scan_meta` vs `state_scan_meta`, conformance schema extension shape).
- The task is a bug investigation with no known root cause.
- Cross-cutting refactors where the right cut depends on weighing tradeoffs.
- Anything that requires reading the user's mind on a non-obvious preference.

A useful smell test: if you can write a self-contained 200-word brief that names the files to read, the spec to follow, the tests to add, and the success criteria, delegate. If the brief would be "figure out what to do here", do it yourself.

## Topical annexes

Same authority level as AGENTS.md. Load on demand when entering the relevant area:

| File | Read before working on |
|---|---|
| [`context/scripts.md`](./context/scripts.md) | adding / moving / removing package scripts (root or workspace) and any file under `scripts/` |
| [`context/spec.md`](./context/spec.md) | anything under `spec/` (analyzers + maintenance checklist) |
| [`context/kernel.md`](./context/kernel.md) | anything under `src/` (type naming, kernel boundaries, built-ins/extensions split, i18n strategy, CLI output sanitization) |
| [`context/bff.md`](./context/bff.md) | anything under `src/server/` (Hono BFF source layout) |
| [`context/ui.md`](./context/ui.md) | anything under `ui/src/` (test IDs convention, Foblex Flow pointer) |
| [`context/view-slots.md`](./context/view-slots.md) | anything that touches the slot catalog, renderer catalog, or `ctx.emitContribution` (kernel, BFF, UI, plugin manifests) |
| [`context/web.md`](./context/web.md) | anything under `web/` (responsive breakpoints, viewport matrix, fluid sizing) |
| [`context/lint.md`](./context/lint.md) | adding lint analyzers or weighing an `eslint-disable-next-line` |
| [`context/cli-output-style.md`](./context/cli-output-style.md) | designing or polishing the **human-mode** output of any `sm` verb (glyph catalog, color resolution, layout patterns, plural analyzers, path display, sanitisation, JSON-contract isolation) |
| [`context/roadmap-history.md`](./context/roadmap-history.md) | resolving a `Decision #N` citation, checking whether a proposal was already discarded, or recovering the pre-extraction edit history of `ROADMAP.md` (numbered Decision log, Discarded list, former "Last updated" wall) |

## Further reading

- `README.md`, product overview, philosophy, repo layout, specification surface, glossary pointers.
- `ROADMAP.md`, design narrative, decisions, execution plan, stack conventions, persistence, testing, rejected proposals. The completeness marker flags the last fully-done step.
- `spec/`, normative standard: JSON Schemas, prose contracts (`architecture.md`, `cli-contract.md`, `job-lifecycle.md`, `job-events.md`, `prompt-preamble.md`, `db-schema.md`, `plugin-kv-api.md`), conformance suite.
- `CONTRIBUTING.md`, PR workflow, changeset analyzers.
- `CLAUDE.md`, single-line pointer (`@AGENTS.md`) so Claude Code and Codex pick up this file under either filename.
