geneseed 2.0.0 → 3.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.
- package/CHANGELOG.md +217 -5
- package/DESIGN.md +149 -1326
- package/QUICKSTART.md +1 -1
- package/README.md +30 -22
- package/SETUP.md +98 -24
- package/SHIPPED.md +6 -5
- package/adapters/bob/README.md +3 -3
- package/adapters/claude-code/README.md +6 -4
- package/adapters/claude-code/skills/geneseed/SKILL.md +10 -6
- package/adapters/copilot/README.md +5 -4
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +11 -6
- package/adapters/opencode/HOW-OPENCODE-LOADS.md +1 -1
- package/adapters/opencode/README.md +15 -9
- package/adapters/opencode/agents/orchestrator.md +1 -1
- package/adapters/opencode/opencode.json +0 -9
- package/adapters/opencode/plugins/geneseed-context.js +7 -7
- package/adapters/opencode/plugins/geneseed-guard.js +6 -6
- package/adapters/opencode/workflows/dispatch.js +1 -1
- package/bin/geneseed.mjs +74 -8
- package/docs/declined.md +53 -2
- package/docs/design-history.md +1290 -0
- package/docs/extending.md +672 -0
- package/docs/limits.md +141 -82
- package/docs/token-footprint.md +15 -4
- package/docs/web/agents.md +1 -1
- package/docs/web/authoring.md +1 -1
- package/docs/web/claude-hooks.md +2 -2
- package/docs/web/collaboration.md +3 -3
- package/docs/web/first-session.md +2 -2
- package/docs/web/footprint.md +14 -7
- package/docs/web/foreman-mode.md +2 -2
- package/docs/web/harnesses.md +5 -3
- package/docs/web/install-quick.md +1 -1
- package/docs/web/model.md +3 -3
- package/docs/web/plugin-context.md +2 -2
- package/docs/web/plugin-guard.md +3 -3
- package/docs/web/plugin-ponytail.md +1 -1
- package/docs/web/rules.md +23 -3
- package/docs/web/setup-choices.md +20 -7
- package/docs/web/themes-author.md +13 -1
- package/docs/web-ui.md +36 -12
- package/geneseed +0 -0
- package/harness.config.json +1 -1
- package/js/catalog.mjs +29 -4
- package/js/checkout.mjs +24 -1
- package/js/cli-table.json +9 -7
- package/js/diff.mjs +8 -3
- package/js/doctor.mjs +374 -26
- package/js/emit.mjs +48 -11
- package/js/generate.mjs +39 -5
- package/js/hooks.mjs +8 -7
- package/js/installs.mjs +101 -4
- package/js/inventory.mjs +150 -20
- package/js/lib/fs.mjs +6 -2
- package/js/mcp.mjs +3 -15
- package/js/migrate.mjs +10 -4
- package/js/render.mjs +116 -11
- package/js/settings.mjs +297 -28
- package/js/setup.mjs +95 -5
- package/js/status.mjs +45 -4
- package/js/themes.mjs +1 -1
- package/js/tui.mjs +46 -3
- package/js/uninstall.mjs +30 -11
- package/js/web/actions.mjs +76 -9
- package/js/web/api.mjs +95 -16
- package/js/web/docs.mjs +33 -5
- package/js/web/jobs.mjs +12 -3
- package/js/web/server.mjs +59 -2
- package/package.json +1 -1
- package/src/AGENT.md.tmpl +75 -65
- package/src/agents/_template.md +4 -4
- package/src/agents/advocate.md +4 -4
- package/src/agents/architect.md +2 -2
- package/src/agents/developer.md +5 -5
- package/src/agents/docs.md +2 -2
- package/src/agents/empiricist.md +3 -3
- package/src/agents/explorer.md +3 -3
- package/src/agents/framer.md +3 -3
- package/src/agents/historian.md +3 -3
- package/src/agents/operator.md +3 -3
- package/src/agents/pragmatist.md +3 -3
- package/src/agents/reviewer.md +2 -2
- package/src/agents/security.md +2 -2
- package/src/agents/skeptic.md +4 -4
- package/src/agents/steward.md +3 -3
- package/src/agents/tester.md +2 -2
- package/src/agents/user-advocate.md +3 -3
- package/src/agents/visionary.md +3 -3
- package/src/doctrines/README.md +41 -0
- package/src/doctrines/craft.md +51 -0
- package/src/doctrines/ops.md +61 -0
- package/src/doctrines/process.md +87 -0
- package/src/doctrines/rigor.md +52 -0
- package/src/laws/universal.md +17 -301
- package/src/memory/README.md +2 -2
- package/src/modes/README.md +2 -2
- package/src/modes/foreman.md +1 -1
- package/src/notebook/README.md +2 -2
- package/src/ontology/universal.md +89 -0
- package/src/postures/README.md +3 -3
- package/src/postures/artisan.md +3 -3
- package/src/postures/assistant.md +1 -1
- package/src/postures/expert.md +1 -1
- package/src/postures/mentor.md +1 -1
- package/src/postures/peer.md +2 -2
- package/src/skills/_template.md +2 -2
- package/src/skills/brainstorm.md +2 -2
- package/src/skills/clarify.md +2 -2
- package/src/skills/codebase-design.md +1 -1
- package/src/skills/commit.md +5 -5
- package/src/skills/council.md +4 -4
- package/src/skills/crash-course.md +1 -1
- package/src/skills/daydream/SKILL.md +1 -1
- package/src/skills/debug.md +2 -2
- package/src/skills/decode.md +1 -1
- package/src/skills/develop.md +6 -6
- package/src/skills/document-project.md +4 -4
- package/src/skills/domain-modeling.md +1 -1
- package/src/skills/drill.md +1 -1
- package/src/skills/feynman.md +1 -1
- package/src/skills/forge-mcp.md +3 -3
- package/src/skills/fresh-eyes.md +4 -4
- package/src/skills/frontend-design.md +1 -1
- package/src/skills/gap-detector.md +1 -1
- package/src/skills/geneseed-code-review.md +1 -1
- package/src/skills/geneseed.md +4 -4
- package/src/skills/git-archaeology.md +1 -1
- package/src/skills/git-rescue.md +3 -3
- package/src/skills/handoff.md +4 -4
- package/src/skills/herdr.md +1 -1
- package/src/skills/ingest.md +3 -3
- package/src/skills/learning-path.md +1 -1
- package/src/skills/migrate.md +5 -5
- package/src/skills/opencode-theme.md +1 -1
- package/src/skills/parallel-agents.md +2 -2
- package/src/skills/pipeline.md +2 -2
- package/src/skills/plan.md +2 -2
- package/src/skills/ponytail.md +1 -1
- package/src/skills/profile.md +1 -1
- package/src/skills/prose.md +2 -2
- package/src/skills/prototype.md +2 -2
- package/src/skills/refactor.md +2 -2
- package/src/skills/release.md +4 -4
- package/src/skills/repo-map.md +5 -5
- package/src/skills/research.md +6 -6
- package/src/skills/review-response.md +3 -3
- package/src/skills/roast-me.md +1 -1
- package/src/skills/rule.md +5 -5
- package/src/skills/ship.md +3 -3
- package/src/skills/tdd.md +2 -2
- package/src/skills/tickets.md +1 -1
- package/src/skills/wayfinder.md +1 -1
- package/src/skills/wiki.md +4 -4
- package/src/skills/workflow.md +2 -2
- package/themes/README.md +17 -7
- package/themes/_TEMPLATE.json +37 -33
- package/themes/biker.json +37 -33
- package/themes/commentator.json +37 -33
- package/themes/cyberpunk.json +37 -33
- package/themes/gamer.json +37 -33
- package/themes/imperial.json +37 -33
- package/themes/joker.json +37 -33
- package/themes/marvin.json +37 -33
- package/themes/mean.json +37 -33
- package/themes/military.json +37 -33
- package/themes/neutral.json +37 -33
- package/themes/pirate.json +37 -33
- package/themes/sports.json +37 -33
- package/themes/verstappen.json +37 -33
- package/themes/wizard.json +37 -33
- package/web/dist/assets/{About-DX1-VTBP.js → About-C0Mk0igT.js} +1 -1
- package/web/dist/assets/{Activity-Dp9NaXOn.js → Activity-C4aJJVXz.js} +1 -1
- package/web/dist/assets/{ActivityDetail-HpOBtrXS.js → ActivityDetail-DaQ85za0.js} +1 -1
- package/web/dist/assets/{Diff-Ct4G3Se8.js → Diff-B3tlO2Yj.js} +1 -1
- package/web/dist/assets/{Doctor-DGjkZFuA.js → Doctor-BNC-5Dmo.js} +1 -1
- package/web/dist/assets/{Graph-b8o-S6kv.js → Graph-us0T6ZcV.js} +1 -1
- package/web/dist/assets/Harnesses-DAMgyfM_.js +1 -0
- package/web/dist/assets/Laws-rOxegJ75.js +1 -0
- package/web/dist/assets/{Library-BEZscyaQ.js → Library-KmwrHA1J.js} +1 -1
- package/web/dist/assets/{Markdown-k_LSLBGq.js → Markdown-0eQOVGSF.js} +1 -1
- package/web/dist/assets/{Profile-DydukTxP.js → Profile-ZU7WV6Xl.js} +1 -1
- package/web/dist/assets/{Rules-CIRG4Ldt.js → Rules-CtDoPhhf.js} +1 -1
- package/web/dist/assets/{Skills-Buoi1IBr.js → Skills-BhoOxdkD.js} +1 -1
- package/web/dist/assets/{StatusBadge-CE_AJttl.js → StatusBadge-CGvYVCwa.js} +1 -1
- package/web/dist/assets/{Themes-CI4o3gJC.js → Themes-Dr7OOkD7.js} +1 -1
- package/web/dist/assets/{activity-BCW7e6Cy.js → activity-B8aq3qtl.js} +1 -1
- package/web/dist/assets/index-2wqPDxuN.css +1 -0
- package/web/dist/assets/{index-B1PIx7RM.js → index-AhMzkdbI.js} +1 -1
- package/web/dist/assets/index-CRftEtBX.js +12 -0
- package/web/dist/assets/{index-Ck2iCdaC.js → index-D0lPN_qW.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +282 -88
- package/web/dist/assets/Harnesses-gr3-aHBF.js +0 -1
- package/web/dist/assets/Laws-DTwF5tTC.js +0 -1
- package/web/dist/assets/index-BnTQUSUz.js +0 -12
- package/web/dist/assets/index-DUCsDv7z.css +0 -1
|
@@ -181,7 +181,7 @@ docs with an `eager`/`lazy` split. The
|
|
|
181
181
|
On `session.created` it **auto-discovers the current repo's docs by convention**
|
|
182
182
|
and injects the `eager` ones into the session via a no-reply prompt
|
|
183
183
|
(`session.prompt({ noReply: true })`) — so they're in context before your first
|
|
184
|
-
turn, enforcing
|
|
184
|
+
turn, enforcing the project-context load by injection, not agent discipline. This is what lets
|
|
185
185
|
the harness live entirely in the global config dir with zero per-repo files.
|
|
186
186
|
|
|
187
187
|
- **Eager** (injected in full, budget-capped): root `AGENTS.md`/`AGENT.md`/
|
|
@@ -200,11 +200,11 @@ the harness live entirely in the global config dir with zero per-repo files.
|
|
|
200
200
|
hard guarantee is still a single install: OpenCode dedups plugins by npm
|
|
201
201
|
name+version only, so two local copies both load.)
|
|
202
202
|
- **Survives compaction:** on the experimental `session.compacting` hook it re-pushes
|
|
203
|
-
the eager docs into the compaction context, so the project context
|
|
203
|
+
the eager docs into the compaction context, so the project context
|
|
204
204
|
persists when a long session is summarised. The `AGENT.md` rules already survive —
|
|
205
205
|
they load via `instructions`, not the conversation — so only the injected project
|
|
206
206
|
context needs re-pushing.
|
|
207
|
-
- **Machine wiki (AGENT.md §
|
|
207
|
+
- **Machine wiki (AGENT.md §8):** the same block carries a `MACHINE WIKI` segment
|
|
208
208
|
for the user's own knowledge base(s) — typically an Obsidian vault — declared in
|
|
209
209
|
`wiki.jsonc` (`$GENESEED_WIKI` → `$GENESEED_HARNESS/wiki.jsonc` → beside the
|
|
210
210
|
install). Per wiki: eager entries inject in full, lazy entries list, and the
|
|
@@ -381,7 +381,7 @@ behaviour** — nothing changes the machine's current agent/model unless you opt
|
|
|
381
381
|
- **Runtime guard plugin** (`geneseed-guard.js`, installed with the others). Enforces
|
|
382
382
|
the safety Laws at the tool boundary: **blocks** writes to private-key/credential
|
|
383
383
|
files (Law I), catastrophic shell like `rm -rf /` (Law IV), and any mutation under
|
|
384
|
-
a declared wiki's `protected` folders (AGENT.md §
|
|
384
|
+
a declared wiki's `protected` folders (AGENT.md §8, from `wiki.jsonc`); **warns** on
|
|
385
385
|
`.env` writes and force-push. `GENESEED_GUARD=off` disables it, `=warn` downgrades
|
|
386
386
|
blocks to warnings.
|
|
387
387
|
- **Invisible context injection** (the default). The context plugin delivers via
|
|
@@ -390,10 +390,15 @@ behaviour** — nothing changes the machine's current agent/model unless you opt
|
|
|
390
390
|
auto-falls back to the visible `session.created` message. `GENESEED_CONTEXT_VISIBLE=1`
|
|
391
391
|
forces the visible block (legacy `GENESEED_CONTEXT_TRANSFORM=0/off` does the same).
|
|
392
392
|
- **Default permissions.** A fresh `opencode.json` gets a minimal policy that **asks**
|
|
393
|
-
before `rm -rf *` and **
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
before `rm -rf *` and a force-push — those are **Rule IV**'s territory and are in every
|
|
394
|
+
build — and, **on an install that built the `process` pack in**, before every
|
|
395
|
+
`git commit` and `git push` (the host-level backstop for **Doctrine process 5**: the
|
|
396
|
+
agent never records or shares code unprompted, on any branch). Build with
|
|
397
|
+
`--doctrines` leaving `process` out and those two entries are simply not written.
|
|
398
|
+
The whole block is added only when you have no `permission` key; an existing policy is
|
|
399
|
+
reconciled key by key — Geneseed adds back what a returning pack wants and **never
|
|
400
|
+
removes** an entry, because it cannot tell its own from one you typed, so a pack-off
|
|
401
|
+
rebuild leaves a `git commit*` you already have in place and reports it instead. Routine local work
|
|
397
402
|
(edits, builds, tests) is unaffected; to allow frictionless commits/pushes, set
|
|
398
403
|
`"git commit*"` / `"git push*"` to `"allow"` in your own `permission.bash` map. (Note:
|
|
399
404
|
OpenCode lets a user pick "always allow" for the session, which makes the gate
|
|
@@ -521,7 +526,8 @@ install `jsonc-parser` yourself — you lose OCX's dependency management and
|
|
|
521
526
|
auto-updates, and you own the upgrade-by-re-copy. Prefer the OCX path.
|
|
522
527
|
|
|
523
528
|
**Consent note.** `worktree_delete` auto-commits before removal. The harness's
|
|
524
|
-
consent-before-commit
|
|
529
|
+
consent-before-commit rule (**Doctrine process 5**, when the `process` pack is built in)
|
|
530
|
+
is enforced for *shell* `git commit` via `opencode.json`
|
|
525
531
|
permissions, but this plugin commits through its own tool path — so review what it
|
|
526
532
|
will commit before invoking delete, or keep the worktree and commit yourself.
|
|
527
533
|
|
|
@@ -21,5 +21,5 @@ yourself: decompose into independent subtasks, route each to the single owning s
|
|
|
21
21
|
above, and converge the results yourself. Prefer the saved `dispatch` workflow when the
|
|
22
22
|
`workflow` tool is available; otherwise follow the parallel-agents Skill. Every handoff
|
|
23
23
|
carries an envelope — the subtask goal, the inputs the agent needs, its output contract —
|
|
24
|
-
and the constraints travel with it: subagents never commit or push (
|
|
24
|
+
and the constraints travel with it: subagents never commit or push (Doctrine process 5 stays
|
|
25
25
|
you), and an unfulfilled contract comes back as a stated gap, never invented content.
|
|
@@ -26,15 +26,6 @@
|
|
|
26
26
|
},
|
|
27
27
|
"enabled": false
|
|
28
28
|
},
|
|
29
|
-
"gitlab-2": {
|
|
30
|
-
"type": "local",
|
|
31
|
-
"command": ["npx", "-y", "@zereight/mcp-gitlab"],
|
|
32
|
-
"environment": {
|
|
33
|
-
"GITLAB_PERSONAL_ACCESS_TOKEN": "",
|
|
34
|
-
"GITLAB_API_URL": "https://gitlab.example.com/api/v4"
|
|
35
|
-
},
|
|
36
|
-
"enabled": false
|
|
37
|
-
},
|
|
38
29
|
"filesystem": {
|
|
39
30
|
"type": "local",
|
|
40
31
|
"command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Geneseed — OpenCode project-context plugin (v2, convention-glob).
|
|
2
2
|
//
|
|
3
|
-
// Enforces
|
|
3
|
+
// Enforces the project-context rule ("Load the Project Context") by INJECTION, not instruction:
|
|
4
4
|
// on `session.created` it puts the repo's documentation in context before your
|
|
5
5
|
// first turn, rather than trusting the agent to read it. v2 removes the need for a
|
|
6
6
|
// committed `context.json` — it AUTO-DISCOVERS the current repo's docs by
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// $GENESEED_MODEL), so the agent reasons within its real limits. The model line
|
|
27
27
|
// is added per request, outside the cached block; unknown -> omitted.
|
|
28
28
|
//
|
|
29
|
-
// MACHINE WIKI (AGENT.md §
|
|
29
|
+
// MACHINE WIKI (AGENT.md §8): the same block also carries the user's own knowledge
|
|
30
30
|
// base(s) — typically an Obsidian vault — declared once per machine in `wiki.jsonc`
|
|
31
31
|
// ($GENESEED_WIKI -> $GENESEED_HARNESS/wiki.jsonc -> beside this plugin's install).
|
|
32
32
|
// Each wiki's eager entries inject in full and lazy entries list, drawing on the
|
|
@@ -484,7 +484,7 @@ async function resolveSource(root) {
|
|
|
484
484
|
|
|
485
485
|
// ---- machine wiki (wiki.jsonc) --------------------------------------------------
|
|
486
486
|
// The user's own knowledge base(s) — typically an Obsidian vault — declared once per
|
|
487
|
-
// machine, not per repo (AGENT.md §
|
|
487
|
+
// machine, not per repo (AGENT.md §8). Same injection mechanics as project context,
|
|
488
488
|
// different scope. Resolution (first match wins, mirroring the learn plugin):
|
|
489
489
|
// 1. $GENESEED_WIKI explicit manifest path
|
|
490
490
|
// 2. $GENESEED_HARNESS/wiki.jsonc pinned install dir
|
|
@@ -675,8 +675,8 @@ async function buildBlock(sets, wikis = [], commands = []) {
|
|
|
675
675
|
|
|
676
676
|
if (!proj.length && !wik.length) return null
|
|
677
677
|
const out = [MARKER]
|
|
678
|
-
if (proj.length) out.push("=== PROJECT CONTEXT — binding for this repo
|
|
679
|
-
if (wik.length) out.push("=== MACHINE WIKI — the user's knowledge base, binding per AGENT.md §
|
|
678
|
+
if (proj.length) out.push("=== PROJECT CONTEXT — binding for this repo ===", "", ...proj)
|
|
679
|
+
if (wik.length) out.push("=== MACHINE WIKI — the user's knowledge base, binding per AGENT.md §8 ===", "", ...wik)
|
|
680
680
|
return { text: out.join("\n"), injected: state.injected, lazy: state.lazy, kb: kb(state.spent) }
|
|
681
681
|
}
|
|
682
682
|
|
|
@@ -820,8 +820,8 @@ export const GeneseedContext = async (ctx) => {
|
|
|
820
820
|
// Survive compaction. The visible injection above is a conversation message, so
|
|
821
821
|
// OpenCode summarises it away when a long session compacts (the AGENT.md rules
|
|
822
822
|
// persist — they load via opencode.json `instructions`, not the conversation).
|
|
823
|
-
// Re-push the eager docs into the compaction context so the project context
|
|
824
|
-
//
|
|
823
|
+
// Re-push the eager docs into the compaction context so the project context
|
|
824
|
+
// outlives the summary. Only needed when delivery is visible
|
|
825
825
|
// (forced or fallback); the transform re-sends per request. Experimental
|
|
826
826
|
// OpenCode hook; if it is absent in a build this key is simply never called.
|
|
827
827
|
"experimental.session.compacting": async (_input, output) => {
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// "enforce by injection, don't just instruct" stance as the context plugin:
|
|
5
5
|
// - Law I (Sealed Secrets): block writes to private-key / credential files.
|
|
6
6
|
// - Law IV (Deletion Is Deliberate): block catastrophic shell commands.
|
|
7
|
-
// -
|
|
7
|
+
// - Doctrine process 1 (rule vs memory): speed-bump the first write to user-rules.md
|
|
8
8
|
// memory file — that choice belongs to the user, via the rule skill.
|
|
9
|
-
// - Wiki (AGENT.md §
|
|
9
|
+
// - Wiki (AGENT.md §8): block mutations under a declared wiki's `protected`
|
|
10
10
|
// folders — the user's knowledge base sets its own no-go zones in wiki.jsonc.
|
|
11
11
|
// High-confidence patterns only, so legitimate work is never caught. Borderline cases
|
|
12
12
|
// (.env edits, force-push) are WARNED, not blocked.
|
|
@@ -114,7 +114,7 @@ const WRITE_TOOLS = ["write", "edit", "patch", "create", "save", "insert", "repl
|
|
|
114
114
|
const SHELL_TOOLS = ["bash", "shell", "exec", "command", "terminal", "run"]
|
|
115
115
|
const hasAny = (name, parts) => parts.some((s) => name.includes(s))
|
|
116
116
|
|
|
117
|
-
// ---- protected wiki folders (AGENT.md §
|
|
117
|
+
// ---- protected wiki folders (AGENT.md §8) --------------------------------------
|
|
118
118
|
// wiki.jsonc (the machine-level knowledge-base manifest) may list `protected` folders
|
|
119
119
|
// per wiki. Mutating anything under one is denied. Same resolution chain as the
|
|
120
120
|
// context plugin: $GENESEED_WIKI -> $GENESEED_HARNESS/wiki.jsonc -> beside the install.
|
|
@@ -207,7 +207,7 @@ async function protectedPrefixes() {
|
|
|
207
207
|
return prefixes
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// ---- the rule / memory stores (
|
|
210
|
+
// ---- the rule / memory stores (Doctrine process 1) -----------------------------
|
|
211
211
|
// Whether something the user wants kept is a standing rule or a durable fact is THEIR
|
|
212
212
|
// call, settled through the rule skill. `tool.execute.before` can only allow or throw —
|
|
213
213
|
// it has no "ask the user" tier like the Claude/Bob rule-gate hook —
|
|
@@ -257,7 +257,7 @@ export const GeneseedGuard = async () => {
|
|
|
257
257
|
if (store && !RULE_STORE_BUMPED.has(p)) {
|
|
258
258
|
RULE_STORE_BUMPED.add(p)
|
|
259
259
|
deny(`writing to ${store} — a standing rule, or a fact to remember? That ` +
|
|
260
|
-
`choice is the user's (
|
|
260
|
+
`choice is the user's (Doctrine process 1). Settle it through the rule skill, ` +
|
|
261
261
|
`then re-issue this write`)
|
|
262
262
|
return
|
|
263
263
|
}
|
|
@@ -268,7 +268,7 @@ export const GeneseedGuard = async () => {
|
|
|
268
268
|
const abs = (path.isAbsolute(p) ? p : path.resolve(p)).replace(/\\/g, "/").toLowerCase()
|
|
269
269
|
const hit = (await protectedPrefixes()).find(
|
|
270
270
|
(x) => abs.startsWith(x.prefix) || abs === x.prefix.slice(0, -1))
|
|
271
|
-
if (hit) { deny(`mutation in protected wiki folder — ${hit.label} (AGENT.md §
|
|
271
|
+
if (hit) { deny(`mutation in protected wiki folder — ${hit.label} (AGENT.md §8)`); return }
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
// NOT else-if: a compound tool name (e.g. exec_and_save) can match both
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The model-driven fallback is the parallel-agents skill; this runs the same
|
|
5
5
|
// shape on rails: the script, not the model, enforces routing and the handoff
|
|
6
|
-
// envelope (context in, output contract back, no commits —
|
|
6
|
+
// envelope (context in, output contract back, no commits — Doctrine process 5 stays with
|
|
7
7
|
// the caller).
|
|
8
8
|
//
|
|
9
9
|
// args: { goal: string, context?: string, agents?: string[] }
|
package/bin/geneseed.mjs
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
// P5d moved these out of this file for the reason P5c moved the host resolvers: `harness
|
|
53
53
|
// status` renders to count, so `bin/geneseed-cli.mjs` needs the same checkout paths and the
|
|
54
54
|
// same cfg. golden.py's 259 cells all build one, which is what made the move safe.
|
|
55
|
-
import { ROOT, CONFIG, THEMES, discoverNames, makeCfg } from '../js/checkout.mjs';
|
|
55
|
+
import { ROOT, CONFIG, THEMES, discoverNames, makeCfg, PACK_ORDER } from '../js/checkout.mjs';
|
|
56
56
|
|
|
57
57
|
// P5f moved these, for the same arithmetic a third time: `harness rebuild-all` reads the
|
|
58
58
|
// registry to find every install it must re-emit, and a CLI verb may not reach into a driver
|
|
@@ -122,7 +122,9 @@ export function resolveOut(raw) {
|
|
|
122
122
|
* byte-for-byte by `tests/golden.py` (:316).
|
|
123
123
|
*/
|
|
124
124
|
function configDefaults() {
|
|
125
|
-
const d = {
|
|
125
|
+
const d = {
|
|
126
|
+
theme: 'neutral', posture: 'peer', mode: 'direct', doctrines: [...PACK_ORDER],
|
|
127
|
+
};
|
|
126
128
|
if (!existsSync(CONFIG)) return d;
|
|
127
129
|
try {
|
|
128
130
|
const data = JSON.parse(readFileSync(CONFIG, 'utf8'));
|
|
@@ -130,6 +132,20 @@ function configDefaults() {
|
|
|
130
132
|
if (data.theme !== undefined) d.theme = data.theme;
|
|
131
133
|
if (data.posture !== undefined) d.posture = data.posture;
|
|
132
134
|
if (data.mode !== undefined) d.mode = data.mode;
|
|
135
|
+
// `doctrines` is the only config key that is a LIST, so it is the only one that can be
|
|
136
|
+
// the right type and still nonsense. A bad value warns and falls back to all packs
|
|
137
|
+
// rather than dying: this file is hand-editable, and a typo here must not brick every
|
|
138
|
+
// rebuild of an install whose AGENT.md is otherwise fine. `[]` is a legitimate value
|
|
139
|
+
// (no packs) and must survive the check — hence an explicit Array test, not truthiness.
|
|
140
|
+
if (data.doctrines !== undefined) {
|
|
141
|
+
const known = discoverNames('doctrines', PACK_ORDER[0]);
|
|
142
|
+
if (Array.isArray(data.doctrines) && data.doctrines.every((x) => known.includes(x))) {
|
|
143
|
+
d.doctrines = PACK_ORDER.filter((p) => data.doctrines.includes(p));
|
|
144
|
+
} else {
|
|
145
|
+
process.stderr.write(`[geneseed] WARN: ${path.basename(CONFIG)} "doctrines" is not a `
|
|
146
|
+
+ 'list of known pack names — using all packs.\n');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
133
149
|
}
|
|
134
150
|
} catch {
|
|
135
151
|
process.stderr.write(`[geneseed] WARN: ${path.basename(CONFIG)} is unreadable — `
|
|
@@ -146,6 +162,7 @@ function configDefaults() {
|
|
|
146
162
|
*/
|
|
147
163
|
const VALUED = {
|
|
148
164
|
'--theme': 'theme', '--posture': 'posture', '--mode': 'mode',
|
|
165
|
+
'--doctrines': 'doctrines',
|
|
149
166
|
'--out': 'out', '--target': 'out', '--emit': 'emit',
|
|
150
167
|
'--footprint': 'footprint', '--root': 'root',
|
|
151
168
|
};
|
|
@@ -160,6 +177,13 @@ const FLAGS = {
|
|
|
160
177
|
* A function and not a constant because two of the four read the checkout: `discoverNames`
|
|
161
178
|
* scans `postures/` and `modes/`, so the answer depends on the source tree this driver is
|
|
162
179
|
* standing in and cannot be frozen at import.
|
|
180
|
+
*
|
|
181
|
+
* `--doctrines` is DELIBERATELY not a fifth row. Every consumer of this table treats an
|
|
182
|
+
* entry as one value drawn from a closed list — `choice` refuses anything else, and
|
|
183
|
+
* `usage`'s metavar prints the list as the whole legal surface. `--doctrines` takes a comma
|
|
184
|
+
* LIST plus the sentinel `none`, so a row here would have refused `craft,rigor` outright and
|
|
185
|
+
* advertised a metavar that lies. It is validated by `parseDoctrines` instead and prints the
|
|
186
|
+
* plain `DOCTRINES` metavar, exactly as `--theme` (also open-ended) already does.
|
|
163
187
|
*/
|
|
164
188
|
function choicesFor() {
|
|
165
189
|
return {
|
|
@@ -170,6 +194,33 @@ function choicesFor() {
|
|
|
170
194
|
};
|
|
171
195
|
}
|
|
172
196
|
|
|
197
|
+
/**
|
|
198
|
+
* `--doctrines craft,rigor` / `--doctrines none` -> the cfg's pack array.
|
|
199
|
+
*
|
|
200
|
+
* Normalised into `PACK_ORDER` rather than kept in the order typed, which also dedupes: the
|
|
201
|
+
* rendered `Active packs:` line is a MARKER a later reader parses back out of a deployed
|
|
202
|
+
* carrier, and a marker whose contents depend on argument order is one that compares unequal
|
|
203
|
+
* to itself. Validation is against DISCOVERY, not `PACK_ORDER`, so the refusal names the
|
|
204
|
+
* packs the checkout actually has; a discovered pack missing from `PACK_ORDER` is a
|
|
205
|
+
* different fault and `js/render.mjs` refuses the whole build for it.
|
|
206
|
+
*/
|
|
207
|
+
function parseDoctrines(value) {
|
|
208
|
+
const s = value.trim();
|
|
209
|
+
const known = discoverNames('doctrines', PACK_ORDER[0]);
|
|
210
|
+
if (s === 'none') return [];
|
|
211
|
+
const names = s.split(',').map((x) => x.trim()).filter(Boolean);
|
|
212
|
+
if (!names.length) {
|
|
213
|
+
die(2, "argument --doctrines: expected a comma-separated list of packs, or 'none'");
|
|
214
|
+
}
|
|
215
|
+
for (const n of names) {
|
|
216
|
+
if (!known.includes(n)) {
|
|
217
|
+
die(2, `argument --doctrines: invalid choice: '${n}' (choose from `
|
|
218
|
+
+ `${known.map((c) => `'${c}'`).join(', ')}, or 'none')`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return PACK_ORDER.filter((p) => names.includes(p));
|
|
222
|
+
}
|
|
223
|
+
|
|
173
224
|
/**
|
|
174
225
|
* `-h/--help` — the flag argparse handed the reference for free and this hand-rolled parser
|
|
175
226
|
* did not, so the two entry points disagreed: the reference printed usage and exited 0 while
|
|
@@ -212,6 +263,7 @@ function usage() {
|
|
|
212
263
|
function parseArgs(argv, defaults) {
|
|
213
264
|
const args = {
|
|
214
265
|
theme: defaults.theme, posture: defaults.posture, mode: defaults.mode,
|
|
266
|
+
doctrines: defaults.doctrines,
|
|
215
267
|
out: 'Harness', emit: 'files', footprint: 'lean', root: null,
|
|
216
268
|
syncThemes: false, validateOnly: false, verbose: false,
|
|
217
269
|
};
|
|
@@ -241,6 +293,9 @@ function parseArgs(argv, defaults) {
|
|
|
241
293
|
}
|
|
242
294
|
const choices = choicesFor();
|
|
243
295
|
for (const flag of Object.keys(choices)) choice(flag, args[VALUED[flag]], choices[flag]);
|
|
296
|
+
// Only when the flag was actually PASSED is this a string; left at its default it is
|
|
297
|
+
// already the array `configDefaults` built, and re-parsing an array would stringify it.
|
|
298
|
+
if (typeof args.doctrines === 'string') args.doctrines = parseDoctrines(args.doctrines);
|
|
244
299
|
return args;
|
|
245
300
|
}
|
|
246
301
|
|
|
@@ -387,7 +442,7 @@ function warnBobGlobalOverProject() {
|
|
|
387
442
|
+ "(doubled context) unless Bob's workspace rules truly shadow the global "
|
|
388
443
|
+ "one there. Review and remove what you don't want:\n");
|
|
389
444
|
for (const root of survivors) {
|
|
390
|
-
process.stderr.write(` - ${root} ->
|
|
445
|
+
process.stderr.write(` - ${root} -> geneseed uninstall --target "${root}"\n`);
|
|
391
446
|
}
|
|
392
447
|
}
|
|
393
448
|
|
|
@@ -501,7 +556,7 @@ function warnCopilotGlobalOverProject() {
|
|
|
501
556
|
+ '(doubled context): Copilot stacks ~/.copilot/copilot-instructions.md on top '
|
|
502
557
|
+ "of a repo's own AGENTS.md. Review and remove what you don't want:\n");
|
|
503
558
|
for (const root of survivors) {
|
|
504
|
-
process.stderr.write(` - ${root} ->
|
|
559
|
+
process.stderr.write(` - ${root} -> geneseed uninstall --target "${root}"\n`);
|
|
505
560
|
}
|
|
506
561
|
}
|
|
507
562
|
|
|
@@ -897,9 +952,14 @@ const PROJECT_EMITS = {
|
|
|
897
952
|
opencode: emitOpencode,
|
|
898
953
|
};
|
|
899
954
|
|
|
900
|
-
export function emitProjectInto(host, { theme, out, root, footprint = 'full' }) {
|
|
955
|
+
export function emitProjectInto(host, { theme, out, root, footprint = 'full', doctrines = null }) {
|
|
901
956
|
const emit = PROJECT_EMITS[host];
|
|
902
|
-
|
|
957
|
+
// `doctrines` rides the same rail posture and mode ride into `emitGlobalInto`, and for the
|
|
958
|
+
// same reason: a drift reader that renders `expected` at the DEFAULT pack set reports the
|
|
959
|
+
// whole doctrines section as edited on every narrowed install. `null` means "no opinion" and
|
|
960
|
+
// `makeCfg` renders all four, which is what every validation caller wants.
|
|
961
|
+
return withPyNewlines(() => emit(makeCfg(doctrines ? { doctrines } : {}),
|
|
962
|
+
{ theme, footprint, root }, out));
|
|
903
963
|
}
|
|
904
964
|
|
|
905
965
|
/**
|
|
@@ -917,7 +977,7 @@ export function buildInto({ theme, out, footprint = 'lean' }) {
|
|
|
917
977
|
}
|
|
918
978
|
|
|
919
979
|
export function emitGlobalInto(host, {
|
|
920
|
-
theme, out, cfgDir, footprint, posture = null, mode = null,
|
|
980
|
+
theme, out, cfgDir, footprint, posture = null, mode = null, doctrines = null,
|
|
921
981
|
}) {
|
|
922
982
|
// `build.HOSTS.get(host, build.HOSTS["opencode"])` — an unknown host falls back rather than
|
|
923
983
|
// raising, because the host comes from a marker file a user can edit.
|
|
@@ -935,7 +995,13 @@ export function emitGlobalInto(host, {
|
|
|
935
995
|
// that chose a register — the same scar the footprint left in `diffCollect`, one axis over.
|
|
936
996
|
return withPyNewlines(
|
|
937
997
|
() => emit(
|
|
938
|
-
|
|
998
|
+
// THE PACK SELECTION RIDES THE SAME RAIL, with one difference: `doctrinesOfDir` answers
|
|
999
|
+
// `null` for "no marker / undetectable", and that must render at ALL packs rather than
|
|
1000
|
+
// at `[]`, so the key is OMITTED instead of passed through — `makeCfg`'s own default is
|
|
1001
|
+
// the fail-closed answer and an explicit `doctrines: null` would not reach it.
|
|
1002
|
+
makeCfg({
|
|
1003
|
+
posture: posture || 'peer', mode: mode || 'direct', ...(doctrines ? { doctrines } : {}),
|
|
1004
|
+
}),
|
|
939
1005
|
{ theme, footprint, root: null, cfgDir }, out,
|
|
940
1006
|
),
|
|
941
1007
|
);
|
package/docs/declined.md
CHANGED
|
@@ -8,6 +8,16 @@ The reference has been deleted, so "the reference does X" is no longer an argume
|
|
|
8
8
|
re-run. What is recorded in `tests/__snapshots__/` is the reference's answer, frozen; what is
|
|
9
9
|
listed here is where this implementation answers differently on purpose.
|
|
10
10
|
|
|
11
|
+
**The recording is smaller than it was.** On **2026-08-17** the emit, cli and web corpora were
|
|
12
|
+
retired — 1381 cells, produced by an implementation that no longer exists and re-recordable by
|
|
13
|
+
nothing, so they reddened on every deliberate content change while saying nothing about a defect
|
|
14
|
+
that happened to be stable. What licensed it was a measurement: all five mutations whose only
|
|
15
|
+
declared gate was a corpus replay are killed by `tests/unit/` alone. `docs/limits.md` carries the
|
|
16
|
+
full argument and what it cost. **The consequence for this page is narrow and worth stating: an
|
|
17
|
+
entry whose "how it stays honest" half named one of those three corpora now names a weaker gate, or
|
|
18
|
+
none.** The help fixtures under `tests/__snapshots__/help/`, which most of this page rests on, are
|
|
19
|
+
untouched.
|
|
20
|
+
|
|
11
21
|
---
|
|
12
22
|
|
|
13
23
|
## `--help`: the prog is `geneseed`, not `harness`
|
|
@@ -101,8 +111,14 @@ entry above and readers keep finding it separately.
|
|
|
101
111
|
|
|
102
112
|
argparse printed a usage block around every parse failure and prefixed it `harness: error:`; both
|
|
103
113
|
entries state the fault on one line prefixed `geneseed: error:` / `geneseed-hook: error:`. See
|
|
104
|
-
`bin/geneseed-cli.mjs`'s `parse` docblock.
|
|
105
|
-
|
|
114
|
+
`bin/geneseed-cli.mjs`'s `parse` docblock.
|
|
115
|
+
|
|
116
|
+
**What used to be gated beside it no longer is.** Every error a command's own body raises was in
|
|
117
|
+
the recorded CLI corpus, replayed cell by cell; that corpus was retired on 2026-08-17 and the
|
|
118
|
+
per-verb error texts are now covered only where `tests/unit/` happens to drive the verb. The
|
|
119
|
+
divergence declared here is unchanged — the port has never reproduced argparse's wording and does
|
|
120
|
+
not start now — but the *neighbouring* claim, that the rest of the error surface is pinned byte for
|
|
121
|
+
byte, has expired.
|
|
106
122
|
|
|
107
123
|
---
|
|
108
124
|
|
|
@@ -147,3 +163,38 @@ docblock this as "argparse's own rule". It is not — that generalised from `dif
|
|
|
147
163
|
which works only because an UNKNOWN option is deferred to the end of `parse_known_args`. Two of
|
|
148
164
|
that test's four cases are cases where the reference errors instead, so the test pins a
|
|
149
165
|
divergence; its docblock now says which cases those are and points here.
|
|
166
|
+
|
|
167
|
+
## A verb that postdates the recorder is exempted from the cell matrix by name
|
|
168
|
+
|
|
169
|
+
**Status:** ACCEPTED, 2026-08-19. This widens an existing exemption rather than minting a second
|
|
170
|
+
one, and it changes a TEST, not the product.
|
|
171
|
+
|
|
172
|
+
**What differs.** `tests/unit/hook_cli.test.mjs`'s `the matrix covers every verb each entry
|
|
173
|
+
claims` asserted a strict equality between the hook verbs `tests/helpers/matrix/cli.*.json`
|
|
174
|
+
covers and the verbs `bin/geneseed-hook.mjs` carries. The CLI half of the same test already
|
|
175
|
+
exempted a named `NATIVE` list; the hook half exempted nothing. Both halves now filter through
|
|
176
|
+
the same list.
|
|
177
|
+
|
|
178
|
+
**Why the recording could not be kept as the whole answer.** The matrix was exported by the
|
|
179
|
+
reference implementation, which was deleted on 2026-08-17 along with every recorder that could
|
|
180
|
+
write a cell. So the covered set is frozen at four hook verbs for good. A fifth hook verb —
|
|
181
|
+
wired in `js/settings.mjs`, carried in `bin/geneseed-hook.mjs`, declared in `js/cli-table.json` —
|
|
182
|
+
would fail this equality with **no green path**: the corpus cannot grow, and the failure message
|
|
183
|
+
correctly forbids deleting cells to pass. The gate had become a wall, and a wall that stops a
|
|
184
|
+
legitimate change is not protection, it is an accident of when the recorder died.
|
|
185
|
+
|
|
186
|
+
The reason the three CLI verbs were exempted in the first place — *the reference had nothing to
|
|
187
|
+
compare against, and authoring one would be recording a copy of the value under test* — is
|
|
188
|
+
exactly the reason a verb that postdates the reference has no cell. Same reason, same exemption.
|
|
189
|
+
|
|
190
|
+
**What was lost, measured.** Nothing that another gate does not already hold. The only property
|
|
191
|
+
the frozen hook half carried beyond `NATIVE` was *"a hook verb cannot be removed by a coordinated
|
|
192
|
+
edit of three files"* — and `tests/unit/hook_cli.test.mjs`'s own literal (`sorted(wired)` against
|
|
193
|
+
the four names) plus its `verbsOf(HOOK) === wired` equality are two editable copies of that same
|
|
194
|
+
set, twelve lines above. `tests/mutate.mjs`'s M8 plants the one-file removal and is killed there,
|
|
195
|
+
not here.
|
|
196
|
+
|
|
197
|
+
**How it stays honest.** The exemption is a NAMED list, not a containment: a new verb has to be
|
|
198
|
+
written into `NATIVE` deliberately, in two files that read each other's source, and the reverse
|
|
199
|
+
check refuses an exemption for a verb neither entry point carries. So the next new verb still
|
|
200
|
+
fails loudly until someone decides about it — which is the whole point of the original design.
|