geneseed 3.1.3 → 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.
Files changed (150) hide show
  1. package/CHANGELOG.md +180 -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 +50 -24
  9. package/adapters/opencode/README.md +5 -1
  10. package/adapters/opencode/plugins/geneseed-guard.js +40 -6
  11. package/bin/build-driver.mjs +143 -89
  12. package/bin/geneseed-cli.mjs +63 -106
  13. package/bin/geneseed-hook.mjs +26 -16
  14. package/docs/candidate-hosts.md +74 -0
  15. package/docs/extending.md +6 -5
  16. package/docs/token-footprint.md +32 -21
  17. package/docs/web-ui.md +5 -13
  18. package/harness.config.json +1 -1
  19. package/js/README.md +5 -15
  20. package/js/build/emit-claude.mjs +26 -12
  21. package/js/build/emit-common.mjs +63 -114
  22. package/js/build/emit-opencode.mjs +74 -66
  23. package/js/build/generate.mjs +4 -6
  24. package/js/build/render.mjs +50 -32
  25. package/js/build/source.mjs +29 -8
  26. package/js/build/stubs.mjs +25 -33
  27. package/js/build/themes.mjs +1 -1
  28. package/js/build/version.mjs +1 -3
  29. package/js/cli-table.json +84 -0
  30. package/js/hosts/hooks.mjs +243 -81
  31. package/js/hosts/hosts.mjs +57 -5
  32. package/js/hosts/installs.mjs +35 -28
  33. package/js/hosts/link.mjs +2 -2
  34. package/js/hosts/mcp.mjs +3 -1
  35. package/js/hosts/native.mjs +54 -19
  36. package/js/hosts/opencode.mjs +39 -28
  37. package/js/hosts/settings.mjs +206 -115
  38. package/js/inspect/checks-authoring.mjs +5 -4
  39. package/js/inspect/checks-build.mjs +1 -1
  40. package/js/inspect/diff.mjs +1 -3
  41. package/js/inspect/doctor.mjs +59 -44
  42. package/js/inspect/excludes.mjs +5 -11
  43. package/js/inspect/inventory.mjs +38 -56
  44. package/js/inspect/registry.mjs +4 -7
  45. package/js/inspect/scan.mjs +8 -15
  46. package/js/inspect/status.mjs +51 -3
  47. package/js/lib/fs.mjs +34 -0
  48. package/js/lib/json.mjs +28 -1
  49. package/js/lib/paths.mjs +0 -10
  50. package/js/lib/udiff.mjs +11 -16
  51. package/js/maintain/memory.mjs +2 -4
  52. package/js/maintain/migrate.mjs +3 -3
  53. package/js/maintain/uninstall.mjs +127 -88
  54. package/js/maintain/update.mjs +4 -4
  55. package/js/ui/anim.mjs +1 -1
  56. package/js/ui/menu.mjs +1 -1
  57. package/js/ui/tui.mjs +3 -20
  58. package/js/web/actions.mjs +230 -217
  59. package/js/web/activity.mjs +71 -89
  60. package/js/web/api.mjs +130 -226
  61. package/js/web/daemon.mjs +92 -110
  62. package/js/web/docs.mjs +59 -126
  63. package/js/web/handler.mjs +94 -123
  64. package/js/web/jobs.mjs +116 -178
  65. package/js/web/routes.mjs +47 -192
  66. package/js/web/server.mjs +47 -82
  67. package/package.json +1 -1
  68. package/registry.json +14 -0
  69. package/src/AGENT.md.tmpl +55 -16
  70. package/src/agents/explorer.md +2 -1
  71. package/src/doctrines/README.md +3 -3
  72. package/src/doctrines/process.md +4 -2
  73. package/src/doctrines/rigor.md +13 -0
  74. package/src/laws/universal.md +133 -43
  75. package/src/ontology/universal.md +38 -13
  76. package/src/skills/_template.md +5 -1
  77. package/src/skills/bruno-collection-generator.md +63 -0
  78. package/src/skills/bruno-test-writer.md +62 -0
  79. package/src/skills/clarify.md +1 -1
  80. package/src/skills/crash-course.md +1 -0
  81. package/src/skills/decode.md +1 -0
  82. package/src/skills/drill.md +1 -0
  83. package/src/skills/feynman.md +1 -0
  84. package/src/skills/forge-mcp.md +2 -1
  85. package/src/skills/fresh-eyes.md +1 -1
  86. package/src/skills/herdr.md +1 -0
  87. package/src/skills/ingest.md +2 -1
  88. package/src/skills/learning-path.md +1 -0
  89. package/src/skills/opencode-theme.md +1 -0
  90. package/src/skills/parallel-agents.md +1 -1
  91. package/src/skills/ponytail.md +2 -2
  92. package/src/skills/research.md +4 -2
  93. package/themes/_TEMPLATE.json +5 -0
  94. package/themes/biker.json +5 -0
  95. package/themes/commentator.json +5 -0
  96. package/themes/cyberpunk.json +5 -0
  97. package/themes/gamer.json +5 -0
  98. package/themes/imperial.json +5 -0
  99. package/themes/joker.json +5 -0
  100. package/themes/marvin.json +5 -0
  101. package/themes/mean.json +5 -0
  102. package/themes/military.json +5 -0
  103. package/themes/neutral.json +5 -0
  104. package/themes/pirate.json +5 -0
  105. package/themes/sports.json +5 -0
  106. package/themes/verstappen.json +5 -0
  107. package/themes/wizard.json +5 -0
  108. package/web/dist/assets/Activity-BfVcLrda.js +1 -0
  109. package/web/dist/assets/ActivityDetail-CQXccGPl.js +1 -0
  110. package/web/dist/assets/CatalogRow-DALEQImZ.js +1 -0
  111. package/web/dist/assets/Diff-BIjxXx46.js +5 -0
  112. package/web/dist/assets/Doctor-CJLbgsrw.js +1 -0
  113. package/web/dist/assets/FilterInput-CkUCk_lY.js +1 -0
  114. package/web/dist/assets/GreenhouseView-DfC5IILJ.js +2 -0
  115. package/web/dist/assets/Harness-Dm5CCU-v.js +1 -0
  116. package/web/dist/assets/JournalView-Cpu2eI4s.js +1 -0
  117. package/web/dist/assets/Laws-DDDVFuQz.js +3 -0
  118. package/web/dist/assets/Library-Dao3T9na.js +1 -0
  119. package/web/dist/assets/{marked.esm-JKvyS0Pp.js → Markdown-C__nuWX5.js} +15 -15
  120. package/web/dist/assets/OperatorHudView-DEpCtAjP.js +2 -0
  121. package/web/dist/assets/Profile-BlXtUlh2.js +1 -0
  122. package/web/dist/assets/{Rules-CfsV1EVy.js → Rules-DIre6gxR.js} +1 -1
  123. package/web/dist/assets/Skills-rY13ZPhM.js +1 -0
  124. package/web/dist/assets/{StatusBadge-C9bZOEZk.js → StatusBadge-wW_RS_my.js} +1 -1
  125. package/web/dist/assets/{activity-Cc0n2jwx.js → activity-DpYPKNrA.js} +1 -1
  126. package/web/dist/assets/index-BEZvPvpF.js +1 -0
  127. package/web/dist/assets/index-DNQE5ydy.js +10 -0
  128. package/web/dist/assets/index-DzdQjvM1.css +1 -0
  129. package/web/dist/assets/index-PmQGRS1X.js +2 -0
  130. package/web/dist/assets/jobBuckets-DfTUJUxR.js +1 -0
  131. package/web/dist/assets/lawCats-DUS9UTH4.js +1 -0
  132. package/web/dist/index.html +2 -2
  133. package/web/src/pages/Laws.jsx +119 -130
  134. package/js/web/graph.mjs +0 -0
  135. package/web/dist/assets/Activity-B49fE_ug.js +0 -1
  136. package/web/dist/assets/ActivityDetail-abt7hzD2.js +0 -1
  137. package/web/dist/assets/Diff-D3_bVkih.js +0 -5
  138. package/web/dist/assets/Doctor-hXp7srsx.js +0 -1
  139. package/web/dist/assets/Graph-DvYMtlvl.js +0 -1
  140. package/web/dist/assets/Harness-BIWei7N5.js +0 -1
  141. package/web/dist/assets/Laws-CxmnRdwt.js +0 -3
  142. package/web/dist/assets/Library-MjbOkHIW.js +0 -1
  143. package/web/dist/assets/Markdown-Dx_w0faR.js +0 -1
  144. package/web/dist/assets/Profile-BRFvv2gr.js +0 -1
  145. package/web/dist/assets/Skills-DArA6IHS.js +0 -1
  146. package/web/dist/assets/index-Bx3oxZ1o.css +0 -1
  147. package/web/dist/assets/index-DC3-GlCY.js +0 -1
  148. package/web/dist/assets/index-DP035wPa.js +0 -12
  149. package/web/dist/assets/index-dHi-7M-F.js +0 -2
  150. package/web/dist/assets/roman-DkYN0LX9.js +0 -1
@@ -149,6 +149,20 @@ function isReadonly(text) {
149
149
  return text.includes('Read-only');
150
150
  }
151
151
 
152
+ /**
153
+ * A skill only the USER may open. The marker is an HTML comment in the source spec
154
+ * (`<!-- invocation: user -->`, placed under the purpose blockquote), and it renders to
155
+ * Claude Code's `disable-model-invocation: true`: the skill still answers `/name`, but its
156
+ * description leaves the model's always-on catalogue and the model never self-triggers it.
157
+ * Same precedent as `isReadonly` — a phrase in the spec, not a second manifest to keep in
158
+ * step. Which skills carry it is an authoring decision: the teaching skills a human runs on
159
+ * themselves, and the ones gated on an environment the model cannot see (a herdr pane).
160
+ */
161
+ const USER_INVOKED_RE = /<!--\s*invocation:\s*user\s*-->/;
162
+ export function isUserInvokedOnly(text) {
163
+ return USER_INVOKED_RE.test(text);
164
+ }
165
+
152
166
  /** `_build_emit._strip_skill_body_links`. */
153
167
  export function stripSkillBodyLinks(body) {
154
168
  return body.replace(SKILL_BODY_LINK_RE, '$1');
@@ -173,14 +187,14 @@ function agentColorMap(theme) {
173
187
  cleaned[k] = 'secondary';
174
188
  }
175
189
  }
176
- if (!Object.prototype.hasOwnProperty.call(cleaned, '_default')) cleaned._default = 'secondary';
190
+ if (!Object.hasOwn(cleaned, '_default')) cleaned._default = 'secondary';
177
191
  return cleaned;
178
192
  }
179
193
 
180
194
  /** `_build_emit._agent_color`. */
181
195
  function agentColor(stem, theme) {
182
196
  const colors = agentColorMap(theme);
183
- return Object.prototype.hasOwnProperty.call(colors, stem) ? colors[stem] : colors._default;
197
+ return Object.hasOwn(colors, stem) ? colors[stem] : colors._default;
184
198
  }
185
199
 
186
200
  function isPlainObject(v) {
@@ -195,7 +209,7 @@ function given(v) {
195
209
  /**
196
210
  * `overrides.get(stem) or {}`.
197
211
  *
198
- * The `hasOwnProperty` guard is not ceremony: a bare `overrides[stem]` reaches
212
+ * The `Object.hasOwn` guard is not ceremony: a bare `overrides[stem]` reaches
199
213
  * `Object.prototype` for an agent named `constructor`, `toString` or `valueOf`, and would
200
214
  * hand back a function where Python hands back `None`.
201
215
  *
@@ -206,7 +220,7 @@ function given(v) {
206
220
  * difference on any path the gate covers — a difference in which malformed input crashes.
207
221
  */
208
222
  function agentOverride(overrides, stem) {
209
- return (Object.prototype.hasOwnProperty.call(overrides, stem) && overrides[stem]) || {};
223
+ return (Object.hasOwn(overrides, stem) && overrides[stem]) || {};
210
224
  }
211
225
 
212
226
  /**
@@ -230,30 +244,48 @@ export function pushOverrideLines(fm, ov) {
230
244
  if (given(ov.steps)) fm.push(`steps: ${formatValue(ov.steps)}`);
231
245
  }
232
246
 
233
- /** `_build_emit._claude_agent_frontmatter`. */
234
- function claudeAgentFrontmatter(stem, text, overrides) {
247
+ /**
248
+ * The readonly-tool line `claudeAgentFrontmatter`/`copilotAgentFrontmatter` each emit — a
249
+ * DENYLIST for Claude, an ALLOWLIST for Copilot — differing only in the key, the base list,
250
+ * the one extra entry the `<!-- bash: allow -->` marker toggles, and whether the value is
251
+ * bracketed. `extraWhenBashAllowed` is `false` for Claude (Bash is denied UNLESS the marker
252
+ * allows it) and `true` for Copilot (`execute` is allowed ONLY IF the marker is present) —
253
+ * the same marker, read in the opposite sense a denylist and an allowlist require.
254
+ */
255
+ const READONLY_TOOLS = {
256
+ claude: {
257
+ key: 'disallowedTools', tools: ['Write', 'Edit', 'NotebookEdit', 'WebFetch'],
258
+ extra: 'Bash', extraWhenBashAllowed: false, brackets: false,
259
+ },
260
+ copilot: {
261
+ key: 'tools', tools: ['read', 'search', 'todo', 'agent'],
262
+ extra: 'execute', extraWhenBashAllowed: true, brackets: true,
263
+ },
264
+ };
265
+
266
+ /** `_build_emit._claude_agent_frontmatter` / `._copilot_agent_frontmatter`. */
267
+ function claudeCopilotAgentFrontmatter(host, stem, text, overrides) {
235
268
  const fm = [`name: ${stem}`, `description: ${jsonDumps(descOf(text))}`];
236
269
  const ov = agentOverride(overrides, stem);
237
270
  if (isTruthy(ov.model)) fm.push(`model: ${formatValue(ov.model)}`);
238
271
  if (isReadonly(text)) {
239
- const denied = ['Write', 'Edit', 'NotebookEdit', 'WebFetch'];
240
- if (!text.includes('<!-- bash: allow -->')) denied.push('Bash');
241
- fm.push(`disallowedTools: ${denied.join(', ')}`);
272
+ const {
273
+ key, tools, extra, extraWhenBashAllowed, brackets,
274
+ } = READONLY_TOOLS[host];
275
+ const list = [...tools];
276
+ if (text.includes('<!-- bash: allow -->') === extraWhenBashAllowed) list.push(extra);
277
+ fm.push(`${key}: ${brackets ? `[${list.join(', ')}]` : list.join(', ')}`);
242
278
  }
243
279
  return fm;
244
280
  }
245
281
 
246
- /** `_build_emit._copilot_agent_frontmatter` — `tools:` is an ALLOWLIST, not a denylist. */
282
+ function claudeAgentFrontmatter(stem, text, overrides) {
283
+ return claudeCopilotAgentFrontmatter('claude', stem, text, overrides);
284
+ }
285
+
286
+ /** `tools:` is an ALLOWLIST, not a denylist. */
247
287
  function copilotAgentFrontmatter(stem, text, overrides) {
248
- const fm = [`name: ${stem}`, `description: ${jsonDumps(descOf(text))}`];
249
- const ov = agentOverride(overrides, stem);
250
- if (isTruthy(ov.model)) fm.push(`model: ${formatValue(ov.model)}`);
251
- if (isReadonly(text)) {
252
- const allowed = ['read', 'search', 'todo', 'agent'];
253
- if (text.includes('<!-- bash: allow -->')) allowed.push('execute');
254
- fm.push(`tools: [${allowed.join(', ')}]`);
255
- }
256
- return fm;
288
+ return claudeCopilotAgentFrontmatter('copilot', stem, text, overrides);
257
289
  }
258
290
 
259
291
  /** `_build_emit._opencode_agent_frontmatter`. */
@@ -401,7 +433,10 @@ export function writeNativeLayer(items, agentsDir, skillsDir, overrides = null,
401
433
  kind = 'agent';
402
434
  } else {
403
435
  // Skills are BYTE-IDENTICAL across hosts: name + description, body link-stripped.
436
+ // The user-only key is emitted for every host too — Claude Code and Bob honour it,
437
+ // OpenCode and Copilot ignore an unknown key — so the identity holds.
404
438
  fm = [`name: ${stem}`, `description: ${jsonDumps(descOf(text))}`];
439
+ if (isUserInvokedOnly(text)) fm.push('disable-model-invocation: true');
405
440
  body = stripSkillBodyLinks(body);
406
441
  dest = path.join(skillsDir, stem, 'SKILL.md');
407
442
  kind = 'skill';
@@ -115,36 +115,47 @@ export function sourceReleaseVersion(cfg) {
115
115
  return '0.0.0';
116
116
  }
117
117
 
118
+ const GRAY = 8;
119
+ const GREEN = 2;
120
+ const RED = 1;
121
+ const YEL = 3;
122
+ const MAG = 5;
123
+ const NONE = 'none';
124
+
125
+ /**
126
+ * `_build_emit._theme_json`'s role -> value map, one entry per `PALETTE_ROLES` member (19 —
127
+ * not the 17 the plan that asked for this derivation guessed; logged as a wrong-plan-claim
128
+ * row). `acc` is the one theme-tinted value; `fn` and `type` happen to share it today but are
129
+ * kept as their own role entries rather than aliased to `accent`, since nothing requires a
130
+ * future theme to tint them together.
131
+ *
132
+ * ONE SLOT IS NOT A PURE FUNCTION OF ITS ROLE, and folding it in here would be wrong rather
133
+ * than tidy: `syntaxPunctuation` carries role `fgMuted`, the same role as `textMuted`,
134
+ * `diffContext`, `diffLineNumber` and `markdownBlockQuote` — all four of which render GRAY —
135
+ * but `syntaxPunctuation` alone has always rendered NONE. A real asymmetry this theme has
136
+ * shipped with, reproduced below as a one-slot override rather than smoothed away, the same
137
+ * pattern `colorThemeJson` already uses for its own `TRANSPARENT_NONE` slots.
138
+ */
139
+ const roleAnsi = (acc) => ({
140
+ accent: acc, secondary: MAG, err: RED, warn: YEL, ok: GREEN,
141
+ fg: NONE, fgMuted: GRAY, border: GRAY,
142
+ bg: NONE, bgPanel: NONE, bgElement: NONE, addBg: NONE, delBg: NONE,
143
+ comment: GRAY, kw: MAG, fn: acc, str: GREEN, num: MAG, type: acc,
144
+ });
145
+
146
+ /** The one slot `roleAnsi`'s role lookup gets wrong on its own — forced to NONE after. */
147
+ const THEME_JSON_NONE_OVERRIDE = new Set(['syntaxPunctuation']);
148
+
118
149
  /** `_build_emit._theme_json` — a COMPLETE terminal-native theme tinted by ACCENT. */
119
150
  export function themeJson(theme) {
120
151
  const raw = theme && theme.ACCENT !== undefined ? theme.ACCENT : 'cyan';
121
152
  const key = String(raw).toLowerCase();
122
- const acc = Object.prototype.hasOwnProperty.call(ANSI, key) ? ANSI[key] : 6;
123
- const GRAY = 8;
124
- const GREEN = 2;
125
- const RED = 1;
126
- const YEL = 3;
127
- const MAG = 5;
128
- const NONE = 'none';
129
- const t = {
130
- primary: acc, secondary: MAG, accent: acc,
131
- error: RED, warning: YEL, success: GREEN, info: acc,
132
- text: NONE, textMuted: GRAY,
133
- background: NONE, backgroundPanel: NONE, backgroundElement: NONE,
134
- border: GRAY, borderActive: acc, borderSubtle: GRAY,
135
- diffAdded: GREEN, diffRemoved: RED, diffContext: GRAY,
136
- diffHunkHeader: acc, diffHighlightAdded: GREEN, diffHighlightRemoved: RED,
137
- diffAddedBg: NONE, diffRemovedBg: NONE, diffContextBg: NONE,
138
- diffLineNumber: GRAY, diffAddedLineNumberBg: NONE, diffRemovedLineNumberBg: NONE,
139
- markdownText: NONE, markdownHeading: acc, markdownLink: MAG,
140
- markdownLinkText: acc, markdownCode: GREEN, markdownBlockQuote: GRAY,
141
- markdownEmph: YEL, markdownStrong: YEL, markdownHorizontalRule: GRAY,
142
- markdownListItem: acc, markdownListEnumeration: acc, markdownImage: MAG,
143
- markdownImageText: acc, markdownCodeBlock: NONE,
144
- syntaxComment: GRAY, syntaxKeyword: MAG, syntaxFunction: acc,
145
- syntaxVariable: NONE, syntaxString: GREEN, syntaxNumber: MAG,
146
- syntaxType: acc, syntaxOperator: MAG, syntaxPunctuation: NONE,
147
- };
153
+ const acc = Object.hasOwn(ANSI, key) ? ANSI[key] : 6;
154
+ const roles = roleAnsi(acc);
155
+ const t = {};
156
+ for (const [slot, role] of Object.entries(SLOT_ROLE)) {
157
+ t[slot] = THEME_JSON_NONE_OVERRIDE.has(slot) ? NONE : roles[role];
158
+ }
148
159
  return { $schema: 'https://opencode.ai/theme.json', theme: t };
149
160
  }
150
161
 
@@ -177,7 +188,7 @@ export function colorThemeJson(palette, transparent) {
177
188
  // `palette[role]` is a plain subscript in Python: a palette missing a role raises
178
189
  // KeyError and aborts the emit. A bare JS lookup would yield undefined, which
179
190
  // JSON.stringify DROPS — silently emitting a theme with missing slots.
180
- if (!Object.prototype.hasOwnProperty.call(palette, role)) {
191
+ if (!Object.hasOwn(palette, role)) {
181
192
  throw new Error(`palette has no role '${role}' (slot '${slot}')`);
182
193
  }
183
194
  t[slot] = palette[role];
@@ -257,7 +268,7 @@ export function writePrimaryAgent(cfg, agentsDir, overrides) {
257
268
  const desc = 'Primary orchestrator — works by the harness Rules and delegates to the '
258
269
  + 'capability subagents.';
259
270
  const fm = [`description: ${jsonDumps(desc)}`, 'mode: primary', 'color: primary'];
260
- const ov = (Object.prototype.hasOwnProperty.call(overrides, 'orchestrator')
271
+ const ov = (Object.hasOwn(overrides, 'orchestrator')
261
272
  && overrides.orchestrator) || {};
262
273
  pushOverrideLines(fm, ov);
263
274
  const dest = path.join(agentsDir, 'orchestrator.md');