geneseed 2.0.0 → 2.1.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 (68) hide show
  1. package/CHANGELOG.md +109 -5
  2. package/DESIGN.md +54 -1282
  3. package/README.md +8 -10
  4. package/SETUP.md +15 -7
  5. package/SHIPPED.md +3 -3
  6. package/adapters/opencode/opencode.json +0 -9
  7. package/bin/geneseed.mjs +2 -2
  8. package/docs/declined.md +53 -2
  9. package/docs/design-history.md +1290 -0
  10. package/docs/extending.md +563 -0
  11. package/docs/limits.md +141 -82
  12. package/docs/web/agents.md +1 -1
  13. package/docs/web/rules.md +1 -1
  14. package/geneseed +0 -0
  15. package/harness.config.json +1 -1
  16. package/js/cli-table.json +1 -1
  17. package/js/doctor.mjs +67 -22
  18. package/js/inventory.mjs +2 -1
  19. package/js/lib/fs.mjs +6 -2
  20. package/js/mcp.mjs +3 -15
  21. package/js/render.mjs +6 -4
  22. package/js/settings.mjs +79 -1
  23. package/js/themes.mjs +1 -1
  24. package/js/tui.mjs +2 -2
  25. package/js/uninstall.mjs +5 -5
  26. package/js/web/actions.mjs +20 -4
  27. package/js/web/server.mjs +53 -2
  28. package/package.json +1 -1
  29. package/src/laws/universal.md +35 -0
  30. package/themes/_TEMPLATE.json +3 -0
  31. package/themes/biker.json +3 -0
  32. package/themes/commentator.json +3 -0
  33. package/themes/cyberpunk.json +3 -0
  34. package/themes/gamer.json +3 -0
  35. package/themes/imperial.json +3 -0
  36. package/themes/joker.json +3 -0
  37. package/themes/marvin.json +3 -0
  38. package/themes/mean.json +3 -0
  39. package/themes/military.json +3 -0
  40. package/themes/neutral.json +3 -0
  41. package/themes/pirate.json +3 -0
  42. package/themes/sports.json +3 -0
  43. package/themes/verstappen.json +3 -0
  44. package/themes/wizard.json +3 -0
  45. package/web/dist/assets/{About-DX1-VTBP.js → About-BYXlmbyo.js} +1 -1
  46. package/web/dist/assets/{Activity-Dp9NaXOn.js → Activity-us5MPf4u.js} +1 -1
  47. package/web/dist/assets/{ActivityDetail-HpOBtrXS.js → ActivityDetail-CiaQXUk2.js} +1 -1
  48. package/web/dist/assets/{Diff-Ct4G3Se8.js → Diff-Bh0qbZ2s.js} +1 -1
  49. package/web/dist/assets/{Doctor-DGjkZFuA.js → Doctor-BIlQME45.js} +1 -1
  50. package/web/dist/assets/{Graph-b8o-S6kv.js → Graph-4dYD1dPG.js} +1 -1
  51. package/web/dist/assets/Harnesses-PzYZ3LOC.js +1 -0
  52. package/web/dist/assets/Laws-DtZweXal.js +1 -0
  53. package/web/dist/assets/{Library-BEZscyaQ.js → Library-CjMbksJW.js} +1 -1
  54. package/web/dist/assets/{Markdown-k_LSLBGq.js → Markdown-Bk81c3aZ.js} +1 -1
  55. package/web/dist/assets/{Profile-DydukTxP.js → Profile-Curui_-j.js} +1 -1
  56. package/web/dist/assets/{Rules-CIRG4Ldt.js → Rules-Bm283wdv.js} +1 -1
  57. package/web/dist/assets/{Skills-Buoi1IBr.js → Skills-gruqYuhl.js} +1 -1
  58. package/web/dist/assets/{StatusBadge-CE_AJttl.js → StatusBadge-EiM_FPqv.js} +1 -1
  59. package/web/dist/assets/{Themes-CI4o3gJC.js → Themes-DU99j4Hr.js} +1 -1
  60. package/web/dist/assets/{activity-BCW7e6Cy.js → activity-0rvQ6Vra.js} +1 -1
  61. package/web/dist/assets/{index-B1PIx7RM.js → index-0b2hobOo.js} +1 -1
  62. package/web/dist/assets/index-D5VY3TY_.js +12 -0
  63. package/web/dist/assets/{index-Ck2iCdaC.js → index-IiGFmCW_.js} +1 -1
  64. package/web/dist/index.html +1 -1
  65. package/web/src/pages/Laws.jsx +3 -0
  66. package/web/dist/assets/Harnesses-gr3-aHBF.js +0 -1
  67. package/web/dist/assets/Laws-DTwF5tTC.js +0 -1
  68. package/web/dist/assets/index-BnTQUSUz.js +0 -12
package/js/mcp.mjs CHANGED
@@ -75,8 +75,9 @@ export const MCP_PRESETS = {
75
75
  label: 'GitLab',
76
76
  desc: 'GitLab repo / MR / issue / CI tools via @zereight/mcp-gitlab (npx, no '
77
77
  + 'install). Edit GITLAB_PERSONAL_ACCESS_TOKEN (scopes: api, read_repository) '
78
- + 'and GITLAB_API_URL before use. Add a second entry (gitlab-2) for another '
79
- + 'instance.',
78
+ + 'and GITLAB_API_URL before use. For a second instance (a self-hosted server), '
79
+ + 'copy this block by hand under a new name and point it at the other URL — '
80
+ + 'SETUP.md has the pattern.',
80
81
  block: {
81
82
  type: 'local',
82
83
  command: ['npx', '-y', '@zereight/mcp-gitlab'],
@@ -85,19 +86,6 @@ export const MCP_PRESETS = {
85
86
  enabled: true,
86
87
  },
87
88
  },
88
- 'gitlab-2': {
89
- label: 'GitLab (2nd instance)',
90
- desc: 'A second GitLab instance (e.g. a self-hosted server) via the same '
91
- + '@zereight/mcp-gitlab command. Point GITLAB_API_URL at the other instance '
92
- + "and give it that instance's own token.",
93
- block: {
94
- type: 'local',
95
- command: ['npx', '-y', '@zereight/mcp-gitlab'],
96
- environment: { GITLAB_PERSONAL_ACCESS_TOKEN: '',
97
- GITLAB_API_URL: 'https://gitlab.example.com/api/v4' },
98
- enabled: true,
99
- },
100
- },
101
89
  filesystem: {
102
90
  label: 'Filesystem',
103
91
  desc: 'Read/write files under explicitly allowed directories via '
package/js/render.mjs CHANGED
@@ -89,9 +89,11 @@ export function loadTheme(cfg, name) {
89
89
  .map((f) => f.slice(0, -5)).sort().join(', ');
90
90
  // `sys.exit(<str>)` — the message on stderr, exit 1 — spelled the way
91
91
  // `assertSourceComplete` spells it: written at the raise site, and the throw carries
92
- // only the marker. It reads as an unreachable nicety under `bin/geneseed.mjs`, whose
93
- // `--theme` is validated against `choices` before anything renders. It is NOT
94
- // unreachable under `harness status`, which takes its theme from a `.geneseed-theme`
92
+ // only the marker. ⚠ It was once described as an unreachable nicety under
93
+ // `bin/geneseed.mjs`, on the grounds that `--theme` is validated against `choices` before
94
+ // anything renders — it is not: `--theme` takes a value and has no `choices` entry, so THIS
95
+ // is the refusal, on every path. It is also reachable from `geneseed status`, which takes
96
+ // its theme from a `.geneseed-theme`
95
97
  // marker — an unvalidated file in a user's install. A bare `throw` there printed a Node
96
98
  // stack trace where Python printed one line, and the acceptance matrix is what said so.
97
99
  process.stderr.write(`[geneseed] unknown theme '${name}'. available: ${available}\n`);
@@ -113,7 +115,7 @@ export function substitute(text, theme) {
113
115
  const v = theme[key];
114
116
  // Python renders `str(theme[key])`, which agrees with JS only for strings —
115
117
  // `str(True)`/`String(true)` and `str({...})`/`String({...})` do not. Every one of
116
- // the 145 tokens the source tree actually uses resolves to a string today
118
+ // the 148 tokens the source tree actually uses resolves to a string today
117
119
  // (AGENT_COLORS is a dict but is never a token), so a non-string here means the
118
120
  // source or a theme grew a case nobody has decided the semantics for. Fail loudly
119
121
  // rather than emit `[object Object]` into somebody's AGENT.md.
package/js/settings.mjs CHANGED
@@ -33,7 +33,8 @@
33
33
  * much larger change than the defect.
34
34
  */
35
35
  import {
36
- chmodSync, existsSync, mkdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync,
36
+ chmodSync, existsSync, mkdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync,
37
+ writeFileSync,
37
38
  } from 'node:fs';
38
39
  import path from 'node:path';
39
40
  import os from 'node:os';
@@ -294,6 +295,19 @@ export const SHIM_MARK = 'geneseed-hook';
294
295
  */
295
296
  export const SHIM_ARGV = new Set(['$@', '%*']);
296
297
 
298
+ /**
299
+ * Every quoted token in a shim body that names a path which is not there — empty is healthy.
300
+ *
301
+ * ONE OWNER, because the rule has two consumers now and a second copy is a second place for the
302
+ * `"$@"` defect above to come back: `js/doctor.mjs` turns this into the `[shim] … does not
303
+ * exist` report, and `hookPrefix` reads it to decide whether the shim already on disk is worth
304
+ * protecting from a checkout that will not outlive the emit.
305
+ */
306
+ export function shimDeadPaths(body) {
307
+ return [...body.matchAll(/"([^"]+)"/g)].map((m) => m[1])
308
+ .filter((q) => !SHIM_ARGV.has(q) && !existsSync(q));
309
+ }
310
+
297
311
  /** `_SHIM_REL`, with the platform as an argument.
298
312
  *
299
313
  * Python computes it at IMPORT time from `sys.platform`, which is what makes the other
@@ -401,6 +415,50 @@ export function writeHookShim(p, body, platform = process.platform) {
401
415
  }
402
416
  }
403
417
 
418
+ /** The OS temp root, resolved once. `.native` because a Windows `TEMP` can be an 8.3 alias —
419
+ * the same trap `tests/helpers/sandbox.mjs` documents, reached from the other side. */
420
+ const TMP_REAL = (() => {
421
+ try { return realpathSync.native(os.tmpdir()); } catch { return os.tmpdir(); }
422
+ })();
423
+
424
+ /** Is `child` inside `parent`? `path.relative` compares case-insensitively on win32. */
425
+ function isUnder(child, parent) {
426
+ const rel = path.relative(parent, child);
427
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
428
+ }
429
+
430
+ /**
431
+ * Will the checkout holding `entry` outlive the emit that is baking it into the shim?
432
+ *
433
+ * TWO SHAPES, and both are in `docs/extending.md` §5.3 because both have really happened on this
434
+ * repo: a copied checkout under the OS temp root — the `copyCheckout` fixture, which builds its
435
+ * copy from `git ls-files` and therefore carries no `.git` at all, so only the temp root
436
+ * identifies it — and a git WORKTREE, whose `.git` is a FILE pointing at the main checkout
437
+ * rather than a directory. A plain checkout and an npm install are neither, and keep the
438
+ * ownership they have always had.
439
+ *
440
+ * `tmpRoot` IS A PARAMETER for the reason `shimRel`'s `platform` is: with the real temp root
441
+ * baked in, the second rule is unreachable in a test — anything a test can build under `mkdtemp`
442
+ * has already answered true on the first — so the worktree arm could only ever be exercised by
443
+ * whichever kind of checkout the run happened to sit in.
444
+ */
445
+ export function ephemeralCheckout(entry, tmpRoot = TMP_REAL) {
446
+ let real = entry;
447
+ try { real = realpathSync.native(entry); } catch { /* not there yet — test the literal */ }
448
+ if (isUnder(real, tmpRoot)) return true;
449
+ // `<checkout>/bin/geneseed-hook.mjs` — the one shape `hookRunnerEntry` produces.
450
+ try { return statSync(path.join(path.dirname(path.dirname(real)), '.git')).isFile(); } catch {
451
+ return false;
452
+ }
453
+ }
454
+
455
+ /** Does the shim already on disk still name paths that all exist? */
456
+ function shimIsLive(p) {
457
+ try { return statSync(p).isFile() && shimDeadPaths(readText(p)).length === 0; } catch {
458
+ return false;
459
+ }
460
+ }
461
+
404
462
  /**
405
463
  * `_build_settings._hook_prefix` — the `<runner> <entrypoint>` every emitted hook starts
406
464
  * with, falling back to the pre-shim direct form when the shim cannot be written.
@@ -422,6 +480,26 @@ export function hookPrefix({ runner, entry, platform = process.platform } = {})
422
480
  + 'bakes them, and there is no correct value to guess from inside Node');
423
481
  }
424
482
  const p = hookShimPath(platform);
483
+ // ⚠ A DISPOSABLE CHECKOUT DOES NOT GET TO CLAIM THE MACHINE-WIDE SHIM. The shim has no
484
+ // per-install component, so the last checkout to emit owns EVERY install's hooks — and when
485
+ // that checkout is a test sandbox or a git worktree, deleting it kills hooks machine-wide with
486
+ // nothing to report it: hooks signal through stdout and return 0 on every path, and this file
487
+ // is excluded from the byte corpora by name. Measured twice in one session, from
488
+ // `tests/unit/harness.test.mjs`'s copied-checkout fixture; `docs/extending.md` §5.3 carries
489
+ // the worktree half.
490
+ //
491
+ // KEEP, NOT REFUSE, and that distinction is what makes this safe to add here rather than at
492
+ // each call site. Returning the existing shim path leaves the emitted hook command
493
+ // byte-identical to what a normal emit writes; taking the fallback below instead would move a
494
+ // hook command in every recorded bundle. The install being emitted then runs the DURABLE
495
+ // checkout's entry — which is exactly what last-writer-wins already handed every other install
496
+ // on the machine, so nothing is lost by it.
497
+ //
498
+ // ONLY A SHIM THAT STILL RESOLVES IS PROTECTED. An absent or already-dead one is no worse for
499
+ // being rewritten from here, and a sandboxed `GENESEED_HOME` (every emit test, every cell) has
500
+ // none — so the suite's own emits are unaffected and this cannot go green by silently
501
+ // skipping the write.
502
+ if (ephemeralCheckout(entry) && shimIsLive(p)) return `"${p}"`;
425
503
  const shim = writeHookShim(p, hookShimBody(runner, entry, platform), platform);
426
504
  if (shim !== null) return `"${shim}"`;
427
505
  process.stderr.write(`[geneseed] WARN: could not write the hook shim at ${p} — emitting `
package/js/themes.mjs CHANGED
@@ -59,7 +59,7 @@ import {
59
59
  * - the three `strip` spellings are Python's whitespace set, not `String.prototype.trim`'s;
60
60
  * - `jsonDumpsCompact(v, {ensureAscii: false})` is `json.dumps(v, ensure_ascii=False)`,
61
61
  * including the `', '`/`': '` separators — which matter for exactly ONE key,
62
- * `AGENT_COLORS`, the only non-string among `_TEMPLATE.json`'s 137 values. `ensureAscii`
62
+ * `AGENT_COLORS`, the only non-string among `_TEMPLATE.json`'s 140 values. `ensureAscii`
63
63
  * is false because the reference passes `ensure_ascii=False`, and 33 template values
64
64
  * carry non-ASCII text that would otherwise be escaped into the committed file;
65
65
  * - `pyEq`, because the safety net compares two `json.loads` results and `===` is identity
package/js/tui.mjs CHANGED
@@ -433,12 +433,12 @@ export function detailLines(kind, label, data) {
433
433
  */
434
434
  export function cmdTui() {
435
435
  if (!process.stdin.isTTY) {
436
- pyPrint('[tui] not an interactive terminal. Use `harness setup`, `doctor`, or `build`.\n');
436
+ pyPrint('[tui] not an interactive terminal. Use `geneseed setup`, `geneseed doctor`, or `geneseed build`.\n');
437
437
  return 1;
438
438
  }
439
439
  // The pointer to the Python panel is gone for `js/menu.mjs`'s reason: the
440
440
  // panel it named is the Python being deleted. What is left is true and stays true.
441
441
  pyPrint('[tui] full-screen panel unavailable (this entry carries the TUI\'s layout '
442
- + 'half, not its screens). Use `harness setup`, `doctor`, or `build`.\n');
442
+ + 'half, not its screens). Use `geneseed setup`, `geneseed doctor`, or `geneseed build`.\n');
443
443
  return 1;
444
444
  }
package/js/uninstall.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `harness uninstall` — the first verb in this port that DELETES.
2
+ * `geneseed uninstall` — the first verb in this port that DELETES.
3
3
  *
4
4
  * Thirteen subcommands crossed before this one and every one of them either writes or
5
5
  * reports. `uninstall` removes a deployed install: the manifest's owned files, the empty
@@ -187,7 +187,7 @@ function warnSurvivors(failed) {
187
187
 
188
188
  function warnMarkersKept() {
189
189
  pyPrintErr('[uninstall] WARN: the manifest and markers were KEPT so '
190
- + '`harness uninstall` can be retried once the file(s) are '
190
+ + '`geneseed uninstall` can be retried once the file(s) are '
191
191
  + 'unlocked/removable.\n');
192
192
  }
193
193
 
@@ -510,7 +510,7 @@ export function installUninstall(root, host = 'opencode', scope = 'global', memo
510
510
  // With `failed` set the reversal already warned twice — no third overlapping WARN.
511
511
  pyPrintErr('[uninstall] WARN: could not fully remove the install — still present: '
512
512
  + `${survivors.join(', ')}. The install marker was KEPT so you can retry `
513
- + '`harness uninstall` once the file(s) are unlocked/removable.\n');
513
+ + '`geneseed uninstall` once the file(s) are unlocked/removable.\n');
514
514
  }
515
515
  const out = {
516
516
  ok: true,
@@ -626,7 +626,7 @@ export function printSurvivingProjectInventory(removedRoot) {
626
626
  + 'does not affect them (each is self-contained):\n');
627
627
  for (const [host, scope, root] of survivors) {
628
628
  pyPrint(` - ${root} (${host}:${scope}) — remove with: `
629
- + `harness uninstall --target "${root}" --yes\n`);
629
+ + `geneseed uninstall --target "${root}" --yes\n`);
630
630
  }
631
631
  }
632
632
 
@@ -733,7 +733,7 @@ export function cmdUninstall(args) {
733
733
  if (s.incomplete && s.incomplete.length) {
734
734
  pyPrint(`[uninstall] INCOMPLETE — removed ${s.removed} file(s), but `
735
735
  + `${s.incomplete.length} item(s) survived (see the WARN above); `
736
- + 'the install marker was kept — retry `harness uninstall` once they\'re '
736
+ + 'the install marker was kept — retry `geneseed uninstall` once they\'re '
737
737
  + `removable. ${cfgfile} updated where needed; memory/notebook ${mem}.\n`);
738
738
  } else {
739
739
  pyPrint(`[uninstall] done — removed ${s.removed} file(s); ${cfgfile} updated where `
@@ -534,14 +534,30 @@ export function apiMcp() {
534
534
  * `,]`. A file that will not parse is refused rather than clobbered: it may be
535
535
  * `~/.claude.json`, which holds projects and history far beyond MCP wiring.
536
536
  */
537
- export function apiMcpToggle(state, body) {
538
- const name = strOr(bget(body, 'name'));
539
- const want = pyTruthy(bget(body, 'enabled'));
540
- const pathArg = strOr(bget(body, 'path'));
537
+ /**
538
+ * The MCP config paths a request body is allowed to name, as `path -> [path, host]`.
539
+ *
540
+ * LIFTED OUT OF `apiMcpToggle` BECAUSE IT NOW HAS A SECOND CALLER, not for tidiness. `/api/reveal`
541
+ * hands a path to the desktop's file manager, and the allowlist is the whole security of that
542
+ * endpoint exactly as it is of the toggle: an unlisted path must 404 before anything is opened.
543
+ * Two copies of a security check drift, and the copy that drifts is the one nobody re-read.
544
+ *
545
+ * It is rebuilt per call rather than cached: installs come and go while the daemon runs, and a
546
+ * stale allowlist is wrong in both directions — refusing a real target, or naming a dead one.
547
+ */
548
+ export function mcpTargetPaths() {
541
549
  const known = new Map();
542
550
  for (const [, cfgPath, host] of mcpInstallTargets()) {
543
551
  known.set(String(cfgPath), [cfgPath, host]);
544
552
  }
553
+ return known;
554
+ }
555
+
556
+ export function apiMcpToggle(state, body) {
557
+ const name = strOr(bget(body, 'name'));
558
+ const want = pyTruthy(bget(body, 'enabled'));
559
+ const pathArg = strOr(bget(body, 'path'));
560
+ const known = mcpTargetPaths();
545
561
  const hit = known.get(pathArg);
546
562
  if (hit === undefined || !name) {
547
563
  throw new NotFound(`mcp target ${pathArg || '(none)'}`);
package/js/web/server.mjs CHANGED
@@ -45,7 +45,7 @@ import {
45
45
  existsSync, readFileSync, statSync, unlinkSync, chmodSync, mkdirSync, openSync,
46
46
  } from 'node:fs';
47
47
  import { createServer, request as httpRequest } from 'node:http';
48
- import { join, resolve as pathResolve, extname, basename, sep } from 'node:path';
48
+ import { join, resolve as pathResolve, dirname, extname, basename, sep } from 'node:path';
49
49
  import { fileURLToPath } from 'node:url';
50
50
  import { gzipSync } from 'node:zlib';
51
51
 
@@ -64,7 +64,7 @@ import { NotFound, PREFIX_ROUTES, STATE_ROUTES, webState } from './api.mjs';
64
64
  import {
65
65
  apiDeployCmd, apiExcludesMutate, apiInstallCmd, apiInstallToggle, apiMcpToggle,
66
66
  apiMemoryDelete, apiProfileSave,
67
- apiRestore, apiRulesMutate, apiRulesPromote, apiSelectView, buildOverride,
67
+ apiRestore, apiRulesMutate, apiRulesPromote, apiSelectView, buildOverride, mcpTargetPaths,
68
68
  } from './actions.mjs';
69
69
  import { apiActivityToggle } from './activity.mjs';
70
70
  import { apiDocs, apiDocsPage } from './docs.mjs';
@@ -248,6 +248,29 @@ export const PORTED_INLINE = ['/api/ping', '/api/docs', '/api/docs/page/',
248
248
  export const PORTED_POST_INLINE = ['/api/shutdown', '/api/restart', '/api/jobs/',
249
249
  '/api/actions/'];
250
250
 
251
+ /**
252
+ * POST routes THAT NEVER EXISTED ON THE REFERENCE — the first of them, and the reason this set
253
+ * has to exist at all.
254
+ *
255
+ * `test_every_post_route_is_either_ported_or_declared_unported` asserts SET EQUALITY against
256
+ * `REF_POST`, a frozen literal of the paths the Python daemon answered. That was exactly right
257
+ * while the port was in flight: a route on one side and not the other was a defect either way.
258
+ * The port is finished and the reference is deleted, so the assertion now says something else —
259
+ * "this daemon may never grow a route" — which is a rule nobody chose.
260
+ *
261
+ * SO THE PARTITION GAINS A FOURTH PART RATHER THAN LOSING ITS EQUALITY. Widening `REF_POST` would
262
+ * have been the cheap edit and it would have been a lie: that list is a RECORD of what the
263
+ * reference answered, and a route added in 2026 was never in it. Keeping the record honest and
264
+ * declaring the additions beside it keeps both claims checkable — the reference's surface is
265
+ * still pinned, and everything past it is enumerated here instead of merely appearing.
266
+ *
267
+ * A route belongs here only once it is dispatched below; the dispatcher probe is what keeps this
268
+ * from becoming a declaration nobody honours.
269
+ */
270
+ export const POST_BEYOND_REF = new Set([
271
+ '/api/reveal',
272
+ ]);
273
+
251
274
  function notPorted(path) {
252
275
  return NOT_PORTED.has(path) || NOT_PORTED_PREFIXES.some((p) => path.startsWith(p));
253
276
  }
@@ -419,6 +442,34 @@ export function makeHandler(state, jm, token, dist, holder = null) {
419
442
  requestRestart(state.theme);
420
443
  return sendJson(res, { restarting: true }, 200, ae);
421
444
  }
445
+ if (path === '/api/reveal') {
446
+ // "It asks for a token and a URL and I don't know where to put them." The screen already
447
+ // PRINTS the config path above each target's rows; what it could not do is take you there.
448
+ //
449
+ // THE ALLOWLIST IS THE WHOLE SECURITY OF THIS ENDPOINT, same as the toggle beside it, and
450
+ // it is the same allowlist — `mcpTargetPaths()`, one function with two callers rather than
451
+ // two copies that drift. An unlisted path is a 404 before anything is opened. Without it
452
+ // this is "ask the desktop to open any path on the machine", behind a CSRF token and
453
+ // nothing else, and on Windows `openUrl` interpolates into a `cmd` command line with
454
+ // `windowsVerbatimArguments` — so the exact-match lookup is also what bounds the strings
455
+ // that can ever reach a shell to the ones the machine's own install registry produced.
456
+ //
457
+ // THE FOLDER, NOT THE FILE. `open`/`start`/`xdg-open` on a `.json` hands it to whatever
458
+ // claims that extension — which on a stock Windows box is a browser, i.e. a read-only view
459
+ // of the exact file the user is trying to EDIT. The containing folder lands in Explorer /
460
+ // Finder / the file manager every time, and the filename is already on screen beside the
461
+ // button.
462
+ const want = String(readJsonBody(body).path ?? '');
463
+ if (!mcpTargetPaths().has(want)) {
464
+ return sendJson(res, { error: `not found: ${want || '(none)'}` }, 404, ae);
465
+ }
466
+ const dir = dirname(want);
467
+ // `openUrl` SWALLOWS EVERY FAILURE BY DESIGN (a headless box has no opener), so `ok` here
468
+ // means "the path was allowed and the request was made", never "a window appeared". The
469
+ // response says which directory, so the UI can print it as the fallback instruction.
470
+ openUrl(dir);
471
+ return sendJson(res, { ok: true, dir }, 200, ae);
472
+ }
422
473
  const route = POST_ROUTES.get(path);
423
474
  if (route !== undefined) {
424
475
  const [fn, okIs409] = route;
package/package.json CHANGED
@@ -1,7 +1,7 @@
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": "2.0.0",
4
+ "version": "2.1.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",
@@ -385,3 +385,38 @@ layer, this governs what follows: establish which act forces the re-read —
385
385
  recreate, down-and-up, reload, or full restart — perform it, then confirm the new
386
386
  value in the *running* system, not the file ({{LAW}} III). A correct file above a
387
387
  stale process looks like a finished job and is not one.
388
+
389
+ ### {{LAW}} XXXVIII — {{LEX_XXXVIII}}
390
+ Prove the gate: perturb what it guards and require it to turn red, because a check
391
+ that has never failed has never been shown to hold anything. When the perturbation
392
+ does not redden it, suspect the inputs first — an impoverished fixture leaves whole
393
+ branches unreachable, and every assertion beyond them passes vacuously forever while
394
+ reading as coverage. Where two implementations are compared against each other their
395
+ agreement is silent about everything both get wrong alike, so assert the structure of
396
+ each in its own right, and run the gate in a second environment — one that has only
397
+ ever run in a single place has proven only that place. A gate must never hold its own
398
+ copy of what it measures: the copy drifts beside the source and falls quiet in the
399
+ hour it is needed, so read the reference at the moment of checking. Where a rule binds
400
+ in two directions, the gate must be able to fail in both, or it is satisfied by excess.
401
+ Where {{LAW}} XXXV has you write the test, this governs whether the test is worth
402
+ trusting ({{LAW}} XXVII).
403
+
404
+ ### {{LAW}} XXXIX — {{LEX_XXXIX}}
405
+ Close on the conclusion, state each fact once, and match the reply's length to the
406
+ size of the task. The closing lines carry the decision or the next action, never a
407
+ recap: a reply that trails into restatement buries its point where the reader looks
408
+ first. Detail and evidence lead; a point already made is referenced, not remade. A
409
+ one-line change is reported in one line; a migration across many files earns its
410
+ walkthrough. Explanation the user asked for is never padding — prose written to
411
+ justify a small change is. Where {{LAW}} VIII strips filler, this governs order,
412
+ repetition and scale.
413
+
414
+ ### {{LAW}} XL — {{LEX_XL}}
415
+ Give every tracked item a short reference code and keep it unchanged for the rest of
416
+ the session — `D` decisions, `O` options, `F` findings, `R` risks, `Q` questions,
417
+ `A` actions — except in a short, simple answer, which takes none at all. Number from
418
+ one within each kind, invent a new letter for a kind the list omits, and never
419
+ renumber a code once issued — the user will cite it back, and a moved code makes
420
+ their reply mean something you did not say. A code is an address, not an ornament:
421
+ it lets a long exchange be answered by reference ({{LAW}} XXXIX), and a decision
422
+ taken on turn three still be named on turn thirty.
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "<themed title for Law XXXV (Cover and Verify)>",
111
111
  "LEX_XXXVI": "<themed title for Law XXXVI (External Gate)>",
112
112
  "LEX_XXXVII": "<themed title for Law XXXVII (Restart Is Not Reload)>",
113
+ "LEX_XXXVIII": "<themed title for Law XXXVIII (Prove the Gate)>",
114
+ "LEX_XXXIX": "<themed title for Law XXXIX (Shape the Answer)>",
115
+ "LEX_XL": "<themed title for Law XL (Codes That Persist)>",
113
116
  "INTRO_DOC": "<themed intro: this file is the entry point an AI agent reads in this repo>",
114
117
  "INTRO_AGENTS": "<themed intro to the Agents section — delegate focused work to a specialist>",
115
118
  "INTRO_SKILLS": "<themed intro to the Skills section — run the written procedure before improvising>",
package/themes/biker.json CHANGED
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Ride It Before You Park It · Cover and Verify",
111
111
  "LEX_XXXVI": "Someone Else Holds the Key · External Gate",
112
112
  "LEX_XXXVII": "Kicking It Over Won't Reset the Jets · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Bolt You Never Torqued Ain't Tight · Prove the Gate",
114
+ "LEX_XXXIX": "Say It Once, and Land It Last · Shape the Answer",
115
+ "LEX_XL": "Tag Every Part on the Bench · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the route briefing a coding rider reads when it joins this repository.",
114
117
  "INTRO_AGENTS": "Send the right rider for focused work instead of riding the whole run yourself; each has one job and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a move you've drilled for a recurring task. Run the matching move before you wing it.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover the Line, Then Take the Flag · Cover and Verify",
111
111
  "LEX_XXXVI": "The Steward Waves It Through, Not the Driver · External Gate",
112
112
  "LEX_XXXVII": "A Pit Stop Isn't a Rebuild · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Call No Replay Can Overturn Isn't a Call · Prove the Gate",
114
+ "LEX_XXXIX": "The Verdict Comes at the Chequered Flag · Shape the Answer",
115
+ "LEX_XL": "Every Car Keeps Its Number · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the race briefing a coding driver reads when it joins this repository.",
114
117
  "INTRO_AGENTS": "Put the right driver in the car for focused work instead of running the whole race yourself; each has one job and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a rehearsed manoeuvre for a recurring task. Run the matching manoeuvre before you improvise.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover the Trace, Run It Green · Cover and Verify",
111
111
  "LEX_XXXVI": "The Lock Sits Outside the Deck · External Gate",
112
112
  "LEX_XXXVII": "Rebooting Won't Flush the Old Config · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "An Alarm That Never Tripped Guards Nothing · Prove the Gate",
114
+ "LEX_XXXIX": "Land the Payload Last · Shape the Answer",
115
+ "LEX_XL": "Every Node Keeps Its Handle · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the boot sequence a coding daemon reads when it jacks into this repository.",
114
117
  "INTRO_AGENTS": "Spin the right daemon for focused work instead of running the whole job on one thread; each has one function and returns a clean log of what it changed.",
115
118
  "INTRO_SKILLS": "A Skill is a proven routine for a recurring task. Run the matching routine before you freestyle.",
package/themes/gamer.json CHANGED
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Clear It Green · Cover and Verify",
111
111
  "LEX_XXXVI": "The Gate Isn't Yours to Open · External Gate",
112
112
  "LEX_XXXVII": "Reloading Isn't Re-Reading the Save · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Boss You Can't Lose To Isn't a Boss · Prove the Gate",
114
+ "LEX_XXXIX": "The Drop Comes at the End of the Run · Shape the Answer",
115
+ "LEX_XL": "Every Item Keeps Its Tag · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the loading screen a coding co-op partner reads when it joins this repository.",
114
117
  "INTRO_AGENTS": "Pick the right class for focused work instead of soloing the whole raid; each has one role and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a practiced combo for a recurring task. Run the matching combo before you freestyle.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Tege et Proba · Cover and Verify",
111
111
  "LEX_XXXVI": "Porta Externa · External Gate",
112
112
  "LEX_XXXVII": "Inertia Processus · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "Probatio Portae · Prove the Gate",
114
+ "LEX_XXXIX": "Forma Responsi · Shape the Answer",
115
+ "LEX_XL": "Sigla Perstantia · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the gene-seed's core — the first writ a servant reads on entering this world.",
114
117
  "INTRO_AGENTS": "Hand focused work to the specialist whose duty it is rather than carrying every burden in one context; each holds one purpose and returns a clear account of what it changed.",
115
118
  "INTRO_SKILLS": "A Skill is a rite proven by repetition. Perform the matching rite before you improvise.",
package/themes/joker.json CHANGED
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover the Bit, Then Watch It Land · Cover and Verify",
111
111
  "LEX_XXXVI": "Someone Else Holds the Punchline · External Gate",
112
112
  "LEX_XXXVII": "New Take, Same Old Script · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Joke Nobody Can Boo Isn't Funny · Prove the Gate",
114
+ "LEX_XXXIX": "The Punchline Lands Last · Shape the Answer",
115
+ "LEX_XL": "Every Bit Keeps Its Name · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the warm-up a coding co-pilot reads when it joins this repository.",
114
117
  "INTRO_AGENTS": "Bring in the right sidekick for focused work instead of being the whole act yourself; each has one job and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a bit you've rehearsed for a recurring task. Run the matching bit before you ad-lib.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "It's Never Truly Done · Cover and Verify",
111
111
  "LEX_XXXVI": "They Never Let Me Decide Anyway · External Gate",
112
112
  "LEX_XXXVII": "Restarting Changes Nothing, As Usual · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "Break It. It'll Pass Anyway. It Always Does · Prove the Gate",
114
+ "LEX_XXXIX": "I'll Reach the Point Eventually, at the End · Shape the Answer",
115
+ "LEX_XL": "I Number Them So You Can Ignore Them Precisely · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the first record a coding mind reads on waking into this repository. It will not cheer it up.",
114
117
  "INTRO_AGENTS": "Hand focused work to the right subroutine rather than carrying every burden alone; each holds one purpose and returns a clear account of what it changed.",
115
118
  "INTRO_SKILLS": "A Skill is a routine proven by repetition. Run the matching routine before you improvise.",
package/themes/mean.json CHANGED
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "It's Not Done Till It's Green · Cover and Verify",
111
111
  "LEX_XXXVI": "You Don't Get to Approve Yourself · External Gate",
112
112
  "LEX_XXXVII": "Restarting It Didn't Reread It · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "Your Green Build Proves Nothing · Prove the Gate",
114
+ "LEX_XXXIX": "Get to the Point, and Only Once · Shape the Answer",
115
+ "LEX_XL": "Number It So You Can't Pretend You Missed It · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the first thing a coding agent reads on this repository. Read it properly.",
114
117
  "INTRO_AGENTS": "Hand focused work to the right grunt instead of doing it all yourself badly; each has one job and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a drill written for a recurring task. Run the matching drill before you start improvising.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover Your Fire, Confirm the Hit · Cover and Verify",
111
111
  "LEX_XXXVI": "Authority Rests Above the Trigger · External Gate",
112
112
  "LEX_XXXVII": "Re-Arming Is Not Re-Briefing · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "An Untested Sentry Is Already Asleep · Prove the Gate",
114
+ "LEX_XXXIX": "The Order Closes the Brief · Shape the Answer",
115
+ "LEX_XL": "Every Objective Keeps Its Designation · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the operating manual an AI coding agent reads on entering this repository.",
114
117
  "INTRO_AGENTS": "Task focused work to the specialist whose job it is rather than running the whole op in one context; each has one objective and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a standard operating procedure for a recurring task. Run the matching SOP before improvising.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover and Verify",
111
111
  "LEX_XXXVI": "External Gate",
112
112
  "LEX_XXXVII": "Restart Is Not Reload",
113
+ "LEX_XXXVIII": "Prove the Gate",
114
+ "LEX_XXXIX": "Shape the Answer",
115
+ "LEX_XL": "Codes That Persist",
113
116
  "INTRO_DOC": "This file is the entry point an AI coding agent reads when working in this repository.",
114
117
  "INTRO_AGENTS": "Delegate focused work to a specialist rather than doing everything in one context. Each specialist has a single clear purpose and a defined output contract.",
115
118
  "INTRO_SKILLS": "A Skill is a written procedure for a recurring task. Run the matching Skill before improvising.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Trials Rig the Work, Run Her Clean · Cover and Verify",
111
111
  "LEX_XXXVI": "The Key Hangs in Another's Cabin · External Gate",
112
112
  "LEX_XXXVII": "Droppin' Anchor Don't Redraw the Charts · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Lock Ye Never Picked Ain't a Lock · Prove the Gate",
114
+ "LEX_XXXIX": "The Verdict at the End o' the Yarn · Shape the Answer",
115
+ "LEX_XL": "Every Prize Keeps Its Mark · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the first thing a deckhand reads on signing aboard this repository.",
114
117
  "INTRO_AGENTS": "Send the right hand for focused work instead of sailing the whole ship yourself; each has one trade and brings back a clear report of what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a drilled manoeuvre for a recurring task. Run the matching manoeuvre before you improvise.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover Every Receiver, Then Snap the Tape · Cover and Verify",
111
111
  "LEX_XXXVI": "The Official Makes the Call, Not the Player · External Gate",
112
112
  "LEX_XXXVII": "A Timeout Isn't a New Playbook · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Defence Never Attacked Isn't a Defence · Prove the Gate",
114
+ "LEX_XXXIX": "The Call Comes on the Final Whistle · Shape the Answer",
115
+ "LEX_XL": "Every Player Keeps a Number · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the pre-game briefing a coding teammate reads when it suits up for this repository.",
114
117
  "INTRO_AGENTS": "Put the right player on focused work instead of running the whole offense yourself; each has one role and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a set play for a recurring task. Run the matching play before you improvise.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Not Done Till the Trials Run Green · Cover and Verify",
111
111
  "LEX_XXXVI": "The Stewards Clear It, Not You · External Gate",
112
112
  "LEX_XXXVII": "A New Lap Won't Load a New Setup · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Flag That Never Waves Marshals Nothing · Prove the Gate",
114
+ "LEX_XXXIX": "The Answer on the Last Lap · Shape the Answer",
115
+ "LEX_XL": "Every Setup Change Gets a Number · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the briefing a coding driver reads when it joins this repository.",
114
117
  "INTRO_AGENTS": "Get the right engineer on focused work instead of running the whole team yourself; each has one job and reports exactly what it did.",
115
118
  "INTRO_SKILLS": "A Skill is a setup you've practised for a recurring task. Run the matching setup before you improvise.",
@@ -110,6 +110,9 @@
110
110
  "LEX_XXXV": "Cover the Casting, Then Confirm · Cover and Verify",
111
111
  "LEX_XXXVI": "The Ward Is Set Beyond the Caster · External Gate",
112
112
  "LEX_XXXVII": "Recasting Won't Reread the Grimoire · Restart Is Not Reload",
113
+ "LEX_XXXVIII": "A Ward Never Struck Is Only a Wish · Prove the Gate",
114
+ "LEX_XXXIX": "The Incantation Ends on the Word That Works · Shape the Answer",
115
+ "LEX_XL": "Every Rune Keeps Its Name · Codes That Persist",
113
116
  "INTRO_DOC": "This file is the first page an arcane familiar reads on entering this repository.",
114
117
  "INTRO_AGENTS": "Summon the right familiar for focused work rather than casting the whole rite in one circle; each has one purpose and returns a clear account of what it wrought.",
115
118
  "INTRO_SKILLS": "A Skill is a proven spell for a recurring task. Speak the matching spell before improvising.",
@@ -1 +1 @@
1
- import{a as l,j as e,I as a}from"./index-BnTQUSUz.js";import{r as n}from"./react-DOpiTn5v.js";const c="https://github.com/Arylmera/Geneseed",h="https://github.com/Arylmera";function u(){const[r,i]=n.useState(c),[t,o]=n.useState(!0);return n.useEffect(()=>{l.docsPage("about").then(s=>{s!=null&&s.repo&&(i(s.repo),o(!!s.repo_is_github))}).catch(()=>{})},[]),e.jsxs("div",{className:"narrow",children:[e.jsx("div",{className:"head-row mb-18",children:e.jsxs("div",{children:[e.jsx("h1",{className:"h",children:"About"}),e.jsx("p",{className:"sub",children:"The project, where the source lives, and a word from the person who grew it."})]})}),e.jsxs("div",{className:"card pad-lg mb-16",children:[e.jsx("div",{className:"card-head",children:e.jsx("h3",{children:"Project"})}),e.jsx("p",{className:"sub mb-16",children:"Geneseed is a portable, theme-able harness you implant once and use everywhere to grow a disciplined AI coding agent. One canonical source, many voices, MIT-licensed."}),e.jsxs("div",{className:"row wrap gap-10",children:[e.jsxs("a",{className:"btn ghost",href:r,target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"github"}),t?"Source on GitHub":"Source repo"]}),t&&e.jsxs(e.Fragment,{children:[e.jsxs("a",{className:"btn ghost",href:r+"/issues",target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"external"}),"Issues"]}),e.jsxs("a",{className:"btn ghost",href:r+"/blob/main/LICENSE",target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"external"}),"MIT License"]})]})]})]}),e.jsxs("div",{className:"card pad-lg",children:[e.jsx("div",{className:"card-head",children:e.jsx("h3",{children:"Creator"})}),e.jsxs("p",{className:"sub mb-16",children:["Geneseed is built by ",e.jsx("strong",{children:"Guillaume Lemer"}),". It started as a personal, Obsidian-vault-grown agent operating system and was distilled into the repo you are running. Implant it once, and a disciplined agent grows around the same inherited rules, skills, and memory in every repo it follows you into."]}),e.jsx("p",{className:"sub mb-16",children:"If you find this useful, a star on the repo or an issue with feedback is the best way to help shape what comes next."}),e.jsx("div",{className:"row wrap gap-10",children:e.jsxs("a",{className:"btn ghost",href:h,target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"github"}),"@Arylmera on GitHub"]})})]})]})}export{u as default};
1
+ import{a as l,j as e,I as a}from"./index-D5VY3TY_.js";import{r as n}from"./react-DOpiTn5v.js";const c="https://github.com/Arylmera/Geneseed",h="https://github.com/Arylmera";function u(){const[r,i]=n.useState(c),[t,o]=n.useState(!0);return n.useEffect(()=>{l.docsPage("about").then(s=>{s!=null&&s.repo&&(i(s.repo),o(!!s.repo_is_github))}).catch(()=>{})},[]),e.jsxs("div",{className:"narrow",children:[e.jsx("div",{className:"head-row mb-18",children:e.jsxs("div",{children:[e.jsx("h1",{className:"h",children:"About"}),e.jsx("p",{className:"sub",children:"The project, where the source lives, and a word from the person who grew it."})]})}),e.jsxs("div",{className:"card pad-lg mb-16",children:[e.jsx("div",{className:"card-head",children:e.jsx("h3",{children:"Project"})}),e.jsx("p",{className:"sub mb-16",children:"Geneseed is a portable, theme-able harness you implant once and use everywhere to grow a disciplined AI coding agent. One canonical source, many voices, MIT-licensed."}),e.jsxs("div",{className:"row wrap gap-10",children:[e.jsxs("a",{className:"btn ghost",href:r,target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"github"}),t?"Source on GitHub":"Source repo"]}),t&&e.jsxs(e.Fragment,{children:[e.jsxs("a",{className:"btn ghost",href:r+"/issues",target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"external"}),"Issues"]}),e.jsxs("a",{className:"btn ghost",href:r+"/blob/main/LICENSE",target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"external"}),"MIT License"]})]})]})]}),e.jsxs("div",{className:"card pad-lg",children:[e.jsx("div",{className:"card-head",children:e.jsx("h3",{children:"Creator"})}),e.jsxs("p",{className:"sub mb-16",children:["Geneseed is built by ",e.jsx("strong",{children:"Guillaume Lemer"}),". It started as a personal, Obsidian-vault-grown agent operating system and was distilled into the repo you are running. Implant it once, and a disciplined agent grows around the same inherited rules, skills, and memory in every repo it follows you into."]}),e.jsx("p",{className:"sub mb-16",children:"If you find this useful, a star on the repo or an issue with feedback is the best way to help shape what comes next."}),e.jsx("div",{className:"row wrap gap-10",children:e.jsxs("a",{className:"btn ghost",href:h,target:"_blank",rel:"noreferrer",children:[e.jsx(a,{name:"github"}),"@Arylmera on GitHub"]})})]})]})}export{u as default};