geneseed 3.12.0 → 3.14.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.
- package/CHANGELOG.md +160 -0
- package/README.md +2 -2
- package/SHIPPED.md +1 -1
- package/adapters/bob/README.md +1 -1
- package/adapters/claude-code/README.md +1 -1
- package/adapters/openclaude/README.md +1 -1
- package/adapters/opencode/README.md +3 -3
- package/adapters/opencode/plugins/geneseed-learn.js +10 -1
- package/bin/geneseed-cli.mjs +10 -14
- package/bin/geneseed-hook.mjs +22 -15
- package/docs/README.md +41 -22
- package/docs/concepts/agents.md +3 -1
- package/docs/concepts/collaboration.md +3 -1
- package/docs/concepts/exclusions.md +4 -2
- package/docs/concepts/footprint.md +3 -1
- package/docs/concepts/foreman-mode.md +3 -1
- package/docs/concepts/hook-gates.md +61 -0
- package/docs/concepts/hooks.md +8 -56
- package/docs/concepts/host-matrix.md +38 -0
- package/docs/concepts/hosts.md +6 -32
- package/docs/concepts/loop-branch.md +66 -0
- package/docs/concepts/loop-bricks.md +48 -0
- package/docs/concepts/loop-contracts.md +33 -0
- package/docs/concepts/loop-gates.md +30 -0
- package/docs/concepts/loop-notes.md +43 -0
- package/docs/concepts/loop-risk.md +36 -0
- package/docs/concepts/loop-templates.md +50 -0
- package/docs/concepts/loops.md +24 -116
- package/docs/concepts/lsp.md +2 -0
- package/docs/concepts/memory.md +3 -1
- package/docs/concepts/notebook.md +3 -1
- package/docs/concepts/rules.md +4 -2
- package/docs/concepts/skills.md +3 -1
- package/docs/concepts/themes.md +3 -1
- package/docs/extending.md +28 -13
- package/docs/guides/autostart.md +3 -1
- package/docs/guides/choose-your-setup.md +5 -80
- package/docs/guides/headless.md +5 -3
- package/docs/guides/install-agent-md.md +37 -0
- package/docs/guides/install-bob.md +20 -0
- package/docs/guides/install-claude-code.md +22 -0
- package/docs/guides/install-clone.md +34 -0
- package/docs/guides/install-openclaude.md +25 -0
- package/docs/guides/install-opencode.md +35 -0
- package/docs/guides/install-scripted.md +21 -0
- package/docs/guides/install.md +9 -118
- package/docs/guides/loop-finish.md +54 -0
- package/docs/guides/loop-running.md +66 -0
- package/docs/guides/loop-which-template.md +35 -0
- package/docs/guides/mcp-filesystem.md +41 -0
- package/docs/guides/mcp-gitlab.md +66 -0
- package/docs/guides/mcp-markitdown.md +36 -0
- package/docs/guides/mcp.md +8 -137
- package/docs/guides/migrate.md +3 -1
- package/docs/guides/project-context.md +4 -2
- package/docs/guides/read-non-markdown.md +17 -0
- package/docs/guides/run-a-loop.md +64 -0
- package/docs/guides/run-anywhere.md +2 -0
- package/docs/guides/setup-doctrines.md +55 -0
- package/docs/guides/setup-footprint.md +48 -0
- package/docs/guides/uninstall.md +3 -1
- package/docs/guides/upgrade.md +3 -1
- package/docs/guides/verify.md +3 -1
- package/docs/guides/wiki.md +3 -1
- package/docs/guides/worktree.md +3 -1
- package/docs/reference/about.md +3 -1
- package/docs/reference/cli.md +2 -0
- package/docs/reference/env-context.md +40 -0
- package/docs/reference/environment.md +10 -59
- package/docs/reference/glossary.md +3 -1
- package/docs/reference/loop-actions.md +20 -0
- package/docs/reference/opencode-plugins.md +14 -118
- package/docs/reference/plugin-activity.md +20 -0
- package/docs/reference/plugin-context.md +27 -0
- package/docs/reference/plugin-guard.md +27 -0
- package/docs/reference/plugin-learn.md +27 -0
- package/docs/reference/plugin-notify.md +20 -0
- package/docs/reference/plugin-ponytail.md +19 -0
- package/docs/reference/plugin-workflow.md +25 -0
- package/docs/reference/troubleshoot-doctor.md +50 -0
- package/docs/reference/troubleshoot-hooks.md +99 -0
- package/docs/reference/troubleshoot-install.md +108 -0
- package/docs/reference/troubleshoot-tools.md +56 -0
- package/docs/reference/troubleshoot-update.md +23 -0
- package/docs/reference/troubleshoot-web.md +66 -0
- package/docs/reference/troubleshooting.md +13 -337
- package/docs/understand/a-day.md +3 -1
- package/docs/understand/enforced-vs-asked.md +3 -1
- package/docs/understand/harness.md +2 -0
- package/docs/understand/machine-bob.md +17 -0
- package/docs/understand/machine-claude.md +35 -0
- package/docs/understand/machine-opencode.md +36 -0
- package/docs/understand/on-your-machine.md +12 -55
- package/docs/understand/take-it-out.md +13 -0
- package/docs/web-ui.md +35 -15
- package/harness.config.json +1 -1
- package/js/README.md +30 -13
- package/js/build/args.mjs +374 -0
- package/js/build/driver.mjs +137 -553
- package/js/build/emit-claude.mjs +2 -4
- package/js/build/generate.mjs +6 -3
- package/js/build/render.mjs +5 -9
- package/js/build/source.mjs +3 -11
- package/js/cli-table.json +27 -1
- package/js/hosts/hooks-context.mjs +431 -0
- package/js/hosts/hooks-learn.mjs +275 -0
- package/js/hosts/hooks-prims.mjs +108 -0
- package/js/hosts/hooks.mjs +50 -977
- package/js/hosts/hosts.mjs +12 -4
- package/js/hosts/installs.mjs +5 -6
- package/js/hosts/mcp.mjs +13 -49
- package/js/hosts/memory-files.mjs +181 -0
- package/js/hosts/native.mjs +43 -5
- package/js/hosts/settings.mjs +135 -414
- package/js/hosts/shim.mjs +373 -0
- package/js/inspect/checks-authoring.mjs +21 -828
- package/js/inspect/checks-build.mjs +32 -4
- package/js/inspect/checks-constitution.mjs +493 -0
- package/js/inspect/checks-counts.mjs +242 -0
- package/js/inspect/checks-repo.mjs +17 -8
- package/js/inspect/diff.mjs +4 -1
- package/js/inspect/doctor.mjs +2 -2
- package/js/inspect/inventory.mjs +4 -4
- package/js/inspect/scan.mjs +1 -1
- package/js/inspect/status.mjs +1 -1
- package/js/inspect/validate.mjs +48 -35
- package/js/loop/catalog.mjs +17 -4
- package/js/loop/cli.mjs +52 -16
- package/js/loop/graph.mjs +19 -1
- package/js/loop/registry.mjs +191 -0
- package/js/loop/score.mjs +46 -7
- package/js/loop/state.mjs +157 -19
- package/js/maintain/memory.mjs +1 -1
- package/js/maintain/migrate.mjs +18 -30
- package/js/maintain/setup.mjs +1 -1
- package/js/maintain/uninstall.mjs +47 -88
- package/js/ui/cli.mjs +12 -12
- package/js/web/actions.mjs +39 -4
- package/js/web/api.mjs +34 -21
- package/js/web/docs.mjs +87 -78
- package/js/web/routes.mjs +5 -1
- package/package.json +1 -1
- package/src/agents/explorer.md +2 -2
- package/src/agents/researcher.md +3 -3
- package/src/bricks/README.md +3 -1
- package/src/bricks/adr-challenge.md +20 -0
- package/src/bricks/adr-draft.md +15 -0
- package/src/bricks/characterize.md +17 -0
- package/src/bricks/ci-fix.md +2 -1
- package/src/bricks/ci-triage.md +20 -0
- package/src/bricks/compat-check.md +16 -0
- package/src/bricks/done-check.md +16 -0
- package/src/bricks/fitness-define.md +22 -0
- package/src/bricks/flake-check.md +17 -0
- package/src/bricks/flake-repro.md +18 -0
- package/src/bricks/identify.md +6 -3
- package/src/bricks/migration-plan.md +21 -0
- package/src/bricks/mutation-check.md +18 -0
- package/src/bricks/plan.md +6 -6
- package/src/bricks/red-test.md +16 -0
- package/src/bricks/review.md +5 -3
- package/src/bricks/spec.md +18 -0
- package/src/bricks/upgrade-scout.md +16 -0
- package/src/loops/api-endpoint.json +31 -0
- package/src/loops/architecture-decision.json +22 -0
- package/src/loops/bugfix.json +1 -0
- package/src/loops/ci-repair.json +19 -0
- package/src/loops/deps-upgrade.json +29 -0
- package/src/loops/enforce-architecture-rule.json +28 -0
- package/src/loops/feature.json +8 -5
- package/src/loops/fix-flaky-tests.json +30 -0
- package/src/loops/legacy-refactor.json +26 -0
- package/src/loops/legacy-tests.json +29 -0
- package/src/loops/refactor.json +5 -1
- package/src/loops/remove-dead-code.json +30 -0
- package/src/loops/spec-first-feature.json +29 -0
- package/src/loops/tdd.json +25 -0
- package/src/loops/update-contract.json +33 -0
- package/src/skills/brick-forge.md +3 -1
- package/src/skills/bruno-collection-generator.md +3 -3
- package/src/skills/bruno-test-writer.md +3 -3
- package/src/skills/ci-fix.md +3 -3
- package/src/skills/consolidate-memory.md +4 -4
- package/src/skills/council.md +1 -1
- package/src/skills/fresh-eyes.md +1 -1
- package/src/skills/git-archaeology.md +1 -1
- package/src/skills/git-rescue.md +4 -3
- package/src/skills/handoff.md +1 -1
- package/src/skills/herdr.md +1 -1
- package/src/skills/learn-mode.md +2 -2
- package/src/skills/loop.md +22 -10
- package/src/skills/opencode-theme.md +3 -3
- package/src/skills/ponytail.md +1 -1
- package/src/skills/profile.md +3 -4
- package/src/skills/prose.md +2 -3
- package/src/skills/quiz.md +1 -1
- package/src/skills/react-view-transitions/SKILL.md +1 -1
- package/src/skills/react-view-transitions/VENDOR.md +8 -3
- package/src/skills/research.md +3 -3
- package/src/skills/review-response.md +2 -2
- package/src/skills/rule.md +3 -3
- package/src/skills/security-audit.md +4 -4
- package/src/skills/teach.md +1 -1
- package/src/skills/wiki.md +3 -3
- package/src/skills/workflow.md +4 -4
- package/src/skills/worktree.md +2 -2
- package/web/dist/assets/Activity-CjRqZnr-.js +1 -0
- package/web/dist/assets/ActivityDetail-OQTxKBj2.js +1 -0
- package/web/dist/assets/Dashboard-J-xCe_Gk.js +2 -0
- package/web/dist/assets/Docs-CvOZ8akL.js +5 -0
- package/web/dist/assets/{GreenhouseView-C6ibc8IE.js → GreenhouseView-6KDjX8da.js} +1 -1
- package/web/dist/assets/{Installs-DRYjYnxs.js → Installs-MWuhNjSm.js} +2 -2
- package/web/dist/assets/JournalView-C8MrOz2c.js +1 -0
- package/web/dist/assets/Laws-Du0-Zyp2.js +3 -0
- package/web/dist/assets/Library-DOkO0TtB.js +1 -0
- package/web/dist/assets/Loops-Dv_aLByJ.js +2 -0
- package/web/dist/assets/{Markdown-qjM2_ofS.js → Markdown-X2a3qvOh.js} +14 -14
- package/web/dist/assets/{OperatorHudView-Bs7paWmh.js → OperatorHudView-BPP1dtLz.js} +1 -1
- package/web/dist/assets/{Personal-BY5n4gTw.js → Personal-26VPYkK0.js} +1 -1
- package/web/dist/assets/Tabs-BzKuFskD.js +1 -0
- package/web/dist/assets/{usePoll-O0sJEw1Y.js → activity-Dr7ZMBI3.js} +1 -1
- package/web/dist/assets/{api-CyMRbU2x.js → api-D_ixdyEY.js} +1 -1
- package/web/dist/assets/drawable-zVVDbsyw.js +1 -0
- package/web/dist/assets/index-Ctp-4poP.css +1 -0
- package/web/dist/assets/index-KHt3wE_K.js +2 -0
- package/web/dist/assets/usePoll-DD8VjtSg.js +1 -0
- package/web/dist/index.html +3 -3
- package/web/src/pages/Laws.jsx +0 -1
- package/web/dist/assets/Activity-GaaZWXTY.js +0 -1
- package/web/dist/assets/ActivityDetail-4t7d45Du.js +0 -1
- package/web/dist/assets/Dashboard-CYn7Tp16.js +0 -2
- package/web/dist/assets/Docs-BeFKLg2T.js +0 -5
- package/web/dist/assets/JournalView-1HbU1mfg.js +0 -1
- package/web/dist/assets/Laws-DFoG8JWB.js +0 -3
- package/web/dist/assets/Library-DDjgNwz_.js +0 -1
- package/web/dist/assets/Loops-D9VEoO0F.js +0 -1
- package/web/dist/assets/Tabs-ho-EYD8i.js +0 -1
- package/web/dist/assets/drawable-CLS9drMb.js +0 -1
- package/web/dist/assets/index-CX_hXMi5.css +0 -1
- package/web/dist/assets/index-Cjr7XCA3.js +0 -2
package/js/web/docs.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* THE `?harness=` QUERY PARAM IS the Docs selector, and it is the ONLY input to these
|
|
15
15
|
* endpoints that is not the checkout itself — the one thing a test can vary. Every
|
|
16
|
-
* filtering rule below is exercised by sending
|
|
16
|
+
* filtering rule below is exercised by sending each host over one page.
|
|
17
17
|
*/
|
|
18
18
|
import { readdirSync } from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
@@ -105,7 +105,7 @@ export function docGroups() {
|
|
|
105
105
|
if (group === undefined) continue;
|
|
106
106
|
const order = Object.hasOwn(meta, 'order') ? meta.order : 0;
|
|
107
107
|
delete meta.order;
|
|
108
|
-
const page = { id, rel, ...meta };
|
|
108
|
+
const page = { id, rel, ...meta, section: sectionOf(meta) };
|
|
109
109
|
if (body.trim()) page.body = body.replace(/\n+$/, '');
|
|
110
110
|
group.pages.push([order, page]);
|
|
111
111
|
}
|
|
@@ -113,15 +113,44 @@ export function docGroups() {
|
|
|
113
113
|
for (const g of groups) {
|
|
114
114
|
const entry = byId.get(g.id);
|
|
115
115
|
// STABLE sort: equal orders keep filename order.
|
|
116
|
-
entry.pages = entry.pages
|
|
116
|
+
entry.pages = bySection(entry.pages
|
|
117
117
|
.map((pair, i) => [pair[0], i, pair[1]])
|
|
118
118
|
.sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]))
|
|
119
|
-
.map((t) => t[2]);
|
|
119
|
+
.map((t) => t[2]));
|
|
120
120
|
out.push(entry);
|
|
121
121
|
}
|
|
122
122
|
return out;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** The heading a page without a `section:` is listed under, inside its part. */
|
|
126
|
+
export const GENERAL_SECTION = 'General';
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A page's `section:` frontmatter, or `General`. Anything but a non-empty string falls back
|
|
130
|
+
* rather than throwing — `tests/unit/docs_tree.test.mjs` is where a bad value goes red.
|
|
131
|
+
*/
|
|
132
|
+
export function sectionOf(meta) {
|
|
133
|
+
const s = meta.section;
|
|
134
|
+
return typeof s === 'string' && s.trim() ? s.trim() : GENERAL_SECTION;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Pages already in `order:` order, regrouped so each section's pages sit together.
|
|
139
|
+
*
|
|
140
|
+
* SECTION ORDER IS THE SMALLEST `order:` AMONG ITS PAGES — which, on a list already sorted by
|
|
141
|
+
* order, is simply the order in which sections first appear. Within a section the pages keep
|
|
142
|
+
* their `order:` order. One rule for the list, the breadcrumb and prev/next alike, so the
|
|
143
|
+
* console never shows two orders for one part.
|
|
144
|
+
*/
|
|
145
|
+
export function bySection(pages) {
|
|
146
|
+
const runs = new Map();
|
|
147
|
+
for (const p of pages) {
|
|
148
|
+
if (!runs.has(p.section)) runs.set(p.section, []);
|
|
149
|
+
runs.get(p.section).push(p);
|
|
150
|
+
}
|
|
151
|
+
return [...runs.values()].flat();
|
|
152
|
+
}
|
|
153
|
+
|
|
125
154
|
// ---- markdown tables as data ------------------------------------------------------------
|
|
126
155
|
|
|
127
156
|
/** One table row's cells; a `\|` inside a cell is kept as a literal bar. */
|
|
@@ -259,8 +288,36 @@ function subCounts(state, body) {
|
|
|
259
288
|
|
|
260
289
|
// ---- harness filtering -----------------------------------------------------------------
|
|
261
290
|
|
|
262
|
-
|
|
263
|
-
|
|
291
|
+
/**
|
|
292
|
+
* The Docs selector names a HOST; a tag names a host or a FAMILY. Bob and OpenClaude emit
|
|
293
|
+
* through the Claude engine, so a `claude` tag (page or block) is the Claude family and shows
|
|
294
|
+
* to all three of them, while `bob` / `openclaude` narrow to the one host. `name` is how a
|
|
295
|
+
* host-only passage's visible label spells the host. One row per host: the family table, the
|
|
296
|
+
* host list and both marker patterns below are read off it.
|
|
297
|
+
*/
|
|
298
|
+
const DOC_HOSTS = {
|
|
299
|
+
opencode: { family: 'opencode', name: 'OpenCode' },
|
|
300
|
+
claude: { family: 'claude', name: 'Claude Code' },
|
|
301
|
+
openclaude: { family: 'claude', name: 'OpenClaude' },
|
|
302
|
+
bob: { family: 'claude', name: 'IBM Bob' },
|
|
303
|
+
};
|
|
304
|
+
export const HOST_FAMILY = Object.fromEntries(
|
|
305
|
+
Object.entries(DOC_HOSTS).map(([id, h]) => [id, h.family]));
|
|
306
|
+
export const HARNESSES = Object.keys(DOC_HOSTS);
|
|
307
|
+
const anyOf = (words) => `(?:${words.join('|')})`;
|
|
308
|
+
/**
|
|
309
|
+
* A page or block tagged `tag` shows to `host`. Untagged content shows to every host. One tag
|
|
310
|
+
* names a host or a family; a LIST names hosts exactly, no family widening, because picking
|
|
311
|
+
* some hosts of a family is the only thing a list says that one tag cannot:
|
|
312
|
+
* `["claude", "openclaude"]` is Claude Code and OpenClaude, not Bob.
|
|
313
|
+
*/
|
|
314
|
+
export const harnessShows = (tag, host) => (Array.isArray(tag)
|
|
315
|
+
? tag.includes(host)
|
|
316
|
+
: !tag || tag === host || tag === HOST_FAMILY[host]);
|
|
317
|
+
/** A block's tag: `claude` alone, or `claude,openclaude` — the list, read as above. */
|
|
318
|
+
const harnessTag = (raw) => (raw.includes(',') ? raw.split(/\s*,\s*/) : raw);
|
|
319
|
+
const HOST_ID = anyOf(HARNESSES);
|
|
320
|
+
const HARNESS_OPEN_RE = new RegExp(String.raw`^\s*<!--\s*harness:(${HOST_ID}(?:\s*,\s*${HOST_ID})*)\s*-->\s*$`);
|
|
264
321
|
const HARNESS_CLOSE_RE = /^\s*<!--\s*\/harness\s*-->\s*$/;
|
|
265
322
|
/**
|
|
266
323
|
* The cheap presence test for the early-out. It must never be NARROWER than the open
|
|
@@ -272,15 +329,17 @@ const HARNESS_HINT_RE = /<!--\s*harness:/;
|
|
|
272
329
|
export function normHarness(value, state) {
|
|
273
330
|
const v = (value || '').trim().toLowerCase();
|
|
274
331
|
if (HARNESSES.includes(v)) return v;
|
|
275
|
-
|
|
332
|
+
// Emit names start with their host id (`claude-global`, `bob`, …); `files` reads OpenCode's.
|
|
333
|
+
const emit = String(state.emit || '');
|
|
334
|
+
return HARNESSES.find((h) => emit.startsWith(h)) || 'opencode';
|
|
276
335
|
}
|
|
277
336
|
|
|
278
337
|
/**
|
|
279
338
|
* Every open has a matching close, with no nesting.
|
|
280
339
|
*
|
|
281
|
-
* A marker inside a ``` fence is example text, not a marker (the same rule
|
|
282
|
-
* uses). Its purpose is to FAIL OPEN: an unbalanced marker leaves the body untouched,
|
|
283
|
-
* typo can never blank the rest of a page.
|
|
340
|
+
* A marker inside a ``` fence is example text, not a marker (the same rule the frontend's
|
|
341
|
+
* outline uses). Its purpose is to FAIL OPEN: an unbalanced marker leaves the body untouched,
|
|
342
|
+
* so a typo can never blank the rest of a page.
|
|
284
343
|
*
|
|
285
344
|
* EXPORTED FOR THE AUTHORING GATE, in the shape `installAgentEntryOf` established: failing
|
|
286
345
|
* open means a typo is INVISIBLE in the rendered output — the page simply shows everything,
|
|
@@ -309,7 +368,8 @@ export function harnessBlocksBalanced(lines) {
|
|
|
309
368
|
* the marker; the console has already filtered the block to the reader's host, so there the
|
|
310
369
|
* label only repeats what the host selector says, and it goes with the marker.
|
|
311
370
|
*/
|
|
312
|
-
const
|
|
371
|
+
const HOST_NAME = anyOf(Object.values(DOC_HOSTS).map((h) => h.name));
|
|
372
|
+
const HOST_LABEL_RE = new RegExp(String.raw`^\s*\*\(${HOST_NAME}(?: and ${HOST_NAME})* only\)\*\s*$`);
|
|
313
373
|
|
|
314
374
|
export function stripHarnessBlocks(body, harnessName) {
|
|
315
375
|
if (!HARNESS_HINT_RE.test(body)) return body;
|
|
@@ -329,7 +389,7 @@ export function stripHarnessBlocks(body, harnessName) {
|
|
|
329
389
|
}
|
|
330
390
|
if (!inFence) {
|
|
331
391
|
const m = HARNESS_OPEN_RE.exec(line);
|
|
332
|
-
if (m) { keep = m[1]
|
|
392
|
+
if (m) { keep = harnessShows(harnessTag(m[1]), harnessName); afterOpen = true; continue; }
|
|
333
393
|
if (HARNESS_CLOSE_RE.test(line)) { keep = true; continue; }
|
|
334
394
|
if (labelSlot && HOST_LABEL_RE.test(line)) continue;
|
|
335
395
|
}
|
|
@@ -353,23 +413,21 @@ function splitLines(s) {
|
|
|
353
413
|
function visibleGroups(harnessName) {
|
|
354
414
|
const groups = [];
|
|
355
415
|
for (const g of docGroups()) {
|
|
356
|
-
if (g.harness
|
|
357
|
-
const pages = g.pages.filter((p) =>
|
|
416
|
+
if (!harnessShows(g.harness, harnessName)) continue;
|
|
417
|
+
const pages = g.pages.filter((p) => harnessShows(p.harness, harnessName));
|
|
358
418
|
if (pages.length) groups.push({ ...g, pages });
|
|
359
419
|
}
|
|
360
420
|
return groups;
|
|
361
421
|
}
|
|
362
422
|
|
|
363
|
-
// ---- slugs
|
|
423
|
+
// ---- heading slugs -----------------------------------------------------------------------
|
|
364
424
|
|
|
365
425
|
const SLUG_STRIP_RE = new RegExp(`[^a-z0-9${WHITESPACE}-]`, 'g');
|
|
366
426
|
const SLUG_WS_RE = new RegExp(`[${WHITESPACE}]+`, 'g');
|
|
367
427
|
const SLUG_DASH_RE = /-+/g;
|
|
368
|
-
const HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
369
428
|
|
|
370
429
|
/**
|
|
371
|
-
* The same rules the frontend's `slug()` uses
|
|
372
|
-
* heading matches the id the renderer assigns.
|
|
430
|
+
* The same rules the frontend's `slug()` uses for the id it gives a rendered heading.
|
|
373
431
|
*
|
|
374
432
|
* `WHITESPACE` and `stripWhitespace` rather than `\s` and `trim()` — see `WHITESPACE`'s own
|
|
375
433
|
* docblock for the measured set of characters where this matters.
|
|
@@ -380,45 +438,6 @@ export function slugifyHeading(text) {
|
|
|
380
438
|
return s.replace(/^-+|-+$/g, '');
|
|
381
439
|
}
|
|
382
440
|
|
|
383
|
-
/**
|
|
384
|
-
* The body trimmed to one heading's section.
|
|
385
|
-
*
|
|
386
|
-
* Returns `[body, ok]`; `ok === false` (with the ORIGINAL body) when the anchor is
|
|
387
|
-
* missing, so the caller falls back to the whole document. An H1 slice stops at the first
|
|
388
|
-
* H2 — `max(level, 2)` — so it captures an intro paragraph rather than the whole file.
|
|
389
|
-
*/
|
|
390
|
-
export function sliceSection(body, anchor) {
|
|
391
|
-
const lines = splitLines(body);
|
|
392
|
-
let start = -1;
|
|
393
|
-
let startLevel = 0;
|
|
394
|
-
let inFence = false;
|
|
395
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
396
|
-
const ln = lines[i];
|
|
397
|
-
if (ln.startsWith('```')) { inFence = !inFence; continue; }
|
|
398
|
-
if (inFence) continue;
|
|
399
|
-
const m = HEADING_RE.exec(ln);
|
|
400
|
-
if (!m) continue;
|
|
401
|
-
if (slugifyHeading(m[2]) === anchor) {
|
|
402
|
-
start = i;
|
|
403
|
-
startLevel = Math.max(m[1].length, 2);
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
if (start < 0) return [body, false];
|
|
408
|
-
const out = [lines[start]];
|
|
409
|
-
inFence = false;
|
|
410
|
-
for (let j = start + 1; j < lines.length; j += 1) {
|
|
411
|
-
const ln = lines[j];
|
|
412
|
-
if (ln.startsWith('```')) { inFence = !inFence; out.push(ln); continue; }
|
|
413
|
-
if (inFence) { out.push(ln); continue; }
|
|
414
|
-
const m = HEADING_RE.exec(ln);
|
|
415
|
-
if (m && m[1].length <= startLevel) break;
|
|
416
|
-
out.push(ln);
|
|
417
|
-
}
|
|
418
|
-
while (out.length && out[out.length - 1].trim() === '') out.pop();
|
|
419
|
-
return [`${out.join('\n')}\n`, true];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
441
|
// ---- the endpoints -----------------------------------------------------------------------
|
|
423
442
|
|
|
424
443
|
function findDocPage(pageId) {
|
|
@@ -472,23 +491,20 @@ function glossary(state, page) {
|
|
|
472
491
|
* what gates the github-shaped deep links in the UI. Both come from `originDisplay()`,
|
|
473
492
|
* which shells out to `git remote get-url` (the `git` row in `ALLOWED_SPAWNS`).
|
|
474
493
|
*
|
|
475
|
-
* `version`
|
|
476
|
-
*
|
|
477
|
-
* the snapshot spells
|
|
478
|
-
* field is `{}`, always, and `sd.version || {}` reproduces the EXPRESSION rather than the
|
|
479
|
-
* constant: if the key ever appears, the page gains it without another edit.
|
|
494
|
+
* `version` carries the status snapshot's fingerprints under the names the About page reads.
|
|
495
|
+
* It used to read a `version` key the snapshot never had, so the page's build rows were
|
|
496
|
+
* always empty; the snapshot spells them `installed_fp` / `source_fp` / `version_verdict`.
|
|
480
497
|
*
|
|
481
498
|
* THERE IS NO INTERPRETER FIELD, and the absence is deliberate — see `apiSetup` in
|
|
482
499
|
* `api.mjs`, which spells the same absence for the same reason.
|
|
483
|
-
*
|
|
484
|
-
* `statusData()` is called for one dead field, and deliberately: this page has always paid that
|
|
485
|
-
* cost, and skipping it would answer faster in a way no gate measures and no user asked for.
|
|
486
500
|
*/
|
|
487
501
|
function about(state) {
|
|
488
502
|
const sd = statusData();
|
|
489
503
|
const od = originDisplay();
|
|
490
504
|
return {
|
|
491
|
-
version:
|
|
505
|
+
version: {
|
|
506
|
+
installed_fp: sd.installed_fp, source_fp: sd.source_fp, verdict: sd.version_verdict,
|
|
507
|
+
},
|
|
492
508
|
theme: state.theme,
|
|
493
509
|
emit: state.emit,
|
|
494
510
|
deployed: deployed(state),
|
|
@@ -510,7 +526,8 @@ export function apiDocs(state, harnessName = null) {
|
|
|
510
526
|
const groups = visibleGroups(hn).map((g) => ({
|
|
511
527
|
id: g.id,
|
|
512
528
|
label: g.label,
|
|
513
|
-
pages: g.pages.map((p) => ({ id: p.id, title: p.title, kind: p.kind
|
|
529
|
+
pages: g.pages.map((p) => ({ id: p.id, title: p.title, kind: p.kind, section: p.section,
|
|
530
|
+
...(typeof p.description === 'string' ? { description: p.description } : {}) })),
|
|
514
531
|
}));
|
|
515
532
|
return { groups, harness: hn };
|
|
516
533
|
}
|
|
@@ -538,18 +555,10 @@ export function apiDocsPage(state, pageId, harnessName = null) {
|
|
|
538
555
|
*/
|
|
539
556
|
const KIND_ROUTES = {
|
|
540
557
|
markdown: (state, pageId, page, hn) => {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
// A successful slice drops the anchor: the heading is already at the top, so the
|
|
544
|
-
// client must not also try to scroll to it.
|
|
545
|
-
if (anchor && page.slice) {
|
|
546
|
-
const [sliced, ok] = sliceSection(body, anchor);
|
|
547
|
-
body = sliced;
|
|
548
|
-
if (ok) anchor = null;
|
|
549
|
-
}
|
|
550
|
-
body = rewriteDocLinks(stripHarnessBlocks(body, hn), page.source, idsByRel());
|
|
558
|
+
const body = rewriteDocLinks(stripHarnessBlocks(readDocSource(page.source), hn),
|
|
559
|
+
page.source, idsByRel());
|
|
551
560
|
return { id: pageId, title: page.title, kind: 'markdown', body,
|
|
552
|
-
source: page.source,
|
|
561
|
+
source: page.source, links: resolveLinks(state, body) };
|
|
553
562
|
},
|
|
554
563
|
concept: (state, pageId, page, hn) => {
|
|
555
564
|
const body = rewriteDocLinks(subCounts(state, stripHarnessBlocks(page.body ?? '', hn)),
|
package/js/web/routes.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* expose X?", which has nothing to do with sockets, daemons or gzip.
|
|
16
16
|
*/
|
|
17
17
|
import { formatValue, isTruthy, parseJson } from '../lib/json.mjs';
|
|
18
|
-
import { apiExcludesMutate, apiInstallToggle, apiMcpToggle, apiMemoryDelete, apiProfileSave, apiRulesMutate, apiRulesPromote, apiSelectView } from './actions.mjs';
|
|
18
|
+
import { apiExcludesMutate, apiInstallToggle, apiLoopsPresetMutate, apiMcpToggle, apiMemoryDelete, apiProfileSave, apiRulesMutate, apiRulesPromote, apiSelectView } from './actions.mjs';
|
|
19
19
|
import { apiActivityToggle } from './activity.mjs';
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -45,6 +45,10 @@ export const POST_ROUTES = new Map([
|
|
|
45
45
|
['/api/rules', [apiRulesMutate, true]],
|
|
46
46
|
['/api/rules/promote', [apiRulesPromote, true]],
|
|
47
47
|
['/api/profile', [apiProfileSave, true]],
|
|
48
|
+
// No `ok: false` arm: success returns `{ok: true, loop}`, and both refusals (unknown root,
|
|
49
|
+
// unknown preset) throw `NotFound`, which `doPost`'s catch answers 404 before this column is
|
|
50
|
+
// ever consulted — the same shape as `/api/memory/delete` and `/api/activity` above.
|
|
51
|
+
['/api/loops/preset', [apiLoopsPresetMutate, false]],
|
|
48
52
|
]);
|
|
49
53
|
|
|
50
54
|
/**
|
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.
|
|
4
|
+
"version": "3.14.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/agents/explorer.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> {{DESC_EXPLORER}}
|
|
4
4
|
|
|
5
5
|
## When to dispatch
|
|
6
|
-
- A question needs sweeping many files or directories, but
|
|
7
|
-
|
|
6
|
+
- A question needs sweeping many files or directories, but only the conclusion
|
|
7
|
+
belongs in the main context — not the file contents.
|
|
8
8
|
- Locating where something lives, how a subsystem fits together, or gathering
|
|
9
9
|
facts scattered across the repo.
|
|
10
10
|
- Your main context is small and the expensive reading should happen elsewhere.
|
package/src/agents/researcher.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
> {{DESC_RESEARCHER}}
|
|
4
4
|
|
|
5
5
|
## When to dispatch
|
|
6
|
-
- A question needs answers from *outside* the repository
|
|
7
|
-
vendor's docs, an upgrade guide, a specification
|
|
8
|
-
findings, not the pages
|
|
6
|
+
- A question needs verified answers from *outside* the repository. The open web, a
|
|
7
|
+
vendor's docs, an upgrade guide, a specification: the main context gets the verified
|
|
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
11
|
- A migrate, forge-mcp, or ingest run needs a spec or changelog read before
|
package/src/bricks/README.md
CHANGED
|
@@ -29,4 +29,6 @@ override rather than hiding it.
|
|
|
29
29
|
Start from `_template.md`, or ask the `brick-forge` skill to draft one for an agent or skill you
|
|
30
30
|
already have. Keep the body to 4-10 lines of direct instruction, and end it with how the model
|
|
31
31
|
should choose among the outcomes you declared — that sentence is what turns a vague brief into a
|
|
32
|
-
loop that actually terminates.
|
|
32
|
+
loop that actually terminates. If the brick finds something the next brick needs, write it to a
|
|
33
|
+
file in the OS temp directory and report that file's path with the outcome — the loop skill
|
|
34
|
+
records it as a note.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adr-challenge
|
|
3
|
+
description: Attack the ADR this iteration drafted; the user reviews the ADR that survives.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: skeptic
|
|
6
|
+
gate: human
|
|
7
|
+
gateOn: pass
|
|
8
|
+
outcomes: pass, fail
|
|
9
|
+
---
|
|
10
|
+
Read the ADR this iteration wrote as a skeptic reading it cold. Is a decision driver missing? Is
|
|
11
|
+
a rejected option strawmanned? Is a consequence — cost, risk, the way back — left unstated? Does
|
|
12
|
+
it contradict an accepted ADR in the same folder, or the code as it stands? A gate amendment in
|
|
13
|
+
LOOP.md's notes for this ADR is a finding: report `fail` until the ADR answers it.
|
|
14
|
+
|
|
15
|
+
Never edit any file — findings go back to `adr-draft`, which revises the ADR.
|
|
16
|
+
|
|
17
|
+
Report `fail` only on findings that would change the decision or its consequences, never on
|
|
18
|
+
wording or style: write each one to a file in the OS temp directory and report that file's path
|
|
19
|
+
with the outcome. Report `pass` otherwise — the user then reviews the ADR before the loop
|
|
20
|
+
continues: write its path and the decision in one line to a temp file and report that path.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adr-draft
|
|
3
|
+
description: Write or revise one architecture decision record for the card's decision.
|
|
4
|
+
effect: mutate
|
|
5
|
+
agent: architect
|
|
6
|
+
outcomes: pass
|
|
7
|
+
---
|
|
8
|
+
Write or revise the ADR file the card's write set names, in Nygard form: Status: Proposed,
|
|
9
|
+
Context, Decision, Consequences — the costs as well as the gains — and at least two options
|
|
10
|
+
considered, each with why it lost. Touch only that file; never change code.
|
|
11
|
+
|
|
12
|
+
If LOOP.md's notes carry `adr-challenge` findings or a gate amendment for this ADR, answer each
|
|
13
|
+
one: change the decision, or record it under Consequences.
|
|
14
|
+
|
|
15
|
+
Report `pass` once the ADR is written.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: characterize
|
|
3
|
+
description: Pin what legacy code does today with characterization tests, touching tests only.
|
|
4
|
+
effect: mutate
|
|
5
|
+
agent: tester
|
|
6
|
+
outcomes: pass, fail
|
|
7
|
+
---
|
|
8
|
+
For the card's change point (with no card, the code the requirement names), write tests that pin
|
|
9
|
+
what the code DOES today, not what it should do: assert a value you expect to be wrong, run it,
|
|
10
|
+
and copy the observed value in. Touch only test files — never production code.
|
|
11
|
+
|
|
12
|
+
Pin odd behaviour as it is and name it in the test's title; do not fix it here. If LOOP.md's notes
|
|
13
|
+
list surviving mutants for this card, add the assertions that kill them.
|
|
14
|
+
|
|
15
|
+
Report `pass` once the new tests run green against the unchanged code.
|
|
16
|
+
Report `fail` if the code cannot be put under test without breaking a dependency first — that is
|
|
17
|
+
a refactor in its own right — naming the dependency.
|
package/src/bricks/ci-fix.md
CHANGED
|
@@ -11,7 +11,8 @@ guess from the check's title, before changing anything.
|
|
|
11
11
|
Touch only what that failure requires; this is a repair, not a chance to improve the pipeline
|
|
12
12
|
along the way.
|
|
13
13
|
|
|
14
|
-
Report `pass` once the named check is fixed and green
|
|
14
|
+
Report `pass` once the named check is fixed and green LOCALLY — CI itself is re-read after the
|
|
15
|
+
push, not here.
|
|
15
16
|
|
|
16
17
|
Report `fail` if the check cannot be fixed from this repo alone (an infra outage, a flake with no
|
|
17
18
|
reproducible cause, a required secret that is missing), naming why.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ci-triage
|
|
3
|
+
description: Read the failing CI checks for this commit and pick one code failure to fix, or stop.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: explorer
|
|
6
|
+
outcomes: more, done
|
|
7
|
+
---
|
|
8
|
+
Key on the commit, not the branch: `gh run list --commit <HEAD sha> --json databaseId,status,conclusion`.
|
|
9
|
+
A commit pushed moments ago may not have a run yet — poll that command for up to ~2 minutes
|
|
10
|
+
before concluding none exists. Wait on a pending run with `gh run watch <id>`, then read
|
|
11
|
+
`gh run view <id> --log-failed` — never guess from a check's title. Classify each failure as
|
|
12
|
+
code, flaky or infra. Never edit any file. `ci-repair` needs CI to run on `loop/*` branches: if
|
|
13
|
+
still no run after that poll, write "CI does not run for <sha>" to a file in the OS temp
|
|
14
|
+
directory and report `done` with that file's path — never as green.
|
|
15
|
+
|
|
16
|
+
Write the classification, one failure per line, to a file in the OS temp directory and report
|
|
17
|
+
that file's path with the outcome. Report `more` with a card for ONE code failure:
|
|
18
|
+
{"intent": "<fix check X: cause>", "writeSet": ["<files the fix touches>"],
|
|
19
|
+
"actions": ["<each of: format, imports, rename, logic, new-file, api, delete, architecture>"]}
|
|
20
|
+
Report `done` when CI is green for HEAD, or only flaky or infra failures remain — naming them.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compat-check
|
|
3
|
+
description: Confirm consumers of the old and the new interface both still work after this iteration.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: tester
|
|
6
|
+
outcomes: pass, fail
|
|
7
|
+
---
|
|
8
|
+
Run the tests of every consumer this change can reach — those still on the old shape as well as
|
|
9
|
+
those already moved — and, where the repo records consumer contracts (Pact, Spring Cloud
|
|
10
|
+
Contract), the provider verification against them. In a contract step, where the old shape is
|
|
11
|
+
removed on purpose, check instead that no consumer still uses it. Never edit any file: send
|
|
12
|
+
reports to the OS temp directory.
|
|
13
|
+
|
|
14
|
+
Report `pass` if every consumer works. Report `fail` naming the broken consumer, the check and
|
|
15
|
+
its raw failure, written to a file in the OS temp directory — report that file's path with the
|
|
16
|
+
outcome.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: done-check
|
|
3
|
+
description: Verify the requirement is met — every acceptance criterion proven by a passing test or a stated manual check.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: reviewer
|
|
6
|
+
outcomes: pass, fail
|
|
7
|
+
---
|
|
8
|
+
Read the requirement's acceptance criteria — `specs/<slug>/spec.md` when the branch has one, else
|
|
9
|
+
the requirement in LOOP.md — and the branch's full diff against its base. Run the suite. For
|
|
10
|
+
every criterion, name the test that proves it, or the manual check (what to do, what to see) when
|
|
11
|
+
no test can. Never edit any file. Judge only the stated criteria — never style or extras.
|
|
12
|
+
|
|
13
|
+
Report `pass` only if every criterion maps to a passing test or a stated manual check.
|
|
14
|
+
Report `fail` otherwise: write each unmet criterion, one per line, with what is missing (no test,
|
|
15
|
+
a failing test, a half-built behaviour) to a file in the OS temp directory and report that file's
|
|
16
|
+
path — the next iteration's `identify` builds from it.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fitness-define
|
|
3
|
+
description: Encode the requirement's architecture rule as a test, with an allowlist of today's violations.
|
|
4
|
+
effect: mutate
|
|
5
|
+
agent: architect
|
|
6
|
+
gate: human
|
|
7
|
+
outcomes: pass, fail
|
|
8
|
+
---
|
|
9
|
+
Turn the requirement's architecture rule into an executable test in the suite, with the repo's
|
|
10
|
+
own tools first: ArchUnit for Java (a `FreezingArchRule`, whose violation store is the
|
|
11
|
+
allowlist), then dependency-cruiser, import-linter, eslint-plugin-boundaries or size-limit.
|
|
12
|
+
Record today's violations in an allowlist beside the test so the suite stays green — each later
|
|
13
|
+
iteration removes one entry. Touch only the test, its allowlist and the tool's test-scoped setup,
|
|
14
|
+
and declare the tool's generated store as a glob in the write set (`--write-set '**/archunit_store/**'`
|
|
15
|
+
— single-quote it, the same as any glob in `--write-set`).
|
|
16
|
+
|
|
17
|
+
The user reviews the rule before the loop continues: write the rule in one sentence, the test's
|
|
18
|
+
path, the allowlist's path and its entry count to a file in the OS temp directory and report that
|
|
19
|
+
file's path with the outcome.
|
|
20
|
+
|
|
21
|
+
Report `pass` once the test runs green with the allowlist in place. Report `fail` if the rule
|
|
22
|
+
cannot be checked deterministically, with the note file naming what is ambiguous.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flake-check
|
|
3
|
+
description: Rerun the card's test until it proves stable — a retry or a sleep is not a fix.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: tester
|
|
6
|
+
outcomes: stable, flaky
|
|
7
|
+
---
|
|
8
|
+
Rerun the card's test 20 times alone, then 20 with its suite, as `flake-repro` did (Maven: a
|
|
9
|
+
loop of `mvn test -Dtest=<Class>#<method>`, shuffled). Never edit any file.
|
|
10
|
+
|
|
11
|
+
Read this iteration's diff first. Polling on the awaited condition (Awaitility
|
|
12
|
+
`await().atMost(…).until(…)`, `waitFor`) in place of a fixed sleep is a fix; adding a sleep or
|
|
13
|
+
raising an existing timeout is not, and neither is a retry (`rerunFailingTestsCount`,
|
|
14
|
+
`@RetryingTest`, `jest.retryTimes`) — report `flaky` naming it, whatever the runs say.
|
|
15
|
+
|
|
16
|
+
Report `stable` only on 0 failures in all runs. Report `flaky` otherwise, with the failure count
|
|
17
|
+
and one failure's output in a file in the OS temp directory — report that file's path.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flake-repro
|
|
3
|
+
description: Confirm which suspected tests are flaky by repeated runs, before anything is fixed.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: tester
|
|
6
|
+
outcomes: flaky, stable, broken
|
|
7
|
+
---
|
|
8
|
+
Run each suspected test the requirement names 20 times (or the N it gives) alone, then 20 with
|
|
9
|
+
its suite in a shuffled order — no code change, no retry option. Maven: loop `mvn test
|
|
10
|
+
-Dtest=<Class>#<method>` (multi-module: add `-pl <module> -Dsurefire.failIfNoSpecifiedTests=false`),
|
|
11
|
+
shuffled with `-Dsurefire.runOrder=random` and, under JUnit 5,
|
|
12
|
+
`-Djunit.jupiter.testmethod.order.default=org.junit.jupiter.api.MethodOrderer$Random`. Never
|
|
13
|
+
`-Dsurefire.rerunFailingTestsCount`: it hides the failures you count. Never edit any file. Write
|
|
14
|
+
each test as `<test>: <failures>/<runs>` with one failure's output to a file in the OS temp
|
|
15
|
+
directory and report that file's path.
|
|
16
|
+
|
|
17
|
+
Report `broken` if any test fails every run — a bug for `bugfix`, not a flake. Report `flaky` if a
|
|
18
|
+
test failed some runs, listing the confirmed ones. Report `stable` if none failed.
|
package/src/bricks/identify.md
CHANGED
|
@@ -9,12 +9,15 @@ Read the requirement and the loop's history (LOOP.md, `git log --format=%(traile
|
|
|
9
9
|
branch). If the requirement is fully met and the suite is green, report `done`.
|
|
10
10
|
|
|
11
11
|
Otherwise pick the SMALLEST next change that moves the requirement forward and can be tested on
|
|
12
|
-
its own
|
|
13
|
-
|
|
12
|
+
its own — when the branch has `specs/*/spec.md`, its next unfinished task. If the latest note is
|
|
13
|
+
`done-check`'s unmet criteria, report `more` with a card for one of them, never `done`. Report
|
|
14
|
+
`more` with a card, as JSON:
|
|
15
|
+
{"intent": "<one line>", "writeSet": ["<every file ANY brick this iteration writes, tests/docs too>"],
|
|
14
16
|
"actions": ["<each of: format, imports, rename, logic, new-file, api, delete, architecture>"]}
|
|
15
17
|
|
|
16
18
|
Declare every action honestly: the diff is scored again before commit, and anything outside the
|
|
17
19
|
write set or under-declared escalates to a blocking stop. If LOOP.md's notes say the previous
|
|
18
|
-
attempt was re-split, make this card strictly smaller than that one.
|
|
20
|
+
attempt was re-split, make this card strictly smaller than that one. The card obeys every
|
|
21
|
+
instruction in the template's `rules` (e.g. its intent cites the proof that code is unused).
|
|
19
22
|
|
|
20
23
|
Never edit, create or delete any file: this brick only reads.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migration-plan
|
|
3
|
+
description: Plan a breaking interface change as expand, migrate each consumer, contract — for the user to review.
|
|
4
|
+
effect: read
|
|
5
|
+
skill: migrate
|
|
6
|
+
gate: human
|
|
7
|
+
outcomes: pass, fail
|
|
8
|
+
---
|
|
9
|
+
Following the migrate skill, plan the change as ordered steps: expand (add the new shape beside
|
|
10
|
+
the old), migrate (one consumer or call-site group per step), contract (remove the old shape).
|
|
11
|
+
Every step must leave the system deployable and fit one `identify` card. Name the contract files
|
|
12
|
+
the expand and contract steps touch — API specs, schema migrations, event schemas. Never edit any
|
|
13
|
+
file.
|
|
14
|
+
|
|
15
|
+
The user reviews the plan before the loop continues: write the steps, one per line, then the
|
|
16
|
+
contract files, to a file in the OS temp directory and report that file's path with the outcome.
|
|
17
|
+
Those files belong in LOOP.md's `contracts`, so the expand and contract steps stop for a human:
|
|
18
|
+
the user adds any the template's defaults miss to LOOP.md's `contracts` before answering ok.
|
|
19
|
+
|
|
20
|
+
Report `pass` with the plan. Report `fail` if no backward-compatible path exists, naming what
|
|
21
|
+
forces the break.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mutation-check
|
|
3
|
+
description: Prove the new tests bite, by mutation testing scoped to the code the card names.
|
|
4
|
+
effect: read
|
|
5
|
+
agent: tester
|
|
6
|
+
outcomes: pass, fail, unavailable
|
|
7
|
+
---
|
|
8
|
+
Run the project's mutation tool scoped to the production code the card's intent names. Java: PIT
|
|
9
|
+
first — `mvn test-compile org.pitest:pitest-maven:mutationCoverage -DtargetClasses=<fq.Class*>
|
|
10
|
+
-DreportsDirectory=<OS temp dir> -DtimestampedReports=false`, or Gradle's `pitest` task; else
|
|
11
|
+
Stryker, mutmut or cargo-mutants. Never edit any file — reports and caches (.mutmut-cache,
|
|
12
|
+
mutants.out/, .stryker-tmp/) go to the OS temp dir, or are removed.
|
|
13
|
+
|
|
14
|
+
Report `pass` if no surviving mutant sits on a line the new tests target. Report `fail` with each
|
|
15
|
+
survivor as `file:line`, the mutation, and the assertion that would kill it, in a file in the OS
|
|
16
|
+
temp directory — report its path. Report `unavailable` when no tool is configured — the pom lacks
|
|
17
|
+
`pitest-junit5-plugin` under JUnit 5, or Gradle does not apply `info.solidsoft.pitest`: never add
|
|
18
|
+
it to the build; write what you checked to a temp file and report its path.
|
package/src/bricks/plan.md
CHANGED
|
@@ -8,10 +8,10 @@ outcomes: pass
|
|
|
8
8
|
Read the requirement and survey the code it touches. Split the work into an ordered list of
|
|
9
9
|
small iterations, each one sized to a single `identify` card — one testable change apiece.
|
|
10
10
|
|
|
11
|
-
Do not edit any code; this brick only plans it.
|
|
12
|
-
the ordered list, one iteration per line,
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
Do not edit any code; this brick only plans it. `plan` is not an iteration head, so its card is
|
|
12
|
+
never recorded — write the ordered list, one iteration per line, to a file in the OS temp
|
|
13
|
+
directory (never inside the worktree) and report that file's path with the outcome, so it is
|
|
14
|
+
recorded into LOOP.md's notes. Report `pass`.
|
|
15
15
|
|
|
16
|
-
If the requirement cannot be split — it is already one atomic change — say so in the
|
|
17
|
-
let the loop proceed with a single iteration.
|
|
16
|
+
If the requirement cannot be split — it is already one atomic change — say so in the note file
|
|
17
|
+
and let the loop proceed with a single iteration.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: red-test
|
|
3
|
+
description: Write the failing test for the card's intent before any production code changes.
|
|
4
|
+
effect: mutate
|
|
5
|
+
agent: tester
|
|
6
|
+
outcomes: red, green, untestable
|
|
7
|
+
---
|
|
8
|
+
Write the smallest test that expresses the card's intent at its seam, touching only the test
|
|
9
|
+
files in the card's `writeSet` — never production code; making it pass is `apply`'s job. Run it.
|
|
10
|
+
|
|
11
|
+
Report `red` if it fails FOR THE REASON the intent states — not an import error, a typo, or a
|
|
12
|
+
broken fixture in the test itself.
|
|
13
|
+
Report `green` if it already passes: the behaviour exists, so keep the test as coverage.
|
|
14
|
+
Report `untestable` if no seam lets a test reach the behaviour without changing production code
|
|
15
|
+
first, naming what blocks it. If the card's `writeSet` names no test file, report `untestable`
|
|
16
|
+
and say so.
|