geneseed 3.2.0 → 3.3.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 +110 -0
- package/README.md +22 -12
- package/SETUP.md +38 -7
- package/SHIPPED.md +6 -5
- package/adapters/bob/README.md +40 -10
- package/adapters/claude-code/README.md +5 -0
- package/adapters/claude-code/settings.json +8 -1
- package/adapters/copilot/README.md +50 -24
- package/adapters/opencode/README.md +5 -1
- package/adapters/opencode/plugins/geneseed-guard.js +40 -6
- package/bin/build-driver.mjs +16 -10
- package/bin/geneseed-cli.mjs +55 -56
- package/bin/geneseed-hook.mjs +21 -14
- package/docs/candidate-hosts.md +74 -0
- package/docs/extending.md +6 -5
- package/docs/token-footprint.md +32 -21
- package/docs/web-ui.md +5 -13
- package/harness.config.json +1 -1
- package/js/README.md +5 -6
- package/js/build/emit-claude.mjs +22 -7
- package/js/build/render.mjs +37 -19
- package/js/build/source.mjs +22 -2
- package/js/cli-table.json +84 -0
- package/js/hosts/hooks.mjs +240 -78
- package/js/hosts/hosts.mjs +56 -2
- package/js/hosts/link.mjs +1 -1
- package/js/hosts/mcp.mjs +3 -1
- package/js/hosts/native.mjs +17 -0
- package/js/hosts/settings.mjs +173 -5
- package/js/inspect/excludes.mjs +1 -1
- package/js/inspect/inventory.mjs +1 -1
- package/js/inspect/status.mjs +50 -2
- package/js/maintain/uninstall.mjs +38 -11
- package/js/maintain/update.mjs +2 -2
- package/js/web/actions.mjs +8 -8
- package/js/web/api.mjs +5 -10
- package/js/web/docs.mjs +9 -31
- package/js/web/handler.mjs +20 -30
- package/js/web/jobs.mjs +5 -26
- package/js/web/routes.mjs +18 -84
- package/js/web/server.mjs +3 -3
- package/package.json +1 -1
- package/registry.json +14 -0
- package/src/AGENT.md.tmpl +55 -16
- package/src/agents/explorer.md +1 -1
- package/src/doctrines/README.md +3 -3
- package/src/doctrines/rigor.md +13 -0
- package/src/laws/universal.md +98 -20
- package/src/ontology/universal.md +38 -13
- package/src/skills/_template.md +5 -1
- package/src/skills/bruno-collection-generator.md +63 -0
- package/src/skills/bruno-test-writer.md +62 -0
- package/src/skills/crash-course.md +1 -0
- package/src/skills/decode.md +1 -0
- package/src/skills/drill.md +1 -0
- package/src/skills/feynman.md +1 -0
- package/src/skills/forge-mcp.md +1 -1
- package/src/skills/herdr.md +1 -0
- package/src/skills/learning-path.md +1 -0
- package/src/skills/opencode-theme.md +1 -0
- package/src/skills/research.md +1 -1
- package/themes/_TEMPLATE.json +3 -0
- package/themes/biker.json +3 -0
- package/themes/commentator.json +3 -0
- package/themes/cyberpunk.json +3 -0
- package/themes/gamer.json +3 -0
- package/themes/imperial.json +3 -0
- package/themes/joker.json +3 -0
- package/themes/marvin.json +3 -0
- package/themes/mean.json +3 -0
- package/themes/military.json +3 -0
- package/themes/neutral.json +3 -0
- package/themes/pirate.json +3 -0
- package/themes/sports.json +3 -0
- package/themes/verstappen.json +3 -0
- package/themes/wizard.json +3 -0
- package/web/dist/assets/Activity-BfVcLrda.js +1 -0
- package/web/dist/assets/ActivityDetail-CQXccGPl.js +1 -0
- package/web/dist/assets/{CatalogRow-DqS4e5XM.js → CatalogRow-DALEQImZ.js} +1 -1
- package/web/dist/assets/{Diff-CIrGbiqm.js → Diff-BIjxXx46.js} +1 -1
- package/web/dist/assets/{Doctor-CpI53oHY.js → Doctor-CJLbgsrw.js} +1 -1
- package/web/dist/assets/FilterInput-CkUCk_lY.js +1 -0
- package/web/dist/assets/GreenhouseView-DfC5IILJ.js +2 -0
- package/web/dist/assets/{Harness-vRYs11kX.js → Harness-Dm5CCU-v.js} +1 -1
- package/web/dist/assets/JournalView-Cpu2eI4s.js +1 -0
- package/web/dist/assets/Laws-DDDVFuQz.js +3 -0
- package/web/dist/assets/Library-Dao3T9na.js +1 -0
- package/web/dist/assets/{Markdown-kgpg9EQM.js → Markdown-C__nuWX5.js} +1 -1
- package/web/dist/assets/OperatorHudView-DEpCtAjP.js +2 -0
- package/web/dist/assets/{Profile-CHr-PPf6.js → Profile-BlXtUlh2.js} +1 -1
- package/web/dist/assets/{Rules-CsC6hfqk.js → Rules-DIre6gxR.js} +1 -1
- package/web/dist/assets/Skills-rY13ZPhM.js +1 -0
- package/web/dist/assets/{StatusBadge-gRrak0nO.js → StatusBadge-wW_RS_my.js} +1 -1
- package/web/dist/assets/{activity-B0-FXZCj.js → activity-DpYPKNrA.js} +1 -1
- package/web/dist/assets/{index-DJOhOwwR.js → index-BEZvPvpF.js} +1 -1
- package/web/dist/assets/index-DNQE5ydy.js +10 -0
- package/web/dist/assets/index-DzdQjvM1.css +1 -0
- package/web/dist/assets/{index-Bx3I7cRW.js → index-PmQGRS1X.js} +1 -1
- package/web/dist/assets/jobBuckets-DfTUJUxR.js +1 -0
- package/web/dist/assets/lawCats-DUS9UTH4.js +1 -0
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +82 -64
- package/js/web/graph.mjs +0 -0
- package/web/dist/assets/Activity-BTCG2gMl.js +0 -1
- package/web/dist/assets/ActivityDetail-julsmPDB.js +0 -1
- package/web/dist/assets/Graph-CS4tA7YS.js +0 -1
- package/web/dist/assets/Laws-BjbHJrJt.js +0 -3
- package/web/dist/assets/Library-CjC7BAWN.js +0 -1
- package/web/dist/assets/Skills-BvatJGfe.js +0 -1
- package/web/dist/assets/index-BnRWQbZb.js +0 -12
- package/web/dist/assets/index-D0f9tJlA.css +0 -1
- package/web/dist/assets/roman-DkYN0LX9.js +0 -1
package/docs/web-ui.md
CHANGED
|
@@ -51,7 +51,7 @@ The left rail mirrors the harness's own shape:
|
|
|
51
51
|
| Group | Views |
|
|
52
52
|
| --- | --- |
|
|
53
53
|
| *(ungrouped)* | Dashboard |
|
|
54
|
-
| **📖 Codex** | Constitution · Rules · Profile · Skills · Agents · Library · Docs
|
|
54
|
+
| **📖 Codex** | Constitution · Rules · Profile · Skills · Agents · Library · Docs |
|
|
55
55
|
| **🩺 Care** | Activity · Changes · Doctor |
|
|
56
56
|
| **🎨 Setup** | Harness · Settings |
|
|
57
57
|
|
|
@@ -72,9 +72,10 @@ streams the output of background jobs and keeps their history across reloads.
|
|
|
72
72
|
### 🧬 Harness
|
|
73
73
|
|
|
74
74
|
- **Dashboard** (`#/`) — a live readout of what's deployed. Three lenses: **Status**
|
|
75
|
-
(readiness ring, a KPI band, a genome grid, recent activity), **Lineage** (
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
(readiness ring, a KPI band, a genome grid, recent activity), **Lineage** (the
|
|
76
|
+
source → render → deployed timeline and the genome-by-volume strand chart), and
|
|
77
|
+
**Operator** (a searchable table of every deployed capability). Headlines reflect the
|
|
78
|
+
active theme's voice.
|
|
78
79
|
|
|
79
80
|
The KPI band reads **Agents · Skills · Invariants · Local edits**. That third tile is
|
|
80
81
|
labelled *Invariants* and not *Constitution* on purpose: its value is the invariant count
|
|
@@ -107,15 +108,6 @@ streams the output of background jobs and keeps their history across reloads.
|
|
|
107
108
|
**Promote to rule** control turns a recurring lesson into a trial rule in
|
|
108
109
|
`user-rules.md` — provenance recorded, the source fact deleted so it isn't loaded
|
|
109
110
|
twice — landing you on the Rules page.
|
|
110
|
-
- **Graph** (`#/graph`) — the cross-link constellation across agents, skills and the
|
|
111
|
-
**invariants only**: every `[[wikilink]]` between agents/skills, plus every `Rule N`
|
|
112
|
-
mention that lands on a real invariant. ⚠ The reference pattern matches the tier noun
|
|
113
|
-
followed by a **Roman numeral** — `Rule`, `Law`, or whatever the active theme calls one,
|
|
114
|
-
which is how `Dictate III` still resolves under `imperial`. A doctrine address is
|
|
115
|
-
`<pack> <n>` and matches nothing, so **doctrine cross-links are not drawn**; the graph is
|
|
116
|
-
a view of the invariant backbone, not of the whole constitution. Hover to isolate a
|
|
117
|
-
neighbourhood, scroll to zoom, drag to pan, search to highlight; orphans dim out. Click a
|
|
118
|
-
node to open its spec.
|
|
119
111
|
|
|
120
112
|
### 📚 Learn
|
|
121
113
|
|
package/harness.config.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
"_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
|
|
3
3
|
"theme": "neutral",
|
|
4
4
|
"product": "Geneseed",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.0"
|
|
6
6
|
}
|
package/js/README.md
CHANGED
|
@@ -13,7 +13,7 @@ a module that does not exist, is a test failure. `geneseed doctor` reports the s
|
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
bin/geneseed-cli.mjs the `geneseed` CLI — 20+ verbs, may NOT spawn
|
|
16
|
-
bin/geneseed-hook.mjs the hook entry —
|
|
16
|
+
bin/geneseed-hook.mjs the hook entry — 5 verbs, loads on EVERY tool call (~14 ms)
|
|
17
17
|
bin/build-driver.mjs `geneseed-build` — the generator's flags and emit targets
|
|
18
18
|
|
|
19
19
|
js/build/ src/ + themes/ → rendered text → a bundle → a per-host install
|
|
@@ -66,7 +66,7 @@ user co-owns, and the hook verbs those emitted configs then run.
|
|
|
66
66
|
| module | owns |
|
|
67
67
|
|---|---|
|
|
68
68
|
| `hosts.mjs` | The four host config dirs (opencode/claude/bob/copilot), plus `resolvePath`/`expanduser` |
|
|
69
|
-
| `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `learn` |
|
|
69
|
+
| `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `tool-gate` (the two gates fused, for Copilot), `learn`. `--host` picks the verdict dialect |
|
|
70
70
|
| `settings.mjs` | Merges into user-owned `settings.json`/`opencode.json`; owns the hook shim and the managed blocks |
|
|
71
71
|
| `native.mjs` | Capability specs → host-native subagents and skills. The impure half of the emit |
|
|
72
72
|
| `opencode.mjs` | OpenCode-only extras: colour themes, primary agent, `/commands`, plugins, overrides stub |
|
|
@@ -168,13 +168,12 @@ the deployed harness as JSON.
|
|
|
168
168
|
| `daemon.mjs` | Detached web start/stop/status/restart, the `.geneseed-web.json` record, and `openUrl` |
|
|
169
169
|
| `jobs.mjs` | `JobManager` and the action→argv table. The only module here that spawns job children |
|
|
170
170
|
| `docs.mjs` | The Docs tab: the `docs/web/` page registry, `?harness=` filtering, the five page kinds |
|
|
171
|
-
| `graph.mjs` | `/api/graph` alone — the agent/skill/law cross-link matrix built from rendered bodies |
|
|
172
171
|
| `activity.mjs` | The `activity/` session files: pid-liveness prune, list, detail, and the on/off flag |
|
|
173
172
|
|
|
174
173
|
**Before editing:** `makeHandler(state, jm, token, dist, holder)`'s arity is load-bearing — three
|
|
175
|
-
test call sites construct one directly and drive it without a socket.
|
|
176
|
-
|
|
177
|
-
`
|
|
174
|
+
test call sites construct one directly and drive it without a socket. `routes.mjs`'s tables
|
|
175
|
+
and inline lists are held against a surface list written out in
|
|
176
|
+
`tests/unit/web_server.test.mjs` — growing or shrinking the API edits both, in one commit.
|
|
178
177
|
|
|
179
178
|
---
|
|
180
179
|
|
package/js/build/emit-claude.mjs
CHANGED
|
@@ -10,8 +10,8 @@ import { VERSION_MARKER } from '../hosts/hosts.mjs';
|
|
|
10
10
|
import { loadAgentOverrides, writeNativeLayer } from '../hosts/native.mjs';
|
|
11
11
|
import { ensureAgentOverridesStub } from '../hosts/opencode.mjs';
|
|
12
12
|
import {
|
|
13
|
-
managedBlockRemove, managedBlockWrite, mergeClaudeSettings,
|
|
14
|
-
unwireClaudeSettings, wireClaudeExcludes,
|
|
13
|
+
managedBlockRemove, managedBlockWrite, mergeClaudeSettings, mergeCopilotSettings,
|
|
14
|
+
unwireClaudeExcludes, unwireClaudeSettings, wireClaudeExcludes,
|
|
15
15
|
} from '../hosts/settings.mjs';
|
|
16
16
|
import { writeText } from '../lib/fs.mjs';
|
|
17
17
|
import { isTruthy } from '../lib/json.mjs';
|
|
@@ -225,12 +225,19 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
225
225
|
// settings.local.json — the personal, untracked file — never the team-shared
|
|
226
226
|
// settings.json, which would hand every teammate failing hooks pointing at this machine's
|
|
227
227
|
// node and this machine's checkout. (Bob documents no local variant, so it keeps
|
|
228
|
-
// settings.json.) Copilot
|
|
229
|
-
//
|
|
230
|
-
//
|
|
228
|
+
// settings.json.) Copilot's settings are a different shape entirely (one command per
|
|
229
|
+
// event, no matcher groups) and take the `else` branch below; its PROJECT scope wires
|
|
230
|
+
// nothing, because the CLI documents hooks in `~/.copilot/settings.json` only and a
|
|
231
|
+
// machine-absolute hook committed into `.github/` would fail on every teammate's machine.
|
|
231
232
|
if (!isCopilot) {
|
|
232
|
-
|
|
233
|
+
// Bob GLOBAL: its hooks doc puts the global file at `~/.bob/settings/settings.json`
|
|
234
|
+
// (nested), where an older emit wrote `~/.bob/settings.json`; the `oldSf !==
|
|
235
|
+
// settingsName` branch below unwires the old file, so a re-emit migrates. Bob's project
|
|
236
|
+
// file stays `.bob/settings.json`.
|
|
237
|
+
const settingsName = scope === 'project' && !isBob ? 'settings.local.json'
|
|
238
|
+
: isBob && scope === 'global' ? path.join('settings', 'settings.json') : 'settings.json';
|
|
233
239
|
const settingsPath = path.join(cfgDir, settingsName);
|
|
240
|
+
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
234
241
|
managed.settings_file = settingsName;
|
|
235
242
|
// Migration: an older install wired hooks/excludes into a different file — unwire the
|
|
236
243
|
// recorded claims there, or they linger (and run) forever.
|
|
@@ -247,7 +254,7 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
247
254
|
// DECIDES the install's packs, so the marker on disk is still the previous build's.
|
|
248
255
|
const [, managedHooks] = mergeClaudeSettings(
|
|
249
256
|
settingsPath, scope, oldSf === settingsName ? get(old, 'settings_hooks') : null, hookOpts,
|
|
250
|
-
doctrines, excludeRules,
|
|
257
|
+
doctrines, excludeRules, host,
|
|
251
258
|
);
|
|
252
259
|
managed.settings_hooks = managedHooks;
|
|
253
260
|
|
|
@@ -280,6 +287,14 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
280
287
|
} else if (priorExcl.length) {
|
|
281
288
|
managed.settings_excludes = priorExcl;
|
|
282
289
|
}
|
|
290
|
+
} else if (scope === 'global') {
|
|
291
|
+
// `copilot_hooks`, not `settings_hooks`: a distinct key so the Claude-shaped unwire and
|
|
292
|
+
// integrity code, which walks `settings_hooks` as event→group arrays, never sees a
|
|
293
|
+
// Copilot record it would misread.
|
|
294
|
+
const settingsPath = path.join(cfgDir, 'settings.json');
|
|
295
|
+
managed.settings_file = 'settings.json';
|
|
296
|
+
const [, claims] = mergeCopilotSettings(settingsPath, get(old, 'copilot_hooks'), hookOpts);
|
|
297
|
+
managed.copilot_hooks = claims;
|
|
283
298
|
}
|
|
284
299
|
return managed;
|
|
285
300
|
}
|
package/js/build/render.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import { readText } from '../lib/fs.mjs';
|
|
|
34
34
|
import { parseJson } from '../lib/json.mjs';
|
|
35
35
|
import { normcase, comparePaths } from '../lib/paths.mjs';
|
|
36
36
|
// `js/build/source.mjs` imports nothing but node builtins, so this direction cannot cycle.
|
|
37
|
-
import { PACK_ORDER } from './source.mjs';
|
|
37
|
+
import { PACK_ORDER, readPackText } from './source.mjs';
|
|
38
38
|
|
|
39
39
|
/** Document STRUCTURE is theme-INDEPENDENT — mirrors `_build_render.STRUCTURE`. */
|
|
40
40
|
export const STRUCTURE = {
|
|
@@ -69,6 +69,16 @@ const TOKEN_RE = /\{\{([A-Z_][A-Z0-9_]*)\}\}/g;
|
|
|
69
69
|
const INCLUDE_RE = /^[ \t]*<!--[ \t]*INCLUDE:[ \t]*(?<path>[^ \t]+)[ \t]*-->[ \t]*$/gm;
|
|
70
70
|
const CATALOG_BLOCK_RE =
|
|
71
71
|
/[ \t]*<!-- CATALOG:begin -->\n(?<table>[\s\S]*?)[ \t]*<!-- CATALOG:else -->\n(?<pointer>[\s\S]*?)[ \t]*<!-- CATALOG:end -->\n/g;
|
|
72
|
+
// The footprint twin of CATALOG. Before it existed, `lean` could only reach the two
|
|
73
|
+
// include-driven corpora (`laws/universal.md` and the doctrine packs, both truncated
|
|
74
|
+
// mechanically to heading + first sentence); every other section of the root template was
|
|
75
|
+
// byte-identical across footprints, which is how ~1.5k tokens of §-level prose rode into
|
|
76
|
+
// every lean install. A LEAN block ships a HAND-WRITTEN condensation instead of a
|
|
77
|
+
// truncation — the objection `docs/token-footprint.md` raised against terse-ing the
|
|
78
|
+
// Ontology ("four orphan sentences") is answered by authoring the short form, not
|
|
79
|
+
// generating it. Same marker grammar as CATALOG so the two stay learnable as one idea.
|
|
80
|
+
const LEAN_BLOCK_RE =
|
|
81
|
+
/[ \t]*<!-- LEAN:begin -->\n(?<full>[\s\S]*?)[ \t]*<!-- LEAN:else -->\n(?<lean>[\s\S]*?)[ \t]*<!-- LEAN:end -->\n/g;
|
|
72
82
|
|
|
73
83
|
/**
|
|
74
84
|
* `PurePath.suffix`. Not `path.extname`: Python returns '' for a name whose only dot is
|
|
@@ -147,6 +157,11 @@ function resolveCatalogs(text, nativeCatalog) {
|
|
|
147
157
|
return text.replace(CATALOG_BLOCK_RE, (_m, table, pointer) => (nativeCatalog ? pointer : table));
|
|
148
158
|
}
|
|
149
159
|
|
|
160
|
+
/** LEAN blocks — full text or the hand-written condensation, by footprint. */
|
|
161
|
+
function resolveLean(text, footprint) {
|
|
162
|
+
return text.replace(LEAN_BLOCK_RE, (_m, full, lean) => (footprint === 'lean' ? lean : full));
|
|
163
|
+
}
|
|
164
|
+
|
|
150
165
|
/**
|
|
151
166
|
* `re.split(r"(?m)^(?=### )", text)`.
|
|
152
167
|
*
|
|
@@ -168,15 +183,15 @@ function splitAtLawHeadings(text) {
|
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
/**
|
|
171
|
-
*
|
|
186
|
+
* Heading + first sentence, per `### ` block. DOCTRINES ONLY, now.
|
|
172
187
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
188
|
+
* The laws used to go through this too, and it made a law's lean footprint a function of its
|
|
189
|
+
* punctuation: Law II shipped as one 36-character sentence and lost its stop-and-ask
|
|
190
|
+
* mechanism, while a law written as one colon-chained run-on shipped nearly whole. The laws
|
|
191
|
+
* now carry authored LEAN blocks in `src/laws/universal.md`, the way the ontology already did,
|
|
192
|
+
* and `resolveLean` picks the half. Doctrine rules stay machine-cut: there are ~23 of them
|
|
193
|
+
* across four packs, and a pack is a practice catalogue whose first sentence is written to
|
|
194
|
+
* stand alone.
|
|
180
195
|
*/
|
|
181
196
|
function terseBlocks(text) {
|
|
182
197
|
const blocks = splitAtLawHeadings(text);
|
|
@@ -197,16 +212,14 @@ function terseBlocks(text) {
|
|
|
197
212
|
return out.join('\n\n');
|
|
198
213
|
}
|
|
199
214
|
|
|
200
|
-
/**
|
|
201
|
-
export function
|
|
215
|
+
/** The pointer under §1 at lean. The laws' lean text itself is authored in LEAN blocks. */
|
|
216
|
+
export function lawsPointer(theme, lawsPrefix = '') {
|
|
202
217
|
const law = theme.LAW ?? 'Law';
|
|
203
218
|
const lawsDir = theme.DIR_LAWS ?? 'laws';
|
|
204
|
-
|
|
205
|
-
`> Each ${law} above is given in brief — the rule, not its reasoning. The `
|
|
219
|
+
return `> Each ${law} above is given in brief — the rule, not its reasoning. The `
|
|
206
220
|
+ `complete, binding text of every ${law} is in \`${lawsPrefix}${lawsDir}/universal.md\`; `
|
|
207
221
|
+ `read it whenever a ${law}'s application is unclear, and before any act touching `
|
|
208
|
-
+
|
|
209
|
-
return terseBlocks(text) + '\n\n' + pointer;
|
|
222
|
+
+ 'secrets, deletion, git history, scope, or untrusted content.';
|
|
210
223
|
}
|
|
211
224
|
|
|
212
225
|
/** `_build_render.render_file`. */
|
|
@@ -225,12 +238,13 @@ export function renderFile(cfg, filePath, theme, footprint = 'full', lawsPrefix
|
|
|
225
238
|
new Set(visiting).add(normcase(here)), nativeCatalog)
|
|
226
239
|
.replace(/\n+$/, ''); // `.rstrip("\n")`
|
|
227
240
|
if (footprint === 'lean' && rel === 'laws/universal.md') {
|
|
228
|
-
inner =
|
|
241
|
+
inner = `${inner}\n\n${lawsPointer(theme, lawsPrefix)}`;
|
|
229
242
|
}
|
|
230
243
|
return inner;
|
|
231
244
|
});
|
|
232
245
|
|
|
233
246
|
text = resolveCatalogs(text, nativeCatalog);
|
|
247
|
+
text = resolveLean(text, footprint);
|
|
234
248
|
return substitute(text, theme);
|
|
235
249
|
}
|
|
236
250
|
|
|
@@ -291,7 +305,7 @@ function activeDoctrines(cfg) {
|
|
|
291
305
|
// `Active packs:` marker a later reader parses back — agrees with what was rendered.
|
|
292
306
|
const excluded = new Set(cfg.excludeRules ?? []);
|
|
293
307
|
const survives = (pack) => {
|
|
294
|
-
const ids = [...
|
|
308
|
+
const ids = [...readPackText(path.join(dir, `${pack}.md`))
|
|
295
309
|
.matchAll(/^### \{\{DOCTRINE\}\} ([a-z]+) (\d+)\b/gm)]
|
|
296
310
|
.map((m) => `${m[1]}.${Number(m[2])}`);
|
|
297
311
|
return ids.some((id) => !excluded.has(id));
|
|
@@ -340,7 +354,7 @@ function doctrinesBody(cfg, theme, active, footprint, lawsPrefix) {
|
|
|
340
354
|
if (!active.length) return '';
|
|
341
355
|
const excluded = new Set(cfg.excludeRules ?? []);
|
|
342
356
|
const parts = active.map((name) => {
|
|
343
|
-
const raw = dropExcludedRules(
|
|
357
|
+
const raw = dropExcludedRules(readPackText(path.join(cfg.src, 'doctrines', `${name}.md`)),
|
|
344
358
|
name, excluded);
|
|
345
359
|
const body = substitute(raw, theme).trim();
|
|
346
360
|
return footprint === 'lean' ? terseBlocks(body) : body;
|
|
@@ -440,8 +454,12 @@ export function renderAll(cfg, themeName, {
|
|
|
440
454
|
for (const file of sortedSourceFiles(cfg.src)) {
|
|
441
455
|
const rel = path.relative(cfg.src, file);
|
|
442
456
|
const outRel = destRel(themedRel(rel, theme)).split(path.sep).join('/'); // as_posix()
|
|
457
|
+
// The on-disk `laws/` and `ontology/` are the "complete, binding text" the lean pointer
|
|
458
|
+
// promises. They render at full whatever the footprint; only what AGENT.md INLINES leans.
|
|
459
|
+
// Keyed on the SOURCE rel, not the themed one, so a DIR_* rename cannot un-exempt them.
|
|
460
|
+
const fp = /^(?:laws|ontology)[\\/]/.test(rel) ? 'full' : footprint;
|
|
443
461
|
items.push(TEXT_SUFFIXES.has(suffixOf(path.basename(file)))
|
|
444
|
-
? { rel: outRel, text: renderFile(cfg, file, theme,
|
|
462
|
+
? { rel: outRel, text: renderFile(cfg, file, theme, fp, lawsPrefix, new Set(), nativeCatalog), src: file }
|
|
445
463
|
: { rel: outRel, text: null, src: file });
|
|
446
464
|
}
|
|
447
465
|
return { theme, items };
|
package/js/build/source.mjs
CHANGED
|
@@ -24,9 +24,29 @@
|
|
|
24
24
|
* over 259 cells and compares the tree byte-for-byte, and every one of them builds a cfg
|
|
25
25
|
* from these paths. A depth error or a renamed key fails 259 cells, not zero.
|
|
26
26
|
*/
|
|
27
|
-
import { readdirSync, readFileSync } from 'node:fs';
|
|
27
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
28
28
|
import path from 'node:path';
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* One doctrine pack file used to be read three times per render — the survives gate and the
|
|
32
|
+
* body in `js/build/render.mjs`, plus `knownRuleIds` below — and the golden matrix multiplies
|
|
33
|
+
* that by 261 cells. Keyed on mtime, not just path, because the one long-lived process (the
|
|
34
|
+
* web daemon) renders previews of a `src/` the user may be editing between requests; a
|
|
35
|
+
* path-only memo would serve yesterday's pack forever. Newlines come back folded to LF, the
|
|
36
|
+
* same normalisation `readText` gave the render callers — `knownRuleIds` matched identically
|
|
37
|
+
* on raw or folded text, so one behaviour serves both.
|
|
38
|
+
*/
|
|
39
|
+
const _packTexts = new Map();
|
|
40
|
+
export function readPackText(file) {
|
|
41
|
+
const key = path.resolve(file);
|
|
42
|
+
const mtime = statSync(file).mtimeMs;
|
|
43
|
+
const hit = _packTexts.get(key);
|
|
44
|
+
if (hit && hit.mtime === mtime) return hit.text;
|
|
45
|
+
const text = readFileSync(file, 'utf8').replace(/\r\n?/g, '\n');
|
|
46
|
+
_packTexts.set(key, { mtime, text });
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
/**
|
|
31
51
|
* `_build_core.ROOT` — the checkout, from this file's own location (`js/..`).
|
|
32
52
|
*
|
|
@@ -104,7 +124,7 @@ export function knownRuleIds() {
|
|
|
104
124
|
for (const pack of PACK_ORDER) {
|
|
105
125
|
const file = path.join(SRC, 'doctrines', `${pack}.md`);
|
|
106
126
|
let text = '';
|
|
107
|
-
try { text =
|
|
127
|
+
try { text = readPackText(file); } catch { continue; }
|
|
108
128
|
for (const m of text.matchAll(/^### \{\{DOCTRINE\}\} ([a-z]+) (\d+)\b/gm)) {
|
|
109
129
|
if (m[1] === pack) out.push(`${pack}.${Number(m[2])}`);
|
|
110
130
|
}
|
package/js/cli-table.json
CHANGED
|
@@ -155,6 +155,21 @@
|
|
|
155
155
|
"is_flag": false,
|
|
156
156
|
"type": null,
|
|
157
157
|
"hidden": false
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"names": [
|
|
161
|
+
"--host"
|
|
162
|
+
],
|
|
163
|
+
"dest": "host",
|
|
164
|
+
"metavar": null,
|
|
165
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'copilot' has no ask tier, so Laws I/IV block and everything else is a stderr warning",
|
|
166
|
+
"choices": null,
|
|
167
|
+
"default": null,
|
|
168
|
+
"required": false,
|
|
169
|
+
"nargs": null,
|
|
170
|
+
"is_flag": false,
|
|
171
|
+
"type": null,
|
|
172
|
+
"hidden": false
|
|
158
173
|
}
|
|
159
174
|
],
|
|
160
175
|
"mutex": []
|
|
@@ -356,6 +371,21 @@
|
|
|
356
371
|
"is_flag": false,
|
|
357
372
|
"type": null,
|
|
358
373
|
"hidden": false
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"names": [
|
|
377
|
+
"--host"
|
|
378
|
+
],
|
|
379
|
+
"dest": "host",
|
|
380
|
+
"metavar": null,
|
|
381
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'copilot' has no ask tier, so Laws I/IV block and everything else is a stderr warning",
|
|
382
|
+
"choices": null,
|
|
383
|
+
"default": null,
|
|
384
|
+
"required": false,
|
|
385
|
+
"nargs": null,
|
|
386
|
+
"is_flag": false,
|
|
387
|
+
"type": null,
|
|
388
|
+
"hidden": false
|
|
359
389
|
}
|
|
360
390
|
],
|
|
361
391
|
"mutex": []
|
|
@@ -604,6 +634,21 @@
|
|
|
604
634
|
"is_flag": false,
|
|
605
635
|
"type": null,
|
|
606
636
|
"hidden": false
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
"names": [
|
|
640
|
+
"--host"
|
|
641
|
+
],
|
|
642
|
+
"dest": "host",
|
|
643
|
+
"metavar": null,
|
|
644
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'copilot' has no ask tier, so Laws I/IV block and everything else is a stderr warning",
|
|
645
|
+
"choices": null,
|
|
646
|
+
"default": null,
|
|
647
|
+
"required": false,
|
|
648
|
+
"nargs": null,
|
|
649
|
+
"is_flag": false,
|
|
650
|
+
"type": null,
|
|
651
|
+
"hidden": false
|
|
607
652
|
}
|
|
608
653
|
],
|
|
609
654
|
"mutex": []
|
|
@@ -764,6 +809,45 @@
|
|
|
764
809
|
]
|
|
765
810
|
]
|
|
766
811
|
},
|
|
812
|
+
{
|
|
813
|
+
"name": "tool-gate",
|
|
814
|
+
"help": "toolCall hook (Copilot): git-gate and rule-gate fused behind one command, dispatched on the payload",
|
|
815
|
+
"description": "",
|
|
816
|
+
"positionals": [],
|
|
817
|
+
"options": [
|
|
818
|
+
{
|
|
819
|
+
"names": [
|
|
820
|
+
"--root"
|
|
821
|
+
],
|
|
822
|
+
"dest": "root",
|
|
823
|
+
"metavar": null,
|
|
824
|
+
"help": "the install's own dir (set by the emitted hook); enables the sovereign-repo bypass from <root>/excludes.json",
|
|
825
|
+
"choices": null,
|
|
826
|
+
"default": null,
|
|
827
|
+
"required": false,
|
|
828
|
+
"nargs": null,
|
|
829
|
+
"is_flag": false,
|
|
830
|
+
"type": null,
|
|
831
|
+
"hidden": false
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
"names": [
|
|
835
|
+
"--host"
|
|
836
|
+
],
|
|
837
|
+
"dest": "host",
|
|
838
|
+
"metavar": null,
|
|
839
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'copilot' has no ask tier, so Laws I/IV block and everything else is a stderr warning",
|
|
840
|
+
"choices": null,
|
|
841
|
+
"default": null,
|
|
842
|
+
"required": false,
|
|
843
|
+
"nargs": null,
|
|
844
|
+
"is_flag": false,
|
|
845
|
+
"type": null,
|
|
846
|
+
"hidden": false
|
|
847
|
+
}
|
|
848
|
+
],
|
|
849
|
+
"mutex": []
|
|
850
|
+
},
|
|
767
851
|
{
|
|
768
852
|
"name": "tui",
|
|
769
853
|
"help": "print the harness at a glance (interactive terminals only)",
|