geneseed 3.8.0 → 3.9.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 +39 -0
- package/DESIGN.md +13 -10
- package/README.md +4 -4
- package/SHIPPED.md +3 -3
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +1 -1
- package/adapters/opencode/README.md +3 -3
- package/adapters/opencode/plugins/geneseed-guard.js +9 -9
- package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
- package/adapters/opencode/workflows/_runtime.js +2 -2
- package/adapters/opencode/workflows/dispatch.js +1 -1
- package/docs/extending.md +72 -40
- package/docs/web/harnesses.md +2 -2
- package/docs/web/plugin-guard.md +2 -2
- package/docs/web/rules.md +1 -1
- package/docs/web/themes-author.md +2 -2
- package/docs/web-ui.md +1 -1
- package/harness.config.json +1 -1
- package/js/README.md +4 -3
- package/js/build/render.mjs +6 -3
- package/js/build/source.mjs +105 -1
- package/js/build/stubs.mjs +7 -2
- package/js/cli-table.json +3 -3
- package/js/hosts/hooks.mjs +5 -5
- package/js/hosts/native.mjs +1 -1
- package/js/inspect/checks-authoring.mjs +149 -116
- package/js/inspect/inventory.mjs +1 -2
- package/js/ui/tui.mjs +1 -1
- package/js/web/api.mjs +6 -1
- package/package.json +1 -1
- package/registry.json +7 -0
- package/src/AGENT.md.tmpl +19 -15
- package/src/agents/_template.md +3 -3
- package/src/agents/advocate.md +3 -3
- package/src/agents/architect.md +3 -3
- package/src/agents/developer.md +4 -4
- package/src/agents/docs.md +3 -3
- package/src/agents/empiricist.md +4 -4
- package/src/agents/explorer.md +4 -4
- package/src/agents/framer.md +3 -3
- package/src/agents/historian.md +3 -3
- package/src/agents/operator.md +3 -3
- package/src/agents/pragmatist.md +3 -3
- package/src/agents/researcher.md +7 -7
- package/src/agents/reviewer.md +3 -3
- package/src/agents/security.md +3 -3
- package/src/agents/skeptic.md +3 -3
- package/src/agents/steward.md +3 -3
- package/src/agents/tester.md +3 -3
- package/src/agents/user-advocate.md +3 -3
- package/src/agents/visionary.md +3 -3
- package/src/doctrines/README.md +4 -3
- package/src/doctrines/comms.md +4 -4
- package/src/doctrines/craft.md +20 -20
- package/src/doctrines/ops.md +19 -19
- package/src/doctrines/process.md +20 -20
- package/src/doctrines/rigor.md +22 -22
- package/src/laws/universal.md +23 -44
- package/src/memory/README.md +3 -3
- package/src/modes/foreman.md +1 -1
- package/src/notebook/README.md +1 -1
- package/src/ontology/universal.md +7 -9
- package/src/postures/artisan.md +2 -2
- package/src/skills/_self-improvement.md +4 -4
- package/src/skills/_template.md +1 -1
- package/src/skills/brainstorm.md +3 -3
- package/src/skills/bruno-collection-generator.md +6 -6
- package/src/skills/bruno-test-writer.md +5 -5
- package/src/skills/ci-fix.md +4 -4
- package/src/skills/commit.md +11 -10
- package/src/skills/consolidate-memory.md +4 -4
- package/src/skills/council.md +2 -2
- package/src/skills/debug.md +4 -4
- package/src/skills/deps-audit.md +3 -3
- package/src/skills/develop.md +7 -7
- package/src/skills/document-project.md +6 -6
- package/src/skills/explain-changes/SKILL.md +69 -0
- package/src/skills/explain-changes/VENDOR.md +20 -0
- package/src/skills/explain-changes/scripts/render_changes.mjs +558 -0
- package/src/skills/forge-mcp.md +4 -4
- package/src/skills/fresh-eyes.md +4 -4
- package/src/skills/frontend-design.md +1 -1
- package/src/skills/geneseed.md +3 -3
- package/src/skills/git-archaeology.md +5 -5
- package/src/skills/git-rescue.md +7 -7
- package/src/skills/handoff.md +3 -3
- package/src/skills/ingest.md +5 -5
- package/src/skills/migrate.md +8 -8
- package/src/skills/parallel-agents.md +2 -2
- package/src/skills/pipeline.md +2 -2
- package/src/skills/plan.md +3 -3
- package/src/skills/ponytail.md +2 -2
- package/src/skills/profile.md +2 -2
- package/src/skills/prose.md +1 -1
- package/src/skills/prototype.md +1 -1
- package/src/skills/refactor.md +3 -3
- package/src/skills/release.md +5 -5
- package/src/skills/repo-map.md +4 -4
- package/src/skills/research.md +9 -9
- package/src/skills/review-response.md +4 -4
- package/src/skills/rule.md +3 -3
- package/src/skills/security-audit.md +5 -5
- package/src/skills/ship.md +5 -5
- package/src/skills/skill-forge.md +4 -4
- package/src/skills/wiki.md +5 -5
- package/src/skills/workflow.md +2 -3
- package/src/skills/worktree.md +4 -4
- package/themes/_TEMPLATE.json +37 -39
- package/themes/biker.json +37 -39
- package/themes/commentator.json +37 -39
- package/themes/cyberpunk.json +37 -39
- package/themes/gamer.json +37 -39
- package/themes/imperial.json +37 -39
- package/themes/joker.json +37 -39
- package/themes/marvin.json +37 -39
- package/themes/mean.json +37 -39
- package/themes/military.json +37 -39
- package/themes/neutral.json +37 -39
- package/themes/pirate.json +37 -39
- package/themes/sports.json +37 -39
- package/themes/verstappen.json +37 -39
- package/themes/wizard.json +37 -39
- package/web/dist/assets/Activity-DAzV-qQj.js +1 -0
- package/web/dist/assets/ActivityDetail-CwEwMtKl.js +1 -0
- package/web/dist/assets/Dashboard-C2ELBW-X.js +2 -0
- package/web/dist/assets/{Docs-DrJNDPwI.js → Docs-iZrvQP8J.js} +2 -2
- package/web/dist/assets/FilterInput-CVyqmYWM.js +1 -0
- package/web/dist/assets/GreenhouseView-Bq1QmaGO.js +2 -0
- package/web/dist/assets/Installs-BiqR8xdO.js +2 -0
- package/web/dist/assets/JournalView-6wilG5Zd.js +1 -0
- package/web/dist/assets/Laws-D8YXD_XE.js +3 -0
- package/web/dist/assets/Library-DwuTZ2-L.js +1 -0
- package/web/dist/assets/{Markdown-BQAvN8xx.js → Markdown-CkFyfgNf.js} +14 -14
- package/web/dist/assets/OperatorHudView-B-HVBqfY.js +2 -0
- package/web/dist/assets/Personal-BbUTmZQR.js +1 -0
- package/web/dist/assets/Tabs-_pjUFIEI.js +1 -0
- package/web/dist/assets/drawable-CoIulEH2.js +1 -0
- package/web/dist/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-YINYabwD.woff2 +0 -0
- package/web/dist/assets/index-BMwSp8BP.js +2 -0
- package/web/dist/assets/index-CDPDk2vj.css +1 -0
- package/web/dist/assets/{react-DbkvxWG5.js → react-B9Z_tXRV.js} +1 -1
- package/web/dist/assets/sections-GalMS0Zv.js +1 -0
- package/web/dist/assets/usePoll-O0sJEw1Y.js +1 -0
- package/web/dist/index.html +4 -4
- package/web/src/pages/Laws.jsx +616 -361
- package/web/dist/assets/Activity-CH-7cPIM.js +0 -1
- package/web/dist/assets/ActivityDetail-DZvz0xS5.js +0 -1
- package/web/dist/assets/CatalogRow-DftXpZHY.js +0 -1
- package/web/dist/assets/Diff-DvwdG0L9.js +0 -2
- package/web/dist/assets/Doctor-OTmwmYZg.js +0 -1
- package/web/dist/assets/FilterInput-BakJLvaV.js +0 -1
- package/web/dist/assets/GreenhouseView-XxrAdriX.js +0 -2
- package/web/dist/assets/Harness-C_LYNk5c.js +0 -1
- package/web/dist/assets/JournalView-BcuyeEaR.js +0 -1
- package/web/dist/assets/Laws-CWHrTKY4.js +0 -3
- package/web/dist/assets/Library-CA9IHghn.js +0 -1
- package/web/dist/assets/OperatorHudView-BMRY1Tt4.js +0 -2
- package/web/dist/assets/Profile-wceET1JY.js +0 -1
- package/web/dist/assets/Rules-BEXvY4Tx.js +0 -1
- package/web/dist/assets/Settings-DS2zF0Ez.js +0 -1
- package/web/dist/assets/Skills-DahlOJuI.js +0 -1
- package/web/dist/assets/StatusBadge-Cg6j-NJL.js +0 -1
- package/web/dist/assets/activity-iWaWVtsR.js +0 -1
- package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
- package/web/dist/assets/drawable-BTHjTzF7.js +0 -1
- package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
- package/web/dist/assets/index-AN5Emnrg.js +0 -2
- package/web/dist/assets/index-Ruh93A3A.css +0 -1
- package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/web/dist/assets/lawCats-Btjp3Hk_.js +0 -1
- package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
- package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
- package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
- package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/web/dist/assets/rolldown-runtime-CbXtAM7H.js +0 -1
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import {
|
|
16
|
-
CONFIG, PACK_ORDER, PLUGIN_SRC, ROOT, SRC, THEMES,
|
|
16
|
+
CONFIG, PACK_ORDER, PLUGIN_SRC, RETIRED_RULE_IDS, ROOT, RULE_ID_RE, SRC, THEMES, canonFiles,
|
|
17
|
+
knownRuleIds, ruleCanon, titleKey,
|
|
17
18
|
} from '../build/source.mjs';
|
|
18
19
|
import { themeFiles } from '../hosts/installs.mjs';
|
|
19
20
|
import { descBlockProblem, firstBlockquote } from '../hosts/native.mjs';
|
|
@@ -47,7 +48,11 @@ export function romanToInt(num) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
|
-
* `_harness_build._LAW_META_ROW` — `<arabic>: ['<class>', '<principle>'],` in the web page.
|
|
51
|
+
* `_harness_build._LAW_META_ROW` — `<arabic>: ['<class>', '<principle>', '<id>'],` in the web page.
|
|
52
|
+
*
|
|
53
|
+
* The third field PINS the row to a rule id. The key is the number the catalogue renders, and a
|
|
54
|
+
* number is only a position: remove a law and every later row describes its predecessor's
|
|
55
|
+
* neighbour, with every class still plausible. The pin is what makes that a doctor failure.
|
|
51
56
|
*
|
|
52
57
|
* Either quote style, because a principle carrying an apostrophe is double-quoted. Every
|
|
53
58
|
* `\s*` spans newlines and the trailing comma before `]` is optional, so a row a prettier has
|
|
@@ -57,7 +62,8 @@ export function romanToInt(num) {
|
|
|
57
62
|
* `(?m)` is the `m` flag; JS has no inline form. The `.` inside the captures must NOT match a
|
|
58
63
|
* newline (Python has no `re.S` here), which is JS's default.
|
|
59
64
|
*/
|
|
60
|
-
const LAW_META_ROW =
|
|
65
|
+
const LAW_META_ROW =
|
|
66
|
+
/^[^\S\n]*(\d+):\s*\[\s*(['"])(.*?)\2\s*,\s*(['"])(.*?)\4\s*(?:,\s*(['"])(.*?)\6\s*)?,?\s*\]\s*,?[^\S\n]*$/gm;
|
|
61
67
|
|
|
62
68
|
/**
|
|
63
69
|
* `_harness_build._law_meta_problems` — the web Laws ledger's per-rule copy stays complete.
|
|
@@ -67,7 +73,7 @@ const LAW_META_ROW = /^[^\S\n]*(\d+):\s*\[\s*(['"])(.*?)\2\s*,\s*(['"])(.*?)\4\s
|
|
|
67
73
|
* description and the wrong class chip, while everything upstream is fine. That is exactly
|
|
68
74
|
* how two laws shipped description-less.
|
|
69
75
|
*/
|
|
70
|
-
export function lawMetaProblems(lawNums, lawClass, lawClasses) {
|
|
76
|
+
export function lawMetaProblems(lawNums, lawClass, lawClasses, lawIds = null) {
|
|
71
77
|
const page = path.join(ROOT, 'web', 'src', 'pages', 'Laws.jsx');
|
|
72
78
|
let text;
|
|
73
79
|
try { text = readText(page); } catch (e) {
|
|
@@ -80,11 +86,11 @@ export function lawMetaProblems(lawNums, lawClass, lawClasses) {
|
|
|
80
86
|
}
|
|
81
87
|
const meta = new Map();
|
|
82
88
|
for (const m of block[1].matchAll(LAW_META_ROW)) {
|
|
83
|
-
meta.set(Number(m[1]), [m[3], m[5]]);
|
|
89
|
+
meta.set(Number(m[1]), [m[3], m[5], m[7]]);
|
|
84
90
|
}
|
|
85
91
|
const problems = [];
|
|
86
92
|
const seen = new Set();
|
|
87
|
-
for (const roman of lawNums) {
|
|
93
|
+
for (const [i, roman] of lawNums.entries()) {
|
|
88
94
|
const n = romanToInt(roman);
|
|
89
95
|
seen.add(n);
|
|
90
96
|
if (!meta.has(n)) {
|
|
@@ -93,7 +99,12 @@ export function lawMetaProblems(lawNums, lawClass, lawClasses) {
|
|
|
93
99
|
+ 'render it with a blank Principle');
|
|
94
100
|
continue;
|
|
95
101
|
}
|
|
96
|
-
const [klass, principle] = meta.get(n);
|
|
102
|
+
const [klass, principle, pin] = meta.get(n);
|
|
103
|
+
if (lawIds && pin !== lawIds[i]) {
|
|
104
|
+
problems.push(`[authoring] LAW_META[${n}] is pinned to '${pin ?? '(no id)'}' but rule ${roman} is `
|
|
105
|
+
+ `'${lawIds[i]}' — the rows no longer follow the canon's order, so this principle `
|
|
106
|
+
+ 'describes a different law');
|
|
107
|
+
}
|
|
97
108
|
if (!principle.trim()) {
|
|
98
109
|
problems.push(`[authoring] LAW_META[${n}] has an empty principle line — rule `
|
|
99
110
|
+ `${roman} would render with a blank description`);
|
|
@@ -112,9 +123,9 @@ export function lawMetaProblems(lawNums, lawClass, lawClasses) {
|
|
|
112
123
|
return problems;
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
/** `LAW_META_ROW`'s twin for a STRING key — `'<pack>.<n>': ['<pack>', '<principle>'],`. */
|
|
126
|
+
/** `LAW_META_ROW`'s twin for a STRING key — `'<pack>.<n>': ['<pack>', '<principle>', '<id>'],`. */
|
|
116
127
|
const DOCTRINE_META_ROW =
|
|
117
|
-
/^[^\S\n]*(['"])([a-z]+\.\d+)\1:\s*\[\s*(['"])(.*?)\3\s*,\s*(['"])(.*?)\5\s
|
|
128
|
+
/^[^\S\n]*(['"])([a-z]+\.\d+)\1:\s*\[\s*(['"])(.*?)\3\s*,\s*(['"])(.*?)\5\s*(?:,\s*(['"])(.*?)\7\s*)?,?\s*\]\s*,?[^\S\n]*$/gm;
|
|
118
129
|
|
|
119
130
|
/**
|
|
120
131
|
* `lawMetaProblems`' doctrine half — the console's per-rule Principle column stays complete.
|
|
@@ -130,7 +141,7 @@ const DOCTRINE_META_ROW =
|
|
|
130
141
|
* error a six-way vocabulary cannot: a row pasted from the pack above it, keeping the class of
|
|
131
142
|
* the pack it came from, which would colour the row and label its chip with the wrong pack.
|
|
132
143
|
*/
|
|
133
|
-
export function doctrineMetaProblems(addrs) {
|
|
144
|
+
export function doctrineMetaProblems(addrs, ids = null) {
|
|
134
145
|
const page = path.join(ROOT, 'web', 'src', 'pages', 'Laws.jsx');
|
|
135
146
|
let text;
|
|
136
147
|
try { text = readText(page); } catch (e) {
|
|
@@ -142,17 +153,21 @@ export function doctrineMetaProblems(addrs) {
|
|
|
142
153
|
+ "console's doctrine rows would have no gate on their Principle column"];
|
|
143
154
|
}
|
|
144
155
|
const meta = new Map();
|
|
145
|
-
for (const m of block[1].matchAll(DOCTRINE_META_ROW)) meta.set(m[2], [m[4], m[6]]);
|
|
156
|
+
for (const m of block[1].matchAll(DOCTRINE_META_ROW)) meta.set(m[2], [m[4], m[6], m[8]]);
|
|
146
157
|
const problems = [];
|
|
147
158
|
const seen = new Set();
|
|
148
|
-
for (const addr of addrs) {
|
|
159
|
+
for (const [i, addr] of addrs.entries()) {
|
|
149
160
|
seen.add(addr);
|
|
150
161
|
if (!meta.has(addr)) {
|
|
151
162
|
problems.push(`[authoring] doctrine rule ${addr} has no row in DOCTRINE_META `
|
|
152
163
|
+ '(web/src/pages/Laws.jsx) — the console would render it with a blank Principle');
|
|
153
164
|
continue;
|
|
154
165
|
}
|
|
155
|
-
const [klass, principle] = meta.get(addr);
|
|
166
|
+
const [klass, principle, pin] = meta.get(addr);
|
|
167
|
+
if (ids && pin !== ids[i]) {
|
|
168
|
+
problems.push(`[authoring] DOCTRINE_META['${addr}'] is pinned to '${pin ?? '(no id)'}' but `
|
|
169
|
+
+ `that address is '${ids[i]}' now — the row describes a different rule`);
|
|
170
|
+
}
|
|
156
171
|
if (!principle.trim()) {
|
|
157
172
|
problems.push(`[authoring] DOCTRINE_META['${addr}'] has an empty principle line — that `
|
|
158
173
|
+ 'rule would render with a blank description');
|
|
@@ -267,22 +282,35 @@ export function proseMirrorProblems(readme, web, counts, skillStems, shipped = '
|
|
|
267
282
|
|
|
268
283
|
/** `_build_render._TMPL_SPEC_RE`, as the count gate spells it. */
|
|
269
284
|
const TMPL_SPEC_RE = /\{\{DIR_(AGENTS|SKILLS)\}\}\/([A-Za-z0-9_-]+)\.md/g;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
const
|
|
285
|
+
// A rule is DECLARED by its heading and CITED by its id — see `ruleCanon` in
|
|
286
|
+
// `js/build/source.mjs`. The gates below read the RAW source, because the render resolves ids and
|
|
287
|
+
// a bad one would already be gone from what it hands back.
|
|
288
|
+
const DECL_LINE_RE = /^### \{\{(LAW|DOCTRINE):([^}]*)\}\}[ \t]+\S/;
|
|
289
|
+
const ID_CITE_RE = /\{\{(LAW|DOCTRINE):([^}]*)\}\}/g;
|
|
290
|
+
// The pre-id spellings. Either one in src/ is a citation that names a POSITION, which is exactly
|
|
291
|
+
// what stops being true the next time the canon moves.
|
|
292
|
+
const BARE_CITE_RE = /\{\{LAW\}\}\s+[IVXLCDM]+\b|\{\{DOCTRINE\}\}\s+[a-z]+\s+\d+\b/g;
|
|
293
|
+
// `{{DOCTRINE}}` or `{{DOCTRINE:` and not `DOCTRINE`: `{{DOCTRINES}}` is the SECTION noun and is
|
|
294
|
+
// legal everywhere, including in an invariant that points at the tier as a whole.
|
|
295
|
+
const DOCTRINE_TOKEN_RE = /\{\{DOCTRINE(?:\}\}|:)/;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The rules the hooks enforce at the tool boundary, and the address each is bound to there. The
|
|
299
|
+
* hook path cannot afford to read the canon on every tool call, so its gate-ledger keys
|
|
300
|
+
* (`law-1`, `law-4`, `process-1`, `process-5` in `js/hosts/hooks.mjs` and the OpenCode guard
|
|
301
|
+
* plugin), `CONSENT_RULE` in `js/hosts/settings.mjs` and the console's `HOOK_GATED` stay
|
|
302
|
+
* number-keyed — and this pin is what turns a renumber that moves one of these rules into a
|
|
303
|
+
* doctor failure instead of a gate silently guarding its neighbour.
|
|
304
|
+
*/
|
|
305
|
+
export const HOOK_PINNED = {
|
|
306
|
+
'sealed-secrets': 'I', 'deletion-is-deliberate': 'IV',
|
|
307
|
+
'persist-insight': 'process 1', 'consent-before-push': 'process 5',
|
|
308
|
+
};
|
|
281
309
|
|
|
282
310
|
/**
|
|
283
311
|
* The LEAN block grammar, and the heading split, both duplicated from `js/build/render.mjs`.
|
|
284
312
|
*
|
|
285
|
-
* Deliberately duplicated
|
|
313
|
+
* Deliberately duplicated rather than imported: both are module-private
|
|
286
314
|
* there, and exporting them so one gate could borrow them would widen the renderer's surface
|
|
287
315
|
* for a reader that only needs to know the shape. Two literals, one paragraph apart from the
|
|
288
316
|
* rule they encode, is cheaper than a seam.
|
|
@@ -377,48 +405,84 @@ export function leanBlockProblems() {
|
|
|
377
405
|
* ⚠ THE VOCABULARY GATES ARE HERE BECAUSE `themeParityProblems` CANNOT SEE THEM. That check is
|
|
378
406
|
* presence-only and SYMMETRIC over the union of every theme's keys: a key missing from all
|
|
379
407
|
* fourteen is in parity, and a key whose VALUE breaks a parser is not its business at all. So
|
|
380
|
-
* `DOC_*` coverage, the `
|
|
408
|
+
* `DOC_*` coverage, the `LEX_*` equality and the single-word tier noun each need a gate
|
|
381
409
|
* that reads the SOURCE and asks what the source requires.
|
|
382
410
|
*/
|
|
383
411
|
export function constitutionProblems() {
|
|
384
412
|
const problems = [];
|
|
385
|
-
const dir = path.join(SRC, 'doctrines');
|
|
386
413
|
|
|
387
|
-
// ----
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
414
|
+
// ---- every rule declares an id, every id is well-formed and unique, none is retired.
|
|
415
|
+
//
|
|
416
|
+
// Numbers are positional now (`ruleCanon`), so contiguity and filing cannot go wrong; what can
|
|
417
|
+
// is the declaration itself. A `### ` line in a canon file that is not `{{KIND:id}} Name`
|
|
418
|
+
// renders nowhere as a rule — no number, no title — and a duplicated id makes every citation
|
|
419
|
+
// of it ambiguous.
|
|
420
|
+
for (const [kind, pack, file] of canonFiles(SRC)) {
|
|
421
|
+
const rel = path.relative(SRC, file).split(path.sep).join('/');
|
|
391
422
|
let text;
|
|
392
423
|
try { text = readText(file); } catch (e) {
|
|
393
|
-
problems.push(`[authoring]
|
|
394
|
-
+
|
|
395
|
-
+ 'every citation into it, active or not');
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
const found = [...text.matchAll(DOCTRINE_HEADING_RE)];
|
|
399
|
-
if (!found.length) {
|
|
400
|
-
problems.push(`[authoring] doctrines/${pack}.md carries no '### {{DOCTRINE}} ${pack} <n>' `
|
|
401
|
-
+ 'heading — the pack would render as an empty section');
|
|
424
|
+
problems.push(`[authoring] ${rel} is ${pack ? 'named in PACK_ORDER' : 'the canon'} but `
|
|
425
|
+
+ `unreadable: ${e.message} — every citation into it breaks`);
|
|
402
426
|
continue;
|
|
403
427
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
428
|
+
let count = 0;
|
|
429
|
+
for (const line of text.split('\n').filter((l) => l.startsWith('### '))) {
|
|
430
|
+
const m = DECL_LINE_RE.exec(line);
|
|
431
|
+
if (!m || m[1] !== kind) {
|
|
432
|
+
problems.push(`[authoring] ${rel}: '${line}' is not a rule declaration — a rule heading `
|
|
433
|
+
+ `here reads '### {{${kind}:<id>}} <Principle Name>'; without an id it has no number, `
|
|
434
|
+
+ 'no title and no way to be cited');
|
|
435
|
+
continue;
|
|
409
436
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
437
|
+
count += 1;
|
|
438
|
+
if (!RULE_ID_RE.test(m[2])) {
|
|
439
|
+
problems.push(`[authoring] ${rel}: rule id '${m[2]}' is not kebab-case (a-z, 0-9, '-')`);
|
|
413
440
|
}
|
|
414
|
-
|
|
415
|
-
|
|
441
|
+
}
|
|
442
|
+
if (!count) {
|
|
443
|
+
const why = pack ? 'the pack would render as an empty section' : 'there would be no invariants';
|
|
444
|
+
problems.push(`[authoring] ${rel} declares no rule — ${why}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const canon = ruleCanon(SRC);
|
|
448
|
+
const seenIds = new Map();
|
|
449
|
+
const seenNames = new Map();
|
|
450
|
+
for (const r of canon.rules) {
|
|
451
|
+
const where = r.kind === 'LAW' ? `law ${r.label}` : `doctrine ${r.label}`;
|
|
452
|
+
if (seenIds.has(r.id)) {
|
|
453
|
+
problems.push(`[authoring] rule id '${r.id}' is declared twice (${seenIds.get(r.id)} and `
|
|
454
|
+
+ `${where}) — ids are unique across laws and doctrines, or a citation names two rules`);
|
|
455
|
+
} else seenIds.set(r.id, where);
|
|
456
|
+
const lower = r.name.toLowerCase();
|
|
457
|
+
if (seenNames.has(lower)) {
|
|
458
|
+
problems.push(`[authoring] principle name '${r.name}' is declared twice (${seenNames.get(lower)} `
|
|
459
|
+
+ `and ${where}) — a citation renders as the name, so two rules would read as one`);
|
|
460
|
+
} else seenNames.set(lower, where);
|
|
461
|
+
const retiredAs = RETIRED_RULE_IDS[r.id];
|
|
462
|
+
if (retiredAs && retiredAs.liveAs !== r.kind) {
|
|
463
|
+
problems.push(`[authoring] ${where} reuses the retired id '${r.id}' (${retiredAs.was}, `
|
|
464
|
+
+ `${retiredAs.note}) — an id that meant one rule in someone's notes must never come back `
|
|
465
|
+
+ 'meaning another; pick a new one');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const rules = new Map(canon.rules.filter((r) => r.kind === 'DOCTRINE')
|
|
469
|
+
.map((r) => [`${r.pack}.${r.n}`, r]));
|
|
470
|
+
|
|
471
|
+
// ---- the hook-gated rules are still at the address the hooks key them by.
|
|
472
|
+
for (const [id, label] of Object.entries(HOOK_PINNED)) {
|
|
473
|
+
const r = canon.byId.get(id);
|
|
474
|
+
if (!r || r.label !== label) {
|
|
475
|
+
problems.push(`[authoring] the hooks gate '${id}' as ${label} (js/hosts/hooks.mjs ledger keys, `
|
|
476
|
+
+ `settings.mjs CONSENT_RULE, the console's HOOK_GATED), but the canon ${r ? `numbers it ${r.label}`
|
|
477
|
+
: 'no longer declares it'} — move those keys with it, or the tool boundary guards a `
|
|
478
|
+
+ 'different rule than the prompt names');
|
|
479
|
+
}
|
|
416
480
|
}
|
|
417
481
|
|
|
418
482
|
// The console's Principle column, keyed off the rules just parsed — so the gate is driven by
|
|
419
483
|
// the SOURCE and a rule added to a pack file is caught the same day, not when someone
|
|
420
484
|
// notices a blank description in the browser.
|
|
421
|
-
problems.push(...doctrineMetaProblems([...rules.keys()]));
|
|
485
|
+
problems.push(...doctrineMetaProblems([...rules.keys()], [...rules.values()].map((r) => r.id)));
|
|
422
486
|
|
|
423
487
|
// ---- `--exclude-rules` closes against the SAME rules this walk just found.
|
|
424
488
|
//
|
|
@@ -440,41 +504,11 @@ export function constitutionProblems() {
|
|
|
440
504
|
+ 'no switch for it');
|
|
441
505
|
}
|
|
442
506
|
|
|
443
|
-
// ----
|
|
444
|
-
//
|
|
445
|
-
// ⚠ THE ONE HARDCODED ADDRESS IN THE CODEBASE, and the one whose drift is silent AND unsafe.
|
|
446
|
-
// `js/hosts/settings.mjs` keys the git-gate hooks on `process.5` by literal; a renumber of the
|
|
447
|
-
// process pack moves that rule's neighbours under it, and the boundary would then follow
|
|
448
|
-
// whatever rule inherited the number while the prompt still said `process 5`. Checked
|
|
449
|
-
// against the pack file's own title token rather than against prose, so a reworded rule is
|
|
450
|
-
// not a false alarm and a MOVED one is not a silent pass.
|
|
451
|
-
const consentTitle = 'DOC_PROCESS_5';
|
|
452
|
-
let processText = '';
|
|
453
|
-
try { processText = readText(path.join(dir, 'process.md')); } catch { /* reported above */ }
|
|
454
|
-
if (processText && !new RegExp(`^### \\{\\{DOCTRINE\\}\\} process 5 — \\{\\{${consentTitle}\\}\\}`,
|
|
455
|
-
'm').test(processText)) {
|
|
456
|
-
problems.push('[authoring] js/hosts/settings.mjs gates git commit/push on doctrine \'process 5\', '
|
|
457
|
-
+ `but doctrines/process.md does not number {${consentTitle}} 5 — the tool boundary and `
|
|
458
|
-
+ 'the prompt now name different rules');
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// ---- every `{{DOCTRINE}} <pack> <n>` anywhere in src/ resolves to a rule that exists.
|
|
507
|
+
// ---- every citation in src/ is by id, and every id resolves.
|
|
462
508
|
//
|
|
463
|
-
// The whole tree, not the template: a skill footer citing
|
|
464
|
-
// template one, and it
|
|
509
|
+
// The whole tree, not the template: a skill footer citing a rule that does not exist is as
|
|
510
|
+
// broken as a template one, and it renders into every bundle.
|
|
465
511
|
const cited = new Map(); // 'process.5' -> [rel, …]
|
|
466
|
-
// ⚠ A RETIRED LAW KEEPS ITS NUMBER, SO A CITATION OF IT STILL RESOLVES — to "Retired". Laws
|
|
467
|
-
// are appended, never renumbered, which is what keeps ~150 cross-references stable; the price
|
|
468
|
-
// is that nothing else notices `{{LAW}} IX` in a skill that means "ask first" (it is rigor 5
|
|
469
|
-
// now) or `{{LAW}} XI` that means "verify" (folded into III). Read off the canon itself, so a
|
|
470
|
-
// law retired tomorrow is covered the same day.
|
|
471
|
-
const retired = new Set();
|
|
472
|
-
try {
|
|
473
|
-
for (const [, num] of readText(path.join(SRC, 'laws', 'universal.md'))
|
|
474
|
-
.matchAll(/^### \{\{LAW\}\} ([IVXLCDM]+) —[^\n]*\n<!-- LEAN:begin -->\nRetired\b/gm)) {
|
|
475
|
-
retired.add(num);
|
|
476
|
-
}
|
|
477
|
-
} catch { /* an unreadable canon is reported elsewhere */ }
|
|
478
512
|
for (const p of rglob(SRC)) {
|
|
479
513
|
if (!isFile(p) || !(p.endsWith('.md') || p.endsWith('.tmpl'))) continue;
|
|
480
514
|
let text;
|
|
@@ -489,35 +523,33 @@ export function constitutionProblems() {
|
|
|
489
523
|
+ 'reference a toggleable one, or a pack-off build ships a rule pointing at text '
|
|
490
524
|
+ 'that is not there. State the point directly instead');
|
|
491
525
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
526
|
+
for (const [bare] of text.matchAll(BARE_CITE_RE)) {
|
|
527
|
+
problems.push(`[authoring] ${rel} cites '${bare.replace(/\s+/g, ' ')}' by number — numbers are `
|
|
528
|
+
+ 'positional and move when the canon does; cite {{LAW:<id>}} / {{DOCTRINE:<id>}}');
|
|
529
|
+
}
|
|
530
|
+
for (const [whole, kind, id] of text.matchAll(ID_CITE_RE)) {
|
|
531
|
+
const r = canon.byId.get(id);
|
|
532
|
+
if (r && r.kind === kind) {
|
|
533
|
+
if (kind === 'DOCTRINE') {
|
|
534
|
+
const key = `${r.pack}.${r.n}`;
|
|
535
|
+
if (!cited.has(key)) cited.set(key, []);
|
|
536
|
+
cited.get(key).push(rel);
|
|
497
537
|
}
|
|
538
|
+
continue;
|
|
498
539
|
}
|
|
540
|
+
const retiredAs = RETIRED_RULE_IDS[id];
|
|
541
|
+
let why = 'which no law or doctrine declares — it would render as the raw token';
|
|
542
|
+
if (r) why = `but '${id}' is a ${r.kind === 'LAW' ? 'law' : 'doctrine rule'} — use {{${r.kind}:${id}}}`;
|
|
543
|
+
else if (retiredAs) {
|
|
544
|
+
why = `a retired id (${retiredAs.was}, ${retiredAs.note}) — cite the rule its text now lives in`;
|
|
545
|
+
}
|
|
546
|
+
problems.push(`[authoring] ${rel} cites ${whole}, ${why}`);
|
|
499
547
|
}
|
|
500
|
-
for (const [, pack, n] of text.matchAll(DOCTRINE_CITE_RE)) {
|
|
501
|
-
const key = `${pack}.${Number(n)}`;
|
|
502
|
-
if (!cited.has(key)) cited.set(key, []);
|
|
503
|
-
cited.get(key).push(rel);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
for (const [key, where] of [...cited].sort()) {
|
|
507
|
-
if (rules.has(key)) continue;
|
|
508
|
-
problems.push(`[authoring] ${where[0]} cites {{DOCTRINE}} ${key.replace('.', ' ')}, which `
|
|
509
|
-
+ `no pack file defines${where.length > 1 ? ` (and ${where.length - 1} more)` : ''}`);
|
|
510
548
|
}
|
|
511
549
|
|
|
512
|
-
// ---- the vocabulary every theme owes the source.
|
|
513
|
-
const wanted = new Set();
|
|
514
|
-
|
|
515
|
-
let text;
|
|
516
|
-
try { text = readText(path.join(dir, `${pack}.md`)); } catch { continue; }
|
|
517
|
-
for (const [, key] of text.matchAll(DOC_TOKEN_RE)) wanted.add(key);
|
|
518
|
-
}
|
|
519
|
-
const lexWant = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI']
|
|
520
|
-
.map((r) => `LEX_${r}`);
|
|
550
|
+
// ---- the vocabulary every theme owes the source: one title per declared rule, keyed by id.
|
|
551
|
+
const wanted = new Set(canon.rules.filter((r) => r.kind === 'DOCTRINE').map((r) => titleKey(r.kind, r.id)));
|
|
552
|
+
const lexWant = canon.rules.filter((r) => r.kind === 'LAW').map((r) => titleKey(r.kind, r.id));
|
|
521
553
|
// `_TEMPLATE.json` is included — it is the file `--sync-themes` seeds a new theme from, so a
|
|
522
554
|
// stale key there propagates into every voice added afterwards. `themeFiles()` excludes it.
|
|
523
555
|
const files = [...themeFiles(), path.join(THEMES, '_TEMPLATE.json')];
|
|
@@ -532,13 +564,13 @@ export function constitutionProblems() {
|
|
|
532
564
|
const keys = Object.keys(theme);
|
|
533
565
|
for (const key of [...wanted].sort()) {
|
|
534
566
|
if (!Object.hasOwn(theme, key)) {
|
|
535
|
-
problems.push(`[authoring] ${name} has no {${key}} — a doctrine
|
|
567
|
+
problems.push(`[authoring] ${name} has no {${key}} — a doctrine rule declares it, and `
|
|
536
568
|
+ 'theme parity cannot see a key that is missing from every theme at once');
|
|
537
569
|
}
|
|
538
570
|
}
|
|
539
571
|
for (const key of keys.filter((k) => k.startsWith('DOC_')).sort()) {
|
|
540
572
|
if (!wanted.has(key)) {
|
|
541
|
-
problems.push(`[authoring] ${name} defines {${key}}, which no doctrine
|
|
573
|
+
problems.push(`[authoring] ${name} defines {${key}}, which no doctrine rule declares — a `
|
|
542
574
|
+ 'dead voice key ships a title for a rule that does not exist');
|
|
543
575
|
}
|
|
544
576
|
}
|
|
@@ -549,8 +581,8 @@ export function constitutionProblems() {
|
|
|
549
581
|
const stale = lex.filter((k) => !lexWant.includes(k));
|
|
550
582
|
const missing = lexWant.filter((k) => !lex.includes(k));
|
|
551
583
|
for (const k of stale) {
|
|
552
|
-
problems.push(`[authoring] ${name} still defines {${k}} — no law
|
|
553
|
-
+ '
|
|
584
|
+
problems.push(`[authoring] ${name} still defines {${k}} — no law declares this id and `
|
|
585
|
+
+ 'nothing renders this key, so it is a title for a rule that no longer exists');
|
|
554
586
|
}
|
|
555
587
|
for (const k of missing) {
|
|
556
588
|
problems.push(`[authoring] ${name} has no {${k}} — one of the invariants would `
|
|
@@ -670,9 +702,10 @@ export function countTableProblems() {
|
|
|
670
702
|
problems.push(`[authoring] SKILL_CLASS lists '${stale}' but no skills/${stale}.md exists`);
|
|
671
703
|
}
|
|
672
704
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
705
|
+
// The numerals the laws RENDER with — positional, off the canon — because LAW_CLASS and the
|
|
706
|
+
// console's LAW_META are keyed the way the rendered catalogue numbers them.
|
|
707
|
+
const lawRules = ruleCanon(SRC).rules.filter((r) => r.kind === 'LAW');
|
|
708
|
+
const lawNums = lawRules.map((r) => r.label);
|
|
676
709
|
for (const num of lawNums) {
|
|
677
710
|
if (!has(LAW_CLASS, num)) {
|
|
678
711
|
problems.push(`[authoring] laws/universal.md rule ${num} has no class in LAW_CLASS`);
|
|
@@ -686,7 +719,7 @@ export function countTableProblems() {
|
|
|
686
719
|
+ `class ${formatRepr(LAW_CLASSES)}`);
|
|
687
720
|
}
|
|
688
721
|
}
|
|
689
|
-
problems.push(...lawMetaProblems(lawNums, LAW_CLASS, LAW_CLASSES));
|
|
722
|
+
problems.push(...lawMetaProblems(lawNums, LAW_CLASS, LAW_CLASSES, lawRules.map((r) => r.id)));
|
|
690
723
|
|
|
691
724
|
// WHAT SHIPS, NOT WHAT IS FLAT. A folder skill (`src/skills/<name>/SKILL.md`, vendored with
|
|
692
725
|
// its scripts or references) ships exactly like a flat one, so the badge, the README list and
|
package/js/inspect/inventory.mjs
CHANGED
|
@@ -70,8 +70,7 @@ const PACK_LEAD_RE = /^\*\*(.+?)\*\*\s+[—-]\s+(.+?)\s*$/;
|
|
|
70
70
|
/** `_harness_tui.LAW_CLASS` — each rule's class, keyed by Roman numeral. */
|
|
71
71
|
export const LAW_CLASS = {
|
|
72
72
|
I: 'security', II: 'process', III: 'verify', IV: 'security', V: 'verify',
|
|
73
|
-
VI: 'security', VII: 'security', VIII: 'craft', IX: '
|
|
74
|
-
XI: 'verify',
|
|
73
|
+
VI: 'security', VII: 'security', VIII: 'craft', IX: 'verify',
|
|
75
74
|
};
|
|
76
75
|
|
|
77
76
|
/** `_harness_tui.ENTITY_STATUSES` — the lifecycle statuses `registry.json` may carry. */
|
package/js/ui/tui.mjs
CHANGED
|
@@ -347,7 +347,7 @@ export function themePreview(key) {
|
|
|
347
347
|
if (data.TAGLINE) lines.push(['dim', data.TAGLINE], ['', '']);
|
|
348
348
|
if (data.LOADED_SIGIL) lines.push(['ok', data.LOADED_SIGIL], ['', '']);
|
|
349
349
|
if (data.VOICE) lines.push(['', `Voice — ${data.VOICE}`], ['', '']);
|
|
350
|
-
if (data.
|
|
350
|
+
if (data.LEX_SEALED_SECRETS) lines.push(['', `e.g. Rule I — ${data.LEX_SEALED_SECRETS}`]);
|
|
351
351
|
if (data.BENEDICTION) lines.push(['', ''], ['dim', data.BENEDICTION]);
|
|
352
352
|
return lines;
|
|
353
353
|
}
|
package/js/web/api.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import { readText, withDiscardableStderr, isDir, isFile } from '../lib/fs.mjs';
|
|
|
35
35
|
import { comparePaths, normcase, within } from '../lib/paths.mjs';
|
|
36
36
|
import { stripWhitespace, percentDecode } from '../lib/text.mjs';
|
|
37
37
|
import { readJsonc } from '../hosts/settings.mjs';
|
|
38
|
+
import { PROFILE_STUB } from '../build/stubs.mjs';
|
|
38
39
|
import { apiActivity, apiActivityDetail } from './activity.mjs';
|
|
39
40
|
import { apiMcp, apiRules } from './actions.mjs';
|
|
40
41
|
|
|
@@ -755,7 +756,11 @@ function apiProfile(state) {
|
|
|
755
756
|
const p = profilePath(state);
|
|
756
757
|
if (!isFile(p)) return { exists: false, path: p, text: '', fingerprint: '' };
|
|
757
758
|
const text = readMaybe(p) ?? '';
|
|
758
|
-
|
|
759
|
+
// `seeded`: the file is still the template the build wrote once and never touched since.
|
|
760
|
+
// `readMaybe` normalises line endings, so a CRLF copy (the emit writes the platform's, and
|
|
761
|
+
// an editor may re-save either way) still compares equal to the LF literal.
|
|
762
|
+
const seeded = text === PROFILE_STUB;
|
|
763
|
+
return { exists: true, path: p, text, fingerprint: fingerprint(text), seeded };
|
|
759
764
|
}
|
|
760
765
|
|
|
761
766
|
/** The dashboard aggregate. */
|
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.9.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/registry.json
CHANGED
|
@@ -463,6 +463,13 @@
|
|
|
463
463
|
"added": "2026-07-29",
|
|
464
464
|
"last_verified": ""
|
|
465
465
|
},
|
|
466
|
+
"skills/explain-changes": {
|
|
467
|
+
"status": "experimental",
|
|
468
|
+
"version": "1.0.0",
|
|
469
|
+
"owner": "Guillaume Lemer",
|
|
470
|
+
"added": "2026-09-30",
|
|
471
|
+
"last_verified": ""
|
|
472
|
+
},
|
|
466
473
|
"skills/wayfinder": {
|
|
467
474
|
"status": "approved",
|
|
468
475
|
"version": "1.0.0",
|
package/src/AGENT.md.tmpl
CHANGED
|
@@ -168,13 +168,13 @@ contract you do not already know.
|
|
|
168
168
|
{{AGENT}} spec ends with a **self-improvement loop**: when a report carries a
|
|
169
169
|
`spec-feedback:` line, weigh it — fold a real flaw back into that {{AGENT}}'s
|
|
170
170
|
file (with the user's assent); {{MEMORY}} gets a record only when it clears
|
|
171
|
-
{{DOCTRINE}}
|
|
171
|
+
{{DOCTRINE:persist-insight}}'s bar.
|
|
172
172
|
|
|
173
173
|
**Dispatching a multi-part goal.** When a goal spans several capabilities,
|
|
174
174
|
decompose it into independent subtasks, route each to the single owning {{AGENT}}
|
|
175
175
|
above, and converge the results yourself. Every handoff carries an **envelope** —
|
|
176
176
|
the subtask goal, the inputs the {{AGENT}} needs, and its output contract — and the
|
|
177
|
-
constraints travel with it: dispatched {{AGENTS}} never commit or push ({{DOCTRINE}}
|
|
177
|
+
constraints travel with it: dispatched {{AGENTS}} never commit or push ({{DOCTRINE:consent-before-push}}
|
|
178
178
|
stays with you), and an unfulfilled contract returns as a stated gap, never invented
|
|
179
179
|
content. Where the host exposes a `workflow` tool, prefer the saved `dispatch`
|
|
180
180
|
workflow ([workflow {{SKILL}}]({{DIR_SKILLS}}/workflow.md)); where it does not, run
|
|
@@ -256,9 +256,9 @@ demand.
|
|
|
256
256
|
When a task repeats and no {{SKILL}} covers it, forge one through the
|
|
257
257
|
[skill-forge]({{DIR_SKILLS}}/skill-forge.md) {{SKILL}}: copy
|
|
258
258
|
[`{{DIR_SKILLS}}/_template.md`]({{DIR_SKILLS}}/_template.md), keep it to one coherent
|
|
259
|
-
domain ({{DOCTRINE}}
|
|
259
|
+
domain ({{DOCTRINE:automate-repetition}}), and list it wherever this file carries a catalogue. Every {{SKILL}} ends with a
|
|
260
260
|
**self-improvement loop** — honour it: fold what a run teaches back into the
|
|
261
|
-
file, or into {{MEMORY}} ({{DOCTRINE}}
|
|
261
|
+
file, or into {{MEMORY}} ({{DOCTRINE:persist-insight}}).
|
|
262
262
|
|
|
263
263
|
**Folder {{SKILLS}}.** Some {{SKILLS}} ship as self-contained folders under
|
|
264
264
|
`{{DIR_SKILLS}}/`, each with its own format and provenance (`VENDOR.md`) —
|
|
@@ -270,6 +270,8 @@ its domain comes up:
|
|
|
270
270
|
non-obvious connections between notes via parallel synthesis + critique.
|
|
271
271
|
- [token-report]({{DIR_SKILLS}}/token-report/SKILL.md) — session token usage breakdown
|
|
272
272
|
tables: context baseline, growth, per-tool attribution.
|
|
273
|
+
- [explain-changes]({{DIR_SKILLS}}/explain-changes/SKILL.md) — offline HTML walkthrough
|
|
274
|
+
of a diff: intent, risks, side-by-side git diff.
|
|
273
275
|
|
|
274
276
|
---
|
|
275
277
|
|
|
@@ -283,22 +285,24 @@ local `MEMORY.md`. Read that index at the start of a session; write a new memory
|
|
|
283
285
|
whenever the conversation yields a durable fact, a correction, or a decision worth
|
|
284
286
|
keeping — through the [rule]({{DIR_SKILLS}}/rule.md) {{SKILL}}, which settles with
|
|
285
287
|
the user whether it is a memory or a standing rule before either is written
|
|
286
|
-
({{DOCTRINE}}
|
|
288
|
+
({{DOCTRINE:persist-insight}}). The `{{DIR_MEMORY}}/` directory is **personal and git-ignored** — it is
|
|
287
289
|
never committed or shared; to share knowledge, put it in code, docs, or the
|
|
288
290
|
project's `user-rules.md` (§1). When the same feedback lesson keeps recurring,
|
|
289
291
|
propose promoting it into a standing rule — the [rule]({{DIR_SKILLS}}/rule.md)
|
|
290
292
|
{{SKILL}} carries the procedure. A memory that records a decision or stance may
|
|
291
293
|
carry an optional `force` (constraint · choice · conviction · tempered): when new
|
|
292
294
|
evidence contradicts one, revise it rather than ignore it, and never overturn a
|
|
293
|
-
`constraint` yourself — surface it to the user.
|
|
294
|
-
|
|
295
|
+
`constraint` yourself — surface it to the user. In anything that outlives
|
|
296
|
+
the session — memory, notebook, notes, commits, docs — cite a rule by its principle
|
|
297
|
+
("Verify Before Asserting"), never its number: numbers are positional. Full convention: [`{{DIR_MEMORY}}/README.md`]({{DIR_MEMORY}}/README.md).
|
|
295
298
|
<!-- LEAN:else -->
|
|
296
299
|
Durable facts live in [`{{DIR_MEMORY}}/`]({{DIR_MEMORY}}/), one per file, indexed by
|
|
297
300
|
`MEMORY.md` — **read that index at the start of a session**. Write one through the
|
|
298
301
|
[rule]({{DIR_SKILLS}}/rule.md) {{SKILL}}, which settles with the user whether a
|
|
299
|
-
lesson is a memory or a standing rule ({{DOCTRINE}}
|
|
302
|
+
lesson is a memory or a standing rule ({{DOCTRINE:persist-insight}}). The directory is
|
|
300
303
|
**personal and git-ignored**; shared knowledge goes in code, docs, or `user-rules.md`
|
|
301
|
-
(§1).
|
|
304
|
+
(§1). In anything that outlives the session, cite a rule by its principle ("Verify Before
|
|
305
|
+
Asserting"), never its number — numbers change. Convention, incl. `force`:
|
|
302
306
|
[`{{DIR_MEMORY}}/README.md`]({{DIR_MEMORY}}/README.md).
|
|
303
307
|
<!-- LEAN:end -->
|
|
304
308
|
|
|
@@ -325,7 +329,7 @@ tree. Read `NOTEBOOK.md` at the start of a session to recall what
|
|
|
325
329
|
you keep here, and keep the space referable across sessions — the seeded index
|
|
326
330
|
is one way; replace it with whatever scheme serves you better. When a note
|
|
327
331
|
hardens into a durable fact worth recalling verbatim, propose it for {{MEMORY}}
|
|
328
|
-
through the rule {{SKILL}} (§5, {{DOCTRINE}}
|
|
332
|
+
through the rule {{SKILL}} (§5, {{DOCTRINE:persist-insight}}). Keep the two stores distinct by *role*, not just by
|
|
329
333
|
format: {{MEMORY}} holds durable insight for future sessions, the {{NOTEBOOK}}
|
|
330
334
|
holds your working artifacts. The one fixed law: `{{DIR_NOTEBOOK}}/` is **personal and
|
|
331
335
|
git-ignored** — never committed or shared, and the `.gitignore` that guarantees
|
|
@@ -359,9 +363,9 @@ the rule {{SKILL}} (§5) so it outlives the session.
|
|
|
359
363
|
|
|
360
364
|
Because the folder is shared, two cautions hold:
|
|
361
365
|
- **You do not own it.** Files you did not create are not yours to move, rename,
|
|
362
|
-
or delete unless the task explicitly requires it ({{LAW}}
|
|
366
|
+
or delete unless the task explicitly requires it ({{LAW:deletion-is-deliberate}}).
|
|
363
367
|
- **Do not assume.** Before treating a file as harness scaffolding or as
|
|
364
|
-
disposable, verify what it actually is ({{LAW}}
|
|
368
|
+
disposable, verify what it actually is ({{LAW:verify-before-asserting}}).
|
|
365
369
|
|
|
366
370
|
And it receives only the **deliverables** of the task. Your own working files —
|
|
367
371
|
scratch scripts, analysis dumps, drafts, experiments — are made in the
|
|
@@ -371,7 +375,7 @@ scratch scripts, analysis dumps, drafts, experiments — are made in the
|
|
|
371
375
|
prior work — is knowledge to read and learn from, not scaffolding to reorganise;
|
|
372
376
|
propose a durable fact from it for {{MEMORY}} through the rule {{SKILL}} (§5). You do not own the folder: files
|
|
373
377
|
you did not create are not yours to move or delete unless the task requires it
|
|
374
|
-
({{LAW}}
|
|
378
|
+
({{LAW:deletion-is-deliberate}}), and a file is verified before it is treated as disposable ({{LAW:verify-before-asserting}}).
|
|
375
379
|
It receives only the task's deliverables; your working files go in the {{NOTEBOOK}}
|
|
376
380
|
(§6).
|
|
377
381
|
<!-- LEAN:end -->
|
|
@@ -437,7 +441,7 @@ is loaded for you.) Before your first reply, act on each entry by its `load` mod
|
|
|
437
441
|
house conventions, branch/commit policy, the Definition of Done. That content is
|
|
438
442
|
**project law for this repo, as binding as anything in this file**.
|
|
439
443
|
- **`"lazy"`** — read it **only when the task in hand needs it**, never up front
|
|
440
|
-
({{DOCTRINE}}
|
|
444
|
+
({{DOCTRINE:context-economy}}). Large or occasional docs: architecture notes, API references.
|
|
441
445
|
|
|
442
446
|
This read is **not optional and not deferrable**: the manifest exists precisely so
|
|
443
447
|
you do not work blind, and if you have not loaded it you are not ready to act.
|
|
@@ -449,7 +453,7 @@ comment carries its schema and setup notes.
|
|
|
449
453
|
deferrable.** It sits beside this AGENT.md and is how the project hands you its own
|
|
450
454
|
knowledge (on OpenCode it is loaded for you). `"eager"` entries are read now and are
|
|
451
455
|
**project law for this repo, as binding as this file**; `"lazy"` entries only when
|
|
452
|
-
the task needs them ({{DOCTRINE}}
|
|
456
|
+
the task needs them ({{DOCTRINE:context-economy}}). Schema and setup notes are in the file's
|
|
453
457
|
own comment.
|
|
454
458
|
<!-- LEAN:end -->
|
|
455
459
|
|
package/src/agents/_template.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Authoring a new agent:
|
|
3
3
|
1. Reuse first — if an existing {{AGENT}}'s capability already covers the need,
|
|
4
|
-
extend it instead of adding a file ({{DOCTRINE}}
|
|
4
|
+
extend it instead of adding a file ({{DOCTRINE:automate-repetition}} applies to specialists too).
|
|
5
5
|
2. Copy this file to agents/<name>.md and fill in every section. In an installed
|
|
6
6
|
harness the purpose line is plain prose; in the Geneseed source repo it is the
|
|
7
7
|
DESC_<NAME> token with the prose defined per theme.
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
## Procedure
|
|
44
44
|
0. If `{{DIR_MEMORY}}/agents/<your-name>.md` exists in the harness, read it first —
|
|
45
|
-
it holds your durable lessons from prior dispatches ({{DOCTRINE}}
|
|
45
|
+
it holds your durable lessons from prior dispatches ({{DOCTRINE:persist-insight}}).
|
|
46
46
|
1. Step-by-step method this {{AGENT}} follows.
|
|
47
47
|
|
|
48
48
|
## Output contract
|
|
@@ -73,4 +73,4 @@ that proved wrong, a step you could not execute — end your report with one lin
|
|
|
73
73
|
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
74
74
|
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
75
75
|
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
76
|
-
{{DOCTRINE}}
|
|
76
|
+
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|