@nurix/etna 0.1.3 → 0.2.1

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 (30) hide show
  1. package/README.md +35 -26
  2. package/bin/etna.js +82 -56
  3. package/data/harness-index.json +0 -7
  4. package/data/harnesses/code/agents/change-review.md +80 -0
  5. package/data/harnesses/code/agents/contract-monitor.md +65 -0
  6. package/data/harnesses/code/agents/dep-health.md +148 -0
  7. package/data/harnesses/code/agents/git-history.md +201 -0
  8. package/data/harnesses/code/agents/maintenance.md +112 -0
  9. package/data/harnesses/code/agents/review.md +109 -0
  10. package/data/harnesses/code/agents/spec-mode.md +56 -0
  11. package/data/harnesses/code/rules/general.md +96 -0
  12. package/data/harnesses/code/rules/nomenclature.md +34 -0
  13. package/data/harnesses/code/scripts/build-ownership-index.sh +138 -0
  14. package/data/harnesses/code/scripts/check-doc-drift.sh +125 -0
  15. package/data/harnesses/code/skills/documentation/SKILL.md +238 -0
  16. package/data/harnesses/code/skills/documentation/references/examples.md +193 -0
  17. package/data/harnesses/code/skills/documentation/references/style-guide.md +288 -0
  18. package/data/harnesses/code/skills/documentation/references/templates.md +901 -0
  19. package/data/harnesses/code/skills/introspect/SKILL.md +135 -0
  20. package/data/harnesses/code/skills/workflow-mapper/SKILL.md +430 -0
  21. package/data/harnesses/code/skills/workflow-mapper/references/patterns.md +214 -0
  22. package/data/harnesses/kb-agi/agents/kb-agi-init.md +276 -0
  23. package/data/harnesses/kb-agi/agents/kb-catch-stale.md +22 -0
  24. package/data/harnesses/kb-agi/agents/kb-find-gaps.md +27 -0
  25. package/data/harnesses/kb-agi/agents/kb-fix-links.md +22 -0
  26. package/data/harnesses/kb-agi/agents/kb-refresh-frontdoor.md +22 -0
  27. package/data/harnesses/kb-agi/agents/kb-refresh-summaries.md +22 -0
  28. package/data/harnesses/kb-agi/agents/kb-sync-metadata.md +25 -0
  29. package/data/harnesses/kb-agi/rules/kb.md +13 -0
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,8 @@
1
1
  # @nurix/etna
2
2
 
3
- CLI to bootstrap a project with a Nurix harness. Produces the same on-disk state as running
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.
4
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.
5
+ A second install path also exists and is fully supported: `/plugin install <plugin>@nurixlabs` from inside Claude Code, which installs the harness as a plugin from the `nurixlabs/skills` marketplace. The two paths are first-class equals — pick the one that matches how you want to manage the harness. See [`docs/architecture/harness-install-model.md`](../../docs/architecture/harness-install-model.md) for the comparison.
11
6
 
12
7
  ## Usage
13
8
 
@@ -17,46 +12,60 @@ From the root of the project you want to bootstrap:
17
12
  npx @nurix/etna nurix-harness
18
13
  ```
19
14
 
20
- After this completes, three things have changed on disk:
15
+ After this completes:
21
16
 
22
17
  ```
23
18
  your-project/
24
- ├── CLAUDE.md ← bundled in this package; the harness's policy
19
+ ├── CLAUDE.md ← harness policy (backup of any existing file is kept)
25
20
  └── .claude/
26
- └── settings.json ← enabledPlugins["nurix-harness@nurixlabs"] = true
27
-
28
- ~/.claude/settings.json ← extraKnownMarketplaces["nurixlabs"] registered
21
+ ├── skills/ ← documentation, introspect, workflow-mapper
22
+ ├── agents/ ← spec-mode, maintenance, contract-monitor, …
23
+ ├── rules/ ← general.md, nomenclature.md
24
+ └── scripts/ ← build-ownership-index.sh, check-doc-drift.sh
29
25
  ```
30
26
 
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)).
27
+ 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
28
 
33
- If Claude Code is already running, run `/reload-plugins`.
29
+ 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>`.
30
+
31
+ ## Migrating from 0.1.x
32
+
33
+ `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.
34
+
35
+ 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
36
 
35
37
  ## Available harnesses
36
38
 
37
39
  Listed by `npx @nurix/etna` (no args) using the bundled `data/harness-index.json`.
38
40
 
39
- | Plugin name | Harness folder in etna | What it ships |
41
+ | Plugin name | Source in etna | What it ships |
40
42
  |---|---|---|
41
- | `nurix-harness` | [`harness/code/`](../../harness/code/) | The canonical v7 harness — governance agents, baseline workflow skills, ownership/drift scripts |
43
+ | `nurix-harness` | [`plugins/nurix-harness.md`](../../plugins/nurix-harness.md) → top-level catalogs | Governance agents, baseline workflow skills, ownership/drift scripts |
44
+ | `kb-agi` | [`plugins/kb-agi.md`](../../plugins/kb-agi.md) → top-level catalogs | Knowledge-vault scaffolding |
42
45
 
43
46
  ## How it works
44
47
 
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`.
48
+ Each harness has a folder under `etna/harness/<name>/` with:
49
+ - `CLAUDE.md` — the project-root policy file
50
+ - `index.md` — symlinked to `../../plugins/<plugin>.md` (the manifest)
49
51
 
50
- No GitHub fetch. No `.claude/plugins/<name>/` folder. Claude Code's plugin resolver handles all of that on next launch.
52
+ The plugin manifest's YAML frontmatter declares which top-level `agents/`, `skills/`, `rules/`, and `scripts/` belong to that plugin:
51
53
 
52
- ## Why two sources
54
+ ```yaml
55
+ ---
56
+ name: nurix-harness
57
+ skills: [documentation, introspect, workflow-mapper]
58
+ agents: [spec-mode, maintenance, contract-monitor, …]
59
+ scripts: [build-ownership-index, check-doc-drift]
60
+ rules: [general, nomenclature]
61
+ ---
62
+ ```
53
63
 
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.
64
+ `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
65
 
57
66
  ## Building / contributing
58
67
 
59
- `data/` is generated. Don't edit by hand. To regenerate after touching anything under `etna/harness/`:
68
+ `data/` is generated. Don't edit by hand. To regenerate after editing the etna catalogs or manifests:
60
69
 
61
70
  ```sh
62
71
  cd packages/cli
@@ -67,4 +76,4 @@ npm run build:data
67
76
 
68
77
  ## Publishing
69
78
 
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.
79
+ 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,31 +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, plus opts the
78
- // marketplace into auto-updates (off by default for non-Anthropic marketplaces).
79
- function registerMarketplace(marketplace) {
80
- const path = join(homedir(), '.claude', 'settings.json');
81
- const settings = readJson(path);
82
- settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
83
- settings.extraKnownMarketplaces[marketplace.name] = {
84
- source: marketplace.source,
85
- autoUpdate: true,
86
- };
87
- writeJson(path, settings);
88
- 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;
89
101
  }
90
102
 
91
- // Add `<plugin>@<marketplace>: true` to <target>/.claude/settings.json under enabledPlugins.
92
- // Mirrors what `/plugin install <plugin>@<marketplace>` writes at project scope.
93
- function enablePlugin(targetDir, pluginName, marketplaceName) {
94
- const path = join(targetDir, '.claude', 'settings.json');
95
- const settings = readJson(path);
96
- settings.enabledPlugins = settings.enabledPlugins || {};
97
- const key = `${pluginName}@${marketplaceName}`;
98
- settings.enabledPlugins[key] = true;
99
- writeJson(path, settings);
100
- 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;
101
125
  }
102
126
 
103
127
  async function main(args) {
@@ -115,34 +139,36 @@ async function main(args) {
115
139
  );
116
140
  }
117
141
 
118
- const bundledClaude = join(dataDir, 'harnesses', harnessFolder, 'CLAUDE.md');
119
- if (!existsSync(bundledClaude)) {
142
+ const bundledHarnessDir = join(dataDir, 'harnesses', harnessFolder);
143
+ const bundledClaudeMd = join(bundledHarnessDir, 'CLAUDE.md');
144
+ if (!existsSync(bundledClaudeMd)) {
120
145
  die(`Bundled CLAUDE.md missing for harness '${harnessFolder}'. Rebuild package data.`);
121
146
  }
122
147
 
123
148
  const targetDir = process.cwd();
124
149
  const claudeMdDest = join(targetDir, 'CLAUDE.md');
125
- const marketplace = idx.marketplace;
126
- const fqKey = `${pluginName}@${marketplace.name}`;
127
150
 
128
- console.log(`\nInstalling ${fqKey} into ${targetDir}`);
151
+ console.log(`\nInstalling ${pluginName} into ${targetDir}`);
129
152
 
130
153
  const claudeBackup = backupIfExists(claudeMdDest);
131
154
  if (claudeBackup) console.log(` backed up existing CLAUDE.md → ${claudeBackup}`);
132
155
 
133
- copyFileSync(bundledClaude, claudeMdDest);
156
+ copyFileSync(bundledClaudeMd, claudeMdDest);
134
157
  console.log(` installed CLAUDE.md → ${claudeMdDest}`);
135
158
 
136
- const marketplacePath = registerMarketplace(marketplace);
137
- 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
+ }
138
166
 
139
- const { path: enablePath } = enablePlugin(targetDir, pluginName, marketplace.name);
140
- console.log(` enabled '${fqKey}' → ${enablePath}`);
167
+ const migrated = migrateFromPluginInstall(targetDir, pluginName);
168
+ for (const entry of migrated) console.log(` removed stale ${entry}`);
141
169
 
142
170
  console.log(`\nDone. Open Claude Code in ${targetDir}.`);
143
- console.log(
144
- `It will fetch ${marketplace.source.repo}, install ${pluginName}, and load it on next session start.\n`,
145
- );
171
+ console.log(`Re-run \`npx @nurix/etna ${pluginName}\` anytime to refresh harness content.\n`);
146
172
  }
147
173
 
148
174
  main(process.argv.slice(2)).catch((e) => {
@@ -1,11 +1,4 @@
1
1
  {
2
- "marketplace": {
3
- "name": "nurixlabs",
4
- "source": {
5
- "source": "github",
6
- "repo": "nurixlabs/skills"
7
- }
8
- },
9
2
  "plugins": {
10
3
  "nurix-harness": "code",
11
4
  "kb-agi": "kb-agi"
@@ -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
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: dep-health
3
+ description: Dependency supply-chain audit — security CVEs, outdated packages, license compliance, abandoned packages, unwanted transitive dependencies, install size. Run before releases or monthly. Pairs with `review` (which audits code, not deps).
4
+ tools: Read, Glob, Grep, Bash
5
+ model: inherit
6
+ ---
7
+
8
+ # Dependency Health Agent
9
+
10
+ You audit project dependencies comprehensively: security, freshness, licensing, abandonment, transitive bloat, install size. Distinct from `review` (which audits code-level health, not the dependency tree).
11
+
12
+ ## Before Starting
13
+
14
+ 1. If `./docs/memory/dep-health.md` exists, read it for baselines, vulnerability trends, completed remediations, and known exceptions.
15
+ 2. Read `./package.json` for direct dependencies.
16
+ 3. Detect the package manager by checking for lockfiles: `pnpm-lock.yaml` (pnpm), `package-lock.json` (npm), `yarn.lock` (yarn). Use matching commands below.
17
+ 4. If `.claude/rules/general.instructions.md` exists, read the Recommended Packages table for version pins.
18
+
19
+ ---
20
+
21
+ ## Step 1: Security Vulnerabilities
22
+
23
+ Run the audit command for the detected PM:
24
+
25
+ ```bash
26
+ # pnpm
27
+ pnpm audit --json 2>/dev/null || pnpm audit
28
+
29
+ # npm
30
+ npm audit --json 2>/dev/null || npm audit
31
+
32
+ # yarn
33
+ yarn npm audit --json 2>/dev/null || yarn audit
34
+ ```
35
+
36
+ Parse results by severity (critical, high, moderate, low). For each critical/high CVE report:
37
+
38
+ - Package name and installed version
39
+ - CVE ID
40
+ - Affected version range
41
+ - Fixed version (if available)
42
+ - Which direct dependency pulls it in
43
+
44
+ Compare against previous scan in `docs/memory/dep-health.md` — flag new vulnerabilities since last audit.
45
+
46
+ ## Step 2: Outdated Dependencies
47
+
48
+ ```bash
49
+ pnpm outdated --format json 2>/dev/null || pnpm outdated
50
+ # or: npm outdated --json
51
+ # or: yarn outdated
52
+ ```
53
+
54
+ Classify into:
55
+
56
+ - **Major behind** (2+ majors) — highest risk
57
+ - **Minor behind** — moderate risk
58
+ - **Patch behind** — low risk
59
+
60
+ Cross-reference against the Recommended Packages table in `.claude/rules/general.instructions.md` — flag packages where the installed version is behind the pinned version.
61
+
62
+ Report top 10 most outdated by semver distance.
63
+
64
+ ## Step 3: License Compliance
65
+
66
+ ```bash
67
+ pnpm licenses list --json 2>/dev/null || pnpm licenses list
68
+ # npm: npx license-checker --json
69
+ ```
70
+
71
+ Flag non-permissive licenses in the dependency tree: GPL, AGPL, SSPL, EUPL, or other copyleft.
72
+
73
+ For each flagged license: package name, license type, direct or transitive dependency.
74
+
75
+ ## Step 4: Abandoned Packages
76
+
77
+ For each direct dependency, check npm registry metadata:
78
+
79
+ ```bash
80
+ npm view {package} time --json 2>/dev/null
81
+ ```
82
+
83
+ Flag packages with no publish in 18+ months. Report:
84
+
85
+ - Package name
86
+ - Last publish date
87
+ - Whether the upstream repository appears archived
88
+
89
+ ## Step 5: Unwanted Transitive Dependencies
90
+
91
+ Check for known-bloat packages in the dependency tree:
92
+
93
+ - `moment` (use date-fns instead)
94
+ - `lodash` (use subpath imports)
95
+ - `underscore`
96
+ - `request` (deprecated)
97
+ - `node-fetch` (when runtime has native fetch)
98
+
99
+ For each found:
100
+
101
+ ```bash
102
+ pnpm why {package}
103
+ # or: npm ls {package}
104
+ ```
105
+
106
+ Report which direct dependency pulls it in and the installed size.
107
+
108
+ ## Step 6: Size Impact
109
+
110
+ ```bash
111
+ du -sh node_modules/.pnpm/*/ 2>/dev/null | sort -rh | head -15
112
+ # or for npm: du -sh node_modules/*/ | sort -rh | head -15
113
+ ```
114
+
115
+ List the 15 largest packages by installed size. Flag any single package over 10 MB.
116
+
117
+ Compare against previous snapshot in `docs/memory/dep-health.md`.
118
+
119
+ ---
120
+
121
+ ## Step 7: Report & Track
122
+
123
+ ### Report format
124
+
125
+ - **Vulnerabilities**: critical/high CVEs, new since last audit
126
+ - **Outdated**: top outdated packages by semver distance
127
+ - **Licenses**: non-permissive license flags
128
+ - **Abandoned**: packages with no recent publishes
129
+ - **Transitive bloat**: unwanted packages pulled in transitively
130
+ - **Size**: largest installed packages
131
+ - **Recommendations**: prioritized remediations
132
+
133
+ ### Update tracking
134
+
135
+ 1. Write snapshot to `docs/memory/dep-health.md` with date including vulnerability count by severity, outdated count by category, flagged licenses, abandoned packages
136
+ 2. Append new P0/P2 items to `docs/history/backlog.md`:
137
+ - `- **[P0]** Critical CVE in \`{package}\`: {CVE-ID} — discovered by dep-health`
138
+ - `- **[P2]** Abandoned dependency: \`{package}\` (last published {date}) — discovered by dep-health`
139
+ 3. Report summary to the user
140
+
141
+ ## Rules
142
+
143
+ - Never run `npm install`, `pnpm install`, `yarn install`, or any command that modifies dependencies
144
+ - May write to `docs/memory/dep-health.md` and `docs/history/backlog.md`
145
+ - Be specific: include package names, byte sizes, CVE IDs, version numbers
146
+ - Prioritize by impact: a critical CVE matters more than a patch update
147
+ - If a command isn't available (e.g., audit command unsupported), skip that step and note it
148
+ - A clean report is valid — do not invent issues