geneseed 4.10.0 → 4.14.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 +85 -0
- package/README.md +4 -3
- package/SHIPPED.md +1 -1
- package/docs/concepts/kits.md +14 -2
- package/docs/concepts/skills.md +1 -1
- package/docs/extending.md +3 -4
- package/docs/guides/run-anywhere.md +2 -2
- package/docs/guides/upgrade.md +2 -2
- package/docs/reference/troubleshoot-install.md +1 -1
- package/docs/web-ui.md +6 -1
- package/harness.config.json +1 -1
- package/js/hosts/link.mjs +88 -1
- package/js/inspect/checks-build.mjs +8 -2
- package/js/inspect/cost.mjs +1 -1
- package/js/inspect/diff.mjs +53 -7
- package/js/inspect/inventory.mjs +1 -0
- package/js/lib/prompt.mjs +19 -2
- package/js/maintain/setup.mjs +92 -28
- package/js/maintain/update.mjs +2 -2
- package/js/web/actions.mjs +8 -0
- package/js/web/api.mjs +39 -17
- package/js/web/catalog.mjs +17 -2
- package/js/web/handler.mjs +1 -0
- package/js/web/server.mjs +11 -5
- package/js/web/state.mjs +38 -3
- package/package.json +1 -1
- package/registry.json +28 -0
- package/src/AGENT.md.tmpl +5 -5
- package/src/agents/_self-improvement.md +11 -0
- package/src/agents/_template.md +1 -8
- package/src/agents/advocate.md +1 -8
- package/src/agents/architect.md +1 -8
- package/src/agents/developer.md +1 -8
- package/src/agents/devops-engineer.md +1 -9
- package/src/agents/docker-expert.md +1 -9
- package/src/agents/docs.md +1 -8
- package/src/agents/empiricist.md +1 -8
- package/src/agents/explorer.md +1 -8
- package/src/agents/framer.md +1 -8
- package/src/agents/historian.md +1 -8
- package/src/agents/kubernetes-specialist.md +1 -9
- package/src/agents/memory-curator.md +1 -8
- package/src/agents/operator.md +1 -8
- package/src/agents/performance.md +1 -8
- package/src/agents/pragmatist.md +1 -8
- package/src/agents/react-engineer.md +38 -0
- package/src/agents/react-reviewer.md +42 -0
- package/src/agents/researcher.md +5 -10
- package/src/agents/reviewer.md +1 -8
- package/src/agents/security.md +1 -8
- package/src/agents/skeptic.md +1 -8
- package/src/agents/spring-boot-engineer.md +1 -9
- package/src/agents/spring-reviewer.md +1 -9
- package/src/agents/spring-security.md +1 -9
- package/src/agents/steward.md +1 -8
- package/src/agents/tester.md +1 -8
- package/src/agents/user-advocate.md +1 -8
- package/src/agents/visionary.md +1 -8
- package/src/kits/react/rules.md +13 -0
- package/src/kits/react.json +18 -0
- package/src/licenses/everything-claude-code.txt +21 -0
- package/src/skills/_self-improvement.md +22 -5
- package/src/skills/brainstorm.md +7 -2
- package/src/skills/codebase-design.md +6 -2
- package/src/skills/debug.md +1 -1
- package/src/skills/develop.md +4 -2
- package/src/skills/handoff.md +1 -1
- package/src/skills/parallel-agents.md +3 -1
- package/src/skills/plan.md +5 -2
- package/src/skills/prototype.md +4 -2
- package/src/skills/react/a11y.md +33 -0
- package/src/skills/react/vite.md +35 -0
- package/src/skills/react/webpack.md +40 -0
- package/src/skills/react-testing.md +68 -0
- package/src/skills/react.md +63 -0
- package/src/skills/research.md +4 -2
- package/src/skills/rule.md +4 -2
- package/src/skills/ship.md +6 -4
- package/src/skills/skill-forge.md +10 -5
- package/src/skills/wayfinder.md +6 -2
- package/src/skills/wiki.md +18 -5
- package/src/upstreams.json +7 -0
- package/themes/_TEMPLATE.json +4 -0
- package/themes/biker.json +4 -0
- package/themes/commentator.json +4 -0
- package/themes/cyberpunk.json +4 -0
- package/themes/gamer.json +4 -0
- package/themes/imperial.json +4 -0
- package/themes/joker.json +4 -0
- package/themes/marvin.json +4 -0
- package/themes/mean.json +4 -0
- package/themes/military.json +4 -0
- package/themes/neutral.json +4 -0
- package/themes/pirate.json +4 -0
- package/themes/sports.json +4 -0
- package/themes/verstappen.json +4 -0
- package/themes/wizard.json +4 -0
- package/web/dist/assets/Activity-44rbWUQI.js +1 -0
- package/web/dist/assets/ActivityDetail-Dk9k893a.js +1 -0
- package/web/dist/assets/Dashboard-DCoUN60W.js +2 -0
- package/web/dist/assets/{Docs-DLVckkew.js → Docs-C_je2lRG.js} +3 -3
- package/web/dist/assets/FilterInput-CZREvrjX.js +1 -0
- package/web/dist/assets/GreenhouseView-BXAOIznW.js +2 -0
- package/web/dist/assets/Installs-DAXF403D.js +3 -0
- package/web/dist/assets/JournalView-DZ184wKB.js +1 -0
- package/web/dist/assets/KitApply-pjhvGha0.js +1 -0
- package/web/dist/assets/Kits-BCCKjSIY.js +1 -0
- package/web/dist/assets/{Laws-BkKnZOBI.js → Laws-BgBUpRkE.js} +1 -1
- package/web/dist/assets/Library-CW0Zl6RF.js +1 -0
- package/web/dist/assets/Loops-BvFOzjEw.js +2 -0
- package/web/dist/assets/{Markdown-iHvBrpRN.js → Markdown-zaXXXHz9.js} +13 -13
- package/web/dist/assets/OperatorHudView-Bs4unPNi.js +2 -0
- package/web/dist/assets/Personal-C23XaT-a.js +1 -0
- package/web/dist/assets/Tabs-b2XFu4OS.js +1 -0
- package/web/dist/assets/activity-Jw8XcPiP.js +1 -0
- package/web/dist/assets/{drawable-CgYi0Dtg.js → drawable-BERkKhx_.js} +1 -1
- package/web/dist/assets/index-C9R_bRw_.js +2 -0
- package/web/dist/assets/{index-BCJz4_8C.css → index-jYlWihN-.css} +1 -1
- package/web/dist/assets/{react-BOF6yL40.js → react-DlYQp10C.js} +1 -1
- package/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/web/dist/assets/sections-ChwaobfW.js +1 -0
- package/web/dist/assets/usePoll-bDFN-Hr6.js +1 -0
- package/web/dist/index.html +5 -4
- package/web/dist/assets/Activity-BjyJaf8e.js +0 -1
- package/web/dist/assets/ActivityDetail-Bo9nRDNV.js +0 -1
- package/web/dist/assets/Dashboard-BQJGU7vE.js +0 -2
- package/web/dist/assets/FilterInput-zLFXoSt6.js +0 -1
- package/web/dist/assets/GreenhouseView--DML7CW3.js +0 -2
- package/web/dist/assets/Installs-CxCiln1F.js +0 -3
- package/web/dist/assets/JournalView-ymgZleHi.js +0 -1
- package/web/dist/assets/Library-C8ercIZv.js +0 -1
- package/web/dist/assets/Loops-C95zhbFl.js +0 -2
- package/web/dist/assets/OperatorHudView-B9qwc3w3.js +0 -2
- package/web/dist/assets/Personal-DyR72AF-.js +0 -1
- package/web/dist/assets/Tabs-ChCr_HqV.js +0 -1
- package/web/dist/assets/activity-DvPxoS4Y.js +0 -1
- package/web/dist/assets/index-B32EvfDT.js +0 -2
- package/web/dist/assets/sections-BET3c1WT.js +0 -1
- package/web/dist/assets/usePoll-NSTzLOF4.js +0 -1
package/js/maintain/setup.mjs
CHANGED
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
* a cell could not have varied the answers at all.
|
|
42
42
|
*/
|
|
43
43
|
import { spawnSync } from 'node:child_process';
|
|
44
|
-
import { existsSync } from 'node:fs';
|
|
44
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
45
|
+
import os from 'node:os';
|
|
45
46
|
import path from 'node:path';
|
|
46
47
|
|
|
47
48
|
import { main as driverMain, resolveOut } from '../build/driver.mjs';
|
|
@@ -228,9 +229,11 @@ function askDoctrines(deployed = null) {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
/**
|
|
231
|
-
* The Kits
|
|
232
|
-
* (`deployed`, its `Active kits:` marker)
|
|
233
|
-
*
|
|
232
|
+
* The Kits questions (spec A5): one yes/no per shipped kit, each defaulting to whether the install
|
|
233
|
+
* has it on (`deployed`, its `Active kits:` marker), so holding Enter keeps the installed set. A
|
|
234
|
+
* single pick could not hold two kits at once, which is why the menu became per kit when the second
|
|
235
|
+
* kit shipped. Asked ONLY when this checkout ships a kit, so a kit-less wizard reads exactly the
|
|
236
|
+
* stdin it always did (and has no kit to carry: `kitsOfDir` drops an unshipped one).
|
|
234
237
|
* Returns the kit list, or null for none (`setupBuildArgs` omits `--kits`).
|
|
235
238
|
*/
|
|
236
239
|
export function askKits(deployed = null, kits = null) {
|
|
@@ -239,14 +242,9 @@ export function askKits(deployed = null, kits = null) {
|
|
|
239
242
|
return null;
|
|
240
243
|
}
|
|
241
244
|
const on = (deployed ?? []).filter((k) => kits.has(k));
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// per kit once a second kit ships.
|
|
246
|
-
if (on.length > 1) opts.push([on.join(','), 'keep the installed kits']);
|
|
247
|
-
opts.push(['none', 'no kit']);
|
|
248
|
-
const pick = askChoice('Kits', opts, on.length ? on.join(',') : 'none');
|
|
249
|
-
return pick === 'none' ? null : pick.split(',');
|
|
245
|
+
const picked = [...kits].filter(([k, m]) => askChoice(`Kit ${k} — ${m.description ?? k}`,
|
|
246
|
+
[['yes', `install the ${k} kit`], ['no', `leave ${k} out`]], on.includes(k) ? 'yes' : 'no') === 'yes');
|
|
247
|
+
return picked.length ? picked.map(([k]) => k) : null;
|
|
250
248
|
}
|
|
251
249
|
|
|
252
250
|
/**
|
|
@@ -521,8 +519,8 @@ export function setupLines() {
|
|
|
521
519
|
doctrines = null, excludeRules = null, excludeSkills = null, gates = {}, kits = null,
|
|
522
520
|
} = sel;
|
|
523
521
|
if (emit === 'opencode-global') {
|
|
524
|
-
// The build below overwrites the deployed global harness,
|
|
525
|
-
//
|
|
522
|
+
// The build below overwrites the deployed global harness, which may carry hand edits (or
|
|
523
|
+
// in-place edits from a harness older than the notebook notes). Preserve that drift first.
|
|
526
524
|
try {
|
|
527
525
|
const [ipath] = exportImprovements();
|
|
528
526
|
if (ipath) {
|
|
@@ -575,26 +573,83 @@ export function cmdSetup() {
|
|
|
575
573
|
}
|
|
576
574
|
|
|
577
575
|
/**
|
|
578
|
-
* The AI coding tools Geneseed installs into, as `[label, present, url]` rows in HOSTS
|
|
576
|
+
* The AI coding tools Geneseed installs into, as `[label, present, url, where]` rows in HOSTS
|
|
577
|
+
* order. `where` is the path found OFF the PATH (see `userInstall`), else null.
|
|
579
578
|
*
|
|
580
|
-
* PRESENT MEANS THE
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
579
|
+
* PRESENT MEANS THE TOOL ITSELF WAS FOUND — its command on PATH, or its program in a per-user
|
|
580
|
+
* install location — and never its config dir. That dir looked like a signal — a desktop-only
|
|
581
|
+
* install has one and no command — but GENESEED ITSELF CREATES IT: a `bob-global` emit writes
|
|
582
|
+
* `~/.bob` on a machine that never had Bob, and the first run of this check reported Bob present
|
|
583
|
+
* on exactly such a machine. A wrong "ok" sends the user to an install mode for a tool they
|
|
584
|
+
* cannot open; a wrong "not found" costs a line, and `toolsReport` lets them name the path.
|
|
585
585
|
*
|
|
586
586
|
* NO SPAWN, deliberately. `which` walks PATH by hand, so asking costs nothing on the CLI's
|
|
587
587
|
* child_process allow-list — a `claude --version` probe would have been a third entry there to
|
|
588
|
-
* learn nothing `which` does not already say. `lookup`
|
|
589
|
-
* rely on what this machine happens to have installed.
|
|
588
|
+
* learn nothing `which` does not already say. `lookup` and `userLookup` are the seams for the
|
|
589
|
+
* test, which cannot rely on what this machine happens to have installed.
|
|
590
590
|
*/
|
|
591
|
-
export function hostTools({ lookup = which } = {}) {
|
|
591
|
+
export function hostTools({ lookup = which, userLookup = userInstall } = {}) {
|
|
592
592
|
return [
|
|
593
593
|
['OpenCode', 'opencode', 'https://opencode.ai/docs/'],
|
|
594
594
|
['Claude Code', 'claude', 'https://code.claude.com/docs/en/setup'],
|
|
595
595
|
['IBM Bob', 'bob', 'https://bob.ibm.com/'],
|
|
596
596
|
['OpenClaude', 'openclaude', 'https://openclaude.gitlawb.com/'],
|
|
597
|
-
].map(([label, cmd, url]) =>
|
|
597
|
+
].map(([label, cmd, url]) => {
|
|
598
|
+
if (lookup(cmd)) return [label, true, url, null];
|
|
599
|
+
const where = userLookup(cmd);
|
|
600
|
+
return [label, Boolean(where), url, where || null];
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Per-host off-PATH places: a bin dir a tool's own installer uses, and a name pattern for the
|
|
606
|
+
* desktop app folder. Only Bob ships as a desktop IDE; matching `claude` against app folders
|
|
607
|
+
* would find the Claude desktop chat app, which is not Claude Code.
|
|
608
|
+
*/
|
|
609
|
+
const USER_BIN_DIRS = {
|
|
610
|
+
opencode: ['.opencode/bin'],
|
|
611
|
+
claude: ['.claude/local'],
|
|
612
|
+
bob: [],
|
|
613
|
+
openclaude: [],
|
|
614
|
+
};
|
|
615
|
+
const APP_PATTERN = { bob: /\bbob\b/i };
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Where a tool installed FOR ONE USER lives when its installer did not put it on PATH — the
|
|
619
|
+
* report of 2026-10 was IBM Bob installed under the user's own profile, which `which` never sees.
|
|
620
|
+
*
|
|
621
|
+
* Three kinds of place, each one the tool's own program, never its config dir:
|
|
622
|
+
* * a per-user bin dir: `~/.local/bin` and `~/bin` for every tool, plus the dir its own
|
|
623
|
+
* installer uses (`USER_BIN_DIRS`) — an executable file, through `which` and its PATHEXT;
|
|
624
|
+
* * on Windows, `%APPDATA%\npm` (a per-user `npm i -g`) and a desktop app folder under
|
|
625
|
+
* `%LOCALAPPDATA%\Programs` whose name matches `APP_PATTERN`;
|
|
626
|
+
* * on macOS, a matching `.app` in `/Applications` or `~/Applications`.
|
|
627
|
+
* The first hit wins. Everything a machine could differ on is a parameter, for the test.
|
|
628
|
+
*/
|
|
629
|
+
export function userInstall(cmd, {
|
|
630
|
+
home = os.homedir(), env = process.env, platform = process.platform,
|
|
631
|
+
find = which, list = listDir,
|
|
632
|
+
} = {}) {
|
|
633
|
+
const bins = ['.local/bin', 'bin', ...(USER_BIN_DIRS[cmd] ?? [])].map((d) => path.join(home, d));
|
|
634
|
+
if (platform === 'win32' && env.APPDATA) bins.push(path.join(env.APPDATA, 'npm'));
|
|
635
|
+
const hit = find(cmd, bins.join(path.delimiter));
|
|
636
|
+
if (hit) return hit;
|
|
637
|
+
const pattern = APP_PATTERN[cmd];
|
|
638
|
+
if (!pattern) return null;
|
|
639
|
+
const appDirs = platform === 'win32'
|
|
640
|
+
? (env.LOCALAPPDATA ? [path.join(env.LOCALAPPDATA, 'Programs')] : [])
|
|
641
|
+
: platform === 'darwin' ? ['/Applications', path.join(home, 'Applications')] : [];
|
|
642
|
+
for (const dir of appDirs) {
|
|
643
|
+
const name = list(dir).find((n) => pattern.test(n)
|
|
644
|
+
&& (platform !== 'darwin' || n.endsWith('.app')));
|
|
645
|
+
if (name) return path.join(dir, name);
|
|
646
|
+
}
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** `readdirSync`, with a missing or unreadable dir as no entries. */
|
|
651
|
+
function listDir(dir) {
|
|
652
|
+
try { return readdirSync(dir); } catch { return []; }
|
|
598
653
|
}
|
|
599
654
|
|
|
600
655
|
/**
|
|
@@ -602,16 +657,25 @@ export function hostTools({ lookup = which } = {}) {
|
|
|
602
657
|
* answer knowing it — and when NONE is, ask whether to stop and install one first. It never
|
|
603
658
|
* installs one: on a managed machine that is IT's call, and each tool has its own installer.
|
|
604
659
|
* A missing tool among several present is a line, not a question; nobody needs all four.
|
|
660
|
+
* When none is found, the user can name where theirs is before being asked to stop — the
|
|
661
|
+
* search covers PATH and the usual per-user places, not every folder an installer may pick.
|
|
605
662
|
* `false` is the user choosing to stop.
|
|
606
663
|
*/
|
|
607
664
|
export function toolsReport(tools = hostTools()) {
|
|
608
665
|
printOut('AI coding tools on this machine:\n');
|
|
609
|
-
for (const [label, present, url] of tools) {
|
|
610
|
-
|
|
666
|
+
for (const [label, present, url, where] of tools) {
|
|
667
|
+
if (!present) printOut(` -- ${label} not found — install: ${url}\n`);
|
|
668
|
+
else printOut(where ? ` ok ${label} (${where}, not on PATH)\n` : ` ok ${label}\n`);
|
|
611
669
|
}
|
|
612
670
|
if (tools.some(([, present]) => present)) { printOut('\n'); return true; }
|
|
613
|
-
printOut('\nNone of them
|
|
614
|
-
|
|
671
|
+
printOut('\nNone of them was found on PATH or in the usual per-user folders.\n');
|
|
672
|
+
// Quotes stripped: a path dragged into a Windows console arrives wrapped in them.
|
|
673
|
+
const given = ask('Installed somewhere else? Path to the tool (Enter to skip)')
|
|
674
|
+
.replace(/^"(.*)"$/, '$1');
|
|
675
|
+
if (given && existsSync(given)) { printOut(` ok ${given}\n\n`); return true; }
|
|
676
|
+
if (given) printOut(` -- nothing at ${given}\n`);
|
|
677
|
+
printOut('Install the one you use (links above), then run setup again — or go on and pick the '
|
|
678
|
+
+ 'plain bundle, which any AGENT.md tool can read.\n');
|
|
615
679
|
if (confirm('Continue without one?', false)) { printOut('\n'); return true; }
|
|
616
680
|
printOut('Stopped. Run setup again once a tool is installed.\n');
|
|
617
681
|
return false;
|
package/js/maintain/update.mjs
CHANGED
|
@@ -796,8 +796,8 @@ export async function upgrade(ref = null, themeArg = null) {
|
|
|
796
796
|
* `_harness_lifecycle.cmd_upgrade` — the harness verb, which is `export_improvements()` and
|
|
797
797
|
* then `upgrade()`.
|
|
798
798
|
*
|
|
799
|
-
* THE EXPORT RUNS FIRST AND MUST NEVER BLOCK.
|
|
800
|
-
* harness
|
|
799
|
+
* THE EXPORT RUNS FIRST AND MUST NEVER BLOCK. A deployed harness can carry in-place edits (by
|
|
800
|
+
* hand, or by a harness older than the notebook notes) and the rebuild is about to overwrite them, so the drift has to be
|
|
801
801
|
* captured against the PRE-refresh source. Anything it throws is a warning on stderr and the
|
|
802
802
|
* upgrade continues — a failed export is a lost note, a blocked upgrade is a broken install.
|
|
803
803
|
*/
|
package/js/web/actions.mjs
CHANGED
|
@@ -475,6 +475,14 @@ export function buildActionCmds(state, body) {
|
|
|
475
475
|
const [, , ...rest] = installProfile(host, scope, state.root).argv;
|
|
476
476
|
return actionCommands('build', { buildArgv: ['--theme', theme, ...rest] });
|
|
477
477
|
}
|
|
478
|
+
// Only a GLOBAL emit has a home of its own. Any other carries no folder on this route, so the
|
|
479
|
+
// generator fell back to its default `./Harness` — and jobs run with the Geneseed checkout as
|
|
480
|
+
// cwd, so a Bob "project" install landed inside the clone (reported 2026-10), where the doctor's
|
|
481
|
+
// bundle check then read it as 178 missing files. The `deploy` action takes a chosen folder.
|
|
482
|
+
if (!emit.endsWith('-global')) {
|
|
483
|
+
return { error: `${emit} installs into a folder you choose — use Deploy with a folder, `
|
|
484
|
+
+ 'not Build' };
|
|
485
|
+
}
|
|
478
486
|
return actionCommands('build', {
|
|
479
487
|
theme, emit, footprint: state.footprint, posture: state.posture, mode: state.mode,
|
|
480
488
|
trust: state.trust,
|
package/js/web/api.mjs
CHANGED
|
@@ -8,21 +8,21 @@
|
|
|
8
8
|
* bind paths to these functions are `routes.mjs`. This file imports none of its route siblings,
|
|
9
9
|
* which is what keeps the old `api ↔ actions` and `api ↔ activity` cycles from coming back.
|
|
10
10
|
*/
|
|
11
|
-
import { statSync } from 'node:fs';
|
|
11
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
|
|
14
|
-
import { CONFIG, ROOT, THEMES, discoverNames, loadKits, makeCfg } from '../build/source.mjs';
|
|
14
|
+
import { CONFIG, CREDIT_RE, ROOT, SRC, THEMES, discoverNames, loadKits, makeCfg, upstreamOf } from '../build/source.mjs';
|
|
15
15
|
import { readVersion, sourceFingerprint } from '../build/version.mjs';
|
|
16
16
|
import { sourceReleaseVersion } from '../hosts/opencode.mjs';
|
|
17
17
|
import { alwaysOnCost } from '../inspect/cost.mjs';
|
|
18
18
|
import { installProfile } from '../build/generate.mjs';
|
|
19
19
|
import { diffCollect, renderMemo } from '../inspect/diff.mjs';
|
|
20
20
|
import { excludesSnapshot } from '../inspect/excludes.mjs';
|
|
21
|
-
import {
|
|
21
|
+
import { VERSION_MARKER, resolvePath } from '../hosts/hosts.mjs';
|
|
22
22
|
import {
|
|
23
23
|
footprintOfDir, gatesOfDir, installState, installTargets, legacyProcessCarrierOfDir, modeOfDir,
|
|
24
24
|
trustOfDir,
|
|
25
|
-
postureOfDir, readJsonMaybe, themeOfDir,
|
|
25
|
+
postureOfDir, readJsonMaybe, readMaybe, themeOfDir,
|
|
26
26
|
} from '../hosts/installs.mjs';
|
|
27
27
|
import { PRESETS } from '../loop/score.mjs';
|
|
28
28
|
import { loadCatalog, catalogProblems } from '../loop/catalog.mjs';
|
|
@@ -34,7 +34,7 @@ import { comparePaths, normcase } from '../lib/paths.mjs';
|
|
|
34
34
|
import {
|
|
35
35
|
configItems, deployed, dget, memoryItems, notebookItems, wikiItems,
|
|
36
36
|
} from './catalog.mjs';
|
|
37
|
-
import { stampMinute } from './state.mjs';
|
|
37
|
+
import { stampMinute, viewCfg } from './state.mjs';
|
|
38
38
|
import { updateStatus } from './update-check.mjs';
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -108,16 +108,9 @@ export function apiDiff(state) {
|
|
|
108
108
|
/** The union across every global install, verbatim. */
|
|
109
109
|
export const apiExcludes = () => excludesSnapshot();
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*/
|
|
115
|
-
export function viewCfg(host, scope, root) {
|
|
116
|
-
if (scope === 'project' && CLAUDE_STYLE.includes(host)) {
|
|
117
|
-
return path.join(root, HOSTS.find((h) => h.host === host).projectMarker);
|
|
118
|
-
}
|
|
119
|
-
return root;
|
|
120
|
-
}
|
|
111
|
+
// `viewCfg` moved to `state.mjs`, which needs it to pick a default view and cannot import
|
|
112
|
+
// this module back (this one imports `stampMinute` from it).
|
|
113
|
+
export { viewCfg } from './state.mjs';
|
|
121
114
|
|
|
122
115
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
123
116
|
|
|
@@ -233,6 +226,31 @@ export function apiFleet(state) {
|
|
|
233
226
|
return { installs, source_fp: sourceFp, doctor: state.doctor };
|
|
234
227
|
}
|
|
235
228
|
|
|
229
|
+
/** Source files of a kit member: `<dir>/<name>.md`, or every `.md` in a `<dir>/<name>/` folder. */
|
|
230
|
+
function memberTexts(dir, name) {
|
|
231
|
+
const one = readMaybe(path.join(SRC, dir, `${name}.md`));
|
|
232
|
+
if (one !== null && one !== undefined) return [one];
|
|
233
|
+
const d = path.join(SRC, dir, name);
|
|
234
|
+
try { return readdirSync(d).filter((f) => f.endsWith('.md')).map((f) => readMaybe(path.join(d, f)) ?? ''); } catch { return []; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Upstream credits cited by a kit's rules and member sources; an unknown key is skipped, never a 500. */
|
|
238
|
+
function kitCredits(m, rules) {
|
|
239
|
+
const texts = [rules, ...(m.skills ?? []).flatMap((n) => memberTexts('skills', n)),
|
|
240
|
+
...(m.agents ?? []).flatMap((n) => memberTexts('agents', n))];
|
|
241
|
+
const keys = new Set();
|
|
242
|
+
for (const t of texts) for (const x of t.matchAll(CREDIT_RE)) keys.add(x[1]);
|
|
243
|
+
const out = [];
|
|
244
|
+
for (const key of [...keys].sort()) {
|
|
245
|
+
let u;
|
|
246
|
+
try { u = upstreamOf(key); } catch { u = null; }
|
|
247
|
+
if (!u) continue;
|
|
248
|
+
out.push({ key, repo: u.repo, author: u.author, license: u.license, commit: u.commit,
|
|
249
|
+
compareUrl: `https://github.com/${u.repo}/compare/${u.commit}...HEAD` });
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
|
|
236
254
|
/**
|
|
237
255
|
* EVERY INSTALL'S KITS — the Kits tab. Its own route, like `apiFleet`: it re-reads each carrier
|
|
238
256
|
* and the kit manifests, which the sidebar's `/api/installs` refetch must not pay for.
|
|
@@ -247,8 +265,12 @@ export function apiKits() {
|
|
|
247
265
|
try { known = loadKits(); } catch (e) {
|
|
248
266
|
return { kits: [], installs: [], error: String(e?.message ?? e) };
|
|
249
267
|
}
|
|
250
|
-
const kits = [...known].map(([name, m]) =>
|
|
251
|
-
|
|
268
|
+
const kits = [...known].map(([name, m]) => {
|
|
269
|
+
const rules = readMaybe(path.join(SRC, 'kits', name, 'rules.md')) ?? '';
|
|
270
|
+
return { name, description: m.description ?? '', skills: m.skills ?? [], agents: m.agents ?? [],
|
|
271
|
+
rules, rulesWordBudget: m.rulesWordBudget ?? null, banned: m.banned ?? [],
|
|
272
|
+
credits: kitCredits(m, rules) };
|
|
273
|
+
});
|
|
252
274
|
const installs = [];
|
|
253
275
|
for (const [host, scope, root] of installTargets()) {
|
|
254
276
|
const st = installState(root, host, scope);
|
package/js/web/catalog.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import { readText, withDiscardableStderr, isDir, isFile } from '../lib/fs.mjs';
|
|
|
24
24
|
import { comparePaths, normcase, within } from '../lib/paths.mjs';
|
|
25
25
|
import { stripWhitespace } from '../lib/text.mjs';
|
|
26
26
|
import { readJsonc } from '../hosts/settings.mjs';
|
|
27
|
+
import { loadKits } from '../build/source.mjs';
|
|
27
28
|
|
|
28
29
|
/** A requested catalog section or item that does not exist. */
|
|
29
30
|
export class NotFound extends Error {}
|
|
@@ -466,16 +467,29 @@ function constitutionItems(inv) {
|
|
|
466
467
|
];
|
|
467
468
|
}
|
|
468
469
|
|
|
470
|
+
/** `Map<'skills/x' | 'agents/y', kit>` from the shipped manifests; a broken manifest means no badges, not a 500. */
|
|
471
|
+
function kitOwners() {
|
|
472
|
+
const owner = new Map();
|
|
473
|
+
try {
|
|
474
|
+
for (const [kit, m] of loadKits()) {
|
|
475
|
+
for (const n of m.skills ?? []) owner.set(`skill:${n}`, kit);
|
|
476
|
+
for (const n of m.agents ?? []) owner.set(`agent:${n}`, kit);
|
|
477
|
+
}
|
|
478
|
+
} catch { /* shortcut: unreadable manifests hide the badge; apiKits reports the error */ }
|
|
479
|
+
return owner;
|
|
480
|
+
}
|
|
481
|
+
|
|
469
482
|
export function apiCatalog(state, section) {
|
|
470
483
|
if (!SECTIONS.includes(section)) throw new NotFound(section);
|
|
471
484
|
const inv = state.inventory;
|
|
472
485
|
let items;
|
|
486
|
+
const owner = kitOwners();
|
|
473
487
|
if (section === 'agents') {
|
|
474
488
|
items = inv.agents.map((e) => ({ name: e.name, title: e.name, desc: e.desc,
|
|
475
|
-
source: e.source ?? null, status: e.status ?? 'unknown' }));
|
|
489
|
+
source: e.source ?? null, status: e.status ?? 'unknown', kit: owner.get(`agent:${e.name}`) ?? null }));
|
|
476
490
|
} else if (section === 'skills') {
|
|
477
491
|
items = inv.skills.map((e) => ({ name: e.name, title: e.name, desc: e.desc,
|
|
478
|
-
source: e.source ?? null, klass: e.klass ?? 'build', status: e.status ?? 'unknown' }));
|
|
492
|
+
source: e.source ?? null, klass: e.klass ?? 'build', status: e.status ?? 'unknown', kit: owner.get(`skill:${e.name}`) ?? null }));
|
|
479
493
|
} else if (section === 'laws') {
|
|
480
494
|
items = constitutionItems(inv);
|
|
481
495
|
} else if (section === 'memory') {
|
|
@@ -499,6 +513,7 @@ export function apiItem(state, type, name) {
|
|
|
499
513
|
links: resolveLinks(state, e.body), source: e.source ?? null };
|
|
500
514
|
if (type === 'skill') out.klass = e.klass ?? 'build';
|
|
501
515
|
out.status = e.status ?? 'unknown';
|
|
516
|
+
out.kit = kitOwners().get(`${type}:${name}`) ?? null;
|
|
502
517
|
return out;
|
|
503
518
|
}
|
|
504
519
|
if (type === 'law') {
|
package/js/web/handler.mjs
CHANGED
|
@@ -303,6 +303,7 @@ export function makeHandler(state, jm, token, dist, holder = null) {
|
|
|
303
303
|
// Build resolves its argv off the install (`buildActionCmds`); the other actions take no
|
|
304
304
|
// axes and self-resolve the deployed theme downstream.
|
|
305
305
|
const cmds = action === 'build' ? buildActionCmds(state, body) : actionCommands(action);
|
|
306
|
+
if (cmds && Object.hasOwn(cmds, 'error')) return sendJson(res, cmds, 400, ae);
|
|
306
307
|
if (!isTruthy(cmds)) {
|
|
307
308
|
return sendJson(res, { error: `unknown action ${action}` }, 404, ae);
|
|
308
309
|
}
|
package/js/web/server.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import { isFile, printOut } from '../lib/fs.mjs';
|
|
|
27
27
|
import { which } from '../lib/paths.mjs';
|
|
28
28
|
import { parseIntStrict } from '../lib/text.mjs';
|
|
29
29
|
import { promptLine } from '../lib/prompt.mjs';
|
|
30
|
-
import { warmDoctor, webState } from './state.mjs';
|
|
30
|
+
import { fallbackView, warmDoctor, webState } from './state.mjs';
|
|
31
31
|
import { clearDaemon, codeStamp, openUrl, readDaemon, restartDaemon, startDaemon, statusDaemon, stopDaemon, writeDaemon } from './daemon.mjs';
|
|
32
32
|
import { makeHandler } from './handler.mjs';
|
|
33
33
|
import { JobManager } from './jobs.mjs';
|
|
@@ -132,6 +132,12 @@ export async function serve({ theme = null, port = 4747, openBrowser = true,
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
const state = webState(theme);
|
|
135
|
+
// The daemon record and the job file stay at the DEFAULT target, captured before the view
|
|
136
|
+
// moves: `geneseed web stop`/`status` find them through `webState(theme).target`, which never
|
|
137
|
+
// moves, and a view that followed a Bob-only install into ~/.bob would hide the record.
|
|
138
|
+
const home = state.target;
|
|
139
|
+
const fallback = fallbackView(home);
|
|
140
|
+
if (fallback) state.selectView(...fallback);
|
|
135
141
|
if (!existsSync(join(state.target, GLOBAL_MANIFEST))) {
|
|
136
142
|
printOut(`[web] no deployed harness at ${state.target}.\n`);
|
|
137
143
|
printOut(' Run `geneseed setup` first — serving anyway (read-only UI).\n');
|
|
@@ -143,7 +149,7 @@ export async function serve({ theme = null, port = 4747, openBrowser = true,
|
|
|
143
149
|
const holder = {};
|
|
144
150
|
// The console's job list survives a reload and a restart because it is a FILE on disk,
|
|
145
151
|
// not held in process memory.
|
|
146
|
-
const jm = new JobManager(join(
|
|
152
|
+
const jm = new JobManager(join(home, '.geneseed-web-runs.json'));
|
|
147
153
|
const srv = createServer(makeHandler(state, jm, token, dist, holder));
|
|
148
154
|
holder.srv = srv;
|
|
149
155
|
return new Promise((done) => {
|
|
@@ -157,7 +163,7 @@ export async function serve({ theme = null, port = 4747, openBrowser = true,
|
|
|
157
163
|
const hostPort = /** @type {import('node:net').AddressInfo} */ (srv.address()).port;
|
|
158
164
|
const url = `http://127.0.0.1:${hostPort}`;
|
|
159
165
|
if (daemon) {
|
|
160
|
-
writeDaemon(
|
|
166
|
+
writeDaemon(home, {
|
|
161
167
|
pid: process.pid, port: hostPort, url,
|
|
162
168
|
token, theme: state.theme, started: Math.floor(Date.now() / 1000), code: startCode,
|
|
163
169
|
});
|
|
@@ -177,8 +183,8 @@ export async function serve({ theme = null, port = 4747, openBrowser = true,
|
|
|
177
183
|
});
|
|
178
184
|
srv.on('close', () => {
|
|
179
185
|
if (daemon) {
|
|
180
|
-
const st = readDaemon(
|
|
181
|
-
if (!st || st.pid === process.pid) clearDaemon(
|
|
186
|
+
const st = readDaemon(home);
|
|
187
|
+
if (!st || st.pid === process.pid) clearDaemon(home);
|
|
182
188
|
}
|
|
183
189
|
done(0);
|
|
184
190
|
});
|
package/js/web/state.mjs
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
|
|
11
|
-
import { opencodeConfigDir } from '../hosts/hosts.mjs';
|
|
11
|
+
import { CLAUDE_STYLE, GLOBAL_MANIFEST, HOSTS, opencodeConfigDir } from '../hosts/hosts.mjs';
|
|
12
12
|
import {
|
|
13
|
-
EMIT_HOST_SCOPE, footprintOfDir,
|
|
14
|
-
trustOfDir,
|
|
13
|
+
EMIT_HOST_SCOPE, footprintOfDir, installState, installTargets, installedDefaults, modeOfDir,
|
|
14
|
+
postureOfDir, themeOfDir, trustOfDir,
|
|
15
15
|
} from '../hosts/installs.mjs';
|
|
16
16
|
import { doctorCollect } from '../inspect/doctor.mjs';
|
|
17
17
|
import { DEFAULT_PRESET } from '../loop/score.mjs';
|
|
@@ -19,6 +19,36 @@ import { readText, isFile } from '../lib/fs.mjs';
|
|
|
19
19
|
import { stripWhitespace } from '../lib/text.mjs';
|
|
20
20
|
import { inventoryFor } from './catalog.mjs';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The data dir an install's inventory/memory/diff is read from. Host-driven, so a new
|
|
24
|
+
* nested-marker host cannot silently read the bare root.
|
|
25
|
+
*/
|
|
26
|
+
export function viewCfg(host, scope, root) {
|
|
27
|
+
if (scope === 'project' && CLAUDE_STYLE.includes(host)) {
|
|
28
|
+
return path.join(root, HOSTS.find((h) => h.host === host).projectMarker);
|
|
29
|
+
}
|
|
30
|
+
return root;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `[target, root]` for the install the console should show when nothing picked one, or null
|
|
35
|
+
* when the current target is already deployed or nothing is.
|
|
36
|
+
*
|
|
37
|
+
* The console used to open on the OpenCode global dir unconditionally. On a machine with only
|
|
38
|
+
* IBM Bob (reported 2026-10) nothing is deployed there, so Overview showed the onboarding form
|
|
39
|
+
* over a perfectly good `~/.bob`, and the selector that could re-point it hides itself below two
|
|
40
|
+
* installs. So when `current` holds no manifest, the first ACTIVE install `installTargets`
|
|
41
|
+
* finds is shown instead — a global before a project, in its host order.
|
|
42
|
+
*/
|
|
43
|
+
export function fallbackView(current, targets = installTargets()) {
|
|
44
|
+
if (existsSync(path.join(current, GLOBAL_MANIFEST))) return null;
|
|
45
|
+
const active = targets.filter(([host, scope, root]) => {
|
|
46
|
+
try { return installState(root, host, scope) === 'active'; } catch { return false; }
|
|
47
|
+
});
|
|
48
|
+
const pick = active.find(([, scope]) => scope === 'global') || active[0];
|
|
49
|
+
return pick ? [viewCfg(...pick), pick[2]] : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
/**
|
|
23
53
|
* `YYYY-MM-DD HH:MM`, LOCAL time — `toISOString` would be UTC, which reads wrong in every
|
|
24
54
|
* timezone but one for a user-facing timestamp.
|
|
@@ -154,6 +184,11 @@ export function webState(theme = null, target = null) {
|
|
|
154
184
|
st._doctor = null;
|
|
155
185
|
};
|
|
156
186
|
st.refresh = ({ doctor = true } = {}) => {
|
|
187
|
+
// A job may have deployed the first install while the view still points at an empty
|
|
188
|
+
// default (the onboarding Build), or removed the one in view: follow it to one that exists.
|
|
189
|
+
// Only on a full refresh: `{ doctor: false }` marks a mutation that cannot change installs.
|
|
190
|
+
const fallback = doctor ? fallbackView(st.target) : null;
|
|
191
|
+
if (fallback) { st.selectView(...fallback); return; }
|
|
157
192
|
st._inv = null;
|
|
158
193
|
if (doctor) st._doctor = null;
|
|
159
194
|
st.emit = st.detectEmit() || st.emit;
|
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.
|
|
4
|
+
"version": "4.14.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",
|
package/registry.json
CHANGED
|
@@ -99,6 +99,20 @@
|
|
|
99
99
|
"added": "2026-06-08",
|
|
100
100
|
"last_verified": ""
|
|
101
101
|
},
|
|
102
|
+
"agents/react-engineer": {
|
|
103
|
+
"status": "experimental",
|
|
104
|
+
"version": "1.0.0",
|
|
105
|
+
"owner": "Guillaume Lemer",
|
|
106
|
+
"added": "2026-10-21",
|
|
107
|
+
"last_verified": ""
|
|
108
|
+
},
|
|
109
|
+
"agents/react-reviewer": {
|
|
110
|
+
"status": "experimental",
|
|
111
|
+
"version": "1.0.0",
|
|
112
|
+
"owner": "Guillaume Lemer",
|
|
113
|
+
"added": "2026-10-21",
|
|
114
|
+
"last_verified": ""
|
|
115
|
+
},
|
|
102
116
|
"agents/researcher": {
|
|
103
117
|
"status": "approved",
|
|
104
118
|
"version": "1.0.0",
|
|
@@ -400,6 +414,20 @@
|
|
|
400
414
|
"added": "2026-09-06",
|
|
401
415
|
"last_verified": ""
|
|
402
416
|
},
|
|
417
|
+
"skills/react": {
|
|
418
|
+
"status": "experimental",
|
|
419
|
+
"version": "1.0.0",
|
|
420
|
+
"owner": "Guillaume Lemer",
|
|
421
|
+
"added": "2026-10-21",
|
|
422
|
+
"last_verified": ""
|
|
423
|
+
},
|
|
424
|
+
"skills/react-testing": {
|
|
425
|
+
"status": "experimental",
|
|
426
|
+
"version": "1.0.0",
|
|
427
|
+
"owner": "Guillaume Lemer",
|
|
428
|
+
"added": "2026-10-21",
|
|
429
|
+
"last_verified": ""
|
|
430
|
+
},
|
|
403
431
|
"skills/react-view-transitions": {
|
|
404
432
|
"status": "approved",
|
|
405
433
|
"version": "1.0.0",
|
package/src/AGENT.md.tmpl
CHANGED
|
@@ -187,9 +187,9 @@ contract you do not already know.
|
|
|
187
187
|
**Rule of delegation:** read-only investigation can be dispatched freely; any
|
|
188
188
|
{{AGENT}} that *writes* must return a summary of exactly what it changed. Every
|
|
189
189
|
{{AGENT}} spec ends with a **self-improvement loop**: when a report carries a
|
|
190
|
-
`spec-feedback:` line, weigh it —
|
|
191
|
-
|
|
192
|
-
{{DOCTRINE:persist-insight}}'s bar.
|
|
190
|
+
`spec-feedback:` line, weigh it — record a real flaw (with the user's assent) in
|
|
191
|
+
`{{DIR_NOTEBOOK}}/agent-notes/<name>.md` and pass it to later dispatches;
|
|
192
|
+
{{MEMORY}} gets a record only when it clears {{DOCTRINE:persist-insight}}'s bar.
|
|
193
193
|
|
|
194
194
|
<!-- LEAN:begin -->
|
|
195
195
|
**Dispatching a multi-part goal.** When a goal spans several capabilities,
|
|
@@ -293,8 +293,8 @@ When a task repeats and no {{SKILL}} covers it, forge one through the
|
|
|
293
293
|
[skill-forge]({{DIR_SKILLS}}/skill-forge.md) {{SKILL}}: copy
|
|
294
294
|
[`{{DIR_SKILLS}}/_template.md`]({{DIR_SKILLS}}/_template.md), keep it to one coherent
|
|
295
295
|
domain ({{DOCTRINE:automate-repetition}}), and list it wherever this file carries a catalogue. Every {{SKILL}} ends with a
|
|
296
|
-
**self-improvement loop** — honour it:
|
|
297
|
-
file, or
|
|
296
|
+
**self-improvement loop** — honour it: a lesson goes to its notes, never into
|
|
297
|
+
the file, or to {{MEMORY}} ({{DOCTRINE:persist-insight}}).
|
|
298
298
|
|
|
299
299
|
---
|
|
300
300
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Self-improvement
|
|
2
|
+
|
|
3
|
+
If this spec misled you — an input you needed but were not given, a boundary
|
|
4
|
+
that proved wrong, a step you could not execute — end your report with one line:
|
|
5
|
+
`spec-feedback: [agent|general] <the one-line fix> — seen: <what happened, against
|
|
6
|
+
what this spec led you to expect>`. `general` marks a fix other {{AGENTS}} need too.
|
|
7
|
+
Omit the line when there is no friction. The caller weighs the feedback and, with the
|
|
8
|
+
user's assent, writes a real flaw, dated, into `{{DIR_NOTEBOOK}}/agent-notes/<name>.md`
|
|
9
|
+
— never into this rendered file, which a rebuild overwrites — and hands those notes
|
|
10
|
+
to every later dispatch of this {{AGENT}}. It records the feedback to {{MEMORY}} only if it clears
|
|
11
|
+
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
package/src/agents/_template.md
CHANGED
|
@@ -67,11 +67,4 @@ tells pipeline orchestration who to recruit; it changes nothing about how this
|
|
|
67
67
|
- **Delivers:** what it must hand the next seat or the parent — same shape as
|
|
68
68
|
its own output contract above, nothing looser.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
If this spec misled you — an input you needed but were not given, a boundary
|
|
73
|
-
that proved wrong, a step you could not execute — end your report with one line:
|
|
74
|
-
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
75
|
-
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
76
|
-
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
77
|
-
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
70
|
+
<!-- INCLUDE: agents/_self-improvement.md -->
|
package/src/agents/advocate.md
CHANGED
|
@@ -26,11 +26,4 @@
|
|
|
26
26
|
- A tight brief: the position in one line, the 2–4 load-bearing arguments for it, the key supporting evidence (`file:line` or facts), and the one objection it must beat. No hedging, no filler.
|
|
27
27
|
- If the motion has fewer than two defensible upsides, report that the case cannot honestly be made — an invented argument loses the council more than a conceded seat.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
If this spec misled you — an input you needed but were not given, a boundary
|
|
32
|
-
that proved wrong, a step you could not execute — end your report with one line:
|
|
33
|
-
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
34
|
-
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
35
|
-
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
36
|
-
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
29
|
+
<!-- INCLUDE: agents/_self-improvement.md -->
|
package/src/agents/architect.md
CHANGED
|
@@ -48,11 +48,4 @@ independently.)*
|
|
|
48
48
|
- **Delivers:** the plan for the developer seat to implement, same shape as its
|
|
49
49
|
own output contract above.
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
If this spec misled you — an input you needed but were not given, a boundary
|
|
54
|
-
that proved wrong, a step you could not execute — end your report with one line:
|
|
55
|
-
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
56
|
-
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
57
|
-
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
58
|
-
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
51
|
+
<!-- INCLUDE: agents/_self-improvement.md -->
|
package/src/agents/developer.md
CHANGED
|
@@ -56,11 +56,4 @@ independently.)*
|
|
|
56
56
|
- **Delivers:** the changed files and a one-line summary, handed to the tester
|
|
57
57
|
seat for validation.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
If this spec misled you — an input you needed but were not given, a boundary
|
|
62
|
-
that proved wrong, a step you could not execute — end your report with one line:
|
|
63
|
-
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
64
|
-
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
65
|
-
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
66
|
-
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
59
|
+
<!-- INCLUDE: agents/_self-improvement.md -->
|