geneseed 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/README.md +24 -14
  3. package/SETUP.md +38 -7
  4. package/SHIPPED.md +6 -5
  5. package/adapters/bob/README.md +40 -10
  6. package/adapters/claude-code/README.md +5 -0
  7. package/adapters/claude-code/settings.json +8 -1
  8. package/adapters/copilot/README.md +52 -25
  9. package/adapters/opencode/README.md +11 -3
  10. package/adapters/opencode/plugins/geneseed-guard.js +40 -6
  11. package/bin/build-driver.mjs +16 -10
  12. package/bin/geneseed-cli.mjs +55 -56
  13. package/bin/geneseed-hook.mjs +21 -14
  14. package/docs/candidate-hosts.md +74 -0
  15. package/docs/extending.md +10 -8
  16. package/docs/token-footprint.md +32 -21
  17. package/docs/web/agents.md +1 -1
  18. package/docs/web/first-session.md +1 -1
  19. package/docs/web/skills.md +1 -1
  20. package/docs/web-ui.md +5 -13
  21. package/harness.config.json +1 -1
  22. package/js/README.md +5 -6
  23. package/js/build/emit-claude.mjs +22 -7
  24. package/js/build/render.mjs +37 -19
  25. package/js/build/source.mjs +22 -2
  26. package/js/cli-table.json +84 -0
  27. package/js/hosts/hooks.mjs +240 -78
  28. package/js/hosts/hosts.mjs +56 -2
  29. package/js/hosts/link.mjs +1 -1
  30. package/js/hosts/mcp.mjs +3 -1
  31. package/js/hosts/native.mjs +86 -15
  32. package/js/hosts/opencode.mjs +14 -0
  33. package/js/hosts/settings.mjs +173 -5
  34. package/js/inspect/excludes.mjs +1 -1
  35. package/js/inspect/inventory.mjs +13 -8
  36. package/js/inspect/status.mjs +50 -2
  37. package/js/maintain/uninstall.mjs +38 -11
  38. package/js/maintain/update.mjs +2 -2
  39. package/js/web/actions.mjs +8 -8
  40. package/js/web/api.mjs +5 -10
  41. package/js/web/docs.mjs +9 -31
  42. package/js/web/handler.mjs +20 -30
  43. package/js/web/jobs.mjs +5 -26
  44. package/js/web/routes.mjs +18 -84
  45. package/js/web/server.mjs +3 -3
  46. package/package.json +1 -1
  47. package/registry.json +99 -78
  48. package/src/AGENT.md.tmpl +69 -28
  49. package/src/agents/architect.md +4 -1
  50. package/src/agents/explorer.md +3 -1
  51. package/src/agents/operator.md +13 -0
  52. package/src/agents/pragmatist.md +12 -0
  53. package/src/agents/researcher.md +72 -0
  54. package/src/agents/security.md +7 -1
  55. package/src/agents/skeptic.md +2 -1
  56. package/src/agents/tester.md +5 -1
  57. package/src/agents/user-advocate.md +13 -0
  58. package/src/doctrines/README.md +3 -3
  59. package/src/doctrines/rigor.md +14 -1
  60. package/src/laws/universal.md +98 -20
  61. package/src/ontology/universal.md +38 -13
  62. package/src/skills/_self-improvement.md +19 -0
  63. package/src/skills/_template.md +6 -13
  64. package/src/skills/brainstorm.md +37 -19
  65. package/src/skills/bruno-collection-generator.md +52 -0
  66. package/src/skills/bruno-test-writer.md +51 -0
  67. package/src/skills/ci-fix.md +49 -0
  68. package/src/skills/codebase-design.md +1 -12
  69. package/src/skills/commit.md +1 -12
  70. package/src/skills/consolidate-memory.md +50 -0
  71. package/src/skills/council.md +1 -12
  72. package/src/skills/debug.md +1 -12
  73. package/src/skills/deps-audit.md +46 -0
  74. package/src/skills/develop.md +38 -20
  75. package/src/skills/document-project.md +1 -12
  76. package/src/skills/domain-modeling.md +1 -12
  77. package/src/skills/forge-mcp.md +2 -13
  78. package/src/skills/fresh-eyes.md +1 -12
  79. package/src/skills/frontend-design.md +1 -12
  80. package/src/skills/geneseed-code-review.md +1 -12
  81. package/src/skills/geneseed.md +1 -12
  82. package/src/skills/git-archaeology.md +1 -12
  83. package/src/skills/git-rescue.md +1 -12
  84. package/src/skills/handoff.md +1 -12
  85. package/src/skills/herdr.md +2 -12
  86. package/src/skills/ingest.md +1 -12
  87. package/src/skills/migrate.md +1 -12
  88. package/src/skills/opencode-theme.md +2 -12
  89. package/src/skills/parallel-agents.md +1 -12
  90. package/src/skills/pipeline.md +2 -13
  91. package/src/skills/plan.md +1 -12
  92. package/src/skills/ponytail.md +1 -12
  93. package/src/skills/profile.md +1 -12
  94. package/src/skills/prose.md +1 -12
  95. package/src/skills/prototype.md +1 -12
  96. package/src/skills/quiz.md +43 -0
  97. package/src/skills/refactor.md +1 -12
  98. package/src/skills/release.md +1 -12
  99. package/src/skills/repo-map.md +1 -12
  100. package/src/skills/research.md +6 -14
  101. package/src/skills/review-response.md +1 -12
  102. package/src/skills/roast-me.md +1 -12
  103. package/src/skills/rule.md +1 -12
  104. package/src/skills/security-audit.md +51 -0
  105. package/src/skills/ship.md +1 -12
  106. package/src/skills/skill-forge.md +59 -0
  107. package/src/skills/teach.md +50 -0
  108. package/src/skills/tickets.md +1 -12
  109. package/src/skills/wayfinder.md +1 -12
  110. package/src/skills/wiki.md +1 -12
  111. package/src/skills/workflow.md +74 -26
  112. package/src/skills/worktree.md +52 -0
  113. package/themes/_TEMPLATE.json +12 -8
  114. package/themes/biker.json +12 -8
  115. package/themes/commentator.json +12 -8
  116. package/themes/cyberpunk.json +12 -8
  117. package/themes/gamer.json +12 -8
  118. package/themes/imperial.json +12 -8
  119. package/themes/joker.json +12 -8
  120. package/themes/marvin.json +12 -8
  121. package/themes/mean.json +12 -8
  122. package/themes/military.json +12 -8
  123. package/themes/neutral.json +12 -8
  124. package/themes/pirate.json +12 -8
  125. package/themes/sports.json +12 -8
  126. package/themes/verstappen.json +12 -8
  127. package/themes/wizard.json +12 -8
  128. package/web/dist/assets/Activity-DMGhhjQc.js +1 -0
  129. package/web/dist/assets/ActivityDetail-CeQcDA7e.js +1 -0
  130. package/web/dist/assets/{CatalogRow-DqS4e5XM.js → CatalogRow-CnSbDPyg.js} +1 -1
  131. package/web/dist/assets/{Diff-CIrGbiqm.js → Diff-D_ksMBD1.js} +1 -1
  132. package/web/dist/assets/{Doctor-CpI53oHY.js → Doctor-Brb3_oCT.js} +1 -1
  133. package/web/dist/assets/FilterInput-DbRWPhtT.js +1 -0
  134. package/web/dist/assets/GreenhouseView-F3VNFr73.js +2 -0
  135. package/web/dist/assets/{Harness-vRYs11kX.js → Harness-DJSwfYeF.js} +1 -1
  136. package/web/dist/assets/JournalView-BLRYRnB3.js +1 -0
  137. package/web/dist/assets/Laws-CKYns9qe.js +3 -0
  138. package/web/dist/assets/Library-BK0YloSv.js +1 -0
  139. package/web/dist/assets/{Markdown-kgpg9EQM.js → Markdown-BzTRzc7a.js} +1 -1
  140. package/web/dist/assets/OperatorHudView-D3OQSGp5.js +2 -0
  141. package/web/dist/assets/{Profile-CHr-PPf6.js → Profile-CPA2u_0g.js} +1 -1
  142. package/web/dist/assets/{Rules-CsC6hfqk.js → Rules-Lukel-Ru.js} +1 -1
  143. package/web/dist/assets/Skills-BKRLHH7m.js +1 -0
  144. package/web/dist/assets/{StatusBadge-gRrak0nO.js → StatusBadge-DI3weRKd.js} +1 -1
  145. package/web/dist/assets/{activity-B0-FXZCj.js → activity-C_H2Bx1P.js} +1 -1
  146. package/web/dist/assets/{index-Bx3I7cRW.js → index-B7_Wga23.js} +1 -1
  147. package/web/dist/assets/index-DzdQjvM1.css +1 -0
  148. package/web/dist/assets/{index-DJOhOwwR.js → index-N8dTrVro.js} +1 -1
  149. package/web/dist/assets/index-Py_IQjgK.js +10 -0
  150. package/web/dist/assets/jobBuckets-DfTUJUxR.js +1 -0
  151. package/web/dist/assets/lawCats-DUS9UTH4.js +1 -0
  152. package/web/dist/index.html +2 -2
  153. package/web/src/pages/Laws.jsx +82 -64
  154. package/js/web/graph.mjs +0 -0
  155. package/src/skills/clarify.md +0 -28
  156. package/src/skills/crash-course.md +0 -28
  157. package/src/skills/decode.md +0 -28
  158. package/src/skills/drill.md +0 -28
  159. package/src/skills/feynman.md +0 -26
  160. package/src/skills/gap-detector.md +0 -27
  161. package/src/skills/learning-path.md +0 -26
  162. package/src/skills/tdd.md +0 -31
  163. package/web/dist/assets/Activity-BTCG2gMl.js +0 -1
  164. package/web/dist/assets/ActivityDetail-julsmPDB.js +0 -1
  165. package/web/dist/assets/Graph-CS4tA7YS.js +0 -1
  166. package/web/dist/assets/Laws-BjbHJrJt.js +0 -3
  167. package/web/dist/assets/Library-CjC7BAWN.js +0 -1
  168. package/web/dist/assets/Skills-BvatJGfe.js +0 -1
  169. package/web/dist/assets/index-BnRWQbZb.js +0 -12
  170. package/web/dist/assets/index-D0f9tJlA.css +0 -1
  171. package/web/dist/assets/roman-DkYN0LX9.js +0 -1
@@ -4,10 +4,11 @@ What a deployed harness costs in context-window tokens, per host. Token counts a
4
4
  chars/4 estimates and shift a little with theme and version.
5
5
 
6
6
  **Treat every number below as a floor, not a reading.** They were measured on a
7
- `--footprint full` build of the neutral theme when the tree carried 41 skills and 17
8
- agents; it carries more skills than that now, and nothing re-measures this page
9
- automatically. For a live figure on your own install, ask the agent for the
10
- `token-report` skill — that is what it is for.
7
+ `--footprint lean` build (the default) of the neutral theme when the tree carried
8
+ 47 skills and 17 agents, and nothing re-measures this page automatically. For a
9
+ live figure on your own install, ask the agent for the `token-report` skill —
10
+ that is what it is for. A `--footprint full` build runs roughly 5k tokens
11
+ heavier on the root file.
11
12
 
12
13
  ## Always-on cost per session
13
14
 
@@ -15,40 +16,50 @@ The context every host injects at session start, before you type anything:
15
16
 
16
17
  | Component | Claude Code | OpenCode | Bob | Copilot |
17
18
  |---|---|---|---|---|
18
- | Root instruction file | ~10.7k (`CLAUDE.md`) | ~10.6k (`AGENT.md`) | ~10.7k (`AGENTS.md`) | ~10.7k (`AGENTS.md`) |
19
- | Skill metadata (name + description) | ~1.6k | ~1.6k | ~1.6k | ~1.6k |
20
- | Agent metadata | ~0.45k | ~0.55k | ~0.45k | ~0.45k |
19
+ | Root instruction file | ~6.4k (`CLAUDE.md`) | ~6.4k (`AGENT.md`) | ~8.3k (`AGENTS.md`) | ~8.3k (`AGENTS.md`) |
20
+ | Skill metadata (name + description) | ~1.7k | ~1.7k | ~1.7k | ~1.7k |
21
+ | Agent metadata | ~0.5k | ~0.55k | ~0.5k | ~0.5k |
21
22
  | Eager memory/notebook injection | ~1.2k (SessionStart hook) | ~1.2k (context plugin) | ~1.2k (SessionStart hook) | — (no hooks; read on demand) |
22
- | **Total** | **~14k** | **~14k** | **~14k** | **~12.8k** |
23
+ | **Total** | **~9.8k** | **~9.9k** | **~11.7k** | **~10.5k** |
23
24
 
24
- The emits are at parity by design: ~14k tokens, about 7% of a 200k
25
- window (Copilot runs slightly lighter because it has no hook mechanism — the
26
- memory/notebook indexes load when the agent reads them, not eagerly). The
25
+ The emits are at parity by design: ~10–12k tokens, about 5–6% of a 200k
26
+ window. Bob and Copilot carry the §3/§4 catalogue tables inline because their
27
+ hosts expose no native skill/agent inventory; Claude Code and OpenCode ship a
28
+ pointer to the host's own inventory instead, ~1.9k tokens lighter. Copilot also
29
+ runs without eager injection — it has no hook mechanism, so the
30
+ memory/notebook indexes load when the agent reads them, not eagerly. The
27
31
  eager-injection path is budget-capped identically everywhere
28
32
  (16 KB per file, 48 KB total ≈ 12k tokens ceiling), so growing Memory degrades
29
33
  every host the same way instead of one silently falling behind.
30
34
 
31
35
  ## Where the tokens go
32
36
 
33
- - **The root file is ~76% of the bill**, and the constitution inside it is the
37
+ - **The root file is ~65% of the bill**, and the constitution inside it is the
34
38
  bulk of that — no longer one section but three: the Ontology, the nine Rules,
35
39
  and the doctrine packs this install built in. **Two** levers move it, not one.
36
- The [footprint dial](#/docs/footprint) (`lean`, the default, keeps each Rule
37
- and each doctrine rule as its heading plus first line, and ships the Ontology
38
- *whole*; `full` inlines every rationale). And the
40
+ The [footprint dial](#/docs/footprint) (`lean`, the default, swaps each Rule,
41
+ the Ontology and the longest prose sections — the preamble, §6 Notebook, §8
42
+ Wiki — for their hand-written condensations, and cuts each doctrine rule to its
43
+ heading plus first line; `full` inlines every rationale). And the
39
44
  [doctrine packs](#/docs/setup-choices) — `geneseed-build --doctrines craft,rigor`
40
45
  drops whole blocks of §2 at build time, which removes far more than the
41
46
  footprint dial trims. Anything else is noise.
42
47
 
43
- The three-tier split grew the root file: measured on `neutral`, `AGENT.md`
44
- went from 31,457 bytes to 36,882 at `lean` (+17.2%) and 53,378 to 54,810 at
45
- `full` (+2.7%). The lean growth is the larger one *by design* — the Ontology
46
- is prose, and truncating four flowing sections to four orphan sentences would
47
- have shipped a worldview nobody could read.
48
+ The lean condensations are authored, not generated. An earlier design shipped
49
+ the Ontology *whole* under `lean` on the grounds that truncating four flowing
50
+ sections to four orphan sentences would ship a worldview nobody could read —
51
+ the `LEAN:` blocks answer that by hand-writing the short form instead
52
+ (measured on `neutral`, the root file at `lean` went from 29,966 bytes to
53
+ 25,614 with them).
48
54
  - **Skill bodies (~53k) and agent bodies (~10k) are lazy** on every host —
49
55
  loaded only when invoked. A typical skill costs ≤3k per invocation; the
50
56
  heaviest (`react-view-transitions`, ~17.5k with its reference files) loads
51
57
  its references progressively.
58
+ - **User-only skills leave the catalogue.** A skill whose source carries
59
+ `<!-- invocation: user -->` renders `disable-model-invocation: true`; Claude
60
+ Code and Bob then drop its description from the always-on skill metadata and
61
+ only `/name` opens it. Seven skills carry it today (the five teaching drills,
62
+ `herdr`, `opencode-theme`), roughly a seventh of the ~1.7k skill-metadata line.
52
63
  - Plugin JavaScript (OpenCode, ~26k on disk) runs in the host runtime and
53
64
  never enters the context window.
54
65
 
@@ -73,7 +84,7 @@ token counter also includes, none of which Geneseed controls:
73
84
  per-repo install, a personal `~/.claude/CLAUDE.md`, or MCP servers each add
74
85
  their own share.
75
86
 
76
- So a ~34k session on a work repo is normal arithmetic: ~14k harness +
87
+ So a ~30k session on a work repo is normal arithmetic: ~10k harness +
77
88
  ~10k repo docs + ~5–10k host overhead. Set `GENESEED_DEBUG=1` (OpenCode) to
78
89
  log exactly which files the context plugin injected, or run the SessionStart
79
90
  command by hand to see the hook's payload.
@@ -5,4 +5,4 @@ title: "Agents"
5
5
  kind: "concept"
6
6
  link: {"hash": "#/section/agents", "label": "Browse the catalog →"}
7
7
  ---
8
- {N_AGENTS} capability specialists — `reviewer`, `tester`, `architect`, `developer`, `docs`, `security`, `explorer`, plus a debate **council** the [[council]] skill convenes (`advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian`). You delegate by capability, not by folder: the harness picks the right specialist from the request.
8
+ {N_AGENTS} capability specialists — `reviewer`, `tester`, `architect`, `developer`, `docs`, `security`, `explorer`, `researcher`, plus a debate **council** the [[council]] skill convenes (`advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian`). You delegate by capability, not by folder: the harness picks the right specialist from the request.
@@ -16,7 +16,7 @@ Once installed, the agent doesn't change *how* you talk to your tool — it chan
16
16
  ### Try these prompts
17
17
 
18
18
  - *"Use the **profile** skill"* — a short interview that drafts `PROFILE.md` (seeded empty at install), so the agent knows who you are and how you like answers pitched.
19
- - *"Use the **clarify** skill on this feature request."*
19
+ - *"Use the **brainstorm** skill on this feature request."*
20
20
  - *"Delegate to the **reviewer** agent on the staged diff."*
21
21
  - *"Use **brainstorm** then **plan** for how to add X."*
22
22
  - *"Use **council** to debate whether we should ship Y now."*
@@ -5,4 +5,4 @@ title: "Skills"
5
5
  kind: "concept"
6
6
  link: {"hash": "#/section/skills", "label": "Browse the catalog →"}
7
7
  ---
8
- {N_SKILLS} repeatable workflows the agent can invoke by name — from [[brainstorm]], [[clarify]], [[plan]], [[tdd]] and [[debug]] through [[council]], [[research]] and [[ship]] to [[roast-me]]. A skill is a markdown playbook under `src/skills/`; the agent reads it before acting. The catalog link lists every one, live.
8
+ {N_SKILLS} repeatable workflows the agent can invoke by name — from [[brainstorm]], [[plan]], [[develop]] and [[debug]] through [[council]], [[research]] and [[ship]] to [[roast-me]]. A skill is a markdown playbook under `src/skills/`; the agent reads it before acting. The catalog link lists every one, live.
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 · Graph |
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** (a mini
76
- cross-link graph), and **Operator** (a searchable table of every deployed capability).
77
- Headlines reflect the active theme's voice.
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
 
@@ -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.2.0"
5
+ "version": "3.4.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 — 4 verbs, loads on EVERY tool call (~14 ms)
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. Five of `routes.mjs`'s
176
- exported sets are deliberately EMPTY and must stay declared: each is half of a partition test.
177
- `graph.mjs` contains a literal NUL byte, so `grep`/`ripgrep` treat it as binary and skip it.
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
 
@@ -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, unwireClaudeExcludes,
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 has NO
229
- // settings.json and no hook mechanism at all, so the whole stage is skipped: nothing
230
- // written, no settings_* keys recorded for the lifecycle to unwire.
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
- const settingsName = scope === 'project' && !isBob ? 'settings.local.json' : 'settings.json';
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
  }
@@ -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
- * `_terse_laws` minus its pointer paragraph — heading + first sentence, per `### ` block.
186
+ * Heading + first sentence, per `### ` block. DOCTRINES ONLY, now.
172
187
  *
173
- * Split out rather than parameterised. The pointer was baked into the function body, and
174
- * the doctrines axis needs the same truncation under a DIFFERENT closing paragraph (one
175
- * that names the pack catalogue, not `laws/universal.md`, and drops the
176
- * secrets/deletion/git-history clause, which is the invariants' domain and not a pack's).
177
- * An extraction leaves `terseLaws` byte-identical for the laws path — the recorded lean
178
- * AGENT.md does not move — where a fourth parameter would have put a branch inside the one
179
- * function every frozen lean render already goes through.
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
- /** `_build_render._terse_laws`. */
201
- export function terseLaws(text, theme, lawsPrefix = '') {
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
- const pointer =
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
- + `secrets, deletion, git history, scope, or untrusted content.`;
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 = terseLaws(inner, theme, lawsPrefix);
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 = [...readText(path.join(dir, `${pack}.md`))
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(readText(path.join(cfg.src, 'doctrines', `${name}.md`)),
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, footprint, lawsPrefix, new Set(), nativeCatalog), src: file }
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 };
@@ -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 = readFileSync(file, 'utf8'); } catch { continue; }
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)",