geneseed 3.8.0 → 3.10.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 (229) hide show
  1. package/CHANGELOG.md +71 -1
  2. package/DESIGN.md +13 -10
  3. package/QUICKSTART.md +1 -1
  4. package/README.md +31 -32
  5. package/SETUP.md +7 -42
  6. package/SHIPPED.md +4 -4
  7. package/adapters/bob/README.md +17 -1
  8. package/adapters/openclaude/README.md +1 -1
  9. package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +1 -1
  10. package/adapters/opencode/README.md +3 -3
  11. package/adapters/opencode/plugins/geneseed-context.js +78 -8
  12. package/adapters/opencode/plugins/geneseed-guard.js +9 -9
  13. package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
  14. package/adapters/opencode/workflows/_runtime.js +2 -2
  15. package/adapters/opencode/workflows/dispatch.js +1 -1
  16. package/bin/geneseed-hook.mjs +1 -1
  17. package/docs/candidate-hosts.md +12 -5
  18. package/docs/extending.md +72 -40
  19. package/docs/token-footprint.md +26 -21
  20. package/docs/web/claude-hooks.md +1 -1
  21. package/docs/web/collaboration.md +1 -1
  22. package/docs/web/exclusions.md +0 -1
  23. package/docs/web/footprint.md +1 -1
  24. package/docs/web/harnesses.md +14 -14
  25. package/docs/web/install-quick.md +1 -1
  26. package/docs/web/plugin-guard.md +2 -2
  27. package/docs/web/rules.md +1 -1
  28. package/docs/web/themes-author.md +2 -2
  29. package/docs/web-ui.md +2 -2
  30. package/harness.config.json +1 -1
  31. package/js/README.md +8 -7
  32. package/js/build/driver.mjs +26 -95
  33. package/js/build/emit-claude.mjs +76 -81
  34. package/js/build/generate.mjs +0 -1
  35. package/js/build/render.mjs +10 -3
  36. package/js/build/source.mjs +105 -1
  37. package/js/build/stubs.mjs +25 -4
  38. package/js/cli-table.json +8 -8
  39. package/js/hosts/hooks.mjs +91 -59
  40. package/js/hosts/hosts.mjs +5 -21
  41. package/js/hosts/installs.mjs +4 -6
  42. package/js/hosts/mcp.mjs +3 -13
  43. package/js/hosts/native.mjs +28 -51
  44. package/js/hosts/opencode.mjs +1 -1
  45. package/js/hosts/settings.mjs +4 -78
  46. package/js/inspect/checks-authoring.mjs +153 -117
  47. package/js/inspect/checks-repo.mjs +45 -1
  48. package/js/inspect/diff.mjs +11 -3
  49. package/js/inspect/doctor.mjs +3 -3
  50. package/js/inspect/excludes.mjs +0 -4
  51. package/js/inspect/inventory.mjs +1 -2
  52. package/js/inspect/status.mjs +4 -4
  53. package/js/maintain/migrate.mjs +5 -5
  54. package/js/maintain/setup.mjs +3 -5
  55. package/js/maintain/uninstall.mjs +8 -42
  56. package/js/ui/tui.mjs +1 -1
  57. package/js/web/actions.mjs +4 -5
  58. package/js/web/api.mjs +10 -5
  59. package/package.json +1 -2
  60. package/registry.json +7 -0
  61. package/src/AGENT.md.tmpl +86 -65
  62. package/src/agents/_template.md +3 -3
  63. package/src/agents/advocate.md +3 -3
  64. package/src/agents/architect.md +3 -3
  65. package/src/agents/developer.md +4 -4
  66. package/src/agents/docs.md +3 -3
  67. package/src/agents/empiricist.md +4 -4
  68. package/src/agents/explorer.md +4 -4
  69. package/src/agents/framer.md +3 -3
  70. package/src/agents/historian.md +3 -3
  71. package/src/agents/operator.md +3 -3
  72. package/src/agents/pragmatist.md +3 -3
  73. package/src/agents/researcher.md +7 -7
  74. package/src/agents/reviewer.md +3 -3
  75. package/src/agents/security.md +3 -3
  76. package/src/agents/skeptic.md +3 -3
  77. package/src/agents/steward.md +3 -3
  78. package/src/agents/tester.md +3 -3
  79. package/src/agents/user-advocate.md +3 -3
  80. package/src/agents/visionary.md +3 -3
  81. package/src/doctrines/README.md +4 -3
  82. package/src/doctrines/comms.md +4 -4
  83. package/src/doctrines/craft.md +20 -20
  84. package/src/doctrines/ops.md +19 -19
  85. package/src/doctrines/process.md +20 -20
  86. package/src/doctrines/rigor.md +22 -22
  87. package/src/laws/universal.md +26 -44
  88. package/src/memory/README.md +3 -3
  89. package/src/modes/foreman.md +1 -1
  90. package/src/notebook/README.md +1 -1
  91. package/src/ontology/universal.md +8 -10
  92. package/src/postures/artisan.md +2 -2
  93. package/src/skills/_self-improvement.md +4 -4
  94. package/src/skills/_template.md +1 -1
  95. package/src/skills/brainstorm.md +3 -3
  96. package/src/skills/bruno-collection-generator.md +6 -6
  97. package/src/skills/bruno-test-writer.md +5 -5
  98. package/src/skills/ci-fix.md +4 -4
  99. package/src/skills/commit.md +11 -10
  100. package/src/skills/consolidate-memory.md +4 -4
  101. package/src/skills/council.md +2 -2
  102. package/src/skills/daydream/SKILL.md +10 -3
  103. package/src/skills/daydream/VENDOR.md +10 -2
  104. package/src/skills/daydream/instructions.md +2 -2
  105. package/src/skills/debug.md +4 -4
  106. package/src/skills/deps-audit.md +3 -3
  107. package/src/skills/develop.md +7 -7
  108. package/src/skills/document-project.md +6 -6
  109. package/src/skills/explain-changes/SKILL.md +69 -0
  110. package/src/skills/explain-changes/VENDOR.md +20 -0
  111. package/src/skills/explain-changes/scripts/render_changes.mjs +558 -0
  112. package/src/skills/forge-mcp.md +4 -4
  113. package/src/skills/fresh-eyes.md +4 -4
  114. package/src/skills/frontend-design.md +1 -1
  115. package/src/skills/geneseed.md +4 -4
  116. package/src/skills/git-archaeology.md +5 -5
  117. package/src/skills/git-rescue.md +7 -7
  118. package/src/skills/handoff.md +3 -3
  119. package/src/skills/ingest.md +5 -5
  120. package/src/skills/migrate.md +8 -8
  121. package/src/skills/parallel-agents.md +2 -2
  122. package/src/skills/pipeline.md +2 -2
  123. package/src/skills/plan.md +3 -3
  124. package/src/skills/ponytail.md +2 -2
  125. package/src/skills/profile.md +2 -2
  126. package/src/skills/prose.md +1 -1
  127. package/src/skills/prototype.md +1 -1
  128. package/src/skills/react-view-transitions/SKILL.md +1 -1
  129. package/src/skills/react-view-transitions/VENDOR.md +7 -2
  130. package/src/skills/refactor.md +3 -3
  131. package/src/skills/release.md +5 -5
  132. package/src/skills/repo-map.md +4 -4
  133. package/src/skills/research.md +9 -9
  134. package/src/skills/review-response.md +4 -4
  135. package/src/skills/rule.md +3 -3
  136. package/src/skills/security-audit.md +5 -5
  137. package/src/skills/ship.md +5 -5
  138. package/src/skills/skill-forge.md +4 -4
  139. package/src/skills/token-report/SKILL.md +2 -3
  140. package/src/skills/token-report/VENDOR.md +2 -3
  141. package/src/skills/token-report/scripts/token_report.mjs +7 -121
  142. package/src/skills/wiki.md +5 -5
  143. package/src/skills/workflow.md +3 -4
  144. package/src/skills/worktree.md +4 -4
  145. package/themes/_TEMPLATE.json +37 -39
  146. package/themes/biker.json +37 -39
  147. package/themes/commentator.json +37 -39
  148. package/themes/cyberpunk.json +37 -39
  149. package/themes/gamer.json +37 -39
  150. package/themes/imperial.json +37 -39
  151. package/themes/joker.json +37 -39
  152. package/themes/marvin.json +37 -39
  153. package/themes/mean.json +37 -39
  154. package/themes/military.json +37 -39
  155. package/themes/neutral.json +37 -39
  156. package/themes/pirate.json +37 -39
  157. package/themes/sports.json +37 -39
  158. package/themes/verstappen.json +37 -39
  159. package/themes/wizard.json +37 -39
  160. package/web/dist/assets/Activity-BjVp522q.js +1 -0
  161. package/web/dist/assets/ActivityDetail-8ep1bspW.js +1 -0
  162. package/web/dist/assets/Dashboard-OkC_PCH6.js +2 -0
  163. package/web/dist/assets/{Docs-DrJNDPwI.js → Docs-CKPWz00o.js} +2 -2
  164. package/web/dist/assets/FilterInput-CVyqmYWM.js +1 -0
  165. package/web/dist/assets/GreenhouseView-D0cbCJ5m.js +2 -0
  166. package/web/dist/assets/Installs-BtGGdoTt.js +2 -0
  167. package/web/dist/assets/JournalView-Ckl4dmzR.js +1 -0
  168. package/web/dist/assets/Laws-CLpdRRk8.js +3 -0
  169. package/web/dist/assets/Library-wPPuKQrI.js +1 -0
  170. package/web/dist/assets/{Markdown-BQAvN8xx.js → Markdown-CkFyfgNf.js} +14 -14
  171. package/web/dist/assets/OperatorHudView-CiAkAkQL.js +2 -0
  172. package/web/dist/assets/Personal-D8CnDo9v.js +1 -0
  173. package/web/dist/assets/Tabs-B4HNrrCs.js +1 -0
  174. package/web/dist/assets/drawable-BiwaOo6x.js +1 -0
  175. package/web/dist/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
  176. package/web/dist/assets/geist-mono-latin-500-normal-YINYabwD.woff2 +0 -0
  177. package/web/dist/assets/index-C1yil5j1.js +2 -0
  178. package/web/dist/assets/index-CDPDk2vj.css +1 -0
  179. package/web/dist/assets/{react-DbkvxWG5.js → react-B9Z_tXRV.js} +1 -1
  180. package/web/dist/assets/sections-GalMS0Zv.js +1 -0
  181. package/web/dist/assets/usePoll-O0sJEw1Y.js +1 -0
  182. package/web/dist/index.html +4 -4
  183. package/web/src/pages/Laws.jsx +616 -361
  184. package/adapters/copilot/README.md +0 -105
  185. package/web/dist/assets/Activity-CH-7cPIM.js +0 -1
  186. package/web/dist/assets/ActivityDetail-DZvz0xS5.js +0 -1
  187. package/web/dist/assets/CatalogRow-DftXpZHY.js +0 -1
  188. package/web/dist/assets/Diff-DvwdG0L9.js +0 -2
  189. package/web/dist/assets/Doctor-OTmwmYZg.js +0 -1
  190. package/web/dist/assets/FilterInput-BakJLvaV.js +0 -1
  191. package/web/dist/assets/GreenhouseView-XxrAdriX.js +0 -2
  192. package/web/dist/assets/Harness-C_LYNk5c.js +0 -1
  193. package/web/dist/assets/JournalView-BcuyeEaR.js +0 -1
  194. package/web/dist/assets/Laws-CWHrTKY4.js +0 -3
  195. package/web/dist/assets/Library-CA9IHghn.js +0 -1
  196. package/web/dist/assets/OperatorHudView-BMRY1Tt4.js +0 -2
  197. package/web/dist/assets/Profile-wceET1JY.js +0 -1
  198. package/web/dist/assets/Rules-BEXvY4Tx.js +0 -1
  199. package/web/dist/assets/Settings-DS2zF0Ez.js +0 -1
  200. package/web/dist/assets/Skills-DahlOJuI.js +0 -1
  201. package/web/dist/assets/StatusBadge-Cg6j-NJL.js +0 -1
  202. package/web/dist/assets/activity-iWaWVtsR.js +0 -1
  203. package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
  204. package/web/dist/assets/drawable-BTHjTzF7.js +0 -1
  205. package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
  206. package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
  207. package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
  208. package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  209. package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  210. package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  211. package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
  212. package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
  213. package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
  214. package/web/dist/assets/index-AN5Emnrg.js +0 -2
  215. package/web/dist/assets/index-Ruh93A3A.css +0 -1
  216. package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  217. package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  218. package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  219. package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  220. package/web/dist/assets/lawCats-Btjp3Hk_.js +0 -1
  221. package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
  222. package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
  223. package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
  224. package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
  225. package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  226. package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  227. package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  228. package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  229. package/web/dist/assets/rolldown-runtime-CbXtAM7H.js +0 -1
@@ -41,7 +41,9 @@ import os from 'node:os';
41
41
  import path from 'node:path';
42
42
 
43
43
  import { emitGlobalInto } from '../build/driver.mjs';
44
- import { GLOBAL_MANIFEST, VERSION_MARKER, expanduser, opencodeConfigDir } from '../hosts/hosts.mjs';
44
+ import {
45
+ GLOBAL_MANIFEST, VERSION_MARKER, expanduser, opencodeConfigDir, resolvePath,
46
+ } from '../hosts/hosts.mjs';
45
47
  import {
46
48
  EMIT_HOST_SCOPE, defaultTheme, doctrinesForBuild, footprintOfDir, modeOfDir, postureOfDir,
47
49
  readJsonMaybe, readMaybe, themeOfDir,
@@ -174,6 +176,12 @@ export function diffCollect({ target = null, theme = null, emit = null, footprin
174
176
  // makes explicit rather than leaving to `makeCfg`'s parameter default two layers down.
175
177
  posture: postureOfDir(dir), mode: modeOfDir(dir), doctrines: doctrinesForBuild(dir),
176
178
  }));
179
+ // The one place a render depends on WHERE it is installed: a global Bob emit writes each
180
+ // folder skill's absolute directory into its SKILL.md (see `writeNativeLayer`), so the
181
+ // reference render names the temp tree. Mapped back to the deployed dir before comparing,
182
+ // or every such skill reads as edited. A deployment that has since MOVED still differs,
183
+ // and that is real drift — its skills point at a directory that is no longer theirs.
184
+ const deployedDir = resolvePath(dir);
177
185
  const rels = [...new Set([...ownedSet(dir), ...ownedSet(expected)])].sort();
178
186
  for (const rel of rels) {
179
187
  const a = path.join(dir, rel);
@@ -182,7 +190,7 @@ export function diffCollect({ target = null, theme = null, emit = null, footprin
182
190
  // `read_text(errors="replace")` — Node's utf8 decoder substitutes U+FFFD for the
183
191
  // same bytes, and `readText` folds the line endings the way `Path.read_text` does.
184
192
  const ta = readText(a);
185
- const tb = readText(b);
193
+ const tb = readText(b).split(expected).join(deployedDir);
186
194
  if (cmpKey(rel, ta) !== cmpKey(rel, tb)) {
187
195
  const diff = unifiedDiff(splitLines(tb), splitLines(ta), {
188
196
  fromfile: `source/${rel}`, tofile: `deployed/${rel}`, lineterm: '',
@@ -349,7 +357,7 @@ export function cmdDiff(args) {
349
357
  + 'installs against a fresh render (a project layer is diffed against '
350
358
  + 'its own repo, which this command does not do). Pass --target at a '
351
359
  + 'global config dir (opencode-global/claude-global/bob-global/'
352
- + 'copilot-global/openclaude-global), or omit '
360
+ + 'openclaude-global), or omit '
353
361
  + '--target to use the default.\n');
354
362
  } else {
355
363
  printErr(`[diff] no global Geneseed install at ${target} (no ${GLOBAL_MANIFEST}). `
@@ -134,7 +134,7 @@ export function globalEmitProblems(themeName) {
134
134
  }
135
135
 
136
136
  /**
137
- * `_harness_build._claude_bob_emit_problems` — validate the claude/bob/copilot/openclaude PER-REPO
137
+ * `_harness_build._claude_bob_emit_problems` — validate the claude/bob/openclaude PER-REPO
138
138
  * emits, which were never checked before.
139
139
  *
140
140
  * That is exactly why the CLAUDE.md/AGENTS.md skill-table dead links shipped unnoticed: the
@@ -143,7 +143,7 @@ export function globalEmitProblems(themeName) {
143
143
  * skills one level deeper than a bundle does.
144
144
  */
145
145
  export function claudeBobEmitProblems(themeName) {
146
- return emitProblemsOver(['claude', 'bob', 'copilot', 'openclaude'], (tmp, label) => {
146
+ return emitProblemsOver(['claude', 'bob', 'openclaude'], (tmp, label) => {
147
147
  const root = path.join(tmp, 'root');
148
148
  return {
149
149
  label: `${themeName} ${label}`,
@@ -211,7 +211,7 @@ export function doctorCollect({
211
211
  problems = problems.concat(ran('global', `Global install (${themeName})`,
212
212
  globalEmitProblems(themeName)));
213
213
  problems = problems.concat(ran('claude_bob',
214
- `Claude/Bob/Copilot/OpenClaude per-repo emit (${themeName})`, claudeBobEmitProblems(themeName)));
214
+ `Claude/Bob/OpenClaude per-repo emit (${themeName})`, claudeBobEmitProblems(themeName)));
215
215
  }
216
216
  });
217
217
  problems = problems.concat(ran('parity', 'Theme parity', themeParityProblems()));
@@ -143,10 +143,6 @@ export function excludeAdd(target) {
143
143
  wired.bob_rules_stub = true;
144
144
  }
145
145
  }
146
- if (host === 'copilot') {
147
- messages.push('copilot: no native suppression exists — the global '
148
- + 'copilot-instructions.md still loads there (documented limitation)');
149
- }
150
146
  const entries = data.excludes.filter((e) => !(isDict(e) && same(get(e, 'path'), repo)));
151
147
  entries.push({ path: repo.split(path.sep).join('/'), wired });
152
148
  data.excludes = entries;
@@ -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: 'security', X: 'verify',
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. */
@@ -61,7 +61,7 @@ import { THEMES, ROOT, makeCfg } from '../build/source.mjs';
61
61
  import { tuiInventory } from './inventory.mjs';
62
62
  import { readVersion, sourceFingerprint } from '../build/version.mjs';
63
63
  import {
64
- claudeConfigDir, bobConfigDir, copilotConfigDir, openclaudeConfigDir, opencodeConfigDir,
64
+ claudeConfigDir, bobConfigDir, openclaudeConfigDir, opencodeConfigDir,
65
65
  resolvePath, resolveMemoryDir, sovereignBypass, GATE_LEDGER,
66
66
  } from '../hosts/hosts.mjs';
67
67
  // P5f moved the install DETECTORS out of this file — `diff` renders its expected copy in the
@@ -99,9 +99,9 @@ export function cmdVersion(args) {
99
99
  let target = args.target ? resolvePath(args.target) : opencodeConfigDir();
100
100
  let installed = readVersion(target);
101
101
  if (installed === null) {
102
- // A claude/bob/copilot-only machine must not report "no install detected". The bundle
102
+ // A claude/bob/openclaude-only machine must not report "no install detected". The bundle
103
103
  // candidates are cwd-relative only — see the Python for why `ROOT / "Harness"` left.
104
- for (const base of [claudeConfigDir(), bobConfigDir(), copilotConfigDir(), openclaudeConfigDir(),
104
+ for (const base of [claudeConfigDir(), bobConfigDir(), openclaudeConfigDir(),
105
105
  path.join(process.cwd(), 'Harness'), process.cwd()]) {
106
106
  const v = readVersion(base);
107
107
  if (v) { installed = v; target = base; break; }
@@ -226,7 +226,7 @@ export function statusData() {
226
226
  let installedFp = null;
227
227
  let verTarget = null;
228
228
  const otherCfg = [];
229
- for (const fn of [claudeConfigDir, bobConfigDir, copilotConfigDir, openclaudeConfigDir]) {
229
+ for (const fn of [claudeConfigDir, bobConfigDir, openclaudeConfigDir]) {
230
230
  try { otherCfg.push(fn()); } catch { /* a missing host dir must not sink status */ }
231
231
  }
232
232
  // Same walk as `cmdVersion`'s fallback, and cwd-relative for the same reason.
@@ -52,11 +52,11 @@ const KNOWN_EMITS = new Set(EMIT_HOST_SCOPE.keys());
52
52
  /**
53
53
  * The settings file a host wires hooks into, or null for a host that has none.
54
54
  *
55
- * Only Claude and Bob carry hook commands. OpenCode's hooks are a PLUGIN (`js/hosts/opencode.mjs`
56
- * emits JS that runs in the host's own process and names neither the shim nor `harness.py`),
57
- * and Copilot has no hook mechanism at all. So those two classify as `none` and their whole
58
- * migration is the re-emit — which is the same re-emit, in the same change, on all three
59
- * hosts, which is what the project's host-parity rule asks for.
55
+ * Only the Claude-style hosts carry hook commands. OpenCode's hooks are a PLUGIN
56
+ * (`js/hosts/opencode.mjs` emits JS that runs in the host's own process and names neither the
57
+ * shim nor `harness.py`), so it classifies as `none` and its whole migration is the re-emit —
58
+ * the same re-emit, in the same change, on every host, which is what the project's
59
+ * host-parity rule asks for.
60
60
  */
61
61
  function hookSettingsFile(root, host, scope) {
62
62
  if (!['claude', 'bob', 'openclaude'].includes(host)) return null;
@@ -280,7 +280,7 @@ export function doctrineOptions() {
280
280
  /**
281
281
  * `_harness_setup.EMIT_OPTIONS`.
282
282
  *
283
- * Exported since P6b: `/api/themes` returns the same eleven as its `emits` list, and a copy
283
+ * Exported since P6b: `/api/themes` returns the same nine as its `emits` list, and a copy
284
284
  * of a table under test silently stops being the table under test.
285
285
  */
286
286
  export const EMIT_OPTIONS = [
@@ -290,8 +290,6 @@ export const EMIT_OPTIONS = [
290
290
  ['claude', 'Per-repo CLAUDE.md + .claude/ committed into one repository.'],
291
291
  ['bob-global', 'IBM Bob global config dir (~/.bob) — rules/geneseed.md, agents, skills, settings.json.'],
292
292
  ['bob', 'Per-repo AGENTS.md + .bob/ for IBM Bob, committed into one repository.'],
293
- ['copilot-global', 'GitHub Copilot personal config dir (~/.copilot) — copilot-instructions.md, agents, skills.'],
294
- ['copilot', 'Per-repo AGENTS.md + .github/ for GitHub Copilot, committed into one repository.'],
295
293
  ['files', 'Plain bundle for any AGENT.md tool.'],
296
294
  // Appended AFTER `files`, not beside the other hosts: the menu is answered by number, and a
297
295
  // scripted `setup` that pipes its answers must keep meaning what it meant.
@@ -377,10 +375,10 @@ export function collectSetupLines() {
377
375
  inst.footprint || 'lean');
378
376
  let out = null;
379
377
  let root = null;
380
- // Every PROJECT emit needs the repo root — claude/bob/copilot included: without `--out`
378
+ // Every PROJECT emit needs the repo root — claude/bob/openclaude included: without `--out`
381
379
  // their CLAUDE.md/.claude land in the generator's default ./Harness, where the host never
382
380
  // looks.
383
- if (['opencode', 'claude', 'bob', 'copilot', 'openclaude'].includes(emit)) {
381
+ if (['opencode', 'claude', 'bob', 'openclaude'].includes(emit)) {
384
382
  root = ask('Repo root to install into', '.');
385
383
  out = root;
386
384
  } else if (emit === 'files') {
@@ -59,10 +59,9 @@ import {
59
59
  } from '../hosts/hosts.mjs';
60
60
  import { mcpCommented } from '../hosts/mcp.mjs';
61
61
  import {
62
- copilotIntegrityCheck, managedBlockRead, managedBlockRemove, managedBlockWrite,
62
+ managedBlockRead, managedBlockRemove, managedBlockWrite,
63
63
  mergeClaudeSettings, opencodeTarget, readJsonc,
64
64
  settingsIntegrityCheck, wireClaudeExcludes, unwireClaudeExcludes, unwireClaudeSettings,
65
- unwireCopilotSettings,
66
65
  } from '../hosts/settings.mjs';
67
66
  import { printOut, printErr, readText, writeText, isFile, isDir, isOsError } from '../lib/fs.mjs';
68
67
  import { indexOfDeepEqual, jsonDumps, jsonDumpsIndent, deepEquals } from '../lib/json.mjs';
@@ -71,7 +70,7 @@ import { comparePaths, isAbsolutePath, within } from '../lib/paths.mjs';
71
70
  const hostSpec = (host) => HOSTS.find((h) => h.host === host);
72
71
 
73
72
  /** The Claude-STYLE hosts — one manifest shape, one reversal. Spelled once. */
74
- const CLAUDE_STYLE = ['claude', 'bob', 'copilot', 'openclaude'];
73
+ const CLAUDE_STYLE = ['claude', 'bob', 'openclaude'];
75
74
 
76
75
  /**
77
76
  * `shutil.rmtree(p, ignore_errors=True)`.
@@ -295,12 +294,8 @@ export function claudeUninstall(cfg, archiveMemory) {
295
294
  if (failed.length) warnSurvivors(failed);
296
295
  const hooks = managed.settings_hooks || [];
297
296
  const sf = settingsFile(cfg, managed);
298
- // Two shapes, two unwires: a Copilot install records `copilot_hooks` (event → one hook)
299
- // and never `settings_hooks`, so exactly one of these does anything.
300
- const unwired = unwireClaudeSettings(sf, hooks)
301
- | unwireCopilotSettings(sf, managed.copilot_hooks || []);
297
+ const unwired = unwireClaudeSettings(sf, hooks);
302
298
  unwireClaudeExcludes(sf, managed.settings_excludes || []);
303
- copilotIntegrityCheck(sf, managed.copilot_hooks || [], 'absent');
304
299
  // The unwire is VERIFIED, not assumed: a commented settings file is never rewritten, so a
305
300
  // supposedly-uninstalled repo could keep firing Geneseed's hooks. Loud, never fatal.
306
301
  settingsIntegrityCheck(sf, managed, 'absent');
@@ -320,7 +315,7 @@ export function claudeUninstall(cfg, archiveMemory) {
320
315
  /**
321
316
  * `_harness_mcp._uninstall_global` — reverse a global install at `target` via its manifest.
322
317
  *
323
- * Host-aware at the top, and that dispatch is the whole reason a Claude/Bob/Copilot global
318
+ * Host-aware at the top, and that dispatch is the whole reason a Claude/Bob/OpenClaude global
324
319
  * install and an OpenCode one can share one entry point: the Claude family has no
325
320
  * opencode.json to unmerge and a settings.json plus a managed block instead.
326
321
  */
@@ -681,7 +676,7 @@ export function cmdUninstall(args) {
681
676
  const targetDesc = resolvePath(expanduser(args.target));
682
677
  printErr(`[uninstall] no Geneseed install detected at ${targetDesc}.\n`
683
678
  + '[uninstall] pass --target <repo> for a project install (.opencode/.claude/'
684
- + '.bob/.github/.openclaude) or --target <config dir> for a global one.\n');
679
+ + '.bob/.openclaude) or --target <config dir> for a global one.\n');
685
680
  return 1;
686
681
  }
687
682
  const [host, scope, root] = hit;
@@ -695,12 +690,7 @@ export function cmdUninstall(args) {
695
690
  const data = installDataDir(root, host, scope);
696
691
  const stores = ['memory', 'notebook'].filter((n) => isDir(path.join(data, n)));
697
692
  printOut(`[uninstall] target: ${root} (${host}:${scope})\n`);
698
- if (host === 'copilot') {
699
- printOut('[uninstall] removes: agents/, skills/, markers, the '
700
- + `${hostSpec(host).agentFile} managed block, and Geneseed's `
701
- + '~/.copilot/settings.json hooks on a global install (your own keys/hooks and '
702
- + '.github files are kept).\n');
703
- } else if (['claude', 'bob', 'openclaude'].includes(host)) {
693
+ if (['claude', 'bob', 'openclaude'].includes(host)) {
704
694
  printOut('[uninstall] removes: agents/, skills/, markers, the '
705
695
  + `${hostSpec(host).agentFile} managed block, and Geneseed's `
706
696
  + 'settings.json hooks/excludes (your own keys/hooks are kept).\n');
@@ -1024,7 +1014,7 @@ export function installReactivate(root, host = 'opencode', scope = 'global') {
1024
1014
  return { ok: true, kind, moved };
1025
1015
  }
1026
1016
 
1027
- // ---- the Claude-style fork (Claude · IBM Bob · GitHub Copilot) ---------------------------
1017
+ // ---- the Claude-style fork (Claude · IBM Bob · OpenClaude) ---------------------------
1028
1018
  //
1029
1019
  // A Claude-style install has NO `instructions` array — the harness reaches the host through
1030
1020
  // the CLAUDE.md managed block and the settings.json hooks. So the reversal is three moving
@@ -1057,27 +1047,6 @@ function cleanHostStash(cfg, host = 'claude') {
1057
1047
  function remergeClaudeHooks(cfg, root = cfg, host = 'claude') {
1058
1048
  const data = claudeReadManifest(cfg);
1059
1049
  const managed = managedOf(data);
1060
- // A LEGACY Copilot install (single-slot `copilot_hooks`, the dead `toolCall` event):
1061
- // reactivating it re-wires the CURRENT Copilot shape — event → array, recorded under
1062
- // `settings_hooks` — rather than restoring hooks Copilot no longer reads. None of the
1063
- // pack/exclude axes below apply: the Copilot gate is not pack-conditional.
1064
- if (Array.isArray(managed.copilot_hooks)) {
1065
- const sf = settingsFile(cfg, managed);
1066
- unwireCopilotSettings(sf, managed.copilot_hooks);
1067
- const [, claims] = mergeClaudeSettings(sf, 'global', null, hookRunnerEntry(), null, [],
1068
- 'copilot', cfg);
1069
- if (Object.keys(data).length > 0) {
1070
- delete managed.copilot_hooks;
1071
- managed.settings_hooks = claims;
1072
- data.managed = managed;
1073
- const tmp = path.join(cfg, `${GLOBAL_MANIFEST}.tmp`);
1074
- try {
1075
- writeText(tmp, `${jsonDumpsIndent(data)}\n`);
1076
- renameSync(tmp, path.join(cfg, GLOBAL_MANIFEST));
1077
- } catch { /* except OSError: pass */ }
1078
- }
1079
- return;
1080
- }
1081
1050
  // `hookOpts` is the one argument `mergeClaudeSettings` will not default, and it is right
1082
1051
  // not to: there is no computable fallback for the runner/entry the shim bakes. The
1083
1052
  // emitter's own originator is imported rather than restated — a reactivate that wired a
@@ -1090,8 +1059,7 @@ function remergeClaudeHooks(cfg, root = cfg, host = 'claude') {
1090
1059
  // PROJECT install `cfg` is `<repo>/.claude` and the carrier is `<repo>/CLAUDE.md`, so
1091
1060
  // reading `cfg` alone answered `null` for every project reactivate and re-wired the gate
1092
1061
  // unconditionally (fail-closed, so not a hole — but wrong, and it made the toggle one-way
1093
- // for project installs). `root` first, `cfg` second: copilot keeps its carrier INSIDE the
1094
- // config dir (`.github/copilot-instructions.md`), and on a global install the two are the
1062
+ // for project installs). `root` first, `cfg` second: on a global install the two are the
1095
1063
  // same directory anyway. Both silent ⇒ `null` ⇒ the gate stays.
1096
1064
  const doctrines = doctrinesOfDir(root) ?? doctrinesOfDir(cfg);
1097
1065
  // Same two-carrier read for the second axis, and its own default: an absent line means
@@ -1134,12 +1102,10 @@ function claudeDeactivate(root, scope = 'global', host = 'claude') {
1134
1102
  return { ok: false, failed, rolled_back: done.length };
1135
1103
  }
1136
1104
  unwireClaudeSettings(settingsFile(cfg, managed), managed.settings_hooks || []);
1137
- unwireCopilotSettings(settingsFile(cfg, managed), managed.copilot_hooks || []);
1138
1105
  unwireClaudeExcludes(settingsFile(cfg, managed), managed.settings_excludes || []);
1139
1106
  // The same integrity check the uninstall path runs: a deactivate that silently failed to
1140
1107
  // unwire would leave hooks firing in a repo the user believes is off.
1141
1108
  settingsIntegrityCheck(settingsFile(cfg, managed), managed, 'absent');
1142
- copilotIntegrityCheck(settingsFile(cfg, managed), managed.copilot_hooks || [], 'absent');
1143
1109
  const cm = claudeMdPath(cfg, managed);
1144
1110
  const block = managedBlockRead(cm);
1145
1111
  mkdirSync(stash, { recursive: true }); // presence == disabled, even if nothing moved
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.LEX_I) lines.push(['', `e.g. Rule I — ${data.LEX_I}`]);
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
  }
@@ -41,7 +41,7 @@ import { excludeAdd, excludeRemove } from '../inspect/excludes.mjs';
41
41
  import { setupBuildArgs } from '../build/generate.mjs';
42
42
  import { frontmatter, memoryDropIndex } from '../hosts/hooks.mjs';
43
43
  import {
44
- HOSTS, bobConfigDir, claudeConfigDir, copilotConfigDir, expanduser, openclaudeConfigDir,
44
+ HOSTS, bobConfigDir, claudeConfigDir, expanduser, openclaudeConfigDir,
45
45
  opencodeConfigDir, resolvePath,
46
46
  } from '../hosts/hosts.mjs';
47
47
  import {
@@ -644,7 +644,7 @@ export function apiMcpToggle(state, body) {
644
644
  return { ok: false, error: 'config holds comments — edit it by hand to keep them' };
645
645
  }
646
646
  let cfg;
647
- if (['claude', 'bob', 'copilot', 'openclaude'].includes(host)) {
647
+ if (['claude', 'bob', 'openclaude'].includes(host)) {
648
648
  if (isFile(p)) {
649
649
  let parsed;
650
650
  try {
@@ -816,7 +816,6 @@ const EMIT_FOR = new Map([
816
816
  ['opencode global', 'opencode-global'], ['opencode project', 'opencode'],
817
817
  ['claude global', 'claude-global'], ['claude project', 'claude'],
818
818
  ['bob global', 'bob-global'], ['bob project', 'bob'],
819
- ['copilot global', 'copilot-global'], ['copilot project', 'copilot'],
820
819
  ['openclaude global', 'openclaude-global'], ['openclaude project', 'openclaude'],
821
820
  ]);
822
821
 
@@ -952,7 +951,7 @@ export function apiDeployCmd(state, body) {
952
951
  return { error: `folder not writable: ${root}` };
953
952
  }
954
953
  const cfgdirs = new Set();
955
- for (const fn of [opencodeConfigDir, claudeConfigDir, bobConfigDir, copilotConfigDir,
954
+ for (const fn of [opencodeConfigDir, claudeConfigDir, bobConfigDir,
956
955
  openclaudeConfigDir]) {
957
956
  try {
958
957
  cfgdirs.add(resolvePath(fn()));
@@ -978,7 +977,7 @@ export function apiDeployCmd(state, body) {
978
977
  // and `PreToolUse::Bash` went with them. `doctrinesForBuild` resolves unknown to ALL packs.
979
978
  const doctrines = bodyDoctrines(body) ?? doctrinesForBuild(root);
980
979
  const excludeRules = bodyExcludeRules(body) ?? excludedRulesOfDir(root);
981
- // project-scope emit name == host name (opencode / claude / bob / copilot / openclaude)
980
+ // project-scope emit name == host name (opencode / claude / bob / openclaude)
982
981
  const argv = setupBuildArgs(theme || 'neutral', host, root, root, fp, pos, mode, doctrines,
983
982
  PACK_ORDER, excludeRules);
984
983
  return { cmd: [process.execPath, path.join(ROOT, 'bin', 'build-driver.mjs'), ...argv] };
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
 
@@ -140,8 +141,8 @@ export function webState(theme = null, target = null) {
140
141
  * Re-point the console at another detected install's data dir.
141
142
  *
142
143
  * `root` is the install ROOT the markers and sigils live at. It defaults to `target` and
143
- * differs only for claude/bob/copilot PROJECT installs, where the data sits under
144
- * `<repo>/.claude|.bob|.github` while `.geneseed-emit`/`-theme`/`-footprint` land at
144
+ * differs only for claude/bob/openclaude PROJECT installs, where the data sits under
145
+ * `<repo>/.claude|.bob|.openclaude` while `.geneseed-emit`/`-theme`/`-footprint` land at
145
146
  * `<repo>/` — reading them from the data dir mis-detects the install as opencode/neutral,
146
147
  * and a Diff or a Restore would then overwrite it in the wrong dialect.
147
148
  */
@@ -724,7 +725,7 @@ export const apiExcludes = () => excludesSnapshot();
724
725
  * nested-marker host cannot silently read the bare root.
725
726
  */
726
727
  export function viewCfg(host, scope, root) {
727
- if (scope === 'project' && ['claude', 'bob', 'copilot', 'openclaude'].includes(host)) {
728
+ if (scope === 'project' && ['claude', 'bob', 'openclaude'].includes(host)) {
728
729
  return path.join(root, HOSTS.find((h) => h.host === host).projectMarker);
729
730
  }
730
731
  return root;
@@ -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
- return { exists: true, path: p, text, fingerprint: fingerprint(text) };
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. */
@@ -790,7 +795,7 @@ export function apiOverview(state) {
790
795
  // can re-emit exactly it. Mirrors `viewCfg`'s rule, spelled out separately here.
791
796
  let install = null;
792
797
  for (const [host, scope, root] of installTargets()) {
793
- const data = (scope === 'project' && ['claude', 'bob', 'copilot', 'openclaude'].includes(host))
798
+ const data = (scope === 'project' && ['claude', 'bob', 'openclaude'].includes(host))
794
799
  ? path.join(root, HOSTS.find((h) => h.host === host).projectMarker) : root;
795
800
  try {
796
801
  if (samePath(data, state.target)) {
package/package.json CHANGED
@@ -1,14 +1,13 @@
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.8.0",
4
+ "version": "3.10.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",
8
8
  "agent",
9
9
  "claude-code",
10
10
  "opencode",
11
- "copilot",
12
11
  "openclaude",
13
12
  "agents-md",
14
13
  "skills",
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",