@nurix/etna 0.18.11 → 0.18.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +8 -9
  2. package/bin/etna.js +63 -2
  3. package/data/harness-index.json +1 -1
  4. package/data/harnesses/code/CLAUDE.md +9 -10
  5. package/data/harnesses/code/agents/brainstorm.md +3 -3
  6. package/data/harnesses/code/agents/change-review.md +4 -4
  7. package/data/harnesses/code/agents/maintenance.md +177 -59
  8. package/data/harnesses/code/agents/spec-mode.md +3 -3
  9. package/data/harnesses/code/harness.json +2 -2
  10. package/data/harnesses/code/hooks/generate-ownership-index.mjs +148 -0
  11. package/data/harnesses/code/hooks/hooks.json +12 -0
  12. package/data/harnesses/code/skills/boundary/references/boundary-taxonomy.md +3 -3
  13. package/data/harnesses/code/skills/brainstorm/README.md +11 -9
  14. package/data/harnesses/code/skills/brainstorm/SKILL.md +2 -2
  15. package/data/harnesses/code/skills/brainstorm/harvest.md +5 -6
  16. package/data/harnesses/code/skills/distiller/SKILL.md +3 -3
  17. package/data/harnesses/code/skills/distiller/references/docs-mode.md +7 -7
  18. package/data/harnesses/code/skills/documentation/SKILL.md +23 -23
  19. package/data/harnesses/code/skills/documentation/references/templates.md +24 -65
  20. package/data/harnesses/etna/CLAUDE.md +18 -27
  21. package/data/harnesses/etna/agents/brainstorm.md +3 -3
  22. package/data/harnesses/etna/agents/change-review.md +4 -4
  23. package/data/harnesses/etna/agents/maintenance.md +177 -59
  24. package/data/harnesses/etna/agents/spec-mode.md +3 -3
  25. package/data/harnesses/etna/harness.json +2 -2
  26. package/data/harnesses/etna/hooks/generate-ownership-index.mjs +148 -0
  27. package/data/harnesses/etna/hooks/hooks.json +12 -0
  28. package/data/harnesses/etna/rules/npm-cli.md +88 -0
  29. package/data/harnesses/etna/skills/boundary/references/boundary-taxonomy.md +3 -3
  30. package/data/harnesses/etna/skills/brainstorm/README.md +11 -9
  31. package/data/harnesses/etna/skills/brainstorm/SKILL.md +2 -2
  32. package/data/harnesses/etna/skills/brainstorm/harvest.md +5 -6
  33. package/data/harnesses/etna/skills/distiller/SKILL.md +3 -3
  34. package/data/harnesses/etna/skills/distiller/references/docs-mode.md +7 -7
  35. package/data/harnesses/etna/skills/documentation/SKILL.md +23 -23
  36. package/data/harnesses/etna/skills/documentation/references/templates.md +24 -65
  37. package/data/harnesses/hr/harness.json +1 -1
  38. package/data/harnesses/hr/skills/evaluator/criteria/testing.md +4 -4
  39. package/data/harnesses/mobile/CLAUDE.md +9 -10
  40. package/data/harnesses/mobile/agents/brainstorm.md +3 -3
  41. package/data/harnesses/mobile/agents/change-review.md +4 -4
  42. package/data/harnesses/mobile/agents/maintenance.md +177 -59
  43. package/data/harnesses/mobile/agents/spec-mode.md +3 -3
  44. package/data/harnesses/mobile/harness.json +2 -2
  45. package/data/harnesses/mobile/hooks/generate-ownership-index.mjs +148 -0
  46. package/data/harnesses/mobile/hooks/hooks.json +12 -0
  47. package/data/harnesses/mobile/skills/boundary/references/boundary-taxonomy.md +3 -3
  48. package/data/harnesses/mobile/skills/brainstorm/README.md +11 -9
  49. package/data/harnesses/mobile/skills/brainstorm/SKILL.md +2 -2
  50. package/data/harnesses/mobile/skills/brainstorm/harvest.md +5 -6
  51. package/data/harnesses/mobile/skills/distiller/SKILL.md +3 -3
  52. package/data/harnesses/mobile/skills/distiller/references/docs-mode.md +7 -7
  53. package/data/harnesses/mobile/skills/documentation/SKILL.md +23 -23
  54. package/data/harnesses/mobile/skills/documentation/references/templates.md +24 -65
  55. package/package.json +1 -1
  56. package/data/harnesses/code/agents/harness-manager.md +0 -184
  57. package/data/harnesses/etna/agents/harness-manager.md +0 -184
  58. package/data/harnesses/mobile/agents/harness-manager.md +0 -184
@@ -0,0 +1,148 @@
1
+ // plugins/code/hooks/generate-ownership-index.mjs — SessionStart hook: scan docs/features/**/*.md,
2
+ // extract the focal frontmatter (owns / module / keywords / status), and inject a compact
3
+ // code-path → owning-doc index via additionalContext. Fires on startup/resume/clear/compact so the
4
+ // map survives /clear and /compact and is regenerated fresh every time. Writes no file.
5
+
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+
9
+ // Read all of stdin synchronously; returns "" if nothing is piped.
10
+ function readStdin() {
11
+ try {
12
+ return fs.readFileSync(0, "utf8");
13
+ } catch (e) {
14
+ return "";
15
+ }
16
+ }
17
+
18
+ // Resolve the project root: CLAUDE_PROJECT_DIR env, else payload cwd, else process cwd.
19
+ function resolveProjectDir(payload) {
20
+ return process.env.CLAUDE_PROJECT_DIR || (payload && payload.cwd) || process.cwd();
21
+ }
22
+
23
+ // Recursively collect every .md file under dir (returns [] if the dir is absent).
24
+ function listMarkdown(dir) {
25
+ const out = [];
26
+ let entries;
27
+ try {
28
+ entries = fs.readdirSync(dir, { withFileTypes: true });
29
+ } catch (e) {
30
+ return out;
31
+ }
32
+ for (const entry of entries) {
33
+ const full = path.join(dir, entry.name);
34
+ if (entry.isDirectory()) out.push(...listMarkdown(full));
35
+ else if (entry.isFile() && entry.name.endsWith(".md")) out.push(full);
36
+ }
37
+ return out;
38
+ }
39
+
40
+ // Pull the leading `--- … ---` frontmatter block; "" if the file has none.
41
+ function frontmatter(text) {
42
+ const m = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(text);
43
+ return m ? m[1] : "";
44
+ }
45
+
46
+ // Trim surrounding whitespace, then strip one layer of matching quotes.
47
+ function unquote(s) {
48
+ return String(s).trim().replace(/^["']|["']$/g, "").trim();
49
+ }
50
+
51
+ // Read one frontmatter key: scalar (`k: v`), inline array (`k: [a, b]`), or a following `- item` block.
52
+ // Returns a string for scalars, an array for lists, or undefined when the key is absent.
53
+ function field(block, key) {
54
+ const lines = block.split(/\r?\n/);
55
+ const head = new RegExp(`^${key}\\s*:\\s*(.*)$`);
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const m = head.exec(lines[i]);
58
+ if (!m) continue;
59
+ const raw = m[1].trim();
60
+ if (raw === "") {
61
+ const items = [];
62
+ for (let j = i + 1; j < lines.length; j++) {
63
+ const li = /^\s*-\s+(.*)$/.exec(lines[j]);
64
+ if (!li) break;
65
+ items.push(unquote(li[1]));
66
+ }
67
+ return items;
68
+ }
69
+ if (raw.startsWith("[")) {
70
+ return raw.replace(/^\[|\]$/g, "").split(",").map((s) => unquote(s)).filter(Boolean);
71
+ }
72
+ return unquote(raw);
73
+ }
74
+ return undefined;
75
+ }
76
+
77
+ // Coerce a field that may be scalar/array/undefined into a clean string array.
78
+ function asList(v) {
79
+ if (v == null) return [];
80
+ return (Array.isArray(v) ? v : [v]).map((s) => String(s).trim()).filter(Boolean);
81
+ }
82
+
83
+ // Build the ownership index text from every feature doc's frontmatter.
84
+ function buildIndex(projectDir) {
85
+ const featuresDir = path.join(projectDir, "docs", "features");
86
+ const files = listMarkdown(featuresDir);
87
+ const lines = [];
88
+ let deprecated = 0;
89
+ for (const file of files) {
90
+ let text;
91
+ try {
92
+ text = fs.readFileSync(file, "utf8");
93
+ } catch (e) {
94
+ continue;
95
+ }
96
+ const block = frontmatter(text);
97
+ if (!block) continue;
98
+ const owns = asList(field(block, "owns"));
99
+ if (owns.length === 0) continue;
100
+ const status = (field(block, "status") || "active").toString().toLowerCase();
101
+ if (status === "deprecated") {
102
+ deprecated += 1;
103
+ continue; // keep deprecated docs out of the routing lines
104
+ }
105
+ const moduleLabel = (field(block, "module") || "—").toString();
106
+ const keywords = asList(field(block, "keywords"));
107
+ const doc = path.relative(projectDir, file).split(path.sep).join("/");
108
+ const kw = keywords.length ? `[${keywords.join(", ")}]` : "[]";
109
+ for (const glob of owns) {
110
+ lines.push(`${glob} → ${doc} · ${moduleLabel} · ${kw} · ${status}`);
111
+ }
112
+ }
113
+ if (lines.length === 0) return "";
114
+ lines.sort();
115
+ const header =
116
+ "# Ownership index (generated) — code-path glob → owning feature doc · module · keywords · status\n" +
117
+ "# Match the file or topic you're about to touch, then read the owning doc before changing it.\n";
118
+ const footer = deprecated > 0 ? `\n# (${deprecated} deprecated feature doc(s) omitted)` : "";
119
+ return header + lines.join("\n") + footer;
120
+ }
121
+
122
+ function main() {
123
+ const raw = readStdin();
124
+ let payload = {};
125
+ try {
126
+ payload = raw ? JSON.parse(raw) : {};
127
+ } catch (e) {
128
+ payload = {};
129
+ }
130
+ const index = buildIndex(resolveProjectDir(payload));
131
+ if (!index) return; // no feature docs yet → inject nothing
132
+ process.stdout.write(
133
+ JSON.stringify({
134
+ hookSpecificOutput: {
135
+ hookEventName: "SessionStart",
136
+ additionalContext: index,
137
+ },
138
+ })
139
+ );
140
+ }
141
+
142
+ try {
143
+ main();
144
+ } catch (e) {
145
+ // Best-effort: a routing index is a convenience, never a gate. Inject nothing on failure.
146
+ } finally {
147
+ process.exit(0);
148
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
2
  "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"",
10
+ "timeout": 10
11
+ }
12
+ ]
13
+ }
14
+ ],
3
15
  "SessionEnd": [
4
16
  {
5
17
  "matcher": "",
@@ -0,0 +1,88 @@
1
+ # npm CLI Design — the `@nurix/*` pattern
2
+
3
+ Conventions for building a distributable Nurix command-line tool, distilled from the two we ship: **`@nurix/etna`** (`packages/cli/` here — the harness installer, plain ESM JS) and **`@nurix/apollo`** (`~/dev/apollo/packages/cli/` — the service-bootstrap CLI, TypeScript → `dist/`). When you build or change a `@nurix` CLI, follow these. Where the two diverge, both variants are noted as valid.
4
+
5
+ ## 1. Packaging
6
+
7
+ - **Scoped + restricted.** Name is `@nurix/<tool>`; `"publishConfig": { "access": "restricted" }`. Never publish public.
8
+ - **ESM only** — `"type": "module"`.
9
+ - **`bin` maps the command to its entry** — `{ "etna": "bin/etna.js" }` / `{ "apollo": "dist/index.js" }`. The entry has a `#!/usr/bin/env node` shebang.
10
+ - **`files` is an allowlist** — ship only the runnable artifact: `["bin/", "data/", "README.md"]` (etna) or `["dist"]` (apollo). Never ship `src/`, tests, or the repo.
11
+ - **`engines.node` ≥ 22** for new CLIs (matches the workspace Node baseline).
12
+ - **One version source** — read the running version from `package.json` at startup; don't hardcode it elsewhere.
13
+ - **Minimal deps.** Both ship with essentially one runtime dep (`@clack/prompts`); apollo adds `commander` + `ajv`. Keep the dependency surface tiny — it's installed on every `npx` run.
14
+
15
+ ## 2. Distribution: `npx`-first, install into the project, never globally
16
+
17
+ - The canonical entry is **`npx @nurix/<tool>`** — no global install step, no marketplace, no plugin-enablement dance. The user runs `npx`, the tool does its job in the current directory.
18
+ - **The CLI writes ONLY inside the folder it was invoked in.** An installer run as `npx @nurix/etna` in a project must touch only that project tree — never `$HOME`, never `~/.claude`, never any shared/user-level location. Reaching outside the target folder is surprising, clobber-prone, and wrong. (This is a hard-won rule: an earlier etna build installed agents to `~/.claude/agents/` and it was reverted precisely for this reason.)
19
+ - **Idempotent refresh.** Re-running the CLI re-pulls the latest and overwrites the files it owns (force), while leaving anything the user authored untouched. "Re-run anytime to refresh" must always be safe.
20
+
21
+ ## 3. Always run the latest — self-update by re-exec
22
+
23
+ **The problem:** `npx` caches aggressively. A bare `npx @nurix/<tool>` can keep running a *stale* unpacked copy long after a newer version is published (npm's packument cache resolves an old `latest`, and npx reuses the matching install). Users shouldn't have to remember `@latest`.
24
+
25
+ **The mechanism** (etna's `selfUpdateOrReexec()`, run before anything else): on startup, ask the registry for the latest version; if this copy is behind, re-exec the latest and hand off.
26
+
27
+ ```js
28
+ function selfUpdateOrReexec() {
29
+ if (process.env.TOOL_SELFUPDATED || process.env.TOOL_NO_SELFUPDATE) return;
30
+ const here = readJson(join(pkgRoot, 'package.json')).version;
31
+ if (!here) return;
32
+ const view = spawnSync('npm', ['view', '@nurix/<tool>', 'version', '--prefer-online'], { encoding: 'utf8' });
33
+ const latest = view.status === 0 ? (view.stdout || '').trim() : null;
34
+ if (!latest || latest === here) return; // current, offline, or unauthed → run this copy
35
+ const child = spawnSync('npx', ['-y', '--prefer-online', `@nurix/<tool>@${latest}`, ...process.argv.slice(2)],
36
+ { stdio: 'inherit', env: { ...process.env, TOOL_SELFUPDATED: '1' } });
37
+ if (!child.error) process.exit(child.status ?? 0); // re-exec ran → hand off complete
38
+ // npx unavailable (child.error) → fall through and run this (older) copy.
39
+ }
40
+ ```
41
+
42
+ Required properties:
43
+
44
+ - **`--prefer-online`** on both the `npm view` and the `npx` re-exec — defeats the metadata cache so npx fetches the fresh tarball.
45
+ - **Loop guard** — set `<TOOL>_SELFUPDATED=1` in the child's env so the re-exec'd process doesn't try to update again.
46
+ - **Opt-out** — `<TOOL>_NO_SELFUPDATE=1` skips the whole thing (CI, offline, or running an intentionally pinned/local-dev build). This is also how you test a local build.
47
+ - **Degrade gracefully** — *any* failure (offline, unauthed, `npm`/`npx` missing, timeout) falls through to running the current copy. The update is a convenience, never a gate.
48
+ - **Reuse existing npm auth** — the `npm view` rides the caller's npm credentials (the same auth that installed the restricted package); never prompt for login.
49
+
50
+ ## 4. Editing the user's files: own a delimited block, idempotently
51
+
52
+ When the CLI must modify a file it doesn't own (`.env`, `settings.json`, `.gitignore`), it **owns only a delimited region** and rewrites that region in place — never the whole file.
53
+
54
+ - apollo wraps its `.env` writes in a managed block:
55
+ `# >>> apollo:managed — do not edit by hand; the apollo CLI rewrites this block >>>` … `# <<< apollo:managed <<<`. Reads/merges/removes happen *inside* the markers; everything outside belongs to the user.
56
+ - etna merges its hooks into `.claude/settings.json` and migrates/retires only its own prior entries.
57
+ - Rewrites are **idempotent** (re-running converges to the same block) and **reversible** (logout/uninstall removes exactly the managed region).
58
+ - If the block holds secrets, ensure `.gitignore` covers the file so they're never committed.
59
+
60
+ ## 5. Interactive when it can, flag-driven when it can't
61
+
62
+ - Use **`@clack/prompts`** for the interactive path (TTY): intro/outro, spinners, notes.
63
+ - When **not a TTY**, require explicit flags and fail loudly with the correct syntax rather than guessing — e.g. etna needs `--name=<harness>` outside a TTY, and a removed/renamed flag (the dropped positional form in 0.18.0) errors with the new syntax instead of silently misfiring.
64
+ - A bare invocation in a TTY may present a menu; the same invocation headless must error, not hang.
65
+
66
+ ## 6. Detect the package manager from the lockfile
67
+
68
+ When the CLI installs packages or runs scripts on the user's behalf, detect the manager: `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, else npm. Never hardcode one. (apollo's `detectPackageManager()`.)
69
+
70
+ ## 7. Publishing is CI-owned
71
+
72
+ Publish from CI on push to `dev`/`main`, path-filtered to the CLI package — never `npm publish` by hand for a routine change.
73
+
74
+ - **Two valid version models:**
75
+ - **Fingerprint auto-bump (etna):** the build stamps a content hash (`fingerprint`) of the shipped payload; CI bumps the **patch** automatically when the fingerprint changed, publishes, and tags. Never hand-bump for a content change — let the fingerprint drive it; hand-edit the version only for a deliberate minor/major.
76
+ - **Manual bump input (apollo):** a `workflow_dispatch` `bump` input (`patch`/`minor`/`major`) drives `npm version`.
77
+ - **Common to both:**
78
+ - The bump commit message carries **`[skip ci]`** so the publish workflow doesn't re-trigger itself.
79
+ - `npm version --no-git-tag-version` (file-only), then commit + tag separately — `npm version` skips the git step when run in a subdirectory of the repo root.
80
+ - Push the bump back to the branch and tag with a **package-scoped prefix** (`v…` for etna, `apollo-v…` for apollo) via `git push origin HEAD:<ref> --follow-tags`.
81
+ - `prepack` / `prepublishOnly` builds the shipped artifact (`data/` via `build-data.js`, or `dist/` via `tsc`) so what's published always matches source.
82
+ - `registry-url: https://registry.npmjs.org` in the Node setup so `.npmrc` uses `$NODE_AUTH_TOKEN`.
83
+
84
+ ## 8. Robustness defaults
85
+
86
+ - **Never hard-block on an advisory/network check.** Version currency, registry lookups, telemetry — all degrade to a one-line notice and continue.
87
+ - **Restricted-scope auth is ambient** — the read token in `~/.npmrc` (or `$NODE_AUTH_TOKEN` in CI) authenticates installs and `npm view`. Just attempt the operation; never prompt for `npm login` or reach for a substitute package.
88
+ - **Keep the hot path fast** — everything runs on every `npx`, so avoid heavy startup work beyond the self-update check.
@@ -86,7 +86,7 @@ Below the table, three standing blocks:
86
86
 
87
87
  If any boundary is pre-code (`point*`/`transcribe*`), add the footnote: _"\* point-mode boundary whose pointer lands at implementation — converts to (or gains) a `file:line` pointer when the module lands."_
88
88
 
89
- **Name the bounding code units** (paths / package names) in every `Bounds` line — the distinct vertices named across all `Bounds` lines are the **module-discovery feed** `harness-manager` reads to propose modules, so name them precisely. A `Bounds` line is not restricted to two modules: it may name two **layers** within one module (app↔DB, render↔hydrate, producer↔consumer) or a module and an **external system** — so a flat / single-module repo still has real boundaries to file (as the intro states).
89
+ **Name the bounding code units** (paths / package names) in every `Bounds` line — the distinct vertices named across all `Bounds` lines are the **module-discovery feed** the `maintenance` agent (baseline mode) reads to propose modules, so name them precisely. A `Bounds` line is not restricted to two modules: it may name two **layers** within one module (app↔DB, render↔hydrate, producer↔consumer) or a module and an **external system** — so a flat / single-module repo still has real boundaries to file (as the intro states).
90
90
 
91
91
  **The clustering key — a convergence heuristic, not a hard count:** _one boundary file per (source-of-truth × doc-mode)_. Types that point at the same source-of-truth in the same doc-mode collapse into one file — all persistence types reading one relational schema fold into the `db-schema` file; split only where the source-of-truth or the doc-mode differs (a `transcribe` shape over the same schema, or a different store). Each catalog entry below carries a fold note: **Folds into `x`** (this type lands in `x`'s file), **Canonical home** (this type _is_ the file others fold into), or **Own home** (a standalone source-of-truth × doc-mode). Two `generate` runs converge on the same file set only where this fold-map names the home — where it doesn't, the count is a judgment call, not a guarantee.
92
92
 
@@ -108,7 +108,7 @@ Organized by the seven boundary categories. Each entry: **type-name — SEVERITY
108
108
 
109
109
  #### `information-architecture` — CRITICAL · transcribe
110
110
 
111
- The entity model + topology (e.g. Brief→Plan→Persona→Audience→Match→Campaign→Run; service planes). **Where:** synthesized from ADRs / module pillars / a `@org/contracts` package — never `package.json`. **Breaks:** re-relating or renaming an entity ripples across _every_ module, migration, and wire type. **Detect:** `docs/architecture/` (domain-model, ADRs), `docs/modules/*/pillars/`, a contracts package, `schema.ts`/`models.py` entity defs. **Safe-change:** map every consumer before re-relating; coordinate migrations + wire types + selectors in one change. _(Own home — the IA model is its own transcribed source-of-truth; never folds into a technology file.)_ _The flagship domain boundary._
111
+ The entity model + topology (e.g. Brief→Plan→Persona→Audience→Match→Campaign→Run; service planes). **Where:** synthesized from ADRs / the domain model / a `@org/contracts` package — never `package.json`. **Breaks:** re-relating or renaming an entity ripples across _every_ module, migration, and wire type. **Detect:** `docs/architecture/` (domain-model, `adr/`), a contracts package, `schema.ts`/`models.py` entity defs. **Safe-change:** map every consumer before re-relating; coordinate migrations + wire types + selectors in one change. _(Own home — the IA model is its own transcribed source-of-truth; never folds into a technology file.)_ _The flagship domain boundary._
112
112
 
113
113
  #### `tenancy` — CRITICAL · transcribe
114
114
 
@@ -608,7 +608,7 @@ The agreement on a flag's key, default value, and the off=legacy/on=new conventi
608
608
 
609
609
  #### `workspace-package-graph` — HIGH · point
610
610
 
611
- The contract that a monorepo package may only import siblings it explicitly declares as workspace deps — no phantom/hoisted imports. **Where:** pnpm workspaces (`pnpm-workspace.yaml`, `workspace:*`, strict `node_modules`), npm/yarn/Bun workspaces, Nx, Turborepo. **Breaks:** under hoisting a package can import an undeclared dep hoisted to root (works locally, breaks on publish or layout change); pnpm's strict layout forbids it; removing/renaming a workspace package breaks every undeclared importer silently. **Detect:** `pnpm-workspace.yaml`/root `workspaces`, `workspace:*` deps, imports of package names not in that package's `dependencies`, `nx graph`/`turbo` graph. **Safe-change:** declare every cross-package import as a `workspace:` dep; on rename/remove update all dependents + lockfile; pin internal deps `workspace:*`; CI dependency-graph check for undeclared imports. _(Own home — the workspace dependency graph is its own source-of-truth.)_ _(A workspace is NOT a module — see how `harness-manager` reads boundary `Bounds` vertices.)_
611
+ The contract that a monorepo package may only import siblings it explicitly declares as workspace deps — no phantom/hoisted imports. **Where:** pnpm workspaces (`pnpm-workspace.yaml`, `workspace:*`, strict `node_modules`), npm/yarn/Bun workspaces, Nx, Turborepo. **Breaks:** under hoisting a package can import an undeclared dep hoisted to root (works locally, breaks on publish or layout change); pnpm's strict layout forbids it; removing/renaming a workspace package breaks every undeclared importer silently. **Detect:** `pnpm-workspace.yaml`/root `workspaces`, `workspace:*` deps, imports of package names not in that package's `dependencies`, `nx graph`/`turbo` graph. **Safe-change:** declare every cross-package import as a `workspace:` dep; on rename/remove update all dependents + lockfile; pin internal deps `workspace:*`; CI dependency-graph check for undeclared imports. _(Own home — the workspace dependency graph is its own source-of-truth.)_ _(A workspace is NOT a module — see how the `maintenance` agent reads boundary `Bounds` vertices.)_
612
612
 
613
613
  #### `import-layering-rule` — MED · point
614
614
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Claude Code skill that is a **thinking partner for designing software** — not a form to fill in. It runs one constant loop (distill → abstract → sync → record) over a conversation, files the thinking into a small set of substance-first pillars, and treats _coherence_ as a measurable target rather than a vibe.
4
4
 
5
- The point of v2 is a single inversion over v1: **the conversation is the work; the artifacts serve it.** v1 had grown into elaborate bookkeeping (a tentative/committed decision state machine, background cascade locks and queues, a 10-pillar eager bootstrap, an unconditional greenfield doctrine) wrapped around thinking that never had to happen. v2 deletes that machinery and keeps the loop.
5
+ The single principle: **the conversation is the work; the artifacts serve it.**
6
6
 
7
7
  ---
8
8
 
@@ -67,7 +67,7 @@ coherent ⇔ open_questions == 0 ∧ last sync ran after the last pillar write
67
67
 
68
68
  ## The sync agent
69
69
 
70
- The background reconciler that replaces v1's cascade apparatus. One sync in flight at a time; at most one queued behind it. It runs after substantive writes, mandatorily on entering converge and at `stop`, and on demand. It audits exactly four sources:
70
+ The background reconciler. One sync in flight at a time; at most one queued behind it. It runs after substantive writes, mandatorily on entering converge and at `stop`, and on demand. It audits exactly four sources:
71
71
 
72
72
  1. **Cross-pillar contradiction** — thesis vs domain model vs decisions vs doctrine vs build.
73
73
  2. **Aspect coverage** — any mapped aspect with no content → a question.
@@ -96,9 +96,9 @@ The complexity dial sets _defaults_, not a cage — any lazy pillar materializes
96
96
 
97
97
  ---
98
98
 
99
- ## Build modes (replacing the greenfield doctrine)
99
+ ## Build modes
100
100
 
101
- v1 applied a present-tense "greenfield" writing doctrine unconditionally — which was a bug for brownfield work, where migration vocabulary is load-bearing. v2 folds this into `build.md` as a **mode** chosen at intake:
101
+ The writing posture is a **mode** chosen at intake, folded into `build.md`:
102
102
 
103
103
  - **greenfield** (default) — present-tense everywhere, no phantom history. Execution sections closed.
104
104
  - **brownfield** — migration vocabulary is legitimate in `build.md`; other pillars still describe the target.
@@ -108,9 +108,9 @@ v1 applied a present-tense "greenfield" writing doctrine unconditionally — whi
108
108
 
109
109
  ---
110
110
 
111
- ## Harvest (optional)
111
+ ## Harvest
112
112
 
113
- By default the session folder _is_ the product — self-contained, resumed by the next `/brainstorm start`. When the repo is v9-harnessed (`docs/architecture/` + `docs/modules/` exist) the skill _offers_ the harvest adapter at `stop`, which routes pillars into the doc tree (ADRs, module pillars, plans, the `boundary` skill). See `harvest.md`. The adapter is never assumed.
113
+ The session writes a **kept** design record at `docs/brainstorm/{topic}/` (one subfolder per topic). At `stop` the skill _offers_ the harvest adapter, which distils the pillars' conclusions into the doc tree (ADRs, module pillars, plans, the `boundary` skill reads contracts from source). The `docs/brainstorm/{topic}/` record is **kept** as the provenance — never deleted. See `harvest.md`.
114
114
 
115
115
  ---
116
116
 
@@ -128,9 +128,11 @@ By default the session folder _is_ the product — self-contained, resumed by th
128
128
  ## Files
129
129
 
130
130
  ```
131
- brainstorm/
131
+ brainstorm/ # the skill (in .claude/skills/)
132
132
  ├── SKILL.md # the skill definition Claude Code loads (~1.8k words)
133
133
  ├── README.md # this file
134
- ├── harvest.md # optional v9 doc-tree adapter
135
- └── templates/ # pillar templates copied into the session folder on first use
134
+ ├── harvest.md # doc-tree harvest adapter (record kept at docs/brainstorm/{topic}/)
135
+ └── templates/ # pillar templates copied into the session record on first use
136
136
  ```
137
+
138
+ The session itself is written to `docs/brainstorm/{topic}/` (kept, committed) — not here.
@@ -50,7 +50,7 @@ Then propose the **aspect map**: the thinking dimensions for _this specific idea
50
50
 
51
51
  ### 3. Bootstrap
52
52
 
53
- Session folder: `./brainstorm/` by default (or `./plan/`, `./design/`, custom — reuse any folder containing `state.md`; confirm in one line). Create only the pillars the complexity calls for, from `templates/` (never overwrite existing files; if a template is missing, create the file from the field lists in **Pillars**). Write top-level `state.md`:
53
+ Session folder: `docs/brainstorm/{topic}/` — a **committed, kept** design record, one subfolder per design topic (slug the topic; confirm it in one line). Resume an existing topic by reusing its folder if it already holds a `state.md`. The record stays in the repo after harvest (it is the provenance the steering pillars distil from — see `harvest.md`), so it lives under `docs/` like any other durable project knowledge, not at the repo root. Create only the pillars the complexity calls for, from `templates/` (never overwrite existing files; if a template is missing, create the file from the field lists in **Pillars**). Write top-level `state.md`:
54
54
 
55
55
  ```
56
56
  STATE: open
@@ -121,7 +121,7 @@ Zero means _"audited against the four sources and clean"_ — never _"nothing ca
121
121
 
122
122
  1. Run a final sync (wait for any in-flight sync first).
123
123
  2. **Coherent** → say so and proceed. **Questions open** → list them plainly (English first, codes in parentheses) and offer per question: resolve now (mini-converge) / park explicitly (backlog or non-goals, with reason) / leave open and stop anyway — the folder keeps full state and resumes exactly where it left off.
124
- 3. **Harvest** — see `harvest.md`. Standalone by default: the session folder _is_ the product. If the repo is v9-harnessed (`docs/architecture/` + `docs/modules/` exist), offer the adapter routing; never assume it.
124
+ 3. **Harvest** — see `harvest.md`. The harvest distils the pillars' conclusions into the live steering docs by scope (`docs/architecture/`, `docs/architecture/adr/`, `docs/history/backlog.md`); the `docs/brainstorm/{topic}/` record itself is **kept** (committed), never deleted — it is the provenance the harvested headlines trace back to and the seed the next session resumes from.
125
125
  4. Write `STATE: closed` + one-line reason. Print: _"Run `/brainstorm start` to resume."_
126
126
 
127
127
  ## Conduct — holds for the whole session
@@ -1,18 +1,17 @@
1
1
  # Harvest adapter
2
2
 
3
- Harvest is **optional and offered, never assumed**. By default the session folder is the product: a self-contained registry the next `/brainstorm start` resumes from. Run this adapter only when (a) the repo is v9-harnessed — `docs/architecture/` and `docs/modules/` exist — and (b) the user accepts the offer at `stop`.
3
+ Harvest distils a closed session's **conclusions** into the live steering docs. It is **offered at `stop`, never forced** — but the `docs/brainstorm/{topic}/` record is **kept either way**: it is a committed design record, not a scratchpad, so harvest copies its conclusions outward and leaves the record in place as their provenance. (In a code-harnessed repo `docs/architecture/` and its `adr/` already exist, so the routing below always applies.)
4
4
 
5
5
  ## Routing
6
6
 
7
- The harvest is a structured diff between the session pillars and the live docs, routed by scope: module-local findings → `docs/modules/{m}/pillars/`, cross-module + IA/contract changes → `docs/architecture/`, backlog → `docs/history/`.
7
+ The harvest is a structured diff between the session pillars and the live docs, routed by scope: doctrine / glossary / non-goals / IA / contract changes → `docs/architecture/`, decisions → `docs/architecture/adr/`, backlog → `docs/history/backlog.md`. Modules are labels, not folders — a finding scoped to one module's context still lands in `docs/architecture/` and names the module in prose; there is no module-local doc home.
8
8
 
9
9
  | Session source | Destination |
10
10
  | --- | --- |
11
11
  | `thesis.md` narrative | `docs/history/plans/YYYY-MM-DD-{slug}.md` (Status: Draft, `documentation` skill §8 Plan template) |
12
12
  | `domain-model.md` | `docs/architecture/domain-model.md`; if it changes the IA or a cross-cutting contract, note the contract change there — the `boundary` skill will pick it up from source at the next plan check |
13
- | Decisions spanning 2+ modules | one ADR per cohesive theme → `docs/history/adr/ADR-NNN-{slug}.md` (a historical decision record, not the deployed-design SOT in `docs/architecture/`), authored to the **`documentation` skill §10 ADR template** (frontmatter `status:` is the lifecycle source of truth — no body Status line; required sections Decision/Context/Consequences); rejected alternatives → the ADR's **Options Considered** table |
14
- | Module-local decisions / doctrine / glossary / rejected | `docs/modules/{m}/pillars/{decisions,doctrine,glossary,rejected}.md` |
15
- | Cross-module doctrine / glossary | `docs/architecture/domain-model.md` / `docs/architecture/glossary.md` |
13
+ | Decisions (any scope) | one ADR per cohesive theme → `docs/architecture/adr/NNN-{slug}.md`, authored to the **`documentation` skill ADR template** (frontmatter `status:` is the lifecycle source of truth — no body Status line; required sections Decision/Context/Consequences); rejected alternatives → the ADR's **Options Considered** table |
14
+ | Doctrine / glossary / rejected | `docs/architecture/domain-model.md` / `docs/architecture/glossary.md`, or the relevant ADR |
16
15
  | `tech.md` deviations | `docs/architecture/references.md` or the relevant ADR |
17
16
  | `non-goals.md` / `references.md` | `docs/architecture/{non-goals,references}.md` |
18
17
  | `backlog.md` | `docs/history/backlog.md` (P0–P3) |
@@ -23,4 +22,4 @@ Append a changelog entry at `docs/history/changelog/YYYY-MM-DD-brainstorm-harves
23
22
 
24
23
  ## After harvest
25
24
 
26
- Offer to delete the session folder (the dispersed homes now carry the content; the next session primes from them by copying relevant docs into `basis/`). Default to **keep** if the user is unsure — a kept folder simply resumes.
25
+ **Keep the `docs/brainstorm/{topic}/` record — never delete it.** The dispersed homes now carry the distilled conclusions, but the record stays committed as their provenance: a traceable source for every harvested headline, the seed the next `/brainstorm start` on this topic resumes from, and a high-surprisal input the `distiller` ingests. Retire a topic's folder by hand only when the design line is truly dead.
@@ -38,13 +38,13 @@ Distill the inbox's undigested entries into fact-cards. The algorithm is in [`re
38
38
 
39
39
  **Thin or empty inbox ⇒ not a NOOP — distill the decision record.** A project whose sessions produced no reflected lessons is **not** a project with nothing to distill. Architecture decisions and reversals are high-surprisal by nature — they exist precisely because they were _not_ predictable. So when the inbox is thin/empty, also ingest, as surprisal sources for steps 2–5:
40
40
  - `docs/architecture/adr/**` — each accepted ADR is a surprising decision (its `Decision` + **Options Considered** — the rejected-alternatives section in the `documentation` §10 ADR template — carry the prediction error).
41
- - `docs/memory/brainstorm.md` — owner pronouncements, reversals, and discovered constraints recorded outside the inbox. Provenance points at the ADR file / brainstorm section, not a session id. These sources are **never consumed** — reconciliation (step 4) is what keeps a re-run over them idempotent.
41
+ - `docs/brainstorm/{topic}/` — the kept Brainstorming-Mode design records (`pillars/decisions.md`, `rejected.md`, etc.): the reasoning, reversals, and roads-not-taken behind each design call, retained as committed provenance. The richest surprisal source the harness produces. (Where a repo keeps no brainstorm record in `docs/` — e.g. an older transient-workspace harness — fall back to `docs/memory/brainstorm.md`, the owner pronouncements recorded outside the inbox.) Provenance points at the ADR file / brainstorm pillar, not a session id. These sources are **never consumed** — reconciliation (step 4) is what keeps a re-run over them idempotent.
42
42
 
43
43
  2. **Surprisal filter → survivors** (method §1). Present the LLM with (a) a summary of the _current_ fact-set, (b) the undigested entries. Keep only what is **surprising** relative to (a). Predictable ⇒ dropped — no importance scores, no templates, prediction error is the only retention signal. Inbox entries arrive semi-distilled (the reflection hook already dropped routine work), so the filter's job here is narrower: drop what the existing fact-set already predicts. (First run, empty fact-set: keep decisions, reversals, discovered constraints, and failures with a cause worth remembering.)
44
44
 
45
45
  3. **Decompose survivors → EDU candidates** (method §2). Each survivor becomes **one** atomic, self-contained statement — entity names normalized, minimal context baked into the sentence — with `provenance` pointing at the session ids and commits its inbox entry names. Decomposition, **not** summarization: one survivor may yield several cards; no information is destroyed; everything stays auditable back to its session and commits.
46
46
 
47
- 4. **Reconcile each candidate against existing cards** (method §3). Query the existing facts for semantic neighbors, then decide **ADD / UPDATE / SUPERSEDE / NOOP** for each. Gate on the **facts collection holding cards**, not on the binary and not on the collection merely being registered — a present qmd whose `{repo}-facts` collection is _registered but empty_ (the common first run: harness-manager's baseline registers + embeds `{repo}-facts` over a freshly-scaffolded `docs/memory/facts/` that has zero cards) returns nothing useful, exactly like an unregistered collection.
47
+ 4. **Reconcile each candidate against existing cards** (method §3). Query the existing facts for semantic neighbors, then decide **ADD / UPDATE / SUPERSEDE / NOOP** for each. Gate on the **facts collection holding cards**, not on the binary and not on the collection merely being registered — a present qmd whose `{repo}-facts` collection is _registered but empty_ (the common first run: the `maintenance` agent's baseline mode registers + embeds `{repo}-facts` over a freshly-scaffolded `docs/memory/facts/` that has zero cards) returns nothing useful, exactly like an unregistered collection.
48
48
  - **Cards present** — `command -v qmd` succeeds **and** `docs/memory/facts/` holds at least one card (`docs/memory/facts/*.md` is non-empty): query scoped to the collection — `qmd query --format json -c {repo}-facts "<the candidate statement>"`. `{repo}` = the repo's directory name. Scope with `-c` always. (If the query returns nothing on a non-empty card set, the collection is registered-but- unbuilt — treat that as the fallback trigger below, not as "no neighbors.")
49
49
  - **No cards to query** (qmd absent, **or** `docs/memory/facts/*.md` empty — the common first-run case where the facts dir was scaffolded but never distilled into): fall back to filename/grep similarity over `docs/memory/facts/*.md` (slug overlap + entity-name grep), and **note in the report** that reconciliation ran without semantic retrieval. Step 7 builds/refreshes the collection at the end of the run, so the _next_ run — with cards now present — reconciles semantically.
50
50
 
@@ -82,7 +82,7 @@ Distill fact-cards into proposed edits to `docs/` (the cold, human-owned SSoT)
82
82
  - **Supersede ≠ delete.** Bi-temporal only: set `invalid_from:` + `superseded_by:` on the old card and leave the file in place. Nothing is ever removed from `docs/memory/facts/`.
83
83
  - **Findings are questions, not verdicts.** When a candidate appears to contradict an existing card, phrase it as a question (is X now superseded by Y?) — never assert staleness or falsity.
84
84
  - **Never auto-runs.** Invoked by the user, by the Completion flow (kit `CLAUDE.md`), or by accepting the maintenance proposal. No cron, no daemon, no hook self-trigger — a workflow step the agent runs is not a self-trigger.
85
- - **qmd is strictly optional.** Guard every qmd call on `command -v qmd`; absent ⇒ silent skip. Reconciliation additionally needs the `{repo}-facts` collection to hold **cards** — not merely to be registered. "Unbuilt" here means **no cards** (`docs/memory/facts/*.md` empty), not "unregistered": harness-manager's baseline registers + embeds `{repo}-facts` over an empty scaffolded facts dir, so on a qmd-equipped, freshly-baselined repo the collection _appears in_ `qmd collection list` yet indexes zero cards. Binary present but collection cardless (first run) falls back to filename/grep too, same as qmd being absent.
85
+ - **qmd is strictly optional.** Guard every qmd call on `command -v qmd`; absent ⇒ silent skip. Reconciliation additionally needs the `{repo}-facts` collection to hold **cards** — not merely to be registered. "Unbuilt" here means **no cards** (`docs/memory/facts/*.md` empty), not "unregistered": the `maintenance` agent's baseline mode registers + embeds `{repo}-facts` over an empty scaffolded facts dir, so on a qmd-equipped, freshly-baselined repo the collection _appears in_ `qmd collection list` yet indexes zero cards. Binary present but collection cardless (first run) falls back to filename/grep too, same as qmd being absent.
86
86
 
87
87
  ## Output locations
88
88
 
@@ -66,16 +66,16 @@ Read [`method.md`](method.md) (surprisal / EDU / reconciliation / bi-temporal vo
66
66
  ### 1. Resolve the target doc, read the three roots in order
67
67
 
68
68
  1. `method.md` (algorithm vocabulary).
69
- 2. The **target doc** — full current prose + the `docs/routing.md` patterns that map to it (the paths it owns). This is the SSoT you are proposing to revise; you read it before you draft so conflicts surface as questions, not silent overwrites.
69
+ 2. The **target doc** — full current prose + the doc's `owns:` globs (the paths it owns). This is the SSoT you are proposing to revise; you read it before you draft so conflicts surface as questions, not silent overwrites.
70
70
  - `--doc <path>` → that doc.
71
- - `--feature <slug>` / bare → the spec that just flipped to `Active` (Completion names it), or the feature doc whose `docs/routing.md` pattern covers the plan's Proposed-Changes paths. Match the routing patterns against changed paths. Architecture/operations docs own no paths in routing — for those, fall to entity/semantic match.
71
+ - `--feature <slug>` / bare → the spec that just flipped to `Active` (Completion names it), or the feature doc whose `owns:` glob covers the plan's Proposed-Changes paths. Match the `owns:` globs against changed paths. Architecture/operations docs declare no `owns:` — for those, fall to entity/semantic match.
72
72
  - No doc resolves → Edge Case A.
73
73
  3. The **pillar headlines** — the doctrine pillar (`## Pn —` headlines) and the decisions pillar (`## Dn —` headlines) plus each one's one-line Principle/Decision statement. Read **every** decision headline except those marked superseded: a decision carrying a `Reversed by D-M` marker (or a legacy `Status: tentative`) is NOT a binding pillar headline and is skipped; absence of any status marker means the decision is live (the new append-only model has no `Status: committed` field — a live decision is simply one nothing has reversed). Headlines only, never the bodies — this is the conservative contradiction-check substrate (Resolution 4).
74
74
 
75
75
  ### 2. INPUT SELECTION — gather the cards "touching" the target (Resolution 1) — _closes F2, F3, F5_
76
76
 
77
- 1. **Structural retriever (always runs, qmd-independent).** Read every card in `docs/memory/facts/*.md`. A card _structurally touches_ the target when its `provenance` paths or `entities` intersect the target's `docs/routing.md` patterns — match defined precisely in Resolution 1.
78
- 2. **Semantic retriever (qmd, optional).** If `command -v qmd` succeeds, for the target's identity (title + each routing-pattern stem + any `anchors:` query) run `qmd query --format json -c {repo}-facts "<the doc's subject>"`. `{repo}` = repo dir name; scope with `-c` always; repeat `-c` per relevant `{repo}-*` facts collection (the set `qmd collection list` names). qmd absent ⇒ skip silently, note in the report that selection ran structural-only (changes recall, not correctness).
77
+ 1. **Structural retriever (always runs, qmd-independent).** Read every card in `docs/memory/facts/*.md`. A card _structurally touches_ the target when its `provenance` paths or `entities` intersect the target's `owns:` globs — match defined precisely in Resolution 1.
78
+ 2. **Semantic retriever (qmd, optional).** If `command -v qmd` succeeds, for the target's identity (title + each `owns:`-glob stem + any `anchors:` query) run `qmd query --format json -c {repo}-facts "<the doc's subject>"`. `{repo}` = repo dir name; scope with `-c` always; repeat `-c` per relevant `{repo}-*` facts collection (the set `qmd collection list` names). qmd absent ⇒ skip silently, note in the report that selection ran structural-only (changes recall, not correctness).
79
79
  3. **Bi-temporal + digestion gate (F2, F3).** From the union, drop:
80
80
  - any card with `invalid_from` ≠ `null` (superseded — list as "skipped: superseded");
81
81
  - any card whose `digested_into:` already lists the target doc path (already consumed here);
@@ -169,8 +169,8 @@ A card **touches** the target doc iff it satisfies **either** retriever, and is
169
169
 
170
170
  **(a) Structural intersection (exact, always evaluated).** True when ANY of:
171
171
 
172
- 1. **Provenance match (STRONG signal).** A `commit <sha>` entry in the card's `provenance` resolves to changed files (`git show --name-only <sha>` when the commit is local); a changed file under the target's `docs/routing.md` patterns is a match. (`session <id>` entries carry no paths and do not participate; an ADR/brainstorm path matches the patterns directly.) This is strong because the card derives from a commit that edited code the doc owns.
173
- 2. **Entity match (SUPPORTING signal).** A normalized name in the card's `entities` equals (case-insensitively, after kebab/camel normalization) a module/feature stem in one of the target's `docs/routing.md` patterns, the doc title, or a symbol the doc explicitly names. **Bare prose mentions do NOT count** — only declared ownership (its routing patterns), so "touching" can't be triggered by a doc that merely discusses an entity it doesn't own.
172
+ 1. **Provenance match (STRONG signal).** A `commit <sha>` entry in the card's `provenance` resolves to changed files (`git show --name-only <sha>` when the commit is local); a changed file under the target's `owns:` globs is a match. (`session <id>` entries carry no paths and do not participate; an ADR/brainstorm path matches the globs directly.) This is strong because the card derives from a commit that edited code the doc owns.
173
+ 2. **Entity match (SUPPORTING signal).** A normalized name in the card's `entities` equals (case-insensitively, after kebab/camel normalization) a module/feature stem in one of the target's `owns:` globs, the doc title, or a symbol the doc explicitly names. **Bare prose mentions do NOT count** — only declared ownership (its `owns:` globs), so "touching" can't be triggered by a doc that merely discusses an entity it doesn't own.
174
174
 
175
175
  **(b) Semantic neighborhood (qmd, optional, recall-only).** A card returned by `qmd query -c {repo}-facts "<doc subject>"` within qmd's default result window. Semantic hits with **no** structural intersection are still candidates but flagged `[semantic-only]` in the delta — the link is associative, not provenance-grounded, so the reviewer treats it as lower-trust and may reject on link-strength alone.
176
176
 
@@ -189,7 +189,7 @@ A card may touch **several** docs (its entities span modules) — expected, hand
189
189
  | **`## Architecture` / `## Key Functions`** (feature doc) | Additive correction only when a card names a concrete, code-anchored fact (a new function, a moved file). Prefer Current Behavior unless the card is structural. | Code-anchored structure, not prose. |
190
190
  | **A new changelog entry** in `docs/history/changelog/` (via `documentation` skill's Changelog template, `### Added`/`### Modified`/`### Removed`) | Draft a changelog line for a code change a card witnesses, when a plan just completed. | Append-only history; a _new_ file, never an edit. |
191
191
 
192
- **Never writable by distill②** (hard stops): `## Goal`, `## Goals & Non-Goals`, `## Approach`, `## Open Questions`, `## State`, the optional `anchors:` frontmatter (the only frontmatter a doc carries; ownership lives in `docs/routing.md` — maintenance/spec axis — P19), any **pillar** file, any **plan** (frozen), any **existing** changelog entry (frozen history), any **boundary** file. A card implying one of these needs a change becomes a **question** (below) or, for pillars, an **escape item** (Resolution 4).
192
+ **Never writable by distill②** (hard stops): `## Goal`, `## Goals & Non-Goals`, `## Approach`, `## Open Questions`, `## State`, the focal frontmatter (`owns`/`module`/`keywords`/`status`) and the optional `anchors:` block (ownership and discovery live there, on the spec/maintenance axis, not the distiller's — P19), any **pillar** file, any **plan** (frozen), any **existing** changelog entry (frozen history), any **boundary** file. A card implying one of these needs a change becomes a **question** (below) or, for pillars, an **escape item** (Resolution 4).
193
193
 
194
194
  **Per-line provenance (the invariant, mechanized).** Every drafted line carries `[from: fact-<slug>]` for each contributing card. A line with no such tag is a **defect** — not presented (Acceptance A2). On accept the tag is stripped from the final doc prose (it was review scaffolding); the durable forward-link survives as `digested_into:` (Resolution 3) — so traceability is preserved without polluting human-owned prose.
195
195
 
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: documentation
3
- description: Template & generator library for technical docs — the skill agents call to produce a doc from a template. Use to (1) document a feature or module, (2) create or update a changelog entry, (3) document data flows or Redux state, (4) add to protected files, (5) record an architecture decision (ADR), (6) create or update the project root README, (7) create or update a plan, (8) derive the routing map (code path → canonical doc + module) for `docs/routing.md` from code, (9) scaffold a standard doc (README, structure.md, routing.md, CHANGELOG, backlog, memory) from its template, or (10) co-author any technical doc. Baselining / reconciling a repo's docs to the installed harness version is the **`harness-manager` agent** (which calls this skill for each template) — this skill does not orchestrate baseline. Triggers on "document this", "create docs", "update changelog", "add to protected files", "ADR", "architecture decision", "root README", "plan", "feature doc".
3
+ description: Template & generator library for technical docs — the skill agents call to produce a doc from a template. Use to (1) document a feature or module, (2) create or update a changelog entry, (3) document data flows or Redux state, (4) add to protected files, (5) record an architecture decision (ADR), (6) create or update the project root README, (7) create or update a plan, (8) derive a feature doc's `owns:` frontmatter (the code-path globs it owns) from code, (9) scaffold a standard doc (README, structure.md, CHANGELOG, backlog, memory) from its template, or (10) co-author any technical doc. Baselining / reconciling a repo's docs to the installed harness version is the **`maintenance` agent (baseline mode)** (which calls this skill for each template) — this skill does not orchestrate baseline. Triggers on "document this", "create docs", "update changelog", "add to protected files", "ADR", "architecture decision", "root README", "plan", "feature doc".
4
4
  ---
5
5
 
6
6
  # Technical Documentation Skill
7
7
 
8
- **This skill is a template & generator library, not an orchestrator.** It supplies the templates, the routing-map-derivation generator, and the co-authoring workflow that _agents_ call to produce individual docs. It does **not** baseline, audit, or reconcile a repo's doc tree — that is the **`harness-manager` agent**, which calls this skill for each template it scaffolds. Reach here to generate _one_ doc from a template; reach for `harness-manager` to bring a whole repo into shape.
8
+ **This skill is a template & generator library, not an orchestrator.** It supplies the templates, the frontmatter-derivation generator, and the co-authoring workflow that _agents_ call to produce individual docs. It does **not** baseline, audit, or reconcile a repo's doc tree — that is the **`maintenance` agent (baseline mode)**, which calls this skill for each template it scaffolds. Reach here to generate _one_ doc from a template; reach for the `maintenance` agent (baseline mode) to bring a whole repo into shape.
9
9
 
10
10
  Document contracts and architecture, not implementation details. Show function signatures and API shapes — never paste full implementations (they rot fast). Use ASCII diagrams over prose for flows. Only document state at persistence boundaries (Redux persistence, DB schemas, API shapes), not component-level or transient state. Mark deprecated fields with `// UNUSED:` comments. Breaking changes belong in changelogs only, not module docs.
11
11
 
12
- **No redirect docs.** When moving a doc to a new location, delete the old file entirely. Update all references (`docs/routing.md`, memory files, cross-doc links) to point to the new path. Never leave a stub file that says "Moved to X" — redirect docs rot, clutter the repo, and create false search hits.
12
+ **No redirect docs.** When moving a doc to a new location, delete the old file entirely. Update all references (cross-doc links, memory files) to point to the new path. Never leave a stub file that says "Moved to X" — redirect docs rot, clutter the repo, and create false search hits.
13
13
 
14
14
  **Present tense, current state only.** Active docs (everything outside `docs/history/`) describe the system _as it is now_ — what exists and how it works today. They never narrate how it got there: no migration/fold history, no dated "shipped/verified/built on YYYY-MM-DD" markers, no "ex-`@nurix/X`, folded 2026-06-12" provenance, no "was X → now Y" change journeys, no "removed/deleted in the <date> fold". The historical record — when and how things changed — lives **only** in `docs/history/` (changelog, plans, ADRs); `docs/memory/` is the agent's dated log and is likewise exempt. A doc may cite an ADR for the _why_ (the decision rationale), but must not narrate the _when/how it changed_. Routing/index docs point only to current authority docs — never to a historical plan or changelog as the "read first" source. When you find historical narration in an active doc, rewrite it to the present-tense fact and let `docs/history/` carry the story.
15
15
 
@@ -20,7 +20,7 @@ Document contracts and architecture, not implementation details. Show function s
20
20
  ```
21
21
  User request
22
22
  │
23
- ├─ "Baseline/reconcile docs" ──────▶ harness-manager agent (it calls this skill per template)
23
+ ├─ "Baseline/reconcile docs" ──────▶ maintenance agent (baseline mode) (it calls this skill per template)
24
24
  │
25
25
  ├─ "Document feature/module" ──────▶ Module Template
26
26
  │
@@ -54,25 +54,25 @@ User request
54
54
 
55
55
  ---
56
56
 
57
- ## Routing-Map Derivation
57
+ ## Frontmatter Derivation
58
58
 
59
- When creating or updating a feature doc, derive its **`docs/routing.md` File-Pattern Routes row(s)** — the code paths it is canonical for — from the source the doc describes. Code ownership lives in `routing.md`, never in feature-doc frontmatter.
59
+ When creating or updating a feature doc, derive its **`owns:` frontmatter** — the code-path globs it is canonical for — from the source the doc describes. Code ownership lives in each feature doc's `owns:` frontmatter (the SessionStart hook unions them into the injected ownership index); there is no hand-maintained ownership map.
60
60
 
61
61
  ### When to run
62
62
 
63
- - **Baseline / reconcile** — for every feature doc whose code paths aren't yet mapped in `routing.md` (the `harness-manager` agent calls this)
64
- - **New feature doc** — when creating a doc via the Feature/Module template, add its routing row(s)
65
- - **Doc update** — when the documented scope changes, update the doc's routing row(s)
63
+ - **Baseline / reconcile** — for every feature doc whose code paths aren't yet declared in its `owns:` frontmatter (the `maintenance` agent (baseline mode) calls this)
64
+ - **New feature doc** — when creating a doc via the Feature/Module template, project its `owns:` globs
65
+ - **Doc update** — when the documented scope changes, update the doc's `owns:` globs
66
66
 
67
- ### How to derive a row
67
+ ### How to derive the frontmatter
68
68
 
69
- A routing row is `| Pattern | Module | Canonical Doc | Memory |`.
69
+ A feature doc's launch-map frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required).
70
70
 
71
- **1. Pattern** (the path scope — the old `owns`)
71
+ **1. `owns:`** (the path scope)
72
72
 
73
- Read the doc's content. Extract every source file path it references (file listings, function locations, component paths). Group them into the narrowest glob patterns that cover all referenced files without overlapping another doc's pattern at equal specificity. A doc may need several rows.
73
+ Read the doc's content. Extract every source file path it references (file listings, function locations, component paths). Group them into the narrowest glob patterns that cover all referenced files without overlapping another doc's `owns:` globs at equal specificity. A doc may own several globs.
74
74
 
75
- | Doc references | Pattern |
75
+ | Doc references | Glob |
76
76
  | ------------------------------------ | ------------------------- |
77
77
  | Multiple files under one directory | `src/feature/**` |
78
78
  | Specific files in a shared directory | List each file explicitly |
@@ -80,22 +80,22 @@ Read the doc's content. Extract every source file path it references (file listi
80
80
 
81
81
  Glob each pattern against the codebase; drop any that match nothing.
82
82
 
83
- **2. Module**
83
+ **2. `module:`**
84
84
 
85
- The declared module the pattern belongs to (the `docs/modules/{m}/` it lives under), or `—` if the repo is flat. Rows sharing a Module value are what ties that module to code.
85
+ The single bounded-context label this feature belongs to, or omit it for a cross-cutting feature that belongs to no single context. A module is a label, not a folder — features sharing a `module:` value are what ties that module to code (a filter over the generated ownership index).
86
86
 
87
- **3. Canonical Doc & Memory**
87
+ **3. `keywords:` & `status:`**
88
88
 
89
- Canonical Doc = this doc's path (`features/…` when flat, `modules/{m}/features/…` when the module is declared). Memory = the `docs/memory/` file for the area, or `—`.
89
+ `keywords:` are the discovery terms topic→doc lookup rides. `status:` is `active`, `draft`, or `deprecated` — it steers the model toward live docs and away from draft/deprecated ones.
90
90
 
91
- > The old `touches`/`depends_on` relationships are no longer recorded as ownership. A doc's dependencies on other docs are expressed inline in its prose (links); cross-cutting contracts are diagnosed by the `boundary` skill from source, not persisted.
91
+ A doc's dependencies on other docs are expressed inline in its prose (links); cross-cutting contracts are diagnosed by the `boundary` skill from source, not persisted.
92
92
 
93
93
  ### Rules
94
94
 
95
- - One canonical doc per path. A pattern must not overlap another row at equal specificity — the narrower pattern wins; equal-specificity overlap is a conflict to resolve.
95
+ - One canonical doc per path. A doc's `owns:` globs must not overlap another doc's at equal specificity — the narrower glob wins; equal-specificity overlap is an ownership conflict to resolve.
96
96
  - Use glob patterns for directories, explicit paths for individual files. Don't list paths that don't exist in the codebase.
97
- - When a doc covers a deep feature with sub-specs (e.g., `editor/`), each sub-spec gets its own row(s) — don't duplicate a path across siblings.
98
- - For baseline/reconcile with many docs, use parallel sub-agents to read source and propose rows.
97
+ - When a doc covers a deep feature with sub-specs (e.g., `editor/`), each sub-spec owns its own globs — don't duplicate a path across siblings.
98
+ - For baseline/reconcile with many docs, use parallel sub-agents to read source and propose `owns:` globs.
99
99
 
100
100
  ---
101
101
 
@@ -119,6 +119,6 @@ For docs that don't match templates:
119
119
 
120
120
  | File | Use For |
121
121
  | --- | --- |
122
- | `references/templates.md` | docs/README.md, structure.md, routing.md, scaffolds (CHANGELOG, backlog, memory), feature, changelog, plan, protected files, ADR, project root README templates |
122
+ | `references/templates.md` | docs/README.md, structure.md, the feature-frontmatter contract, scaffolds (CHANGELOG, backlog, memory), feature, changelog, plan, protected files, ADR, project root README templates |
123
123
  | `references/style-guide.md` | Writing rules, formatting conventions |
124
124
  | `references/examples.md` | Good vs bad documentation examples |