geneseed 3.20.0 → 4.0.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 (88) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +9 -1
  3. package/SHIPPED.md +1 -1
  4. package/adapters/bob/README.md +31 -12
  5. package/adapters/claude-code/README.md +24 -17
  6. package/adapters/claude-code/settings.json +4 -6
  7. package/adapters/openclaude/README.md +13 -5
  8. package/adapters/opencode/plugins/geneseed-guard.js +291 -174
  9. package/bin/geneseed-hook.mjs +36 -5
  10. package/docs/README.md +3 -1
  11. package/docs/candidate-hosts.md +0 -8
  12. package/docs/concepts/enforcement-table.md +66 -0
  13. package/docs/concepts/hook-gates.md +2 -2
  14. package/docs/concepts/hooks.md +6 -6
  15. package/docs/concepts/host-matrix.md +4 -3
  16. package/docs/concepts/loop-branch.md +6 -1
  17. package/docs/concepts/rules.md +1 -1
  18. package/docs/design-history.md +59 -0
  19. package/docs/extending.md +11 -3
  20. package/docs/guides/install-opencode.md +1 -1
  21. package/docs/guides/loop-running.md +1 -1
  22. package/docs/reference/env-context.md +3 -2
  23. package/docs/reference/environment.md +2 -1
  24. package/docs/reference/plugin-guard.md +3 -3
  25. package/docs/reference/troubleshoot-hooks.md +34 -0
  26. package/docs/understand/design-choices-engineering.md +29 -0
  27. package/docs/understand/design-choices-gates.md +23 -0
  28. package/docs/understand/design-choices-hosts.md +21 -0
  29. package/docs/understand/design-choices-install.md +19 -0
  30. package/docs/understand/design-choices-shape.md +21 -0
  31. package/docs/understand/design-choices.md +34 -0
  32. package/docs/understand/enforced-vs-asked.md +5 -4
  33. package/docs/understand/machine-bob.md +1 -1
  34. package/docs/understand/machine-opencode.md +2 -2
  35. package/harness.config.json +1 -1
  36. package/js/build/driver.mjs +42 -16
  37. package/js/build/emit-claude.mjs +24 -8
  38. package/js/build/emit-opencode.mjs +33 -15
  39. package/js/build/generate.mjs +38 -9
  40. package/js/build/source.mjs +39 -0
  41. package/js/cli-table.json +6 -6
  42. package/js/hosts/gitref.mjs +3 -3
  43. package/js/hosts/hooks-context.mjs +11 -30
  44. package/js/hosts/hooks-learn.mjs +89 -8
  45. package/js/hosts/hooks.mjs +212 -18
  46. package/js/hosts/hosts.mjs +217 -13
  47. package/js/hosts/installs.mjs +259 -46
  48. package/js/hosts/native.mjs +48 -12
  49. package/js/hosts/opencode.mjs +47 -17
  50. package/js/hosts/settings.mjs +366 -42
  51. package/js/hosts/shim.mjs +37 -0
  52. package/js/inspect/checks-authoring.mjs +81 -1
  53. package/js/inspect/checks-constitution.mjs +2 -1
  54. package/js/inspect/checks-repo.mjs +68 -4
  55. package/js/inspect/doctor.mjs +40 -9
  56. package/js/inspect/scan.mjs +5 -4
  57. package/js/inspect/status.mjs +62 -12
  58. package/js/lib/text.mjs +23 -0
  59. package/js/loop/catalog.mjs +7 -2
  60. package/js/maintain/migrate.mjs +42 -5
  61. package/js/maintain/uninstall.mjs +150 -38
  62. package/js/maintain/update.mjs +22 -2
  63. package/js/web/actions.mjs +28 -10
  64. package/js/web/api.mjs +5 -3
  65. package/js/web/state.mjs +28 -15
  66. package/package.json +1 -1
  67. package/src/agents/_template.md +1 -1
  68. package/src/bricks/_template.md +1 -1
  69. package/src/doctrines/ops.md +9 -7
  70. package/src/laws/universal.md +1 -1
  71. package/src/skills/_template.md +3 -3
  72. package/src/skills/herdr.md +1 -1
  73. package/web/dist/assets/{Activity-IbwdHHyx.js → Activity-BCtqxjfy.js} +1 -1
  74. package/web/dist/assets/{ActivityDetail-B5uivdWI.js → ActivityDetail-Ba3Oo4dE.js} +1 -1
  75. package/web/dist/assets/{Dashboard-v6WQ37bD.js → Dashboard-DpHza68-.js} +2 -2
  76. package/web/dist/assets/{Docs-CypIfDBX.js → Docs-Bw_-UK0O.js} +1 -1
  77. package/web/dist/assets/{GreenhouseView-ClxP0hQG.js → GreenhouseView-DnJ1Ggp1.js} +1 -1
  78. package/web/dist/assets/{Installs-CPkQeFjO.js → Installs-CWoNKFBk.js} +1 -1
  79. package/web/dist/assets/{JournalView-BeEInhUf.js → JournalView-g6-bw4c8.js} +1 -1
  80. package/web/dist/assets/{Laws-Ds6U5xBQ.js → Laws-DkVWR1tb.js} +1 -1
  81. package/web/dist/assets/{Library-BraUKPyQ.js → Library-CK9HZzjV.js} +1 -1
  82. package/web/dist/assets/{Loops-DWcy3mwK.js → Loops-BP98SMOH.js} +1 -1
  83. package/web/dist/assets/{OperatorHudView-CQyrJQ9m.js → OperatorHudView-DI_n12M9.js} +1 -1
  84. package/web/dist/assets/{Personal-YWqs6qFL.js → Personal-Bk89f13a.js} +1 -1
  85. package/web/dist/assets/{Tabs-EhzEC-Xh.js → Tabs-3WuYIE1b.js} +1 -1
  86. package/web/dist/assets/{drawable-CEKPNV7X.js → drawable-oCUeq9e5.js} +1 -1
  87. package/web/dist/assets/{index-CPi22b7y.js → index-CiXwChvf.js} +2 -2
  88. package/web/dist/index.html +1 -1
@@ -28,7 +28,10 @@ import { jsonDumps, jsonDumpsIndent, parseJson } from '../lib/json.mjs';
28
28
  import { comparePaths } from '../lib/paths.mjs';
29
29
  import { sourceReleaseVersion } from '../build/version.mjs';
30
30
  import { aliasesOf } from '../build/source.mjs';
31
- import { firstBlockquote, stripSkillBodyLinks, pointSideFiles, pushOverrideLines } from './native.mjs';
31
+ import {
32
+ firstBlockquote, isUserInvokedOnly, skillDescription, stripSkillBodyLinks, pointSideFiles,
33
+ pushOverrideLines,
34
+ } from './native.mjs';
32
35
 
33
36
  // Re-exported for `js/web/api.mjs` and the tests, which import it from here.
34
37
  export { sourceReleaseVersion };
@@ -169,10 +172,19 @@ export function themeJson(theme) {
169
172
  return { $schema: 'https://opencode.ai/theme.json', theme: t };
170
173
  }
171
174
 
172
- /** `_build_emit._write_theme` — `<themes_dir>/geneseed-<theme>.json`. */
173
- export function writeTheme(themesDir, themeName, theme) {
174
- mkdirSync(themesDir, { recursive: true });
175
+ /**
176
+ * `_build_emit._write_theme` — `<themes_dir>/geneseed-<theme>.json`.
177
+ *
178
+ * `claim` — the same `claimer` closure the native layer and command writers share (see
179
+ * `writePrimaryAgent`'s docblock). Without it a user's own same-named theme file was
180
+ * overwritten and recorded in `owned`, so uninstall deleted it next. Default: claim
181
+ * everything, for a caller with no manifest to consult. Returns `null`, not the dest,
182
+ * when the claim is refused — so the caller does not push a non-write into `owned`.
183
+ */
184
+ export function writeTheme(themesDir, themeName, theme, claim = (_dest) => true) {
175
185
  const dest = path.join(themesDir, `geneseed-${themeName}.json`);
186
+ if (!claim(dest)) return null;
187
+ mkdirSync(themesDir, { recursive: true });
176
188
  writeText(dest, `${jsonDumpsIndent(themeJson(theme))}\n`);
177
189
  return dest;
178
190
  }
@@ -215,8 +227,8 @@ export function colorThemeFiles(cfg) {
215
227
  .sort(comparePaths);
216
228
  }
217
229
 
218
- /** `_build_emit._write_color_themes` — each curated palette in both flavours. */
219
- export function writeColorThemes(cfg, themesDir) {
230
+ /** `_build_emit._write_color_themes` — each curated palette in both flavours. `claim` — see `writeTheme`. */
231
+ export function writeColorThemes(cfg, themesDir, claim = (_dest) => true) {
220
232
  mkdirSync(themesDir, { recursive: true });
221
233
  const written = [];
222
234
  for (const src of colorThemeFiles(cfg)) {
@@ -224,6 +236,7 @@ export function writeColorThemes(cfg, themesDir) {
224
236
  const palette = spec.palette;
225
237
  for (const [flavour, transparent] of [['solid', false], ['transparent', true]]) {
226
238
  const dest = path.join(themesDir, `geneseed-${spec.name}-${flavour}.json`);
239
+ if (!claim(dest)) continue;
227
240
  writeText(dest, `${jsonDumpsIndent(colorThemeJson(palette, transparent))}\n`);
228
241
  written.push(dest);
229
242
  }
@@ -336,9 +349,17 @@ export function writeCommandLayer(cfg, items, commandDir, claim = (_dest) => tru
336
349
  * second copy of its target. UNCONDITIONAL, unlike the `GENESEED_COMMANDS` set — an old name
337
350
  * that only works behind an opt-in is a name that broke. Read off the already-filtered
338
351
  * `items`, so excluding the target drops its aliases.
352
+ *
353
+ * `alsoUserOnly` (Bob host-compat O2/R1): Bob's skills page documents no model-invocation
354
+ * key either, so a genuinely user-only skill (not just an alias) needs the same treatment —
355
+ * `writeNativeLayer({ bobDialect: true })` already skipped its SKILL.md, and this is the one
356
+ * place left to give `/<stem>` a home. Default `false`: OpenCode's own call site leaves this
357
+ * off, because a real user-only skill there is handled by `permission.skill` deny instead
358
+ * (Task 4) — it keeps its SKILL.md and does not need a second, command-shaped copy.
339
359
  */
340
360
  export function writeAliasCommands(cfg, items, commandDir, claim = (_dest) => true,
341
- skillDirOf = (n) => path.join(path.dirname(commandDir), 'skills', n).split(path.sep).join('/')) {
361
+ skillDirOf = (n) => path.join(path.dirname(commandDir), 'skills', n).split(path.sep).join('/'),
362
+ alsoUserOnly = false) {
342
363
  const written = [];
343
364
  for (const { text, src } of items) {
344
365
  if (text === null) continue;
@@ -346,11 +367,13 @@ export function writeAliasCommands(cfg, items, commandDir, claim = (_dest) => tr
346
367
  if (sp.length !== 2 || sp[0] !== 'skills' || !sp[1].endsWith('.md') || sp[1].startsWith('_')) continue;
347
368
  const stem = sp[1].slice(0, -3);
348
369
  const body = stripSkillBodyLinks(pointSideFiles(text.replace(/^\n+/, ''), stem, skillDirOf(stem)));
349
- for (const alias of aliasesOf(text)) {
350
- const dest = path.join(commandDir, `${alias}.md`);
370
+ const names = alsoUserOnly && isUserInvokedOnly(text) ? [...aliasesOf(text), stem] : aliasesOf(text);
371
+ for (const name of names) {
372
+ const dest = path.join(commandDir, `${name}.md`);
351
373
  if (!claim(dest)) continue;
352
374
  mkdirSync(path.dirname(dest), { recursive: true });
353
- writeText(dest, `---\ndescription: ${jsonDumps(`Alias of ${sp[1].slice(0, -3)}.`)}\n---\n\n${body}`);
375
+ const desc = name === stem ? skillDescription(text) : `Alias of ${stem}.`;
376
+ writeText(dest, `---\ndescription: ${jsonDumps(desc)}\n---\n\n${body}`);
354
377
  written.push(dest);
355
378
  }
356
379
  }
@@ -367,8 +390,13 @@ export function writePonytailCommand(commandDir, claim = (_dest) => true) {
367
390
  return dest;
368
391
  }
369
392
 
370
- /** `_copy_plugins` / `_copy_workflows` — maintained files, copied verbatim (copy2, so LF). */
371
- function copyJsDir(srcDir, dst, owned, prefix) {
393
+ /**
394
+ * `_copy_plugins` / `_copy_workflows` — maintained files, copied verbatim (copy2, so LF).
395
+ *
396
+ * `claim` — see `writeTheme`. A file the claim refuses is skipped: not copied, not counted,
397
+ * not pushed into `owned`, exactly as a declined native-layer write is.
398
+ */
399
+ function copyJsDir(srcDir, dst, owned, prefix, claim = (_dest) => true) {
372
400
  let n = 0;
373
401
  if (existsSync(srcDir) && statSync(srcDir).isDirectory()) {
374
402
  mkdirSync(dst, { recursive: true });
@@ -377,7 +405,9 @@ function copyJsDir(srcDir, dst, owned, prefix) {
377
405
  .map((name) => path.join(srcDir, name))
378
406
  .sort(comparePaths);
379
407
  for (const js of files) {
380
- copyFile(js, path.join(dst, path.basename(js)));
408
+ const dest = path.join(dst, path.basename(js));
409
+ if (!claim(dest)) continue;
410
+ copyFile(js, dest);
381
411
  if (owned !== null && owned !== undefined) owned.push(`${prefix}/${path.basename(js)}`);
382
412
  n += 1;
383
413
  }
@@ -386,11 +416,11 @@ function copyJsDir(srcDir, dst, owned, prefix) {
386
416
  }
387
417
 
388
418
  /** `_build_emit._copy_plugins`. */
389
- export function copyPlugins(cfg, dst, owned = null) {
390
- return copyJsDir(cfg.pluginSrc, dst, owned, 'plugins');
419
+ export function copyPlugins(cfg, dst, owned = null, claim = (_dest) => true) {
420
+ return copyJsDir(cfg.pluginSrc, dst, owned, 'plugins', claim);
391
421
  }
392
422
 
393
423
  /** `_build_emit._copy_workflows`. */
394
- export function copyWorkflows(cfg, dst, owned = null) {
395
- return copyJsDir(cfg.workflowSrc, dst, owned, 'workflows');
424
+ export function copyWorkflows(cfg, dst, owned = null, claim = (_dest) => true) {
425
+ return copyJsDir(cfg.workflowSrc, dst, owned, 'workflows', claim);
396
426
  }