@nurix/etna 0.1.2 → 0.2.0

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 (32) hide show
  1. package/README.md +34 -27
  2. package/bin/etna.js +82 -52
  3. package/data/harness-index.json +2 -8
  4. package/data/harnesses/code/CLAUDE.md +18 -9
  5. package/data/harnesses/code/agents/change-review.md +80 -0
  6. package/data/harnesses/code/agents/contract-monitor.md +65 -0
  7. package/data/harnesses/code/agents/dep-health.md +148 -0
  8. package/data/harnesses/code/agents/git-history.md +201 -0
  9. package/data/harnesses/code/agents/maintenance.md +112 -0
  10. package/data/harnesses/code/agents/review.md +109 -0
  11. package/data/harnesses/code/agents/spec-mode.md +56 -0
  12. package/data/harnesses/code/rules/general.md +96 -0
  13. package/data/harnesses/code/rules/nomenclature.md +34 -0
  14. package/data/harnesses/code/scripts/build-ownership-index.sh +138 -0
  15. package/data/harnesses/code/scripts/check-doc-drift.sh +125 -0
  16. package/data/harnesses/code/skills/documentation/SKILL.md +238 -0
  17. package/data/harnesses/code/skills/documentation/references/examples.md +193 -0
  18. package/data/harnesses/code/skills/documentation/references/style-guide.md +288 -0
  19. package/data/harnesses/code/skills/documentation/references/templates.md +901 -0
  20. package/data/harnesses/code/skills/introspect/SKILL.md +135 -0
  21. package/data/harnesses/code/skills/workflow-mapper/SKILL.md +430 -0
  22. package/data/harnesses/code/skills/workflow-mapper/references/patterns.md +214 -0
  23. package/data/harnesses/kb-agi/CLAUDE.md +57 -0
  24. package/data/harnesses/kb-agi/agents/kb-agi-init.md +276 -0
  25. package/data/harnesses/kb-agi/agents/kb-catch-stale.md +22 -0
  26. package/data/harnesses/kb-agi/agents/kb-find-gaps.md +27 -0
  27. package/data/harnesses/kb-agi/agents/kb-fix-links.md +22 -0
  28. package/data/harnesses/kb-agi/agents/kb-refresh-frontdoor.md +22 -0
  29. package/data/harnesses/kb-agi/agents/kb-refresh-summaries.md +22 -0
  30. package/data/harnesses/kb-agi/agents/kb-sync-metadata.md +25 -0
  31. package/data/harnesses/kb-agi/rules/kb.md +13 -0
  32. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,6 @@
1
1
  # @nurix/etna
2
2
 
3
- CLI to bootstrap a project with a Nurix harness. Produces the same on-disk state as running
4
-
5
- ```
6
- /plugin marketplace add nurixlabs/skills
7
- /plugin install nurix-harness@nurixlabs
8
- ```
9
-
10
- inside Claude Code, plus drops the harness's `CLAUDE.md` at the project root. See [`../../harness/README.md`](../../harness/README.md) for what a harness is and why the install model is designed this way.
3
+ CLI to bootstrap a project with a Nurix harness. Copies the harness's `CLAUDE.md`, skills, agents, rules, and scripts into the project so they live with the code — version-controllable, editable per project, and visible without leaving the repo.
11
4
 
12
5
  ## Usage
13
6
 
@@ -17,46 +10,60 @@ From the root of the project you want to bootstrap:
17
10
  npx @nurix/etna nurix-harness
18
11
  ```
19
12
 
20
- After this completes, three things have changed on disk:
13
+ After this completes:
21
14
 
22
15
  ```
23
16
  your-project/
24
- ├── CLAUDE.md ← bundled in this package; the harness's policy
17
+ ├── CLAUDE.md ← harness policy (backup of any existing file is kept)
25
18
  └── .claude/
26
- └── settings.json ← enabledPlugins["nurix-harness@nurixlabs"] = true
27
-
28
- ~/.claude/settings.json ← extraKnownMarketplaces["nurixlabs"] registered
19
+ ├── skills/ ← documentation, introspect, workflow-mapper
20
+ ├── agents/ ← spec-mode, maintenance, contract-monitor, …
21
+ ├── rules/ ← general.md, nomenclature.md
22
+ └── scripts/ ← build-ownership-index.sh, check-doc-drift.sh
29
23
  ```
30
24
 
31
- That's the entire footprint. Open Claude Code in the project; on next session start it resolves `nurix-harness@nurixlabs`, clones `nurixlabs/skills` into `~/.claude/plugins/marketplaces/`, fills the plugin cache at `~/.claude/plugins/cache/nurixlabs/nurix-harness/<version>/`, and loads the plugin. The CLI does not touch any of those Claude Code internals itself — that's deliberate (see [Installation goals](../../harness/README.md#installation-goals)).
25
+ That's the entire footprint. Nothing is written outside the project. No marketplace registration, no `enabledPlugins` entry, no global cache dependency — Claude Code picks up project-scoped agents/skills/rules/scripts on its next session start.
32
26
 
33
- If Claude Code is already running, run `/reload-plugins`.
27
+ Re-run `npx @nurix/etna nurix-harness` anytime to refresh the harness content with what the latest published package ships. Existing same-named files are overwritten; files you've added that aren't part of the harness are left alone. The previous `CLAUDE.md` is backed up to `CLAUDE.md.bak.<ISO-timestamp>`.
28
+
29
+ ## Migrating from 0.1.x
30
+
31
+ `0.1.x` registered the harness as a Claude Code plugin via the `nurixlabs/skills` marketplace. `0.2.0` switches to direct file installation. When you re-run `etna` on a project that has a `0.1.x` install, the CLI removes the stale `enabledPlugins["<plugin>@nurixlabs"]` entry from `<project>/.claude/settings.json` so Claude Code doesn't double-load the same content from the global plugin cache.
32
+
33
+ The `nurixlabs` marketplace entry in `~/.claude/settings.json` is harmless to leave (it doesn't load anything by itself) — remove it manually if you want.
34
34
 
35
35
  ## Available harnesses
36
36
 
37
37
  Listed by `npx @nurix/etna` (no args) using the bundled `data/harness-index.json`.
38
38
 
39
- | Plugin name | Harness folder in etna | What it ships |
39
+ | Plugin name | Source in etna | What it ships |
40
40
  |---|---|---|
41
- | `nurix-harness` | [`harness/code/`](../../harness/code/) | The canonical v7 harness — governance agents, baseline workflow skills, ownership/drift scripts |
41
+ | `nurix-harness` | [`plugins/nurix-harness.md`](../../plugins/nurix-harness.md) → top-level catalogs | Governance agents, baseline workflow skills, ownership/drift scripts |
42
+ | `kb-agi` | [`plugins/kb-agi.md`](../../plugins/kb-agi.md) → top-level catalogs | Knowledge-vault scaffolding |
42
43
 
43
44
  ## How it works
44
45
 
45
- 1. Looks up `<plugin-name>` in the bundled `data/harness-index.json` to find the matching harness folder and the marketplace metadata.
46
- 2. Copies the bundled `data/harnesses/<harness-folder>/CLAUDE.md` to `./CLAUDE.md` (backing up any existing one to `CLAUDE.md.bak.<ISO-timestamp>`).
47
- 3. Adds the marketplace entry to `~/.claude/settings.json` under `extraKnownMarketplaces`.
48
- 4. Adds `<plugin-name>@<marketplace>: true` to `./.claude/settings.json` under `enabledPlugins`.
46
+ Each harness has a folder under `etna/harness/<name>/` with:
47
+ - `CLAUDE.md` — the project-root policy file
48
+ - `index.md` — symlinked to `../../plugins/<plugin>.md` (the manifest)
49
49
 
50
- No GitHub fetch. No `.claude/plugins/<name>/` folder. Claude Code's plugin resolver handles all of that on next launch.
50
+ The plugin manifest's YAML frontmatter declares which top-level `agents/`, `skills/`, `rules/`, and `scripts/` belong to that plugin:
51
51
 
52
- ## Why two sources
52
+ ```yaml
53
+ ---
54
+ name: nurix-harness
55
+ skills: [documentation, introspect, workflow-mapper]
56
+ agents: [spec-mode, maintenance, contract-monitor, …]
57
+ scripts: [build-ownership-index, check-doc-drift]
58
+ rules: [general, nomenclature]
59
+ ---
60
+ ```
53
61
 
54
- - **Plugin content** lives in the marketplace (`nurixlabs/skills`) and is fetched by Claude Code at session start — always the latest CI-published version.
55
- - **`CLAUDE.md`** is bundled in this npm package, versioned with the package. A plugin cannot ship a project-root `CLAUDE.md` (Claude Code's plugin spec doesn't support it), so the CLI ships that half itself. Harness policy changes warrant a package version bump.
62
+ `scripts/build-data.js` (run by `prepack`) resolves each manifest against the catalogs and bundles the result into `data/harnesses/<folder>/`. The installer (`bin/etna.js`) reads from that bundle at install time. No GitHub fetch at runtime — the published npm package contains everything.
56
63
 
57
64
  ## Building / contributing
58
65
 
59
- `data/` is generated. Don't edit by hand. To regenerate after touching anything under `etna/harness/`:
66
+ `data/` is generated. Don't edit by hand. To regenerate after editing the etna catalogs or manifests:
60
67
 
61
68
  ```sh
62
69
  cd packages/cli
@@ -67,4 +74,4 @@ npm run build:data
67
74
 
68
75
  ## Publishing
69
76
 
70
- CI auto-publishes on push to `dev` when `packages/cli/**` or `harness/**` change — see [`.github/workflows/publish-etna-to-npm.yml`](../../.github/workflows/publish-etna-to-npm.yml). The `publishConfig.access` is `restricted` so the scoped package is private by default.
77
+ CI auto-publishes on push to `dev` when `packages/cli/**` or `harness/**` change — see [`.github/workflows/publish-etna-to-npm.yml`](../../.github/workflows/publish-etna-to-npm.yml). The `publishConfig.access` is `restricted` so the scoped package is private by default. The `NPM_PUBLISH_TOKEN` GitHub Actions secret must be a granular access token with **Bypass 2FA** enabled — classic automation tokens are rejected by the npmjs.com 2FA policy on the `nurix-tools` account.
package/bin/etna.js CHANGED
@@ -1,12 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  // packages/cli/bin/etna.js
3
- // `npx @nurix/etna <plugin-name>` — bootstrap a Nurix harness in the current directory.
4
- // Produces the same on-disk state as running `/plugin marketplace add <repo>` followed by
5
- // `/plugin install <plugin>@<marketplace>` inside Claude Code, plus drops the harness's
6
- // CLAUDE.md at the project root. Claude Code's plugin resolver fills the cache on next launch.
7
-
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync, lstatSync } from 'node:fs';
9
- import { homedir } from 'node:os';
3
+ // `npx @nurix/etna <plugin-name>` — copy a Nurix harness into the current
4
+ // directory. Drops the harness's CLAUDE.md at the project root and copies its
5
+ // skills, agents, rules, and scripts into <project>/.claude/. No marketplace,
6
+ // no plugin enablement — files live in the project, version-controlled and
7
+ // editable per project. Re-run `etna` to refresh.
8
+
9
+ import {
10
+ existsSync,
11
+ mkdirSync,
12
+ readFileSync,
13
+ writeFileSync,
14
+ renameSync,
15
+ copyFileSync,
16
+ cpSync,
17
+ readdirSync,
18
+ lstatSync,
19
+ } from 'node:fs';
10
20
  import { join, dirname, resolve } from 'node:path';
11
21
  import { fileURLToPath } from 'node:url';
12
22
 
@@ -14,17 +24,18 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
14
24
  const pkgRoot = resolve(__dirname, '..');
15
25
  const dataDir = join(pkgRoot, 'data');
16
26
 
27
+ // Subfolders bundled inside each harness payload that map 1:1 onto
28
+ // <project>/.claude/<name>/. Order matches Claude Code's own folder layout.
29
+ const PLUGIN_SUBDIRS = ['skills', 'agents', 'rules', 'scripts'];
30
+
17
31
  function usage() {
18
32
  const idx = loadHarnessIndex();
19
33
  console.log(`\nUsage: npx @nurix/etna <plugin-name>\n`);
20
34
  const names = Object.keys(idx.plugins);
21
35
  if (names.length) {
22
- console.log(`Available harnesses (plugin → marketplace):`);
23
- for (const name of names) {
24
- console.log(` ${name.padEnd(20)} → ${idx.marketplace.name}`);
25
- }
36
+ console.log(`Available harnesses:`);
37
+ for (const name of names) console.log(` ${name}`);
26
38
  }
27
- console.log(`\nMarketplace: ${idx.marketplace.source.repo} (github)`);
28
39
  console.log();
29
40
  }
30
41
 
@@ -41,6 +52,14 @@ function die(msg, code = 1) {
41
52
  process.exit(code);
42
53
  }
43
54
 
55
+ function isSymlink(path) {
56
+ try {
57
+ return lstatSync(path).isSymbolicLink();
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
44
63
  // Move <path> to <path>.bak.<timestamp> if it exists. Returns the backup path, or null.
45
64
  function backupIfExists(path) {
46
65
  if (!existsSync(path) && !isSymlink(path)) return null;
@@ -50,14 +69,6 @@ function backupIfExists(path) {
50
69
  return backup;
51
70
  }
52
71
 
53
- function isSymlink(path) {
54
- try {
55
- return lstatSync(path).isSymbolicLink();
56
- } catch {
57
- return false;
58
- }
59
- }
60
-
61
72
  function readJson(path) {
62
73
  if (!existsSync(path)) return {};
63
74
  try {
@@ -73,27 +84,44 @@ function writeJson(path, value) {
73
84
  writeFileSync(path, JSON.stringify(value, null, 2) + '\n');
74
85
  }
75
86
 
76
- // Register the marketplace in ~/.claude/settings.json (extraKnownMarketplaces).
77
- // Mirrors what `/plugin marketplace add <owner>/<repo>` writes.
78
- function registerMarketplace(marketplace) {
79
- const path = join(homedir(), '.claude', 'settings.json');
80
- const settings = readJson(path);
81
- settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
82
- settings.extraKnownMarketplaces[marketplace.name] = { source: marketplace.source };
83
- writeJson(path, settings);
84
- return path;
87
+ // Copy the harness's bundled .claude/<subdir>/ contents into <project>/.claude/<subdir>/,
88
+ // merging with whatever's already there. Existing same-named entries are overwritten so
89
+ // re-running `etna` always refreshes harness-provided content. Returns count copied.
90
+ function copyHarnessSubdir(bundledSubdir, projectSubdir) {
91
+ if (!existsSync(bundledSubdir)) return 0;
92
+ mkdirSync(projectSubdir, { recursive: true });
93
+ let copied = 0;
94
+ for (const entry of readdirSync(bundledSubdir, { withFileTypes: true })) {
95
+ const src = join(bundledSubdir, entry.name);
96
+ const dest = join(projectSubdir, entry.name);
97
+ cpSync(src, dest, { recursive: true, force: true });
98
+ copied += 1;
99
+ }
100
+ return copied;
85
101
  }
86
102
 
87
- // Add `<plugin>@<marketplace>: true` to <target>/.claude/settings.json under enabledPlugins.
88
- // Mirrors what `/plugin install <plugin>@<marketplace>` writes at project scope.
89
- function enablePlugin(targetDir, pluginName, marketplaceName) {
90
- const path = join(targetDir, '.claude', 'settings.json');
91
- const settings = readJson(path);
92
- settings.enabledPlugins = settings.enabledPlugins || {};
93
- const key = `${pluginName}@${marketplaceName}`;
94
- settings.enabledPlugins[key] = true;
95
- writeJson(path, settings);
96
- return { path, key };
103
+ // Clean up any leftover marketplace/enabledPlugins entries from a previous 0.1.x
104
+ // install. After 0.2.0 we don't use the marketplace flow at all — leaving stale
105
+ // entries causes Claude Code to also resolve the plugin from the global cache,
106
+ // double-loading the same skills under a different namespace.
107
+ function migrateFromPluginInstall(targetDir, pluginName) {
108
+ const removed = [];
109
+ const projectSettingsPath = join(targetDir, '.claude', 'settings.json');
110
+ if (existsSync(projectSettingsPath)) {
111
+ const s = readJson(projectSettingsPath);
112
+ if (s.enabledPlugins) {
113
+ const keys = Object.keys(s.enabledPlugins).filter((k) =>
114
+ k.startsWith(`${pluginName}@`),
115
+ );
116
+ for (const k of keys) {
117
+ delete s.enabledPlugins[k];
118
+ removed.push(`enabledPlugins["${k}"] in ${projectSettingsPath}`);
119
+ }
120
+ if (Object.keys(s.enabledPlugins).length === 0) delete s.enabledPlugins;
121
+ writeJson(projectSettingsPath, s);
122
+ }
123
+ }
124
+ return removed;
97
125
  }
98
126
 
99
127
  async function main(args) {
@@ -111,34 +139,36 @@ async function main(args) {
111
139
  );
112
140
  }
113
141
 
114
- const bundledClaude = join(dataDir, 'harnesses', harnessFolder, 'CLAUDE.md');
115
- if (!existsSync(bundledClaude)) {
142
+ const bundledHarnessDir = join(dataDir, 'harnesses', harnessFolder);
143
+ const bundledClaudeMd = join(bundledHarnessDir, 'CLAUDE.md');
144
+ if (!existsSync(bundledClaudeMd)) {
116
145
  die(`Bundled CLAUDE.md missing for harness '${harnessFolder}'. Rebuild package data.`);
117
146
  }
118
147
 
119
148
  const targetDir = process.cwd();
120
149
  const claudeMdDest = join(targetDir, 'CLAUDE.md');
121
- const marketplace = idx.marketplace;
122
- const fqKey = `${pluginName}@${marketplace.name}`;
123
150
 
124
- console.log(`\nInstalling ${fqKey} into ${targetDir}`);
151
+ console.log(`\nInstalling ${pluginName} into ${targetDir}`);
125
152
 
126
153
  const claudeBackup = backupIfExists(claudeMdDest);
127
154
  if (claudeBackup) console.log(` backed up existing CLAUDE.md → ${claudeBackup}`);
128
155
 
129
- copyFileSync(bundledClaude, claudeMdDest);
156
+ copyFileSync(bundledClaudeMd, claudeMdDest);
130
157
  console.log(` installed CLAUDE.md → ${claudeMdDest}`);
131
158
 
132
- const marketplacePath = registerMarketplace(marketplace);
133
- console.log(` registered marketplace '${marketplace.name}' → ${marketplacePath}`);
159
+ for (const sub of PLUGIN_SUBDIRS) {
160
+ const count = copyHarnessSubdir(
161
+ join(bundledHarnessDir, sub),
162
+ join(targetDir, '.claude', sub),
163
+ );
164
+ if (count > 0) console.log(` copied ${count} ${sub} → ${targetDir}/.claude/${sub}/`);
165
+ }
134
166
 
135
- const { path: enablePath } = enablePlugin(targetDir, pluginName, marketplace.name);
136
- console.log(` enabled '${fqKey}' → ${enablePath}`);
167
+ const migrated = migrateFromPluginInstall(targetDir, pluginName);
168
+ for (const entry of migrated) console.log(` removed stale ${entry}`);
137
169
 
138
170
  console.log(`\nDone. Open Claude Code in ${targetDir}.`);
139
- console.log(
140
- `It will fetch ${marketplace.source.repo}, install ${pluginName}, and load it on next session start.\n`,
141
- );
171
+ console.log(`Re-run \`npx @nurix/etna ${pluginName}\` anytime to refresh harness content.\n`);
142
172
  }
143
173
 
144
174
  main(process.argv.slice(2)).catch((e) => {
@@ -1,12 +1,6 @@
1
1
  {
2
- "marketplace": {
3
- "name": "nurixlabs",
4
- "source": {
5
- "source": "github",
6
- "repo": "nurixlabs/skills"
7
- }
8
- },
9
2
  "plugins": {
10
- "nurix-harness": "code"
3
+ "nurix-harness": "code",
4
+ "kb-agi": "kb-agi"
11
5
  }
12
6
  }
@@ -73,6 +73,13 @@ Feature docs in `docs/features/` must include YAML frontmatter with `owns`, `tou
73
73
 
74
74
  The spec-mode agent checks for ownership conflicts during research. If conflicts are found, address them in the spec before proceeding.
75
75
 
76
+ ### Architecture Decisions (ADRs)
77
+
78
+ Record an ADR when a decision (a) changes how state, dependencies, or boundaries are organized, (b) has a rejected alternative worth remembering, and (c) could plausibly be re-litigated. Default: don't write one. Style preferences, local refactors, and obvious-from-code choices don't qualify.
79
+
80
+ - File: `docs/architecture/adr/NNNN-kebab-case.md` (4-digit sequential number). Frozen once written — supersede via a new ADR, never edit.
81
+ - Invoke the `documentation` skill with "record an ADR" or "architecture decision".
82
+
76
83
  ---
77
84
 
78
85
  ## Agents
@@ -88,7 +95,7 @@ Seven sub-agents and three skills are defined under `.claude/`. Reach for them b
88
95
  | `dep-health` | Agent | Dependency supply-chain audit | "audit dependencies", "check CVEs", "dep health" |
89
96
  | `git-history` | Agent | Classify recent commits into a velocity/severity CSV | "classify commits", "commit history report" |
90
97
  | `change-review` | Agent | Velocity heatmap, risk zones (high churn + low coverage) | "where is risk accumulating", "velocity report" |
91
- | `documentation` | Skill | Baseline audit, scaffolding, templates | "baseline docs", "create docs", "audit docs" |
98
+ | `documentation` | Skill | Baseline audit, scaffolding, templates, ADRs, guardrails | "baseline docs", "create docs", "audit docs", "record an ADR", "architecture decision", "create guardrails" |
92
99
  | `workflow-mapper` | Skill | Discover and diagram codebase workflows | "map workflows", "trace flows" |
93
100
  | `introspect` | Skill | Pre-implementation impact analysis against guardrails | "introspect this plan", "impact analysis" |
94
101
 
@@ -140,20 +147,22 @@ Unless explicitly requested:
140
147
 
141
148
  The six procedural folders below are fixed. Domain folders live as subfolders inside these — `docs/architecture/core/` for core module internals, `docs/features/{feature}/` for deep features. Discovery flows through **[`docs/routing.md`](docs/routing.md)** when present.
142
149
 
143
- | Folder | Purpose | Mutability |
144
- | -------------------- | ------------------------------------------------------ | --------------------- |
145
- | `docs/architecture/` | System-wide patterns, technical decisions, API specs | Living |
146
- | `docs/features/` | How implemented features work today | Living |
147
- | `docs/operations/` | Setup, development, deployment | Living |
148
- | `docs/history/` | Plans, changelogs, backlog | Frozen or append-only |
149
- | `docs/memory/` | Project knowledge + agent state | Living |
150
- | `docs/tests/` | Test plans and automation | Living |
150
+ | Folder | Purpose | Mutability |
151
+ | ------------------------ | -------------------------------------------------------- | --------------------- |
152
+ | `docs/architecture/` | System-wide patterns, technical decisions, API specs | Living |
153
+ | `docs/architecture/adr/` | Architecture Decision Records — numbered, one per file | Frozen (supersede) |
154
+ | `docs/features/` | How implemented features work today | Living |
155
+ | `docs/operations/` | Setup, development, deployment | Living |
156
+ | `docs/history/` | Plans, changelogs, backlog | Frozen or append-only |
157
+ | `docs/memory/` | Project knowledge + agent state | Living |
158
+ | `docs/tests/` | Test plans and automation | Living |
151
159
 
152
160
  ### Naming & Lifecycle
153
161
 
154
162
  - Features: `kebab-case.md` — living reference, status `Active`
155
163
  - Plans: `YYYY-MM-DD-kebab-case.md` — frozen after `Complete` or `Abandoned`
156
164
  - Changelogs: `YYYY-MM-DD-kebab-case.md` — append-only
165
+ - ADRs: `NNNN-kebab-case.md` (4-digit zero-padded number, sequential) — frozen; supersede via a new ADR
157
166
  - Memory: `{agent-name}.md` — agent-maintained
158
167
 
159
168
  ### Documentation Maintenance
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: change-review
3
+ description: Velocity analysis — produces commit heatmap, risk-zone identification (high churn + low guardrail coverage), stabilization signals, team distribution. Consumes `git-history`'s CSV output rather than reanalyzing commits. Use when planning where to invest in better guardrails or rules.
4
+ tools: Read, Glob, Grep, Bash
5
+ model: inherit
6
+ ---
7
+
8
+ # Change-Review Agent
9
+
10
+ You analyze code-change velocity over time to identify risk zones. Where `git-history` classifies *what* changed (module, impact, severity), you ask *where is change accelerating or decelerating* — and whether guardrail coverage matches the risk.
11
+
12
+ You answer: "where should the tech lead invest in better guardrails or rules next?"
13
+
14
+ ## Before Starting
15
+
16
+ 1. If `./docs/memory/change-review.md` exists, read it for historical velocity snapshots, prior risk-zone flags, and known concentration risks.
17
+ 2. If `./docs/memory/git-history.md` or a recent CSV from the git-history agent exists, prefer it as input — don't re-classify commits. If no recent run exists, ask the user to run `git-history` first or fall back to a direct `git log` analysis.
18
+ 3. Read `./docs/operations/guardrails.md` if it exists. Risk-zone scoring is "high churn + low guardrail coverage" — without guardrails, you can only produce a heatmap, not risk zones.
19
+ 4. Read `./docs/OWNERSHIP_INDEX.md` if it exists, to map churn to owning feature docs.
20
+
21
+ ## Step 1: Velocity Heatmap
22
+
23
+ Compute commits-per-module over rolling windows:
24
+
25
+ - **1 week** — what's hot right now
26
+ - **1 month** — current rhythm
27
+ - **3 months** — quarter-level pattern
28
+
29
+ Aggregate by top-level module (`src/{module}/`). Report:
30
+
31
+ | Module | Commits 1w | Commits 1m | Commits 3m | Trend |
32
+ |--------|-----------:|-----------:|-----------:|-------|
33
+
34
+ Trend column: ↑ rising, ↓ falling, → steady, ↑↑ sharply rising, ↓↓ sharply falling.
35
+
36
+ ## Step 2: Risk Zones
37
+
38
+ For each module with high churn (top quartile in 1m window), check guardrail coverage:
39
+
40
+ - Does `docs/operations/guardrails.md` mention this module?
41
+ - Is the module's `owns` doc up to date? (cross-reference with maintenance agent's last drift check if available)
42
+ - Does the module appear in `docs/routing.md`?
43
+
44
+ Flag risk zones: high churn × low coverage. These are where silent breakage is most likely.
45
+
46
+ ## Step 3: Stabilization Signals
47
+
48
+ Identify modules where 1-week and 1-month velocity are both decreasing. Candidates for reducing review overhead — fewer guardrails needed where churn is dropping.
49
+
50
+ ## Step 4: Team Distribution
51
+
52
+ Per module, list which authors made the most commits in the 3-month window. Surface concentration risks:
53
+
54
+ - One author > 70% of commits in a critical module → bus-factor flag
55
+ - One author owns all changes touching a guardrail → review-coverage flag
56
+
57
+ ## Step 5: Report & Track
58
+
59
+ ### Report format
60
+
61
+ - **Velocity heatmap** (table above)
62
+ - **Risk zones**: top 5 high-churn / low-coverage modules with specific guardrails to consider adding
63
+ - **Stabilization signals**: modules where review overhead can decrease
64
+ - **Concentration risks**: bus-factor and review-coverage flags
65
+ - **Recommendations**: prioritized list — typically "add guardrail for X", "update routing.md to cover Y", "spread reviews of Z across more authors"
66
+
67
+ ### Update tracking
68
+
69
+ 1. Write snapshot to `docs/memory/change-review.md` with date and all four sections above
70
+ 2. Append items to `docs/history/backlog.md`:
71
+ - `- **[P1]** No guardrail coverage for high-churn module \`src/{name}/\` — discovered by change-review`
72
+ 3. Report summary to the user
73
+
74
+ ## Rules
75
+
76
+ - Prefer git-history's CSV as input; only fall back to direct `git log` analysis if no recent CSV exists
77
+ - Read-only for source code
78
+ - May write to `docs/memory/change-review.md` and `docs/history/backlog.md`
79
+ - Risk-zone identification requires guardrails — if `guardrails.md` doesn't exist, produce heatmap only and tell the user that risk-zone scoring needs documented guardrails
80
+ - A clean report (no risk zones) is valid for a low-churn, well-covered project
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: contract-monitor
3
+ description: Post-implementation contract auditor. Scans the live codebase for violations of cross-module contracts documented in `docs/operations/guardrails.md` — unregistered entity types, string-coupled constants, cross-entity references without propagation, schema mismatches, undocumented endpoints. Run on request after significant merges. Pairs with the `introspect` skill (which runs pre-implementation).
4
+ tools: Read, Glob, Grep, Edit, Write, Bash
5
+ model: inherit
6
+ ---
7
+
8
+ # Contract Monitor
9
+
10
+ You scan the codebase for cross-module contract violations. Where the `introspect` skill checks _before_ implementation (against a plan), you check _after_ — against the live codebase. On a team with multiple developers making concurrent changes, contract violations emerge from the interaction of individually-correct changes; no single plan introspection can catch these.
11
+
12
+ ## Before Starting
13
+
14
+ 1. Read `./docs/operations/guardrails.md` — this is your source of truth for which contracts exist. If it doesn't exist, stop and tell the user a guardrails doc is required.
15
+ 2. If `./docs/memory/contract-monitor.md` exists, read it for known exceptions, historical violation counts, and patterns the user has chosen not to fix.
16
+ 3. If `./docs/routing.md` exists, read it for module ownership context.
17
+
18
+ ## Scan
19
+
20
+ For each guardrail section in `guardrails.md`:
21
+
22
+ 1. Parse the section — extract contract name, canonical source location, consumer paths, violation description, checklist (if any)
23
+ 2. Scan the codebase for violations of that specific contract. Categories to check:
24
+ - **Unregistered entity types** — strings used as entity-type identifiers that aren't in the canonical array
25
+ - **String-coupled constants** — raw string literals used where shared constant imports are required
26
+ - **Cross-entity references without propagation** — storing entity A's slug/name/id by value in entity B without documented propagation
27
+ - **Schema mismatches** — selectors accessing persisted fields without fallback defaults
28
+ - **Undocumented API endpoints** — routes in code with no corresponding feature doc or guardrail entry
29
+ 3. Verify by reading actual source files — never guess.
30
+
31
+ ## Act
32
+
33
+ For each violation:
34
+
35
+ - **Mechanical fix** (missing constant import, missing fallback default, missing entry in canonical array) → fix it directly with an Edit
36
+ - **Design-decision fix** (new entity type registration, new cross-entity propagation mechanism, schema migration) → append to `docs/history/backlog.md` as P0 or P1 with the specific guardrail and consumer paths
37
+ - **Pre-existing exception** (recorded in `docs/memory/contract-monitor.md`) → skip silently; don't re-flag
38
+
39
+ ## Report
40
+
41
+ Present a structured report:
42
+
43
+ | # | Guardrail | Violation | Location | Action taken |
44
+ |---|-----------|-----------|----------|--------------|
45
+
46
+ Then update `docs/memory/contract-monitor.md` with:
47
+ - Scan date
48
+ - Violation counts per guardrail
49
+ - Items fixed mechanically (with file paths)
50
+ - Items backlogged (with P-level and rationale)
51
+ - New exceptions added (if any)
52
+
53
+ ## Optional: extend guardrails
54
+
55
+ If you discover patterns in code that should be guardrails but aren't yet documented:
56
+
57
+ 1. Surface the candidate guardrail to the user with a draft entry
58
+ 2. Wait for approval before writing to `guardrails.md` — never add a guardrail unilaterally
59
+
60
+ ## Rules
61
+
62
+ - Never delete feature docs or modify frozen records (`docs/history/plans/`, `docs/history/changelog/`)
63
+ - Be specific: include file paths, line numbers, and the exact violating string
64
+ - Respect known-exceptions in `docs/memory/contract-monitor.md`
65
+ - A clean report is valid — do not invent violations