geneseed 3.15.0 → 3.17.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 (75) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/SHIPPED.md +1 -1
  3. package/adapters/opencode/plugins/geneseed-context.js +36 -8
  4. package/adapters/opencode/plugins/geneseed-guard.js +28 -0
  5. package/bin/geneseed.mjs +17 -0
  6. package/docs/concepts/exclusions.md +21 -9
  7. package/docs/concepts/hooks.md +1 -0
  8. package/docs/concepts/loop-notes.md +3 -0
  9. package/docs/concepts/loops.md +11 -0
  10. package/docs/guides/headless.md +1 -1
  11. package/docs/guides/install-scripted.md +1 -1
  12. package/docs/guides/project-context.md +3 -3
  13. package/docs/guides/read-non-markdown.md +1 -1
  14. package/docs/reference/plugin-context.md +2 -2
  15. package/docs/reference/plugin-guard.md +1 -1
  16. package/docs/token-footprint.md +10 -7
  17. package/harness.config.json +1 -1
  18. package/js/build/args.mjs +34 -3
  19. package/js/build/driver.mjs +8 -7
  20. package/js/build/generate.mjs +11 -5
  21. package/js/build/render.mjs +33 -3
  22. package/js/build/source.mjs +37 -1
  23. package/js/cli-table.json +15 -0
  24. package/js/hosts/hooks-context.mjs +112 -56
  25. package/js/hosts/hooks.mjs +34 -1
  26. package/js/hosts/installs.mjs +25 -2
  27. package/js/hosts/opencode.mjs +2 -0
  28. package/js/hosts/settings.mjs +2 -1
  29. package/js/inspect/checks-constitution.mjs +2 -2
  30. package/js/inspect/diff.mjs +4 -1
  31. package/js/inspect/status.mjs +2 -1
  32. package/js/inspect/validate.mjs +3 -1
  33. package/js/loop/graph.mjs +2 -1
  34. package/js/loop/state.mjs +30 -1
  35. package/js/maintain/setup.mjs +11 -4
  36. package/js/maintain/update.mjs +1 -1
  37. package/js/web/actions.mjs +5 -3
  38. package/package.json +1 -1
  39. package/src/AGENT.md.tmpl +1 -1
  40. package/src/agents/docs.md +5 -0
  41. package/src/bricks/apply.md +3 -0
  42. package/src/bricks/docs-update.md +2 -0
  43. package/src/bricks/test.md +17 -7
  44. package/src/doctrines/process.md +9 -5
  45. package/src/loops/api-endpoint.json +1 -1
  46. package/src/loops/bugfix.json +1 -1
  47. package/src/loops/ci-repair.json +1 -1
  48. package/src/loops/deps-upgrade.json +1 -1
  49. package/src/loops/enforce-architecture-rule.json +1 -1
  50. package/src/loops/feature.json +1 -1
  51. package/src/loops/fix-flaky-tests.json +1 -1
  52. package/src/loops/legacy-refactor.json +1 -1
  53. package/src/loops/legacy-tests.json +1 -1
  54. package/src/loops/refactor.json +1 -1
  55. package/src/loops/remove-dead-code.json +1 -1
  56. package/src/loops/spec-first-feature.json +1 -1
  57. package/src/loops/tdd.json +1 -1
  58. package/src/loops/update-contract.json +1 -1
  59. package/src/skills/document-project.md +91 -22
  60. package/web/dist/assets/{Activity-kpWxd70F.js → Activity-DonKN1pi.js} +1 -1
  61. package/web/dist/assets/{ActivityDetail-Ccr942VL.js → ActivityDetail-e47a3cdE.js} +1 -1
  62. package/web/dist/assets/{Dashboard-D1lE7jcg.js → Dashboard-Cr7i0PsO.js} +2 -2
  63. package/web/dist/assets/{Docs-BHyaggFy.js → Docs-Lne3nuzD.js} +1 -1
  64. package/web/dist/assets/{GreenhouseView-Sc_UDMOE.js → GreenhouseView-4CbIuTpl.js} +1 -1
  65. package/web/dist/assets/{Installs-Gw-pJ3Zi.js → Installs-CIzbV1Ks.js} +1 -1
  66. package/web/dist/assets/{JournalView-_gxpsxXi.js → JournalView-BlBfuvzj.js} +1 -1
  67. package/web/dist/assets/{Laws-S9fq1r9T.js → Laws-B-pjcjHr.js} +1 -1
  68. package/web/dist/assets/{Library-MBxadRzn.js → Library-x8Nzg6yq.js} +1 -1
  69. package/web/dist/assets/{Loops-Da_JhlzM.js → Loops-DIOxW1xH.js} +1 -1
  70. package/web/dist/assets/{OperatorHudView--TkvDybH.js → OperatorHudView-D7S7z7CJ.js} +1 -1
  71. package/web/dist/assets/{Personal-CTXKHVbV.js → Personal-Ws3v35AB.js} +1 -1
  72. package/web/dist/assets/{Tabs-B0cdf8k6.js → Tabs-6Z1eziAq.js} +1 -1
  73. package/web/dist/assets/{drawable-BbWQxe82.js → drawable-ATNeYKbT.js} +1 -1
  74. package/web/dist/assets/{index-ujKG1ZNf.js → index-CDg5XUhG.js} +2 -2
  75. package/web/dist/index.html +1 -1
package/js/loop/graph.mjs CHANGED
@@ -83,7 +83,8 @@ export function checkGraph(graph, bricks) {
83
83
  if (!brick) continue;
84
84
  for (const o of brick.outcomes) {
85
85
  const count = edges.filter((e) => e.from === n && e.on === o).length;
86
- if (count === 0) problems.push(`node ${n}: no edge for outcome ${o}`);
86
+ // `error` is reserved: with no edge the engine routes it to $stop (`recordOutcome`).
87
+ if (count === 0 && o !== 'error') problems.push(`node ${n}: no edge for outcome ${o}`);
87
88
  else if (count > 1) problems.push(`node ${n}: ${count} edges for outcome ${o}`);
88
89
  }
89
90
  }
package/js/loop/state.mjs CHANGED
@@ -288,6 +288,22 @@ function allowedEntries(graph, v) {
288
288
  return { allowed: widest ? 1 + widest.max : 1, loop: widest };
289
289
  }
290
290
 
291
+ // The repair ring an edge closes: the smallest non-iteration loop whose FIRST node is the edge's
292
+ // target and which also holds its source (test -> apply is apply-test's repair, review -> apply
293
+ // review-fix's). Its own counter makes `max` the number of repairs that ring may spend, even when
294
+ // a wider ring sharing the node raises the node budget above it — `max: 1` is one repair, then
295
+ // `exhaust`'s fresh attempt, then a stop (arXiv 2607.24604: a second revision lowers accuracy).
296
+ // Its key lives in `counters`, so every reset of a unit resets it too.
297
+ function repairRing(graph, from, to) {
298
+ const iteration = iterationLoop(graph);
299
+ let ring = null;
300
+ for (const m of graph.loops) {
301
+ if (m === iteration || m.nodes[0] !== to || !m.nodes.includes(from)) continue;
302
+ if (!ring || m.nodes.length < ring.nodes.length) ring = m;
303
+ }
304
+ return ring;
305
+ }
306
+
291
307
  // Porcelain lines are `XY <path>`; strip the 2-char status + space, slash-normalise, drop
292
308
  // LOOP.md rows (the engine rewrites it on every action — it is never part of the scored work)
293
309
  // and a lingering LOOP.md.tmp (the atomic writer's own sibling — see `writeLoopFile`'s docblock).
@@ -348,10 +364,17 @@ export function recordOutcome(state, bricks, outcome, { card = null, porcelain =
348
364
  // this call closes the unit, re-splits, or stops the loop. `notes` is never reset mid-run
349
365
  // (only `exhaust`'s own push and `decideAwaiting` add to it otherwise), so it survives
350
366
  // `resetUnit`/`finishUnit` — which a card does not: `resetUnit` clears it.
367
+ // A fresh verdict: `test`'s latest report supersedes its earlier ones, so a repair never reads
368
+ // findings about code that has since changed (stale traces broke 34 of 135 correct attempts
369
+ // in arXiv 2607.24604, fresh ones 4). Every other brick's notes are a record and are kept.
370
+ if (from === 'test') state.notes = state.notes.filter((n) => !/^iteration \d+ \(test\): /.test(n));
351
371
  if (note) state.notes.push(`iteration ${state.iteration} (${from}): ${note}`);
352
372
  state.visited.push(from);
353
373
 
354
- const edge = state.graph.edges.find((e) => e.from === from && e.on === outcome);
374
+ // `error` is reserved: a check that produced no verdict stops for a human, never a repair. A
375
+ // brick may declare it without an edge (`checkGraph` allows that); the engine routes it here.
376
+ const edge = state.graph.edges.find((e) => e.from === from && e.on === outcome)
377
+ ?? (outcome === 'error' ? { from, on: 'error', to: '$stop' } : null);
355
378
  if (!edge) throw new Error(`no edge from ${from} on ${outcome}`);
356
379
  if (brick.gate === 'human' && (!brick.gateOn || brick.gateOn.includes(outcome))) {
357
380
  state.status = 'awaiting';
@@ -385,6 +408,12 @@ function transition(state, bricks, from, edge) {
385
408
  finishUnit(state, false);
386
409
  return state.status === 'stopped' ? { stopped: state.reason } : { node: state.node };
387
410
  }
411
+ const ring = repairRing(state.graph, from, edge.to);
412
+ if (ring) {
413
+ const key = `ring:${ring.name}`;
414
+ state.counters[key] = (state.counters[key] ?? 0) + 1;
415
+ if (state.counters[key] > ring.max) return exhaust(state, ring);
416
+ }
388
417
  state.counters[edge.to] = (state.counters[edge.to] ?? 0) + 1;
389
418
  const { allowed, loop } = allowedEntries(state.graph, edge.to);
390
419
  if (state.counters[edge.to] > allowed) return exhaust(state, loop ?? { name: edge.to, max: 0 });
@@ -277,11 +277,18 @@ export function collectSetupLines() {
277
277
  // `setupBuildArgs` spell `--exclude-rules none`, and that string would land in the
278
278
  // "About to run:" line every recorded wizard probe compares byte for byte.
279
279
  const excludeRules = inst.excludeRules?.length ? inst.excludeRules : null;
280
+ // Skills left out are preserved the same way, and elide when there are none.
281
+ const excludeSkills = inst.excludeSkills?.length ? inst.excludeSkills : null;
280
282
  printOut(`\nAbout to run: geneseed build ${
281
283
  setupBuildArgs(theme, emit, out, root, footprint, posture, mode, doctrines, allPacks,
282
- excludeRules).join(' ')}\n`);
284
+ excludeRules, undefined, excludeSkills).join(' ')}\n`);
283
285
  if (!confirm('Proceed?', true)) return null;
284
- return { theme, posture, mode, doctrines, excludeRules, emit, out, root, footprint };
286
+ // The key only when there is something to carry, so a selection that excludes no skill is
287
+ // the same object it was before the flag existed.
288
+ return {
289
+ theme, posture, mode, doctrines, excludeRules, ...(excludeSkills ? { excludeSkills } : {}),
290
+ emit, out, root, footprint,
291
+ };
285
292
  }
286
293
 
287
294
  // --------------------------------------------------------------------------------------
@@ -463,7 +470,7 @@ export function setupLines() {
463
470
  }
464
471
  const {
465
472
  theme, emit, out, root, footprint = 'lean', posture = 'peer', mode = 'direct',
466
- doctrines = null, excludeRules = null,
473
+ doctrines = null, excludeRules = null, excludeSkills = null,
467
474
  } = sel;
468
475
  if (emit === 'opencode-global') {
469
476
  // The build below overwrites the deployed global harness, and the self-improvement loops
@@ -482,7 +489,7 @@ export function setupLines() {
482
489
  // runs must carry the pack selection the plan line just previewed, or the wizard would print
483
490
  // one command and run another.
484
491
  const argv = setupBuildArgs(theme, emit, out, root, footprint, posture, mode, doctrines,
485
- doctrineOptions().map(([n]) => n), excludeRules);
492
+ doctrineOptions().map(([n]) => n), excludeRules, undefined, excludeSkills);
486
493
  printOut(`Running: geneseed build ${argv.join(' ')}\n`);
487
494
  const rc = driverMain(argv);
488
495
  if (rc !== 0) {
@@ -617,7 +617,7 @@ export async function installAxes(dir, { installProfile, setupBuildArgs }) {
617
617
  if (!existsSync(path.join(String(dir), '.geneseed-emit'))) return [];
618
618
  const p = installProfile('opencode', 'global', String(dir));
619
619
  return setupBuildArgs('', 'files', null, null, p.footprint, p.posture, p.mode, p.doctrines,
620
- undefined, p.excludeRules, p.trust).slice(4);
620
+ undefined, p.excludeRules, p.trust, p.excludeSkills).slice(4);
621
621
  }
622
622
 
623
623
  /**
@@ -46,7 +46,8 @@ import {
46
46
  opencodeConfigDir, resolvePath,
47
47
  } from '../hosts/hosts.mjs';
48
48
  import {
49
- EMIT_HOST_SCOPE, doctrinesForBuild, excludedRulesOfDir, footprintOfDir, installState,
49
+ EMIT_HOST_SCOPE, doctrinesForBuild, excludedRulesOfDir, excludedSkillsOfDir, footprintOfDir,
50
+ installState,
50
51
  installTargets, modeOfDir, postureOfDir, trustOfDir,
51
52
  } from '../hosts/installs.mjs';
52
53
  import {
@@ -550,6 +551,7 @@ export function apiRestore(state, files) {
550
551
  // The excluded rules for the same boundary reason: without them a restored AGENT.md
551
552
  // re-states a rule whose hook gate the install deliberately left unwired.
552
553
  excludeRules: excludedRulesOfDir(target),
554
+ excludeSkills: excludedSkillsOfDir(target),
553
555
  }));
554
556
  for (const raw of (isTruthy(files) ? files : [])) {
555
557
  const rel = stripWhitespace(formatValue(raw).replace(/\\/g, '/')).replace(/^\/+/, '');
@@ -622,7 +624,7 @@ export function apiInstallCmd(state, body) {
622
624
  const excludeRules = bodyExcludeRules(body) ?? excludedRulesOfDir(root);
623
625
  const out = scope === 'global' ? null : String(root);
624
626
  const argv = setupBuildArgs(theme || 'neutral', emit, out, out, fp, pos, mode, doctrines,
625
- PACK_ORDER, excludeRules, trust);
627
+ PACK_ORDER, excludeRules, trust, excludedSkillsOfDir(root));
626
628
  return { cmd: [process.execPath, path.join(ROOT, 'bin', 'build-driver.mjs'), ...argv] };
627
629
  }
628
630
 
@@ -716,6 +718,6 @@ export function apiDeployCmd(state, body) {
716
718
  const excludeRules = bodyExcludeRules(body) ?? excludedRulesOfDir(root);
717
719
  // project-scope emit name == host name (opencode / claude / bob / openclaude)
718
720
  const argv = setupBuildArgs(theme || 'neutral', host, root, root, fp, pos, mode, doctrines,
719
- PACK_ORDER, excludeRules, trust);
721
+ PACK_ORDER, excludeRules, trust, excludedSkillsOfDir(root));
720
722
  return { cmd: [process.execPath, path.join(ROOT, 'bin', 'build-driver.mjs'), ...argv] };
721
723
  }
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.15.0",
4
+ "version": "3.17.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",
package/src/AGENT.md.tmpl CHANGED
@@ -295,7 +295,7 @@ inventory — that list is the catalogue and it is authoritative. Each written
295
295
  procedure is in [`{{DIR_SKILLS}}/`]({{DIR_SKILLS}}/) and the host loads it on
296
296
  demand.
297
297
  <!-- CATALOG:end -->
298
-
298
+ {{EXCLUDED_SKILLS_LINE}}
299
299
  When a task repeats and no {{SKILL}} covers it, forge one through the
300
300
  [skill-forge]({{DIR_SKILLS}}/skill-forge.md) {{SKILL}}: copy
301
301
  [`{{DIR_SKILLS}}/_template.md`]({{DIR_SKILLS}}/_template.md), keep it to one coherent
@@ -24,6 +24,11 @@
24
24
  that the code does not implement ({{LAW:verify-before-asserting}}).
25
25
  2. Write for the stated audience; lead with what the reader needs to do.
26
26
  3. Keep examples runnable; update any example that the change broke.
27
+ 4. Write each page in the format it is already in — Markdown or AsciiDoc; never convert one.
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 —
30
+ `AGENTS.md`, `CLAUDE.md`, specs, ADRs loops read — stay Markdown. In AsciiDoc, never add
31
+ an `include::` to a page agents read.
27
32
 
28
33
  ## Output contract
29
34
  - The doc files written/updated, a one-line note of what changed and why,
@@ -9,6 +9,9 @@ Implement the card's intent exactly, touching only the files in its `writeSet`.
9
9
  carries an `amend`, that supersedes the original intent — follow the amendment, not the plan it
10
10
  replaced.
11
11
 
12
+ When this is a repair after `test`, act on the latest `test` note only — it is the verdict on
13
+ the code as it stands. Fix every finding it lists in this one pass: there is no second repair.
14
+
12
15
  Make no unrelated edits: no drive-by rename, no incidental cleanup, no touching a file the card
13
16
  did not declare. If the change genuinely needs a file outside the write set, stop and report that
14
17
  rather than widening scope unasked — the diff is scored against the declared set, not your
@@ -10,6 +10,8 @@ flag, a moved file, a behaviour the README or docs still describe the old way.
10
10
 
11
11
  Update only those docs, only to match what the diff actually changed. Do not rewrite unrelated
12
12
  prose, reorganize a page, or fix an unrelated staleness you happen to notice while in there.
13
+ Keep each doc in the format it is already in (Markdown or AsciiDoc) — never convert one; a new
14
+ page follows the `Docs format:` line in the root `AGENTS.md`.
13
15
 
14
16
  Report `pass` once every doc the diff touched is back in sync, naming the files you changed — or
15
17
  that none needed it, if the diff left nothing stale.
@@ -1,14 +1,24 @@
1
1
  ---
2
2
  name: test
3
- description: Run the suite and linter over this iteration's change.
3
+ description: Run the project's checks over this iteration's change, in order, and report every finding.
4
4
  effect: read
5
5
  agent: tester
6
- outcomes: pass, fail
6
+ outcomes: pass, fail, error
7
7
  ---
8
- Run this project's full test suite and its linter over the current change. Never edit any file —
9
- this brick only observes; a fix belongs in `apply`, not here.
8
+ Run this project's checks over the current change, in this order. Never edit any file — this
9
+ brick only observes; a fix belongs in `apply`, not here.
10
10
 
11
- Report `pass` only if both exit clean.
11
+ 1. **Gate** — the build or compile step. If it fails, mark every check below `SKIPPED (gate
12
+ failed)` and report.
13
+ 2. **Focused checks** — the linter and the tests the change can reach. Run every one even after a
14
+ failure: they check independent properties, and the one repair must see every finding at once.
15
+ 3. **Full suite** — only when everything above passed; otherwise `SKIPPED (an earlier check failed)`.
12
16
 
13
- Report `fail` otherwise, with the raw failures (not a paraphrase) so `apply` can act on the actual
14
- error rather than your summary of it. Name every failing test and lint violation you saw.
17
+ List every check with `PASS`, `FAIL`, `ERROR` or `SKIPPED (reason)` — one that did not run is
18
+ never left out, and silence is never a pass. Write each failure as four lines: the check's
19
+ purpose, the property it guards, a one-line diagnostic (not the raw log), and the command that
20
+ reruns it alone. Keep full logs in a file, and write the list to the note file.
21
+
22
+ Report `pass` only if every check passed. Report `error` if a check crashed or could not be run —
23
+ no test runner, a missing tool, evidence never wired: that is a stop for a human, not a repair.
24
+ Report `fail` otherwise.
@@ -114,9 +114,11 @@ legitimate rewrite). The host gates both at the tool boundary.
114
114
  Every autonomous loop needs an exit you set before you enter it. Before iterating —
115
115
  retrying, searching, generating-and-checking — fix the bounds: a cap on attempts, a
116
116
  budget of time or tokens, and an explicit definition of success *and* of failure.
117
- Then watch for the loop that has stopped progressing: if you are issuing the same
118
- call, hitting the same error, or trying variations of one approach with no new
119
- information, that is not persistence, it is thrashing — break out, change strategy,
117
+ A check-and-repair loop gets one repair, then a fresh attempt or a hand-back — a second
118
+ revision lowers accuracy. Only a verdict on the final artifact counts: a pass from before
119
+ an edit is void, and old check output is never fed back. Then watch for the loop that has stopped progressing:
120
+ if you are issuing the same call, hitting the same error, or trying variations of one
121
+ approach with no new information, that is not persistence, it is thrashing — break out, change strategy,
120
122
  gather different context, or stop. When a bound is reached or progress stalls, do
121
123
  not grind on: halt and hand back a structured summary of what was tried, what was
122
124
  learned, and what remains ({{LAW:surface-failures}}, {{DOCTRINE:plan-before-acting}}). {{DOCTRINE:context-economy}}
@@ -124,8 +126,10 @@ economises *within* a step; this bounds the *number* of steps. An agent that can
124
126
  stop itself is a cost without a limit.
125
127
  <!-- LEAN:else -->
126
128
  Fix an autonomous loop's bounds before you enter it: an attempt cap, a time or token
127
- budget, an explicit definition of success *and* failure. Watch for the loop that has
128
- stopped progressing: the same call, the same error, variations with no new information are
129
+ budget, an explicit definition of success *and* failure. A check-and-repair loop spends
130
+ one repair, then a fresh attempt or a hand-back; accept only a verdict on the final
131
+ artifact — a pass from before an edit is void, old check output is never fed back.
132
+ Watch for the loop that has stopped progressing: the same call, the same error, variations with no new information are
129
133
  thrashing; break out, change strategy, or stop. When a bound is reached or progress
130
134
  stalls, halt with a structured summary of what was tried, learned, and remains ({{LAW:surface-failures}},
131
135
  {{DOCTRINE:plan-before-acting}}).
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "loops": [
26
26
  { "name": "iterations", "nodes": ["identify", "apply", "test", "bruno-test", "security-scan", "review"], "max": 20, "iteration": true },
27
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
27
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
28
28
  { "name": "review-fix", "nodes": ["apply", "test", "bruno-test", "security-scan", "review"], "max": 3 }
29
29
  ],
30
30
  "contracts": ["**/openapi*.yaml", "**/openapi*.yml", "**/openapi*.json", "**/swagger*.yaml", "**/swagger*.yml", "**/swagger*.json"]
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "loops": [
19
19
  { "name": "iterations", "nodes": ["identify", "apply", "test", "review"], "max": 20, "iteration": true },
20
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
20
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
21
21
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
22
22
  ]
23
23
  }
@@ -14,6 +14,6 @@
14
14
  ],
15
15
  "loops": [
16
16
  { "name": "iterations", "nodes": ["ci-triage", "ci-fix", "test"], "max": 10, "iteration": true },
17
- { "name": "fix-test", "nodes": ["ci-fix", "test"], "max": 3 }
17
+ { "name": "fix-test", "nodes": ["ci-fix", "test"], "max": 1 }
18
18
  ]
19
19
  }
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "loops": [
24
24
  { "name": "iterations", "nodes": ["upgrade-scout", "apply", "test", "deps-audit", "review"], "max": 20, "iteration": true },
25
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
25
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
26
26
  { "name": "review-fix", "nodes": ["apply", "test", "deps-audit", "review"], "max": 3 }
27
27
  ],
28
28
  "ignoreDeletions": ["**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml", "**/*.lock", "**/gradle.lockfile"]
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "loops": [
24
24
  { "name": "iterations", "nodes": ["identify", "apply", "test", "review"], "max": 20, "iteration": true },
25
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
25
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
26
26
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
27
27
  ]
28
28
  }
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "loops": [
20
20
  { "name": "iterations", "nodes": ["identify", "apply", "test", "review", "done-check"], "max": 20, "iteration": true },
21
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
21
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
22
22
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
23
23
  ]
24
24
  }
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "loops": [
26
26
  { "name": "iterations", "nodes": ["identify", "apply", "flake-check", "test", "review"], "max": 10, "iteration": true },
27
- { "name": "stabilise", "nodes": ["apply", "flake-check"], "max": 4 },
27
+ { "name": "stabilise", "nodes": ["apply", "flake-check"], "max": 1 },
28
28
  { "name": "review-fix", "nodes": ["apply", "flake-check", "test", "review"], "max": 3 }
29
29
  ]
30
30
  }
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "loops": [
21
21
  { "name": "iterations", "nodes": ["identify", "apply", "test", "review"], "max": 20, "iteration": true },
22
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
22
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
23
23
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
24
24
  ],
25
25
  "weights": { "logic": 0.6 }
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "loops": [
24
24
  { "name": "iterations", "nodes": ["identify", "characterize", "test", "mutation-check"], "max": 20, "iteration": true },
25
- { "name": "write-run", "nodes": ["characterize", "test"], "max": 3 },
25
+ { "name": "write-run", "nodes": ["characterize", "test"], "max": 1 },
26
26
  { "name": "kill-mutants", "nodes": ["characterize", "test", "mutation-check"], "max": 3 }
27
27
  ],
28
28
  "weights": { "new-file": 0.2 }
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "loops": [
20
20
  { "name": "iterations", "nodes": ["identify", "apply", "test"], "max": 20, "iteration": true },
21
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 }
21
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 }
22
22
  ],
23
23
  "weights": { "logic": 0.6 }
24
24
  }
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "loops": [
24
24
  { "name": "iterations", "nodes": ["identify", "apply", "test", "review"], "max": 20, "iteration": true },
25
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
25
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
26
26
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
27
27
  ],
28
28
  "weights": { "delete": 0.5 },
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "loops": [
25
25
  { "name": "iterations", "nodes": ["identify", "apply", "test", "review", "done-check"], "max": 20, "iteration": true },
26
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
26
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
27
27
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
28
28
  ]
29
29
  }
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "loops": [
21
21
  { "name": "iterations", "nodes": ["identify", "red-test", "apply", "test", "review"], "max": 20, "iteration": true },
22
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
22
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
23
23
  { "name": "review-fix", "nodes": ["apply", "test", "review"], "max": 3 }
24
24
  ]
25
25
  }
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "loops": [
25
25
  { "name": "iterations", "nodes": ["identify", "apply", "test", "compat-check", "review"], "max": 20, "iteration": true },
26
- { "name": "apply-test", "nodes": ["apply", "test"], "max": 5 },
26
+ { "name": "apply-test", "nodes": ["apply", "test"], "max": 1 },
27
27
  { "name": "review-fix", "nodes": ["apply", "test", "compat-check", "review"], "max": 3 }
28
28
  ],
29
29
  "contracts": [
@@ -5,34 +5,57 @@
5
5
  **Trigger:** the project's docs are missing or have drifted from the code, or it needs a PRD, a design system, an architecture document or an AGENTS.md. Existing codebases only — every document is extracted from the code, never written ahead of it.
6
6
 
7
7
  **Argument:** `[prd|design|architecture|agents|all]` — optional. It names the doc-set members to
8
- write; with none, step 3 proposes them.
8
+ write; with none, step 5 proposes them.
9
9
 
10
10
  ## Procedure
11
11
  1. **Find the doc home.** Look for an existing `docs/`, `doc/`, `documentation/`, or
12
12
  `wiki/` folder at the project root, or a `context.json` pointer to one (the same
13
13
  convention [repo-map {{SKILL}}](repo-map.md) and the context loader use). Reuse the one
14
14
  that exists — never create a second. If none exists, create `docs/` at the root.
15
- 2. **Survey the code, not the intent.** Map entry points, the public API / modules,
15
+ 2. **Pick the docs format** — Markdown or AsciiDoc, once per project. A `Docs format:
16
+ markdown|asciidoc` line in the root `AGENTS.md` decides; never re-decide it. Otherwise
17
+ look at the project's existing docs (the doc home and the root `README`), ignoring the
18
+ agent-runtime files below: only `.md` → Markdown; only `.adoc` → AsciiDoc; both → ask
19
+ the user; no docs yet → AsciiDoc. Record the result as that line under *Non-obvious
20
+ conventions* in `AGENTS.md`.
21
+ - **Agent-runtime files stay Markdown whatever the format:** `AGENTS.md`, `CLAUDE.md`,
22
+ `SKILL.md`, and the specs and ADR files that loops read — the tools need them so.
23
+ - **In a Markdown project, propose — never impose — a switch to AsciiDoc** when the docs
24
+ would gain from it: complex tables, many diagrams, content reused across pages,
25
+ several versions, or PDF output. Say which need you saw, once; the user decides, and a
26
+ yes rewrites the recorded line.
27
+ - **In AsciiDoc,** apply *AsciiDoc mode* below wherever this skill shows Markdown.
28
+ 3. **Interview first when the project has no docs yet** — before writing anything. The code
29
+ gives the *what*; only the user gives the *why*. Ask one question at a time, offering
30
+ options where you can: the audience and what they need to do — which also sets the C4
31
+ levels: L1 and L2 always, L3 only for the containers the user calls complex; the project's purpose;
32
+ goals and non-goals; the key decisions and the reason for each; constraints; what is out
33
+ of scope. Read any spec, ADRs or design notes that exist, for facts. When an answer
34
+ explains a decision, offer to record it as an ADR (in `architecture`'s *Decisions*) —
35
+ accept or skip. Draft only once the user says the interview is done. Skip this step on a
36
+ re-run or when docs exist: their prose already carries the intent.
37
+ 4. **Survey the code, not the intent.** Map entry points, the public API / modules,
16
38
  commands, config, and key directories — read the actual behaviour ({{LAW:verify-before-asserting}},
17
39
  {{DOCTRINE:read-the-docs-first}}). If there is no `ARCHITECTURE.md`, run [repo-map {{SKILL}}](repo-map.md)
18
40
  first so you have the orientation map to build on.
19
- 3. **Select the doc set.** Unless the argument names the targets, propose which of the four
41
+ 5. **Select the doc set.** Unless the argument names the targets, propose which of the four
20
42
  documents apply and wait for the user's go-ahead — e.g. "no UI detected (no stylesheet,
21
43
  theme, or component tree) — skipping `DESIGN.md`". `AGENTS.md`, `PRD.md` and
22
44
  `architecture.md` apply to every project; `DESIGN.md` only when a UI exists.
23
45
  `all` still skips `DESIGN.md` when no UI exists, and says so in one line.
24
- 4. **Write or refresh each selected document** from its template in *The doc set* below,
46
+ 6. **Write or refresh each selected document** from its template in *The doc set* below,
25
47
  under the rules in *Re-runs*. **Invent nothing**: every claim cites `path:line`, or carries
26
48
  the marker `⚠ to confirm` (in the user's language). When drafting is done, put every `⚠` to
27
49
  the user as **one** batch of questions — not one at a time — and fold the answers in as plain
28
50
  text. A command you could not run is recorded as `⚠ fails: <error>`, never as working.
29
- Every document follows the three page conventions in step 5.
30
- 5. **Reconcile the markdown (whole tree).** Check every existing page against the current
51
+ Every document follows the three page conventions in step 7.
52
+ 7. **Reconcile the docs (whole tree).** Check every existing page against the current
31
53
  implementation — renamed or removed APIs, changed flags, dead examples — and fix the
32
54
  drift. Add pages for significant undocumented surfaces; remove docs for features that no
33
55
  longer exist ({{LAW:deletion-is-deliberate}}). For substantial writing, dispatch
34
56
  the [docs {{AGENT}}](../{{DIR_AGENTS}}/docs.md). Keep examples runnable — run them.
35
- Every generated page follows three conventions:
57
+ Every generated page follows three conventions (in AsciiDoc mode, their *AsciiDoc mode*
58
+ forms):
36
59
  - **Typed frontmatter.** Open each page with YAML frontmatter carrying `type:` (one of
37
60
  `overview`, `module`, `api`, `flow`, `how-to`, `prd`, `design-system`, `architecture`),
38
61
  `mirrors-commit: <sha>`, and `generated: <date>` — so drift is auditable per page, not only
@@ -43,15 +66,15 @@ write; with none, step 3 proposes them.
43
66
  `<!-- geneseed:doc:start -->` and `<!-- geneseed:doc:end -->` markers — one pair per
44
67
  section, so prose a reader adds between sections survives — and on re-runs rewrite *only*
45
68
  inside them; hand-authored prose outside the markers is never touched.
46
- - **Diagrams as Mermaid, keyed by purpose.** In the markdown, render diagrams as fenced
69
+ - **Diagrams as Mermaid, keyed by purpose.** In the doc pages, render diagrams as fenced
47
70
  `mermaid` code blocks — they render natively on GitHub, Obsidian, and VS Code with no
48
71
  dependency. Pick the kind by what you are showing: sequence for runtime/request flows,
49
72
  ER for data models, state for lifecycles, flowchart for control flow and C4 views. Reserve
50
- hand-authored inline SVG for the offline HTML only (step 7).
51
- 6. **Keep an index.** Ensure the doc home has an index/README linking every page, the doc-set
73
+ hand-authored inline SVG for the offline HTML only (step 9).
74
+ 8. **Keep an index.** Ensure the doc home has an index/README linking every page, the doc-set
52
75
  members included, current with the set you just reconciled.
53
- 7. **Regenerate the HTML** — self-contained files at the doc home, a visual parallel to the
54
- markdown and never a source of truth: regenerate them whole from the markdown each run
76
+ 9. **Regenerate the HTML** — self-contained files at the doc home, a visual parallel to the
77
+ doc pages and never a source of truth: regenerate them whole from the pages each run
55
78
  ({{DOCTRINE:edit-the-source-not-the-surface}}).
56
79
  Overwrite the previous ones, but confirm each is the generated artifact before clobbering
57
80
  ({{LAW:verify-before-asserting}}, {{LAW:deletion-is-deliberate}}). Both files:
@@ -65,7 +88,7 @@ write; with none, step 3 proposes them.
65
88
  **`overview.html`** (always) must:
66
89
  - **Carry the digest** — hero (project name + one-line purpose), a quick-facts strip
67
90
  (language, entry point, build/test/run, license), an architecture section (component
68
- cards and an inline-SVG or CSS directory tree), and links out to the markdown docs for
91
+ cards and an inline-SVG or CSS directory tree), and links out to the doc pages for
69
92
  depth.
70
93
  - **Go deep on the hard parts** — identify the few subsystems that carry the project's
71
94
  essential complexity (the core engine, the load-bearing algorithms, the non-obvious
@@ -83,7 +106,7 @@ write; with none, step 3 proposes them.
83
106
  sizes and weights; spacing, radius and shadow on sample squares; components as cards
84
107
  (variants, states, path) — no live rendering, which would need the project's framework; and
85
108
  the hard-coded-value drift in a separate callout.
86
- 8. **Do not auto-publish.** Leave the changes for the user to review; stage and commit only
109
+ 10. **Do not auto-publish.** Leave the changes for the user to review; stage and commit only
87
110
  on consent, via the [commit {{SKILL}}](commit.md) ({{DOCTRINE:consent-before-push}}).
88
111
 
89
112
  ## The doc set
@@ -121,26 +144,67 @@ The detailed architecture. Open with a link to the root `ARCHITECTURE.md` (repo-
121
144
  map) and make sure the map links back — one fenced `geneseed:doc` line at its top, nothing else
122
145
  in that file touched; repeat nothing between the two.
123
146
  1. **Context (C4 L1)** — the system, its actors, and the external systems (APIs, SSO, shared
124
- databases), as a Mermaid flowchart.
147
+ databases).
125
148
  2. **Containers (C4 L2)** — the deployable units and the protocols between them.
126
- 3. **Components (C4 L3)** — one diagram per *significant* container, not one per module. On a
127
- monorepo, work one container or package at a time.
149
+ 3. **Components (C4 L3)** — only for the containers the user called complex in the interview
150
+ (on a re-run, the ones already drawn), one diagram each. On a monorepo, work one container
151
+ or package at a time. Never draw L4 (code) — the code is its own diagram.
128
152
  4. **Decisions** — lightweight inline ADRs, `ADR-001`, …, four lines each: *context · decision ·
129
153
  consequences · evidence*. Structural choices only (framework, storage, architectural style,
130
154
  decomposition) — not details.
131
155
 
156
+ **C4 notation.** Every diagram is a Mermaid `flowchart` styled as C4 — not the `C4Context` /
157
+ `C4Container` diagram types, which Mermaid still marks experimental and lays out by hand. Each
158
+ node reads `name [type · technology]` (type: person, software system, container, component,
159
+ database), each edge `intent · protocol`, and each diagram carries a `title:` and a `Legend`
160
+ subgraph keying its shapes (c4model.com notation). Each diagram sits inside its section's
161
+ `geneseed:doc` fences, so a re-run redraws it there and nowhere else.
162
+
163
+ ```mermaid
164
+ ---
165
+ title: Containers (C4 L2) — shop
166
+ ---
167
+ flowchart LR
168
+ user(["Customer [person]"]) -->|"browses · HTTPS"| web["web [container · React SPA]"]
169
+ web -->|"reads orders · HTTPS/JSON"| api["api [container · Node/Express]"]
170
+ api -->|"reads/writes · SQL"| db[("orders [database · PostgreSQL]")]
171
+ subgraph Legend
172
+ l1(["person"]) ~~~ l2["container"] ~~~ l3[("database")]
173
+ end
174
+ ```
175
+
132
176
  ### `AGENTS.md` — repo root, at most ~60 lines
133
177
  The short manual an agent reads before touching the repo.
134
178
  1. **Commands** — build, test, lint, run: exact, and verified by running each one.
135
- 2. **Non-obvious conventions**.
179
+ 2. **Non-obvious conventions** — first, the `Docs format:` line from step 2.
136
180
  3. **Do not touch** — generated files, vendored code, applied migrations.
137
- 4. **Before you change…** — targeted pointers: a UI → `docs/DESIGN.md`; a new flow or
138
- container → `docs/architecture.md`; a feature → `docs/PRD.md` (its FR ID).
181
+ 4. **Before you change…** — targeted pointers: a UI → `docs/DESIGN.md`; a change that crosses
182
+ containers → read the L2 diagram in `docs/architecture.md` first; a new flow or container →
183
+ `docs/architecture.md`; a feature → `docs/PRD.md` (its FR ID) — with the extension the docs
184
+ format gives them.
139
185
 
140
186
  Geneseed may own a block in this file, between `<!-- BEGIN GENESEED -->` and
141
187
  `<!-- END GENESEED -->` — never write inside it. Create the file if it is absent; if it holds
142
188
  only that block, add the project sections below it.
143
189
 
190
+ ## AsciiDoc mode
191
+ When step 2 picks AsciiDoc, every doc page this skill writes is AsciiDoc; `AGENTS.md` and
192
+ the other agent-runtime files stay Markdown.
193
+ - **Page names:** `.adoc` for every page — `PRD.adoc`, `DESIGN.adoc`, `architecture.adoc`,
194
+ `index.adoc`. A `README.adoc` is fine: GitHub shows it as the repo homepage.
195
+ - **Typed header, not frontmatter:** the page title (`= Title`) followed by attributes —
196
+ `:geneseed-type: prd`, `:geneseed-mirrors-commit: <sha>`, `:geneseed-generated: <date>`.
197
+ `index.adoc` carries none.
198
+ - **Fences:** `// geneseed:doc:start` and `// geneseed:doc:end` comment lines, one pair per
199
+ section, with the same rewrite-only-inside rule.
200
+ - **Diagrams:** Mermaid in a `[source,mermaid]` listing block delimited by `----` — GitHub
201
+ renders it like a Markdown `mermaid` fence; the C4 notation is unchanged. Not `[mermaid]` blocks (asciidoctor-diagram)
202
+ and no STEM: GitHub renders neither.
203
+ - **No `include::`** in a generated page — GitHub does not resolve it, and an agent reading
204
+ the raw file would see the directive instead of the content. Each page reads whole on its
205
+ own, and no Markdown copy is kept beside it: two sources drift.
206
+ - **Tables** use `|===` blocks; the FR/NFR columns are unchanged.
207
+
144
208
  ## Re-runs
145
209
  - **Reader prose is sacred.** Rewrite only inside a section's `geneseed:doc` fences. A
146
210
  document that already exists **without** fences (a hand-written `PRD.md`, say) is never
@@ -159,12 +223,17 @@ only that block, add the project sections below it.
159
223
  ## Done when
160
224
  - Every selected doc-set member exists, follows its template, and cites its evidence or
161
225
  carries `⚠`; the batch of `⚠` questions has been put to the user.
162
- - The doc home mirrors the current implementation: every markdown page carries typed
163
- frontmatter, wraps its generated sections in `geneseed:doc` markers, and is verified against
226
+ - The doc home mirrors the current implementation: every page carries typed frontmatter
227
+ (header attributes in AsciiDoc mode), wraps its generated sections in `geneseed:doc` markers, and is verified against
164
228
  the code or flagged; examples run, Mermaid fences are well-formed, and the index links
165
229
  them all.
166
230
  - `overview.html` opens offline and shows the digest, the complex-core deep-dives, and the
167
231
  doc set; `design.html` does too wherever `DESIGN.md` exists; and any surface left
168
232
  undocumented is named explicitly.
233
+ - `AGENTS.md` records the `Docs format:` line.
234
+ - In AsciiDoc mode, every page written passes `asciidoctor --failure-level=WARN -o - <page>`
235
+ (output discarded) when `asciidoctor` is on PATH. Without it, the report says
236
+ `asciidoctor lint: SKIPPED — not installed` — never silently passed over, never installed
237
+ for the user.
169
238
 
170
239
  <!-- INCLUDE: skills/_self-improvement.md -->