geneseed 3.17.0 → 3.19.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 (164) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/DESIGN.md +1 -1
  3. package/README.md +23 -5
  4. package/SHIPPED.md +3 -3
  5. package/adapters/claude-code/skills/geneseed/SKILL.md +1 -1
  6. package/adapters/opencode/README.md +2 -2
  7. package/adapters/opencode/plugins/geneseed-context.js +19 -17
  8. package/adapters/opencode/plugins/geneseed-guard.js +7 -7
  9. package/bin/geneseed-cli.mjs +5 -0
  10. package/docs/concepts/exclusions.md +1 -1
  11. package/docs/concepts/skills.md +6 -2
  12. package/docs/extending.md +33 -0
  13. package/docs/guides/project-context.md +20 -0
  14. package/docs/guides/read-non-markdown.md +1 -1
  15. package/docs/guides/uninstall.md +1 -1
  16. package/docs/guides/upgrade.md +1 -1
  17. package/docs/guides/wiki.md +4 -4
  18. package/docs/opencode-plugin-setup.md +1 -1
  19. package/docs/reference/env-context.md +2 -2
  20. package/docs/reference/plugin-context.md +2 -2
  21. package/docs/reference/plugin-guard.md +1 -1
  22. package/docs/token-footprint.md +18 -15
  23. package/docs/understand/take-it-out.md +1 -1
  24. package/harness.config.json +1 -1
  25. package/js/README.md +4 -1
  26. package/js/build/args.mjs +22 -20
  27. package/js/build/bundle.mjs +1 -3
  28. package/js/build/emit-claude.mjs +5 -4
  29. package/js/build/emit-opencode.mjs +9 -2
  30. package/js/build/generate.mjs +6 -6
  31. package/js/build/render.mjs +19 -12
  32. package/js/build/source.mjs +82 -7
  33. package/js/build/stubs.mjs +35 -6
  34. package/js/build/themes.mjs +1 -3
  35. package/js/cli-table.json +53 -0
  36. package/js/hosts/hooks-context.mjs +13 -6
  37. package/js/hosts/hosts.mjs +1 -3
  38. package/js/hosts/installs.mjs +13 -7
  39. package/js/hosts/mcp.mjs +1 -1
  40. package/js/hosts/native.mjs +60 -5
  41. package/js/hosts/opencode.mjs +37 -6
  42. package/js/hosts/settings.mjs +1 -0
  43. package/js/hosts/shim.mjs +1 -0
  44. package/js/inspect/checks-authoring.mjs +4 -1
  45. package/js/inspect/checks-counts.mjs +8 -3
  46. package/js/inspect/checks-repo.mjs +166 -2
  47. package/js/inspect/diff.mjs +5 -1
  48. package/js/inspect/doctor.mjs +2 -1
  49. package/js/inspect/inventory.mjs +7 -9
  50. package/js/inspect/scorecard-checks.mjs +571 -0
  51. package/js/inspect/scorecard-svg.mjs +85 -0
  52. package/js/inspect/scorecard.mjs +839 -0
  53. package/js/lib/json.mjs +1 -1
  54. package/js/maintain/migrate.mjs +2 -1
  55. package/js/maintain/uninstall.mjs +2 -3
  56. package/js/maintain/update.mjs +10 -3
  57. package/js/ui/menu.mjs +2 -2
  58. package/js/ui/tui.mjs +1 -0
  59. package/js/web/api.mjs +2 -0
  60. package/js/web/catalog.mjs +24 -7
  61. package/js/web/daemon.mjs +2 -1
  62. package/js/web/routes.mjs +2 -0
  63. package/js/web/server.mjs +1 -1
  64. package/js/web/user-files.mjs +1 -0
  65. package/package.json +11 -3
  66. package/registry.json +12 -75
  67. package/src/AGENT.md.tmpl +19 -24
  68. package/src/agents/docs.md +1 -1
  69. package/src/agents/researcher.md +1 -1
  70. package/src/agents/reviewer.md +3 -0
  71. package/src/bricks/bruno-test.md +2 -2
  72. package/src/bricks/ci-fix.md +3 -3
  73. package/src/bricks/deps-audit.md +3 -3
  74. package/src/bricks/migration-plan.md +2 -2
  75. package/src/doctrines/comms.md +19 -0
  76. package/src/doctrines/process.md +2 -2
  77. package/src/laws/universal.md +2 -2
  78. package/src/skills/brainstorm.md +14 -10
  79. package/src/skills/bruno.md +84 -0
  80. package/src/skills/codebase-design.md +41 -23
  81. package/src/skills/daydream/SKILL.md +4 -3
  82. package/src/skills/daydream/VENDOR.md +6 -2
  83. package/src/skills/daydream/instructions.md +1 -1
  84. package/src/skills/debug/ci.md +11 -0
  85. package/src/skills/debug.md +11 -8
  86. package/src/skills/dependencies.md +65 -0
  87. package/src/skills/develop.md +9 -4
  88. package/src/skills/document-project.md +27 -22
  89. package/src/skills/explain-changes/SKILL.md +1 -1
  90. package/src/skills/fresh-eyes.md +1 -1
  91. package/src/skills/geneseed-code-review.md +21 -9
  92. package/src/skills/geneseed.md +4 -1
  93. package/src/skills/git-history.md +78 -0
  94. package/src/skills/handoff.md +4 -3
  95. package/src/skills/parallel-agents.md +2 -1
  96. package/src/skills/pipeline.md +1 -1
  97. package/src/skills/plan.md +5 -1
  98. package/src/skills/ponytail.md +1 -1
  99. package/src/skills/prototype.md +10 -7
  100. package/src/skills/react-view-transitions/SKILL.md +1 -0
  101. package/src/skills/react-view-transitions/VENDOR.md +4 -0
  102. package/src/skills/research.md +6 -1
  103. package/src/skills/review-response.md +8 -2
  104. package/src/skills/roast-me.md +8 -7
  105. package/src/skills/rule/consolidate.md +32 -0
  106. package/src/skills/rule.md +16 -4
  107. package/src/skills/security-audit.md +1 -1
  108. package/src/skills/ship.md +16 -9
  109. package/src/skills/teach.md +5 -3
  110. package/src/skills/wayfinder.md +19 -16
  111. package/src/skills/wiki.md +2 -2
  112. package/themes/_TEMPLATE.json +9 -17
  113. package/themes/biker.json +9 -17
  114. package/themes/commentator.json +9 -17
  115. package/themes/cyberpunk.json +9 -17
  116. package/themes/gamer.json +9 -17
  117. package/themes/imperial.json +9 -17
  118. package/themes/joker.json +9 -17
  119. package/themes/marvin.json +9 -17
  120. package/themes/mean.json +9 -17
  121. package/themes/military.json +9 -17
  122. package/themes/neutral.json +9 -17
  123. package/themes/pirate.json +9 -17
  124. package/themes/sports.json +9 -17
  125. package/themes/verstappen.json +9 -17
  126. package/themes/wizard.json +9 -17
  127. package/web/dist/assets/{Activity-DonKN1pi.js → Activity-IbwdHHyx.js} +1 -1
  128. package/web/dist/assets/{ActivityDetail-e47a3cdE.js → ActivityDetail-B5uivdWI.js} +1 -1
  129. package/web/dist/assets/{Dashboard-Cr7i0PsO.js → Dashboard-v6WQ37bD.js} +2 -2
  130. package/web/dist/assets/{Docs-Lne3nuzD.js → Docs-CypIfDBX.js} +1 -1
  131. package/web/dist/assets/FilterInput-BSfpStOZ.js +1 -0
  132. package/web/dist/assets/{GreenhouseView-4CbIuTpl.js → GreenhouseView-ClxP0hQG.js} +1 -1
  133. package/web/dist/assets/{Installs-CIzbV1Ks.js → Installs-CPkQeFjO.js} +1 -1
  134. package/web/dist/assets/{JournalView-BlBfuvzj.js → JournalView-BeEInhUf.js} +1 -1
  135. package/web/dist/assets/{Laws-B-pjcjHr.js → Laws-Ds6U5xBQ.js} +1 -1
  136. package/web/dist/assets/Library-BraUKPyQ.js +1 -0
  137. package/web/dist/assets/{Loops-DIOxW1xH.js → Loops-DWcy3mwK.js} +1 -1
  138. package/web/dist/assets/Markdown-C6fRd4A4.js +67 -0
  139. package/web/dist/assets/{OperatorHudView-D7S7z7CJ.js → OperatorHudView-CQyrJQ9m.js} +1 -1
  140. package/web/dist/assets/{Personal-Ws3v35AB.js → Personal-YWqs6qFL.js} +1 -1
  141. package/web/dist/assets/{Tabs-6Z1eziAq.js → Tabs-EhzEC-Xh.js} +1 -1
  142. package/web/dist/assets/{activity-Dr7ZMBI3.js → activity-dcg5vF9j.js} +1 -1
  143. package/web/dist/assets/{drawable-ATNeYKbT.js → drawable-CEKPNV7X.js} +1 -1
  144. package/web/dist/assets/{index-CDg5XUhG.js → index-CPi22b7y.js} +2 -2
  145. package/web/dist/assets/react-DRhoX0kS.js +9 -0
  146. package/web/dist/assets/{usePoll-DD8VjtSg.js → usePoll-BmR631BB.js} +1 -1
  147. package/web/dist/index.html +2 -2
  148. package/web/src/pages/Laws.jsx +5 -0
  149. package/src/skills/bruno-collection-generator.md +0 -52
  150. package/src/skills/bruno-test-writer.md +0 -51
  151. package/src/skills/ci-fix.md +0 -49
  152. package/src/skills/consolidate-memory.md +0 -50
  153. package/src/skills/deps-audit.md +0 -46
  154. package/src/skills/domain-modeling.md +0 -27
  155. package/src/skills/git-archaeology.md +0 -36
  156. package/src/skills/git-rescue.md +0 -58
  157. package/src/skills/migrate.md +0 -34
  158. package/src/skills/opencode-theme.md +0 -42
  159. package/src/skills/repo-map.md +0 -25
  160. package/src/skills/tickets.md +0 -33
  161. package/web/dist/assets/FilterInput-CVyqmYWM.js +0 -1
  162. package/web/dist/assets/Library-x8Nzg6yq.js +0 -1
  163. package/web/dist/assets/Markdown-X2a3qvOh.js +0 -38
  164. package/web/dist/assets/react-B9Z_tXRV.js +0 -8
package/js/lib/json.mjs CHANGED
@@ -38,7 +38,7 @@ class JsonNumber {
38
38
  * Python would have \u2014 NOT the original literal, because `json.dumps(json.loads('1.50'))`
39
39
  * is `1.5`: Python re-formats through repr rather than echoing the source.
40
40
  */
41
- toJSON() { return JSON.rawJSON(formatValue(this)); }
41
+ toJSON() { return /** @type {any} */ (JSON).rawJSON(formatValue(this)); }
42
42
  }
43
43
 
44
44
  /**
@@ -213,8 +213,9 @@ export function cmdMigrate(args = {}) {
213
213
  // `process 5` among them) and dropped `--config-dir`, so a global found through
214
214
  // `CLAUDE_CONFIG_DIR`/`OPENCODE_CONFIG_DIR` re-emitted into the DEFAULT dir instead.
215
215
  for (const r of rows) {
216
- const { emit, theme, argv } = installProfile(r.host, r.scope, r.root);
217
216
  const label = `${r.host}:${r.scope} (${r.root})`;
217
+ const { emit, theme, argv } = installProfile(r.host, r.scope, r.root,
218
+ (n) => printErr(`[migrate] ${label}: ${n}\n`));
218
219
  printOut(`[migrate] re-emitting ${label}: theme=${theme} emit=${emit}\n`);
219
220
  let rc = 1;
220
221
  try { rc = driverMain(argv); } catch { rc = 1; }
@@ -155,6 +155,7 @@ function pruneAncestors(start, stop) {
155
155
  * `ownedWithin` first, the same containment boundary deactivate draws: the manifest is a file
156
156
  * on disk, and without it an `owned` entry of `../../.bashrc` was unlinked — and the prune
157
157
  * then climbed from ITS parent, which never meets `base`, removing empty directories as it went.
158
+ * @returns {[number, string[]]}
158
159
  */
159
160
  function unlinkOwned(base, owned, label = '') {
160
161
  let removed = 0;
@@ -758,9 +759,7 @@ export function cmdUninstall(args) {
758
759
  */
759
760
  function moveTree(src, dst) {
760
761
  if (existsSync(dst)) {
761
- const e = new Error(String(dst));
762
- e.code = 'EEXIST';
763
- throw e;
762
+ throw Object.assign(new Error(String(dst)), { code: 'EEXIST' });
764
763
  }
765
764
  mkdirSync(path.dirname(dst), { recursive: true });
766
765
  renameSync(src, dst);
@@ -270,6 +270,7 @@ export function killTree(child) {
270
270
  * `tests/unit/update_git.test.mjs` runs it over a recorded fetch transcript instead.
271
271
  *
272
272
  * Returns `[linesToLog, lastPhase]`, splitting the reference's loop from its side effects.
273
+ * @returns {[string[], string]}
273
274
  */
274
275
  export function fetchPhases(rawLines, last = '') {
275
276
  const out = [];
@@ -609,13 +610,17 @@ async function drainStdout() {
609
610
  * rewritten markers back through `installProfile`, so the loss stuck. `installProfile` is the
610
611
  * one reader of an install's axes; this takes its answer rather than growing a second one.
611
612
  *
613
+ * `notify` hears the one-line notice for an excluded skill since merged or retired, as
614
+ * `rebuild-all` prints it. `upgrade` reads it BEFORE the pull, so it reflects the pre-pull
615
+ * alias table: a skill merged by the very commits being pulled is not named until the next run.
616
+ *
612
617
  * Only the TAIL of the argv is kept: theme and emit are `upgrade`'s to decide (a theme argument
613
618
  * may override the marker), and `setupBuildArgs` opens with exactly those four tokens when it
614
619
  * is given no out and no root.
615
620
  */
616
- export async function installAxes(dir, { installProfile, setupBuildArgs }) {
621
+ export async function installAxes(dir, { installProfile, setupBuildArgs }, notify = null) {
617
622
  if (!existsSync(path.join(String(dir), '.geneseed-emit'))) return [];
618
- const p = installProfile('opencode', 'global', String(dir));
623
+ const p = installProfile('opencode', 'global', String(dir), notify);
619
624
  return setupBuildArgs('', 'files', null, null, p.footprint, p.posture, p.mode, p.doctrines,
620
625
  undefined, p.excludeRules, p.trust, p.excludeSkills).slice(4);
621
626
  }
@@ -704,7 +709,8 @@ export async function upgrade(ref = null, themeArg = null) {
704
709
  // `installAxes` for why the target's own axes must be carried into the rebuild.
705
710
  const [generate, { bounceDaemonIfRunning }] = await Promise.all([
706
711
  import('../build/generate.mjs'), import('../web/daemon.mjs')]);
707
- const axes = await installAxes(emit === 'opencode-global' ? cfg : out, generate);
712
+ const axes = await installAxes(emit === 'opencode-global' ? cfg : out, generate,
713
+ (n) => log(`[upgrade] ${n}`));
708
714
 
709
715
  log(`[geneseed] fetching from origin (git: ${which('git') || 'git'}, `
710
716
  + `timeout: ${fetchTimeout()}s) ...`);
@@ -921,6 +927,7 @@ async function bootstrapPlain() {
921
927
  // deployed harness's local edits are overwritten by the rebuild with nothing recorded.
922
928
  // Caught by `bootstrap/an-improvements-file-the-step-exported-is-re-announced-afterwards`,
923
929
  // which was written for `_flush_export_notes` and found this instead.
930
+ /** @type {Array<[string, string[], () => Promise<number>]>} */
924
931
  const steps = [['Update & rebuild', updateStepCmd('upgrade'),
925
932
  () => cmdUpgrade({ ref: null, theme: null })]];
926
933
  let failed = false;
package/js/ui/menu.mjs CHANGED
@@ -103,11 +103,11 @@ export function cmdMenu() {
103
103
  }
104
104
 
105
105
  /** `cmd_home` — the default for a bare `geneseed`. */
106
- export async function cmdHome(args) {
106
+ export async function cmdHome(_args) {
107
107
  if (webFirstOk()) {
108
108
  printOut('[geneseed] opening the web console — `geneseed menu` for the terminal UI, '
109
109
  + 'GENESEED_NO_WEB=1 to disable.\n');
110
110
  return startDaemon(null, 4747, true);
111
111
  }
112
- return cmdMenu(args);
112
+ return cmdMenu();
113
113
  }
package/js/ui/tui.mjs CHANGED
@@ -279,6 +279,7 @@ export function mark(kind) {
279
279
  * — the order they are read in, the order AGENT.md renders them, and the order the console's
280
280
  * three bands appear in. `Rule` and `Doctrine` are hardcoded English here, as `Rule` already
281
281
  * was: this function receives an inventory, not a theme's nouns.
282
+ * @returns {any[][]}
282
283
  */
283
284
  export function tuiEntries(inv) {
284
285
  const rows = [['head', `AGENTS (${inv.agents.length})`, null]];
package/js/web/api.mjs CHANGED
@@ -275,6 +275,7 @@ const RECENT_LIMIT = 8;
275
275
  * law files, so a per-rule mtime would be the file's, repeated — one wrong date on nine rows.
276
276
  */
277
277
  export function apiRecent(state) {
278
+ /** @type {Array<[string, string, () => any[]]>} */
278
279
  const sections = [
279
280
  ['memory', 'memory', () => memoryItems(state)],
280
281
  ['notebook', 'notebook', () => notebookItems(state)],
@@ -316,6 +317,7 @@ export function apiLoops(state) {
316
317
  const opts = { projectRoot: state.root };
317
318
  const catalog = loadCatalog(opts);
318
319
  const { bricks, templates, overridden } = catalog;
320
+ /** @type {(x: [string, any], y: [string, any]) => number} */
319
321
  const byName = ([a], [b]) => (a < b ? -1 : 1);
320
322
  return {
321
323
  templates: [...templates].sort(byName).map(([name, { origin, ...graph }]) => ({
@@ -77,6 +77,10 @@ function specEntries(root, nested) {
77
77
  for (const p of files) {
78
78
  if (!isFile(p)) continue;
79
79
  const [fm, body] = frontmatter(readMaybe(p) ?? '');
80
+ // An alias folder (native.mjs: an old name kept answering `/name`) is its target skill
81
+ // under a second name, not a skill of the user's own — listing it would read "personal".
82
+ if (nested && fm.get('disable-model-invocation') === 'true'
83
+ && (fm.get('description') ?? '').startsWith('Alias of ')) continue;
80
84
  const name = nested ? path.basename(path.dirname(p)) : path.basename(p, '.md');
81
85
  out.push({ name, desc: specDesc(fm, body), body, source: resolvePath(p) });
82
86
  }
@@ -179,15 +183,27 @@ export function notebookItems(state) {
179
183
  }));
180
184
  }
181
185
 
182
- /** The two setup manifests, in listing order. */
186
+ /**
187
+ * The wiki manifest's names, current first. `wiki.jsonc` is the name before the `geneseed-`
188
+ * prefix: the build renames it on the next emit, and until then it is still read — one release.
189
+ */
190
+ const WIKI_NAMES = ['geneseed-wiki.jsonc', 'wiki.jsonc'];
191
+ const WIKI_META = ['Wiki manifest', 'your machine-wide knowledge base(s)'];
192
+
193
+ /** The two setup manifests, in listing order (the wiki under either of its names). */
183
194
  const CONFIG_META = {
184
195
  'context.json': ['Project context', 'what the agent loads for this project'],
185
- 'wiki.jsonc': ['Wiki manifest', 'your machine-wide knowledge base(s)'],
196
+ 'geneseed-wiki.jsonc': WIKI_META,
197
+ 'wiki.jsonc': WIKI_META,
186
198
  };
187
199
 
200
+ /** The wiki manifest beside the bundle under whichever name it has, or `null`. */
201
+ const wikiName = (state) => WIKI_NAMES.find((n) => isFile(path.join(state.target, n))) ?? null;
202
+
188
203
  export function configItems(state) {
189
204
  const out = [];
190
- for (const fname of ['context.json', 'wiki.jsonc']) {
205
+ for (const fname of ['context.json', wikiName(state)]) {
206
+ if (!fname) continue;
191
207
  const p = path.join(state.target, fname);
192
208
  if (!isFile(p)) continue;
193
209
  const [title, desc] = CONFIG_META[fname] ?? [fname, ''];
@@ -227,7 +243,7 @@ function wikiPath(p) {
227
243
  }
228
244
 
229
245
  /**
230
- * `$GENESEED_WIKI` first, else `wiki.jsonc` beside the deployed bundle, read with the
246
+ * `$GENESEED_WIKI` first, else the wiki manifest beside the deployed bundle, read with the
231
247
  * harness's generic JSONC loader.
232
248
  *
233
249
  * `resolvePath`, not merely `expanduser`: harmless here because `p` is consumed by `isFile`
@@ -236,7 +252,8 @@ function wikiPath(p) {
236
252
  */
237
253
  function wikiManifest(state) {
238
254
  const cand = process.env.GENESEED_WIKI;
239
- const p = cand ? wikiPath(cand) : path.join(state.target, 'wiki.jsonc');
255
+ const name = cand ? null : wikiName(state);
256
+ const p = cand ? wikiPath(cand) : name && path.join(state.target, name);
240
257
  if (!p || !isFile(p)) return [];
241
258
  const cfg = mcpLoad(p);
242
259
  const wikis = cfg.wikis;
@@ -248,7 +265,7 @@ function wikiManifest(state) {
248
265
  * unparseable, or not an object.
249
266
  *
250
267
  * Comment-tolerant is the whole point, and getting this wrong is silent: the two files it
251
- * reads are `wiki.jsonc` and `context.json`, hand-maintained, so a `//` line is expected.
268
+ * reads are `geneseed-wiki.jsonc` and `context.json`, hand-maintained, so a `//` line is expected.
252
269
  * Plain `JSON.parse` would throw on it and this loader would then answer `{}` — the wiki
253
270
  * section listing nothing and the config item's `manifest` coming back empty, with no
254
271
  * error surfaced anywhere.
@@ -389,7 +406,7 @@ export function resolveLinks(state, body) {
389
406
  */
390
407
  function configManifest(name, p) {
391
408
  const cfg = mcpLoad(p);
392
- if (name === 'wiki.jsonc') {
409
+ if (WIKI_NAMES.includes(name)) {
393
410
  const wikis = cfg.wikis;
394
411
  return { kind: 'wiki', wikis: Array.isArray(wikis) ? wikis : [] };
395
412
  }
package/js/web/daemon.mjs CHANGED
@@ -31,7 +31,7 @@ const ROOT = pathResolve(import.meta.dirname, '..', '..');
31
31
  export function codeStamp(root = ROOT) {
32
32
  const read = (rel) => { try { return readFileSync(join(root, rel)); } catch { return ''; } };
33
33
  let version = 'unknown';
34
- try { version = JSON.parse(read('package.json')).version ?? version; } catch { /* unknown */ }
34
+ try { version = JSON.parse(String(read('package.json'))).version ?? version; } catch { /* unknown */ }
35
35
  const html = createHash('sha256').update(read(join('web', 'dist', 'index.html'))).digest('hex');
36
36
  return `${version}:${html.slice(0, 12)}`;
37
37
  }
@@ -161,6 +161,7 @@ export const none = (v) => (v === undefined || v === null ? 'None' : String(v));
161
161
  */
162
162
  function spawnDetached(webArgs, log) {
163
163
  const cmd = [process.execPath, join(ROOT, 'bin', 'geneseed-cli.mjs'), 'web', ...webArgs];
164
+ /** @type {'ignore' | number} */
164
165
  let out = 'ignore';
165
166
  try {
166
167
  out = openSync(log, 'a');
package/js/web/routes.mjs CHANGED
@@ -41,6 +41,7 @@ import { apiProfile, apiProfileSave, apiRules, apiRulesMutate, apiRulesPromote }
41
41
  * `NotFound` is caught once, in `handler`, not per route — repeating the catch here would
42
42
  * only duplicate it.
43
43
  */
44
+ /** @type {Map<string, [(state: any, body: any) => any, boolean]>} */
44
45
  export const POST_ROUTES = new Map([
45
46
  ['/api/mcp', [apiMcpToggle, true]],
46
47
  ['/api/install', [apiInstallToggle, true]],
@@ -117,6 +118,7 @@ export function readJsonBody(buf) {
117
118
  * `percentDecode` and not `decodeURIComponent`: the JS builtin throws a `URIError` on a `%`
118
119
  * that is not an escape, where this shell instead answers a 404 naming the literal text.
119
120
  */
121
+ /** @type {Array<[string, (state: any, path: string) => any]>} */
120
122
  export const PREFIX_ROUTES = [
121
123
  ['/api/catalog/', (state, p) => apiCatalog(state, p.split('/').pop())],
122
124
  // /api/item/<type>/<name> — TYPE has no slash, the NAME keeps its slashes so a wiki
package/js/web/server.mjs CHANGED
@@ -154,7 +154,7 @@ export async function serve({ theme = null, port = 4747, openBrowser = true,
154
154
  srv.listen(port, '127.0.0.1', () => ready());
155
155
 
156
156
  function ready() {
157
- const hostPort = srv.address().port;
157
+ const hostPort = /** @type {import('node:net').AddressInfo} */ (srv.address()).port;
158
158
  const url = `http://127.0.0.1:${hostPort}`;
159
159
  if (daemon) {
160
160
  writeDaemon(state.target, {
@@ -107,6 +107,7 @@ function parseRuleMeta(line) {
107
107
  * Every rule carries its `start`/`end` LINE INDICES, which is what lets `apiRulesMutate`
108
108
  * splice exactly one block and leave every other byte of the user's file — prose,
109
109
  * formatting, hand-written sections — untouched.
110
+ * @returns {[Array<{id: number, start: number, end: number} & Record<string, any>>, string[]]}
110
111
  */
111
112
  export function parseRules(text) {
112
113
  const lines = splitLines(text);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "Every field here is argued and gated in tests/unit/package_manifest.test.mjs. `files`, `bin` and `engines` each have a test that fails when reality drifts from this file; `version` mirrors harness.config.json and is gated too. Read that docblock before editing this one.",
3
3
  "name": "geneseed",
4
- "version": "3.17.0",
4
+ "version": "3.19.0",
5
5
  "description": "A portable, theme-able harness you implant once and use everywhere to grow a disciplined AI coding agent.",
6
6
  "keywords": [
7
7
  "ai",
@@ -54,11 +54,19 @@
54
54
  "SHIPPED.md"
55
55
  ],
56
56
  "devDependencies": {
57
+ "@commitlint/cli": "^21.2.3",
58
+ "@commitlint/config-conventional": "^21.2.3",
57
59
  "@eslint/js": "^10.0.1",
60
+ "@stryker-mutator/core": "^10.0.0",
61
+ "@types/node": "^22.20.5",
62
+ "c8": "^12.0.0",
58
63
  "eslint": "^10.9.0",
59
- "globals": "^17.11.0"
64
+ "fast-check": "^4.10.2",
65
+ "globals": "^17.11.0",
66
+ "typescript": "^5.9.3"
60
67
  },
61
68
  "scripts": {
62
- "lint": "eslint js bin tests adapters"
69
+ "lint": "eslint js bin tests adapters fuzz",
70
+ "typecheck": "tsc -p ."
63
71
  }
64
72
  }
package/registry.json CHANGED
@@ -141,25 +141,11 @@
141
141
  "added": "2026-10-03",
142
142
  "last_verified": ""
143
143
  },
144
- "skills/bruno-collection-generator": {
144
+ "skills/bruno": {
145
145
  "status": "experimental",
146
146
  "version": "1.0.0",
147
147
  "owner": "Guillaume Lemer",
148
- "added": "2026-09-03",
149
- "last_verified": ""
150
- },
151
- "skills/bruno-test-writer": {
152
- "status": "experimental",
153
- "version": "1.0.0",
154
- "owner": "Guillaume Lemer",
155
- "added": "2026-09-03",
156
- "last_verified": ""
157
- },
158
- "skills/ci-fix": {
159
- "status": "experimental",
160
- "version": "1.0.0",
161
- "owner": "Guillaume Lemer",
162
- "added": "2026-09-06",
148
+ "added": "2026-10-08",
163
149
  "last_verified": ""
164
150
  },
165
151
  "skills/codebase-design": {
@@ -176,13 +162,6 @@
176
162
  "added": "2026-06-01",
177
163
  "last_verified": "2026-09-06"
178
164
  },
179
- "skills/consolidate-memory": {
180
- "status": "experimental",
181
- "version": "1.0.0",
182
- "owner": "Guillaume Lemer",
183
- "added": "2026-09-06",
184
- "last_verified": ""
185
- },
186
165
  "skills/council": {
187
166
  "status": "approved",
188
167
  "version": "1.0.0",
@@ -204,11 +183,11 @@
204
183
  "added": "2026-06-03",
205
184
  "last_verified": "2026-09-06"
206
185
  },
207
- "skills/deps-audit": {
186
+ "skills/dependencies": {
208
187
  "status": "experimental",
209
188
  "version": "1.0.0",
210
189
  "owner": "Guillaume Lemer",
211
- "added": "2026-09-06",
190
+ "added": "2026-10-08",
212
191
  "last_verified": ""
213
192
  },
214
193
  "skills/develop": {
@@ -225,12 +204,12 @@
225
204
  "added": "2026-06-10",
226
205
  "last_verified": "2026-09-06"
227
206
  },
228
- "skills/domain-modeling": {
229
- "status": "approved",
207
+ "skills/explain-changes": {
208
+ "status": "experimental",
230
209
  "version": "1.0.0",
231
210
  "owner": "Guillaume Lemer",
232
- "added": "2026-07-13",
233
- "last_verified": "2026-09-06"
211
+ "added": "2026-09-30",
212
+ "last_verified": ""
234
213
  },
235
214
  "skills/forge-mcp": {
236
215
  "status": "approved",
@@ -267,19 +246,12 @@
267
246
  "added": "2026-06-19",
268
247
  "last_verified": "2026-09-06"
269
248
  },
270
- "skills/git-archaeology": {
271
- "status": "approved",
272
- "version": "1.0.0",
273
- "owner": "Guillaume Lemer",
274
- "added": "2026-06-10",
275
- "last_verified": "2026-09-06"
276
- },
277
- "skills/git-rescue": {
278
- "status": "approved",
249
+ "skills/git-history": {
250
+ "status": "experimental",
279
251
  "version": "1.0.0",
280
252
  "owner": "Guillaume Lemer",
281
- "added": "2026-06-10",
282
- "last_verified": "2026-09-06"
253
+ "added": "2026-10-08",
254
+ "last_verified": ""
283
255
  },
284
256
  "skills/handoff": {
285
257
  "status": "approved",
@@ -316,20 +288,6 @@
316
288
  "added": "2026-10-03",
317
289
  "last_verified": ""
318
290
  },
319
- "skills/migrate": {
320
- "status": "approved",
321
- "version": "1.0.0",
322
- "owner": "Guillaume Lemer",
323
- "added": "2026-06-08",
324
- "last_verified": "2026-09-06"
325
- },
326
- "skills/opencode-theme": {
327
- "status": "approved",
328
- "version": "1.0.0",
329
- "owner": "Guillaume Lemer",
330
- "added": "2026-06-19",
331
- "last_verified": "2026-09-06"
332
- },
333
291
  "skills/parallel-agents": {
334
292
  "status": "approved",
335
293
  "version": "1.0.0",
@@ -407,13 +365,6 @@
407
365
  "added": "2026-06-07",
408
366
  "last_verified": "2026-09-06"
409
367
  },
410
- "skills/repo-map": {
411
- "status": "approved",
412
- "version": "1.0.0",
413
- "owner": "Guillaume Lemer",
414
- "added": "2026-06-01",
415
- "last_verified": "2026-09-06"
416
- },
417
368
  "skills/research": {
418
369
  "status": "approved",
419
370
  "version": "1.0.0",
@@ -470,13 +421,6 @@
470
421
  "added": "2026-09-06",
471
422
  "last_verified": ""
472
423
  },
473
- "skills/tickets": {
474
- "status": "approved",
475
- "version": "1.0.0",
476
- "owner": "Guillaume Lemer",
477
- "added": "2026-07-13",
478
- "last_verified": "2026-09-06"
479
- },
480
424
  "skills/token-report": {
481
425
  "status": "experimental",
482
426
  "version": "1.0.0",
@@ -484,13 +428,6 @@
484
428
  "added": "2026-07-29",
485
429
  "last_verified": ""
486
430
  },
487
- "skills/explain-changes": {
488
- "status": "experimental",
489
- "version": "1.0.0",
490
- "owner": "Guillaume Lemer",
491
- "added": "2026-09-30",
492
- "last_verified": ""
493
- },
494
431
  "skills/wayfinder": {
495
432
  "status": "approved",
496
433
  "version": "1.0.0",
package/src/AGENT.md.tmpl CHANGED
@@ -45,7 +45,7 @@ it as your very first tool call — before loading context, reading files, or se
45
45
  skipped) and the `eager` entries of `context.json`. If no `=== SESSION FILES ===` or
46
46
  `=== PROJECT CONTEXT ===` block is in your context, read them yourself first:
47
47
  `{{AT_HARNESS}}user-rules.md`, `{{AT_HARNESS}}PROFILE.md`, `{{DIR_MEMORY}}/MEMORY.md`,
48
- `{{DIR_NOTEBOOK}}/NOTEBOOK.md`, `{{AT_HARNESS}}wiki.jsonc`, `{{AT_HARNESS}}context.json`.
48
+ `{{DIR_NOTEBOOK}}/NOTEBOOK.md`, `{{AT_HARNESS}}geneseed-wiki.jsonc`, `{{AT_HARNESS}}context.json`.
49
49
 
50
50
  **Response voice.** {{VOICE}} This sets *tone and vocabulary* — the section *layout*,
51
51
  the law *numbers*, file paths, and folder names stay plain English so tooling and links
@@ -226,17 +226,14 @@ the ten council seats are convened by the [council {{SKILL}}]({{DIR_SKILLS}}/cou
226
226
  | --- | --- |
227
227
  | [brainstorm]({{DIR_SKILLS}}/brainstorm.md) | a new feature or project with no design yet, or whose goal, scope, or success criteria need pinning down first |
228
228
  | [plan]({{DIR_SKILLS}}/plan.md) | a task has more than a couple of steps |
229
- | [codebase-design]({{DIR_SKILLS}}/codebase-design.md) | designing a module's interface, placing a seam, or needing the deep-module vocabulary |
230
- | [domain-modeling]({{DIR_SKILLS}}/domain-modeling.md) | pinning domain terminology in CONTEXT.md or recording an architectural decision as an ADR |
229
+ | [codebase-design]({{DIR_SKILLS}}/codebase-design.md) | designing a module's interface, placing a seam, or needing the deep-module vocabulary; pinning domain terms in GLOSSARY.md or recording a hard-to-reverse decision as an ADR |
231
230
  | [wayfinder]({{DIR_SKILLS}}/wayfinder.md) | a chunk of work too big for one session, still in the fog — chart decision tickets before building |
232
- | [tickets]({{DIR_SKILLS}}/tickets.md) | splitting a spec into tracker-ready tracer-bullet tickets with blocking edges |
233
231
  | [parallel-agents]({{DIR_SKILLS}}/parallel-agents.md) | several independent subtasks and a tool that runs subagents |
234
232
  | [develop]({{DIR_SKILLS}}/develop.md) | implementing a feature, fixing a bug, or changing behaviour — failing test first, affected suite green, one committed slice at a time |
235
233
  | [worktree]({{DIR_SKILLS}}/worktree.md) | work must not share a tree with what is in flight — a crew, a second task, another session in this checkout |
236
- | [debug]({{DIR_SKILLS}}/debug.md) | a bug or failure to diagnose before fixing |
234
+ | [debug]({{DIR_SKILLS}}/debug.md) | a bug or failure to diagnose before fixing — a red CI run or pre-merge check included, reproduced under CI's conditions |
237
235
  | [prototype]({{DIR_SKILLS}}/prototype.md) | a design question needs a runnable answer before real implementation |
238
- | [repo-map]({{DIR_SKILLS}}/repo-map.md) | orienting to the repo, or structure changed |
239
- | [document-project]({{DIR_SKILLS}}/document-project.md) | missing/drifted docs, or a PRD, design system, architecture doc or AGENTS.md |
236
+ | [document-project]({{DIR_SKILLS}}/document-project.md) | missing/drifted docs, or a PRD, design system, architecture doc or AGENTS.md; orienting to the repo, or refreshing its map after the structure changed |
240
237
  | [ingest]({{DIR_SKILLS}}/ingest.md) | a task needs content from a PDF, Office doc, or URL |
241
238
  | [research]({{DIR_SKILLS}}/research.md) | a question needs answers gathered and verified from the open web |
242
239
  | [teach]({{DIR_SKILLS}}/teach.md) | the user wants to be taught — a crash course, confusing material decoded, or a day-by-day path to a deadline |
@@ -247,35 +244,29 @@ the ten council seats are convened by the [council {{SKILL}}]({{DIR_SKILLS}}/cou
247
244
  | [review-response]({{DIR_SKILLS}}/review-response.md) | acting on review feedback you have received |
248
245
  | [fresh-eyes]({{DIR_SKILLS}}/fresh-eyes.md) | a finished artifact needs an independent verdict on whether it meets the original spec, from an agent with none of the solving session's context |
249
246
  | [security-audit]({{DIR_SKILLS}}/security-audit.md) | a change touches the security surface — auth, untrusted input, secrets, shell or file access, a new dependency — or "is this safe" |
250
- | [ci-fix]({{DIR_SKILLS}}/ci-fix.md) | a CI run or pre-merge check is red — reproduce under CI's conditions, fix at the cause, watch the re-run |
251
- | [deps-audit]({{DIR_SKILLS}}/deps-audit.md) | deciding which dependencies to upgrade, remove, or refuse — outdated, vulnerable, abandoned, unused |
247
+ | [dependencies]({{DIR_SKILLS}}/dependencies.md) | auditing dependencies — outdated, vulnerable, abandoned, unused — or upgrading a dependency, framework, or language version one change at a time |
252
248
  | [ship]({{DIR_SKILLS}}/ship.md) | a verified change is ready to open as a PR or merge |
253
249
  | [release]({{DIR_SKILLS}}/release.md) | cutting a version: bump, changelog, tag |
254
- | [migrate]({{DIR_SKILLS}}/migrate.md) | upgrading a dependency, framework, or language version |
255
- | [git-archaeology]({{DIR_SKILLS}}/git-archaeology.md) | finding when a regression landed or why code exists — bisect, blame, pickaxe |
256
- | [git-rescue]({{DIR_SKILLS}}/git-rescue.md) | recovering a broken or messy repo, or safely rewriting history — reflog, undo, rebase behind a backup |
250
+ | [git-history]({{DIR_SKILLS}}/git-history.md) | finding when a regression landed or why code exists — bisect, blame, pickaxe — or recovering a broken repo and safely rewriting history behind a backup branch |
257
251
  | [refactor]({{DIR_SKILLS}}/refactor.md) | improving code structure without changing behaviour |
258
252
  | [ponytail]({{DIR_SKILLS}}/ponytail.md) | the user wants the minimal, laziest-that-works solution — "ponytail", "be lazy", "simplest path", or a complaint about over-engineering, bloat, or needless dependencies |
259
253
  | [forge-mcp]({{DIR_SKILLS}}/forge-mcp.md) | building, extending, or hardening a Model Context Protocol (MCP) server |
260
- | [bruno-collection-generator]({{DIR_SKILLS}}/bruno-collection-generator.md) | creating or converting a Bruno API collection — requests, environments, docs — from routes, an OpenAPI snippet, curl commands, or an endpoint list |
261
- | [bruno-test-writer]({{DIR_SKILLS}}/bruno-test-writer.md) | writing or strengthening Bruno tests, assertions, scripts, and request chaining for a request or collection |
254
+ | [bruno]({{DIR_SKILLS}}/bruno.md) | creating, converting, or testing a Bruno API collection — requests, environments, assertions, chaining — from routes, OpenAPI, curl, or an endpoint list |
262
255
  | [prose]({{DIR_SKILLS}}/prose.md) | writing prose for a human — a commit body, PR, doc, comment, memory note, or reply — that should not read like a model wrote it |
263
256
  | [frontend-design]({{DIR_SKILLS}}/frontend-design.md) | building or reshaping a UI that should carry a deliberate point of view, not a templated default |
264
257
  | [handoff]({{DIR_SKILLS}}/handoff.md) | context filling, or passing work to another agent or session |
265
- | [roast-me]({{DIR_SKILLS}}/roast-me.md) | you want an artifact torn apart, brutally and actionably |
258
+ | [roast-me]({{DIR_SKILLS}}/roast-me.md) | you want your own ask, plan or idea torn apart, brutally and actionably (code goes to code review) |
266
259
  | [council]({{DIR_SKILLS}}/council.md) | a decision, change, or discussion needs debating from several points of view before committing |
267
260
  | [workflow]({{DIR_SKILLS}}/workflow.md) | deterministic, code-driven orchestration of subagents (fan-out, find→verify, phased handoff) and the host exposes a workflow tool |
268
261
  | [pipeline]({{DIR_SKILLS}}/pipeline.md) | foreman mode triages a substantial task, or the user asks to run a crew (analyst → developer → tester) on one, isolated in its own worktree and merged on mechanical proof |
269
262
  | [loop]({{DIR_SKILLS}}/loop.md) | the user asks to run a loop |
270
263
  | [brick-forge]({{DIR_SKILLS}}/brick-forge.md) | a new or adapted loop brick |
271
- | [wiki]({{DIR_SKILLS}}/wiki.md) | consulting or growing the user's machine-wide knowledge base declared in `wiki.jsonc` (§8) |
264
+ | [wiki]({{DIR_SKILLS}}/wiki.md) | consulting or growing the user's machine-wide knowledge base declared in `geneseed-wiki.jsonc` (§8) |
272
265
  | [herdr]({{DIR_SKILLS}}/herdr.md) | running inside a [herdr](https://herdr.dev) pane (`HERDR_ENV=1`) and a task needs another pane — a sibling server, a watched build, a spawned agent |
273
- | [rule]({{DIR_SKILLS}}/rule.md) | anything the user wants to outlive this session — a standing rule of their own (§1) or a durable fact to remember (§5); also a recurring {{MEMORY}} lesson due for promotion |
266
+ | [rule]({{DIR_SKILLS}}/rule.md) | anything the user wants to outlive this session — a standing rule of their own (§1) or a durable fact to remember (§5); also tending the {{MEMORY}} set — duplicates, stale facts, a recurring lesson due for promotion |
274
267
  | [profile]({{DIR_SKILLS}}/profile.md) | the user wants the agent to know who they are — creating or refreshing the `PROFILE.md` identity file (§1) through a short interview |
275
- | [consolidate-memory]({{DIR_SKILLS}}/consolidate-memory.md) | the {{MEMORY}} set needs tending — an overgrown index, duplicates, stale facts, a lesson due for promotion |
276
268
  | [skill-forge]({{DIR_SKILLS}}/skill-forge.md) | a repeated task has no {{SKILL}}, a self-improvement loop produced an edit, or two {{SKILLS}} should merge |
277
- | [geneseed]({{DIR_SKILLS}}/geneseed.md) | the user mentions Geneseed, the harness, or `AGENT.md`, or you need to inspect or read from the deployed bundle ({{LAWS}}, {{AGENTS}}, {{SKILLS}}, {{MEMORY}}) |
278
- | [opencode-theme]({{DIR_SKILLS}}/opencode-theme.md) | the user wants their own OpenCode colour theme — pick or tweak a palette, get solid + transparent variants that survive rebuilds |
269
+ | [geneseed]({{DIR_SKILLS}}/geneseed.md) | the user mentions Geneseed, the harness, or `AGENT.md`, wants their own OpenCode colour theme, or you need to inspect or read from the deployed bundle ({{LAWS}}, {{AGENTS}}, {{SKILLS}}, {{MEMORY}}) |
279
270
 
280
271
  **Folder {{SKILLS}}.** Some {{SKILLS}} ship as self-contained folders under
281
272
  `{{DIR_SKILLS}}/`, each with its own format and provenance (`VENDOR.md`) —
@@ -283,7 +274,7 @@ third-party vendors plus first-party ones bundling scripts. Reach for one only w
283
274
  its domain comes up:
284
275
  - [react-view-transitions]({{DIR_SKILLS}}/react-view-transitions/SKILL.md) — animating
285
276
  React UIs with the View Transition API (`<ViewTransition>`, shared elements, Next.js).
286
- - [daydream]({{DIR_SKILLS}}/daydream/SKILL.md) — mining the `wiki.jsonc` vault (§8) for
277
+ - [daydream]({{DIR_SKILLS}}/daydream/SKILL.md) — mining the `geneseed-wiki.jsonc` vault (§8) for
287
278
  non-obvious connections between notes via parallel synthesis + critique.
288
279
  - [token-report]({{DIR_SKILLS}}/token-report/SKILL.md) — session token usage breakdown
289
280
  tables: context baseline, growth, per-tool attribution.
@@ -416,7 +407,7 @@ It receives only the task's deliverables; your working files go in the {{NOTEBOO
416
407
  *{{EPI_WIKI}}*
417
408
 
418
409
  <!-- LEAN:begin -->
419
- {{INTRO_WIKI}} It is declared in `{{AT_HARNESS}}wiki.jsonc`, a session file — or
410
+ {{INTRO_WIKI}} It is declared in `{{AT_HARNESS}}geneseed-wiki.jsonc`, a session file — or
420
411
  wherever `$GENESEED_WIKI` points. An empty `wikis` list means none is declared and this
421
412
  section stays dormant. (On OpenCode its `eager` entries are loaded for you.)
422
413
 
@@ -443,7 +434,7 @@ durable fact worth sharing across projects goes here via the
443
434
  [wiki]({{DIR_SKILLS}}/wiki.md) {{SKILL}}; one that matters only to you stays in
444
435
  {{MEMORY}} (§5).
445
436
  <!-- LEAN:else -->
446
- {{INTRO_WIKI}} Declared in `{{AT_HARNESS}}wiki.jsonc`, a session file (or at
437
+ {{INTRO_WIKI}} Declared in `{{AT_HARNESS}}geneseed-wiki.jsonc`, a session file (or at
447
438
  `$GENESEED_WIKI`; an empty `wikis` list means dormant) — honour each wiki: load `entries` by `load` mode as in `context.json` (§9), read
448
439
  `conventions` before your first write there, drop an unplaceable note in `inbox`,
449
440
  never write inside `protected`. It is the user's: wikilinks over bare paths, every
@@ -473,6 +464,9 @@ start**, above); where it did not, act on each entry by its `load` mode yourself
473
464
 
474
465
  Each `path` may be absolute or relative to the repository root. The file's own
475
466
  comment carries its schema and setup notes.
467
+
468
+ **Reviewing code** — with any review command, built-in or not — first apply the nearest
469
+ `REVIEW.md` above each changed file. It overrides the review defaults, never a {{LAW}}.
476
470
  <!-- LEAN:else -->
477
471
  **Load `context.json` before your first reply — not deferrable.** The repo's own
478
472
  (`context.json` or `.harness/context.json` at its root), else `{{AT_HARNESS}}context.json`,
@@ -480,7 +474,8 @@ is how the project hands you its own knowledge; the hook injects its `eager` ent
480
474
  (**Session start**). `"eager"` entries are read now and are
481
475
  **project law for this repo, as binding as this file**; `"lazy"` entries only when
482
476
  the task needs them ({{DOCTRINE:context-economy}}). Schema and setup notes are in the file's
483
- own comment.
477
+ own comment. Reviewing code (any review command): first apply the nearest `REVIEW.md`
478
+ above each changed file — it overrides review defaults, never a {{LAW}}.
484
479
  <!-- LEAN:end -->
485
480
 
486
481
  ---
@@ -26,7 +26,7 @@
26
26
  3. Keep examples runnable; update any example that the change broke.
27
27
  4. Write each page in the format it is already in — Markdown or AsciiDoc; never convert one.
28
28
  A new page follows the `Docs format:` line in the root `AGENTS.md`, else the format the
29
- project's docs already use (only AsciiDoc when it has none yet). Agent-runtime files —
29
+ project's docs already use (Markdown when it has none yet; AsciiDoc only on request). Agent-runtime files —
30
30
  `AGENTS.md`, `CLAUDE.md`, specs, ADRs loops read — stay Markdown. In AsciiDoc, never add
31
31
  an `include::` to a page agents read.
32
32
 
@@ -8,7 +8,7 @@
8
8
  findings, not the pages ({{DOCTRINE:context-economy}}).
9
9
  - The research {{SKILL}} is running and the host can run subagents: this seat
10
10
  does the fetching and cross-checking so the main context stays small.
11
- - A migrate, forge-mcp, or ingest run needs a spec or changelog read before
11
+ - A dependencies, forge-mcp, or ingest run needs a spec or changelog read before
12
12
  deciding.
13
13
 
14
14
  ## When NOT to dispatch
@@ -21,6 +21,9 @@
21
21
  ## Procedure
22
22
  0. If `{{DIR_MEMORY}}/agents/<your-name>.md` exists, read it first — your durable lessons from prior dispatches ({{DOCTRINE:persist-insight}}).
23
23
  1. Confirm the change actually does what the task required (read the spec/issue).
24
+ Then read, for each changed file, the nearest `REVIEW.md` above it — a deeper one
25
+ overrides a shallower one, and they override this procedure and the output contract,
26
+ never a {{LAW}}.
24
27
  2. Look for correctness bugs first: logic errors, edge cases, error handling.
25
28
  3. Then quality: duplication, unclear naming, dead code, oversized units.
26
29
  4. Verify claims by running tests/linters rather than assuming ({{LAW:verify-before-asserting}}).
@@ -2,10 +2,10 @@
2
2
  name: bruno-test
3
3
  description: Write or update Bruno requests for the endpoints this iteration touched.
4
4
  effect: mutate
5
- skill: bruno-test-writer
5
+ skill: bruno
6
6
  outcomes: pass, fail
7
7
  ---
8
- Follow the bruno-test-writer skill for every endpoint this iteration's diff added or changed.
8
+ Follow the bruno skill for every endpoint this iteration's diff added or changed.
9
9
  Touch only the Bruno collection — new or updated `.bru` requests and their expectations — never
10
10
  the application code.
11
11
 
@@ -2,11 +2,11 @@
2
2
  name: ci-fix
3
3
  description: Fix the failing CI check named in the card.
4
4
  effect: mutate
5
- skill: ci-fix
5
+ skill: debug
6
6
  outcomes: pass, fail
7
7
  ---
8
- Follow the ci-fix skill against the failing check the card names — read its actual log, not a
9
- guess from the check's title, before changing anything.
8
+ Follow the debug skill's CI branch against the failing check the card names — read its actual
9
+ log, not a guess from the check's title, before changing anything.
10
10
 
11
11
  Touch only what that failure requires; this is a repair, not a chance to improve the pipeline
12
12
  along the way.