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
package/js/web/api.mjs
CHANGED
|
@@ -61,7 +61,7 @@ import { doctorCollect } from '../doctor.mjs';
|
|
|
61
61
|
import { excludesSnapshot } from '../excludes.mjs';
|
|
62
62
|
import { GLOBAL_MANIFEST, HOSTS, opencodeConfigDir, pyResolve } from '../hosts.mjs';
|
|
63
63
|
import {
|
|
64
|
-
footprintOfDir, installState, installTargets, installedDefaults, modeOfDir,
|
|
64
|
+
doctrinesForBuild, footprintOfDir, installState, installTargets, installedDefaults, modeOfDir,
|
|
65
65
|
postureOfDir, readJsonMaybe, readMaybe, themeOfDir,
|
|
66
66
|
} from '../installs.mjs';
|
|
67
67
|
import { frontmatter } from '../hooks.mjs';
|
|
@@ -273,16 +273,22 @@ export function specEntries(root, nested) {
|
|
|
273
273
|
* `_web_core._deployed_inventory` — the agents and skills actually installed at
|
|
274
274
|
* `state.target`, not a fresh render of `src/`.
|
|
275
275
|
*
|
|
276
|
-
*
|
|
277
|
-
* separate files, so the deployed arm replaces
|
|
278
|
-
* deployed spec file carries neither a category nor a
|
|
279
|
-
* BY NAME from the same two sources the source render
|
|
280
|
-
* does not know reads "personal" for both, which is the
|
|
281
|
-
* borrowed one: filing it under the `build` fallback would claim
|
|
282
|
-
* it was never given.
|
|
276
|
+
* All three constitutional tiers still come from the render: once deployed they live inside
|
|
277
|
+
* AGENT.md rather than as separate files, so the deployed arm replaces the two ENTITY rosters
|
|
278
|
+
* and none of the constitution. A deployed spec file carries neither a category nor a
|
|
279
|
+
* lifecycle status, so each is tagged BY NAME from the same two sources the source render
|
|
280
|
+
* uses — and an entity the registry does not know reads "personal" for both, which is the
|
|
281
|
+
* honest answer rather than a borrowed one: filing it under the `build` fallback would claim
|
|
282
|
+
* a Geneseed taxonomy slot it was never given.
|
|
283
|
+
*
|
|
284
|
+
* ⚠ THE PACK SELECTION IS THE ONE THING THE RENDER CANNOT KNOW. `renderAll` emits all four
|
|
285
|
+
* pack files whatever `cfg.doctrines` says — measured: `makeCfg({doctrines:['craft']})` yields
|
|
286
|
+
* the same 105 items — because the filtering happens when AGENT.md's §2 is assembled, not in
|
|
287
|
+
* the walk. So the catalogue is always whole, and which packs this INSTALL built in comes from
|
|
288
|
+
* its own carrier via `doctrinesForBuild`, which resolves "no marker" to every pack.
|
|
283
289
|
*/
|
|
284
290
|
export function deployedInventory(state) {
|
|
285
|
-
const render = tuiInventory(state.theme);
|
|
291
|
+
const render = tuiInventory(state.theme, doctrinesForBuild(state.target));
|
|
286
292
|
const registry = loadRegistry();
|
|
287
293
|
const skills = specEntries(path.join(state.target, 'skills'), true);
|
|
288
294
|
for (const e of skills) {
|
|
@@ -292,7 +298,12 @@ export function deployedInventory(state) {
|
|
|
292
298
|
}
|
|
293
299
|
const agents = specEntries(path.join(state.target, 'agents'), false);
|
|
294
300
|
for (const e of agents) e.status = entityStatus(registry, `agents/${e.name}`);
|
|
295
|
-
return { agents,
|
|
301
|
+
return { agents,
|
|
302
|
+
skills,
|
|
303
|
+
laws: render.laws,
|
|
304
|
+
ontology: render.ontology,
|
|
305
|
+
doctrines: render.doctrines,
|
|
306
|
+
theme: state.theme };
|
|
296
307
|
}
|
|
297
308
|
|
|
298
309
|
/** `WebState.inventory` — the deployed record when there is one, else the source render. */
|
|
@@ -592,9 +603,53 @@ function configManifest(name, p) {
|
|
|
592
603
|
return null;
|
|
593
604
|
}
|
|
594
605
|
|
|
595
|
-
/**
|
|
606
|
+
/**
|
|
607
|
+
* `_web_core.SECTIONS` — a closed list; anything else is a 404.
|
|
608
|
+
*
|
|
609
|
+
* ⚠ `laws` STAYS ONE SECTION FOR ALL THREE TIERS, and that is a decision rather than an
|
|
610
|
+
* oversight. The console has ONE Constitution entry, not three, so a reader keeps the "read it
|
|
611
|
+
* top to bottom" property; the id stays `laws` because `tests/helpers/cli_golden.mjs`
|
|
612
|
+
* hard-requires `web/src/pages/Laws.jsx` and because a route rename buys nothing and costs a
|
|
613
|
+
* redirect. The tier lives on the ITEM (`tier: 'ontology' | 'invariant' | 'doctrine'`), which
|
|
614
|
+
* is where a consumer can act on it.
|
|
615
|
+
*/
|
|
596
616
|
export const SECTIONS = ['agents', 'skills', 'laws', 'memory', 'notebook', 'wiki', 'config'];
|
|
597
617
|
|
|
618
|
+
/**
|
|
619
|
+
* The `laws` section's items — the whole constitution, in constitutional order, one flat list.
|
|
620
|
+
*
|
|
621
|
+
* FLAT AND NOT NESTED, because every other section is `{section, items}` and a second shape
|
|
622
|
+
* for one section is a second client path for one roster. The pack's grouping metadata rides
|
|
623
|
+
* on each doctrine row (`pack`, `packTitle`, `packDesc`, `active`), so a consumer groups by
|
|
624
|
+
* `pack` and has everything a header needs without a second fetch.
|
|
625
|
+
*
|
|
626
|
+
* Names are ADDRESSES and the three shapes cannot collide: `ont:<slug>` carries a colon no
|
|
627
|
+
* numeral has, an invariant is `[IVXLCDM]+`, a doctrine rule is `<pack>.<n>`. `apiItem`
|
|
628
|
+
* resolves all three off the same list.
|
|
629
|
+
*/
|
|
630
|
+
function constitutionItems(inv) {
|
|
631
|
+
return [
|
|
632
|
+
...(inv.ontology ?? []).map((e) => ({ name: `ont:${e.id}`, title: e.title, desc: '',
|
|
633
|
+
tier: 'ontology' })),
|
|
634
|
+
...inv.laws.map((e) => ({ name: e.num, title: `Rule ${e.num} — ${e.title}`, desc: '',
|
|
635
|
+
klass: e.klass ?? 'craft', tier: 'invariant' })),
|
|
636
|
+
...(inv.doctrines ?? []).flatMap((p) => p.rules.map((r) => ({
|
|
637
|
+
name: `${r.pack}.${r.n}`,
|
|
638
|
+
title: `Doctrine ${r.pack} ${r.n} — ${r.title}`,
|
|
639
|
+
desc: '',
|
|
640
|
+
klass: r.klass ?? p.pack,
|
|
641
|
+
tier: 'doctrine',
|
|
642
|
+
pack: p.pack,
|
|
643
|
+
packTitle: p.title,
|
|
644
|
+
packDesc: p.desc,
|
|
645
|
+
// ⚠ EVERY RULE IS LISTED WHETHER OR NOT ITS PACK IS BUILT IN. An inactive pack that
|
|
646
|
+
// vanished from the payload would be indistinguishable from one that never shipped, and
|
|
647
|
+
// the console could not then offer the command that turns it back on.
|
|
648
|
+
active: p.active,
|
|
649
|
+
}))),
|
|
650
|
+
];
|
|
651
|
+
}
|
|
652
|
+
|
|
598
653
|
/** `_web_catalog.api_catalog`. */
|
|
599
654
|
export function apiCatalog(state, section) {
|
|
600
655
|
if (!SECTIONS.includes(section)) throw new NotFound(section);
|
|
@@ -607,8 +662,7 @@ export function apiCatalog(state, section) {
|
|
|
607
662
|
items = inv.skills.map((e) => ({ name: e.name, title: e.name, desc: e.desc,
|
|
608
663
|
source: e.source ?? null, klass: e.klass ?? 'build', status: e.status ?? 'unknown' }));
|
|
609
664
|
} else if (section === 'laws') {
|
|
610
|
-
items = inv
|
|
611
|
-
desc: '', klass: e.klass ?? 'craft' }));
|
|
665
|
+
items = constitutionItems(inv);
|
|
612
666
|
} else if (section === 'memory') {
|
|
613
667
|
items = memoryItems(state);
|
|
614
668
|
} else if (section === 'notebook') {
|
|
@@ -634,10 +688,21 @@ export function apiItem(state, type, name) {
|
|
|
634
688
|
return out;
|
|
635
689
|
}
|
|
636
690
|
if (type === 'law') {
|
|
637
|
-
|
|
691
|
+
// ONE ARM FOR THREE TIERS, resolved off the same list the catalogue publishes — so a name
|
|
692
|
+
// that appears in `/api/catalog/laws` always opens, and one that does not always 404s.
|
|
693
|
+
// Building the list twice is what would let the two drift.
|
|
694
|
+
const e = constitutionItems(inv).find((x) => x.name === name);
|
|
638
695
|
if (!e) throw new NotFound(name);
|
|
639
|
-
|
|
640
|
-
|
|
696
|
+
const body = e.tier === 'ontology'
|
|
697
|
+
? (inv.ontology.find((s) => `ont:${s.id}` === name)?.body ?? '')
|
|
698
|
+
: (e.tier === 'invariant'
|
|
699
|
+
? (inv.laws.find((x) => x.num === name)?.body ?? '')
|
|
700
|
+
: (inv.doctrines.flatMap((p) => p.rules)
|
|
701
|
+
.find((r) => `${r.pack}.${r.n}` === name)?.body ?? ''));
|
|
702
|
+
// `links: []` — a constitution body is not link-resolved. It cites its siblings by
|
|
703
|
+
// ADDRESS (`Rule IV`, `Doctrine ops 1`, `Ontology: Telos`), not by path, and there is no
|
|
704
|
+
// file behind those to resolve to.
|
|
705
|
+
return { ...e, type, name, body, links: [] };
|
|
641
706
|
}
|
|
642
707
|
if (type === 'memory' || type === 'notebook') {
|
|
643
708
|
flatName(name);
|
|
@@ -795,7 +860,21 @@ export function apiOverview(state) {
|
|
|
795
860
|
counts: {
|
|
796
861
|
agents: inv.agents.length,
|
|
797
862
|
skills: inv.skills.length,
|
|
863
|
+
// ⚠ `laws` STAYS THE INVARIANT COUNT. The rail badge reads it, `docCounts` mirrors it as
|
|
864
|
+
// `{N_LAWS}`, and the doctor's frozen `proseMirrorProblems` compares README and
|
|
865
|
+
// SHIPPED.md prose against the same number. Widening it to "the whole constitution"
|
|
866
|
+
// would silently move all four at once.
|
|
798
867
|
laws: inv.laws.length,
|
|
868
|
+
ontology: (inv.ontology ?? []).length,
|
|
869
|
+
// The tier's own summary, kept out of the section counts because it is not a section:
|
|
870
|
+
// `active`/`total` is the fraction the dashboard shows, `rules` counts only the packs
|
|
871
|
+
// this install built in — what it is bound by, not what its bundle carries.
|
|
872
|
+
doctrines: {
|
|
873
|
+
active: (inv.doctrines ?? []).filter((p) => p.active).length,
|
|
874
|
+
total: (inv.doctrines ?? []).length,
|
|
875
|
+
rules: (inv.doctrines ?? []).filter((p) => p.active)
|
|
876
|
+
.reduce((n, p) => n + p.rules.length, 0),
|
|
877
|
+
},
|
|
799
878
|
memory: memoryItems(state).length,
|
|
800
879
|
notebook: notebookItems(state).length,
|
|
801
880
|
wiki: wikiItems(state).length,
|
package/js/web/docs.mjs
CHANGED
|
@@ -130,8 +130,15 @@ export function docGroups() {
|
|
|
130
130
|
* the one-line description. A key of `null` marks a term no theme renames.
|
|
131
131
|
*/
|
|
132
132
|
const GLOSSARY_KEYS = [
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
// The three tiers, in constitutional order and before the entities — a reader who does not
|
|
134
|
+
// know what an Ontology is here cannot read the rest of the glossary's first rows.
|
|
135
|
+
['Ontology', 'ONTOLOGY', 'the always-on worldview under the rules — telos, evidence, '
|
|
136
|
+
+ 'decisions, conduct; it holds the Pact'],
|
|
137
|
+
['Rule (Law)', 'LAW', 'one of the nine always-on invariants: what is never done'],
|
|
138
|
+
['Rules (Laws)', 'LAWS', 'the body of always-on invariants'],
|
|
139
|
+
['Doctrine', 'DOCTRINE', 'one practice rule, cited `<pack> <n>` — how work is done here'],
|
|
140
|
+
['Doctrines', 'DOCTRINES', 'the practice packs, chosen at build time; inactive packs still '
|
|
141
|
+
+ 'ship in the bundle'],
|
|
135
142
|
['Agent', 'AGENT', 'a capability specialist'],
|
|
136
143
|
['Agents', 'AGENTS', 'the roster of specialists'],
|
|
137
144
|
['Skill', 'SKILL', 'a repeatable workflow'],
|
|
@@ -139,7 +146,10 @@ const GLOSSARY_KEYS = [
|
|
|
139
146
|
['Memory', 'MEMORY', 'durable, one-fact-per-file knowledge'],
|
|
140
147
|
['Notebook', 'NOTEBOOK', "the agent's sovereign space"],
|
|
141
148
|
['Wiki', 'WIKI', 'the machine-wide knowledge base'],
|
|
142
|
-
|
|
149
|
+
// Still a token, still themed, but it names a CONCEPT INSIDE the ontology's Telos now
|
|
150
|
+
// rather than a peer of the Rules — `src/postures/peer.md` cites it and the section that
|
|
151
|
+
// used to carry it is the Ontology.
|
|
152
|
+
['Pact', 'PACT', "the two-way collaboration contract, stated in the Ontology's Telos"],
|
|
143
153
|
['Posture', null, 'the relationship register the agent works in '
|
|
144
154
|
+ '(peer, mentor, expert, assistant, artisan)'],
|
|
145
155
|
['Mode', null, 'how work gets executed — direct (the agent works every '
|
|
@@ -156,8 +166,14 @@ const GLOSSARY_KEYS = [
|
|
|
156
166
|
|
|
157
167
|
// ---- counts substituted into concept bodies -------------------------------------------
|
|
158
168
|
|
|
159
|
-
/**
|
|
160
|
-
|
|
169
|
+
/**
|
|
170
|
+
* `_web_docs._doc_counts`.
|
|
171
|
+
*
|
|
172
|
+
* Exported so the tier tokens can be gated on the day they are ADDED rather than on the day a
|
|
173
|
+
* page first spends one. Only one page carries `{N_LAWS}` today; a test that could only reach
|
|
174
|
+
* these through a rendered body would leave four of the seven untested until Task 6.2.
|
|
175
|
+
*/
|
|
176
|
+
export function docCounts(state) {
|
|
161
177
|
const inv = state.inventory;
|
|
162
178
|
const plugins = path.join(ROOT, 'adapters', 'opencode', 'plugins');
|
|
163
179
|
let n = 0;
|
|
@@ -165,11 +181,23 @@ function docCounts(state) {
|
|
|
165
181
|
n = readdirSync(plugins).filter((f) => f.startsWith('geneseed-') && f.endsWith('.js'))
|
|
166
182
|
.length;
|
|
167
183
|
} catch { n = 0; }
|
|
184
|
+
// `{N_LAWS}` IS THE INVARIANT COUNT AND NOTHING ELSE. The doctor's frozen
|
|
185
|
+
// `proseMirrorProblems` holds README and SHIPPED.md prose to the same number, so a token
|
|
186
|
+
// that quietly meant "the whole constitution" would put the docs and the gate at odds with
|
|
187
|
+
// no way to re-bless either. The other three tiers get their own tokens.
|
|
188
|
+
const packs = inv.doctrines ?? [];
|
|
189
|
+
const on = packs.filter((p) => p.active);
|
|
168
190
|
return {
|
|
169
191
|
'{N_LAWS}': (inv.laws || []).length,
|
|
170
192
|
'{N_AGENTS}': (inv.agents || []).length,
|
|
171
193
|
'{N_SKILLS}': (inv.skills || []).length,
|
|
172
194
|
'{N_PLUGINS}': n,
|
|
195
|
+
'{N_ONTOLOGY}': (inv.ontology ?? []).length,
|
|
196
|
+
'{N_PACKS}': packs.length,
|
|
197
|
+
'{N_PACKS_ACTIVE}': on.length,
|
|
198
|
+
// Rules in the packs this install BUILT IN — the number a reader of these pages is
|
|
199
|
+
// actually bound by. The catalogue always ships whole, so it is never the smaller claim.
|
|
200
|
+
'{N_DOCTRINE_RULES}': on.reduce((t, p) => t + p.rules.length, 0),
|
|
173
201
|
};
|
|
174
202
|
}
|
|
175
203
|
|
package/js/web/jobs.mjs
CHANGED
|
@@ -107,7 +107,7 @@ import { statSync } from 'node:fs';
|
|
|
107
107
|
import { StringDecoder } from 'node:string_decoder';
|
|
108
108
|
import path from 'node:path';
|
|
109
109
|
|
|
110
|
-
import { ROOT } from '../checkout.mjs';
|
|
110
|
+
import { ROOT, PACK_ORDER } from '../checkout.mjs';
|
|
111
111
|
import { setupBuildArgs } from '../generate.mjs';
|
|
112
112
|
import { isDict } from '../mcp.mjs';
|
|
113
113
|
import { jsonDumpsCompact, parseJson, pyTruthy, readText, writeText } from '../lib/fs.mjs';
|
|
@@ -444,11 +444,20 @@ export const NOT_PORTED_ACTIONS = new Set();
|
|
|
444
444
|
*/
|
|
445
445
|
export const INLINE_ACTIONS = ['restore', 'install', 'deploy'];
|
|
446
446
|
|
|
447
|
+
/**
|
|
448
|
+
* ⚠ `doctrines` DEFAULTS TO THE FULL SET, and it is the one axis here whose default is not the
|
|
449
|
+
* generator's own. `theme`/`emit`/`footprint`/`posture`/`mode` fall back to a frozen literal
|
|
450
|
+
* that costs nothing when it is wrong; a missing `--doctrines` falls through to
|
|
451
|
+
* `harness.config.json`, and a `{"doctrines":["craft"]}` there re-emits the deployed install
|
|
452
|
+
* without its commit/push consent gate. So a caller that forgets the axis gets ALL packs —
|
|
453
|
+
* unknown resolves to the full set, never to a config value (`doctrinesForBuild`).
|
|
454
|
+
*/
|
|
447
455
|
function actionTable({
|
|
448
456
|
theme = 'neutral', emit = 'opencode-global', footprint = 'full',
|
|
449
|
-
posture = 'peer', mode = 'direct',
|
|
457
|
+
posture = 'peer', mode = 'direct', doctrines = [...PACK_ORDER],
|
|
450
458
|
} = {}) {
|
|
451
|
-
const buildArgv = setupBuildArgs(theme, emit, null, null, footprint, posture, mode
|
|
459
|
+
const buildArgv = setupBuildArgs(theme, emit, null, null, footprint, posture, mode,
|
|
460
|
+
doctrines);
|
|
452
461
|
return {
|
|
453
462
|
doctor: [[NODE(), CLI(), 'doctor']],
|
|
454
463
|
build: [[NODE(), GEN(), ...buildArgv]],
|
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
|
|
|
@@ -60,11 +60,12 @@ import { promptLine } from '../setup.mjs';
|
|
|
60
60
|
// which resolves because everything either side needs of the other is a hoisted function
|
|
61
61
|
// declaration, not a value read at module-evaluation time.
|
|
62
62
|
import { preflight } from '../update.mjs';
|
|
63
|
+
import { doctrinesForBuild } from '../installs.mjs';
|
|
63
64
|
import { NotFound, PREFIX_ROUTES, STATE_ROUTES, webState } from './api.mjs';
|
|
64
65
|
import {
|
|
65
66
|
apiDeployCmd, apiExcludesMutate, apiInstallCmd, apiInstallToggle, apiMcpToggle,
|
|
66
67
|
apiMemoryDelete, apiProfileSave,
|
|
67
|
-
apiRestore, apiRulesMutate, apiRulesPromote, apiSelectView, buildOverride,
|
|
68
|
+
apiRestore, apiRulesMutate, apiRulesPromote, apiSelectView, buildOverride, mcpTargetPaths,
|
|
68
69
|
} from './actions.mjs';
|
|
69
70
|
import { apiActivityToggle } from './activity.mjs';
|
|
70
71
|
import { apiDocs, apiDocsPage } from './docs.mjs';
|
|
@@ -248,6 +249,29 @@ export const PORTED_INLINE = ['/api/ping', '/api/docs', '/api/docs/page/',
|
|
|
248
249
|
export const PORTED_POST_INLINE = ['/api/shutdown', '/api/restart', '/api/jobs/',
|
|
249
250
|
'/api/actions/'];
|
|
250
251
|
|
|
252
|
+
/**
|
|
253
|
+
* POST routes THAT NEVER EXISTED ON THE REFERENCE — the first of them, and the reason this set
|
|
254
|
+
* has to exist at all.
|
|
255
|
+
*
|
|
256
|
+
* `test_every_post_route_is_either_ported_or_declared_unported` asserts SET EQUALITY against
|
|
257
|
+
* `REF_POST`, a frozen literal of the paths the Python daemon answered. That was exactly right
|
|
258
|
+
* while the port was in flight: a route on one side and not the other was a defect either way.
|
|
259
|
+
* The port is finished and the reference is deleted, so the assertion now says something else —
|
|
260
|
+
* "this daemon may never grow a route" — which is a rule nobody chose.
|
|
261
|
+
*
|
|
262
|
+
* SO THE PARTITION GAINS A FOURTH PART RATHER THAN LOSING ITS EQUALITY. Widening `REF_POST` would
|
|
263
|
+
* have been the cheap edit and it would have been a lie: that list is a RECORD of what the
|
|
264
|
+
* reference answered, and a route added in 2026 was never in it. Keeping the record honest and
|
|
265
|
+
* declaring the additions beside it keeps both claims checkable — the reference's surface is
|
|
266
|
+
* still pinned, and everything past it is enumerated here instead of merely appearing.
|
|
267
|
+
*
|
|
268
|
+
* A route belongs here only once it is dispatched below; the dispatcher probe is what keeps this
|
|
269
|
+
* from becoming a declaration nobody honours.
|
|
270
|
+
*/
|
|
271
|
+
export const POST_BEYOND_REF = new Set([
|
|
272
|
+
'/api/reveal',
|
|
273
|
+
]);
|
|
274
|
+
|
|
251
275
|
function notPorted(path) {
|
|
252
276
|
return NOT_PORTED.has(path) || NOT_PORTED_PREFIXES.some((p) => path.startsWith(p));
|
|
253
277
|
}
|
|
@@ -419,6 +443,34 @@ export function makeHandler(state, jm, token, dist, holder = null) {
|
|
|
419
443
|
requestRestart(state.theme);
|
|
420
444
|
return sendJson(res, { restarting: true }, 200, ae);
|
|
421
445
|
}
|
|
446
|
+
if (path === '/api/reveal') {
|
|
447
|
+
// "It asks for a token and a URL and I don't know where to put them." The screen already
|
|
448
|
+
// PRINTS the config path above each target's rows; what it could not do is take you there.
|
|
449
|
+
//
|
|
450
|
+
// THE ALLOWLIST IS THE WHOLE SECURITY OF THIS ENDPOINT, same as the toggle beside it, and
|
|
451
|
+
// it is the same allowlist — `mcpTargetPaths()`, one function with two callers rather than
|
|
452
|
+
// two copies that drift. An unlisted path is a 404 before anything is opened. Without it
|
|
453
|
+
// this is "ask the desktop to open any path on the machine", behind a CSRF token and
|
|
454
|
+
// nothing else, and on Windows `openUrl` interpolates into a `cmd` command line with
|
|
455
|
+
// `windowsVerbatimArguments` — so the exact-match lookup is also what bounds the strings
|
|
456
|
+
// that can ever reach a shell to the ones the machine's own install registry produced.
|
|
457
|
+
//
|
|
458
|
+
// THE FOLDER, NOT THE FILE. `open`/`start`/`xdg-open` on a `.json` hands it to whatever
|
|
459
|
+
// claims that extension — which on a stock Windows box is a browser, i.e. a read-only view
|
|
460
|
+
// of the exact file the user is trying to EDIT. The containing folder lands in Explorer /
|
|
461
|
+
// Finder / the file manager every time, and the filename is already on screen beside the
|
|
462
|
+
// button.
|
|
463
|
+
const want = String(readJsonBody(body).path ?? '');
|
|
464
|
+
if (!mcpTargetPaths().has(want)) {
|
|
465
|
+
return sendJson(res, { error: `not found: ${want || '(none)'}` }, 404, ae);
|
|
466
|
+
}
|
|
467
|
+
const dir = dirname(want);
|
|
468
|
+
// `openUrl` SWALLOWS EVERY FAILURE BY DESIGN (a headless box has no opener), so `ok` here
|
|
469
|
+
// means "the path was allowed and the request was made", never "a window appeared". The
|
|
470
|
+
// response says which directory, so the UI can print it as the fallback instruction.
|
|
471
|
+
openUrl(dir);
|
|
472
|
+
return sendJson(res, { ok: true, dir }, 200, ae);
|
|
473
|
+
}
|
|
422
474
|
const route = POST_ROUTES.get(path);
|
|
423
475
|
if (route !== undefined) {
|
|
424
476
|
const [fn, okIs409] = route;
|
|
@@ -498,8 +550,13 @@ export function makeHandler(state, jm, token, dist, holder = null) {
|
|
|
498
550
|
// install, so a re-theme preserves lean/full, the register and the operating mode.
|
|
499
551
|
const [theme, emit] = action === 'build'
|
|
500
552
|
? buildOverride(state, body) : [state.theme, state.emit];
|
|
553
|
+
// The pack selection follows the install like footprint/posture/mode, and is read here
|
|
554
|
+
// rather than left to the table's default: a console Build must re-emit the constitution
|
|
555
|
+
// the deployment already carries. Unknown (no `Active packs:` marker) resolves to ALL
|
|
556
|
+
// packs — never to `harness.config.json`, which would drop the consent gate.
|
|
501
557
|
const cmds = actionCommands(action, {
|
|
502
558
|
theme, emit, footprint: state.footprint, posture: state.posture, mode: state.mode,
|
|
559
|
+
doctrines: doctrinesForBuild(state.target),
|
|
503
560
|
});
|
|
504
561
|
if (!pyTruthy(cmds)) {
|
|
505
562
|
return sendJson(res, { error: `unknown action ${action}` }, 404, ae);
|
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": "
|
|
4
|
+
"version": "3.0.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",
|