geneseed 4.9.3 → 4.10.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 +31 -0
- package/README.md +14 -1
- package/SHIPPED.md +1 -1
- package/adapters/claude-code/README.md +1 -1
- package/adapters/opencode/plugins/geneseed-guard.js +20 -1
- package/docs/README.md +1 -1
- package/docs/concepts/collaboration.md +1 -1
- package/docs/concepts/enforcement-table.md +2 -2
- package/docs/concepts/foreman-mode.md +1 -1
- package/docs/concepts/hooks.md +3 -2
- package/docs/concepts/kits.md +79 -0
- package/docs/concepts/loop-branch.md +1 -1
- package/docs/concepts/loop-bricks.md +1 -1
- package/docs/concepts/loop-contracts.md +1 -1
- package/docs/concepts/loop-gates.md +1 -1
- package/docs/concepts/loop-notes.md +1 -1
- package/docs/concepts/loop-risk.md +1 -1
- package/docs/concepts/loop-templates.md +1 -1
- package/docs/concepts/loops.md +1 -1
- package/docs/concepts/lsp.md +1 -1
- package/docs/extending.md +108 -4
- package/docs/guides/install-scripted.md +1 -1
- package/docs/reference/glossary.md +1 -0
- package/docs/reference/plugin-guard.md +1 -1
- package/docs/understand/enforced-vs-asked.md +1 -1
- package/docs/understand/machine-claude.md +1 -1
- package/docs/web-ui.md +4 -0
- package/harness.config.json +1 -1
- package/js/build/args.mjs +27 -6
- package/js/build/driver.mjs +29 -8
- package/js/build/emit-claude.mjs +3 -1
- package/js/build/generate.mjs +10 -5
- package/js/build/render.mjs +94 -11
- package/js/build/source.mjs +117 -4
- package/js/cli-table.json +15 -0
- package/js/hosts/hooks.mjs +2 -1
- package/js/hosts/installs.mjs +35 -2
- package/js/hosts/loss.mjs +23 -2
- package/js/hosts/native.mjs +14 -4
- package/js/inspect/checks-authoring.mjs +6 -3
- package/js/inspect/checks-build.mjs +7 -1
- package/js/inspect/checks-counts.mjs +153 -6
- package/js/inspect/checks-repo.mjs +95 -20
- package/js/inspect/cost.mjs +1 -1
- package/js/inspect/diff.mjs +3 -1
- package/js/inspect/inventory.mjs +1 -0
- package/js/inspect/status.mjs +44 -3
- package/js/inspect/validate.mjs +3 -3
- package/js/maintain/setup.mjs +30 -6
- package/js/maintain/update.mjs +1 -1
- package/js/web/actions.mjs +30 -6
- package/js/web/api.mjs +40 -3
- package/js/web/routes.mjs +2 -1
- package/package.json +1 -1
- package/registry.json +63 -0
- package/src/AGENT.md.tmpl +2 -2
- package/src/agents/devops-engineer.md +45 -0
- package/src/agents/docker-expert.md +45 -0
- package/src/agents/kubernetes-specialist.md +45 -0
- package/src/agents/reviewer.md +2 -0
- package/src/agents/spring-boot-engineer.md +48 -0
- package/src/agents/spring-reviewer.md +49 -0
- package/src/agents/spring-security.md +50 -0
- package/src/kits/spring-boot/rules.md +10 -0
- package/src/kits/spring-boot.json +23 -0
- package/src/laws/universal.md +13 -13
- package/src/licenses/mattpocock-skills.txt +21 -0
- package/src/licenses/piomin-spring-boot.txt +190 -0
- package/src/licenses/superpowers.txt +21 -0
- package/src/licenses/vibe-wise.txt +21 -0
- package/src/skills/brainstorm/builder-check.md +2 -0
- package/src/skills/brainstorm.md +4 -0
- package/src/skills/codebase-design.md +4 -0
- package/src/skills/daydream/VENDOR.md +7 -7
- package/src/skills/debug.md +5 -1
- package/src/skills/develop.md +2 -0
- package/src/skills/geneseed-code-review.md +6 -2
- package/src/skills/git-history.md +2 -0
- package/src/skills/handoff.md +2 -0
- package/src/skills/learn-mode.md +3 -2
- package/src/skills/parallel-agents.md +2 -0
- package/src/skills/pipeline.md +2 -0
- package/src/skills/plan.md +4 -0
- package/src/skills/prototype.md +2 -0
- package/src/skills/react-view-transitions/AGENTS.md +646 -221
- package/src/skills/react-view-transitions/SKILL.md +28 -16
- package/src/skills/react-view-transitions/VENDOR.md +10 -8
- package/src/skills/react-view-transitions/references/css-recipes.md +72 -2
- package/src/skills/react-view-transitions/references/implementation.md +14 -26
- package/src/skills/react-view-transitions/references/nextjs.md +94 -37
- package/src/skills/react-view-transitions/references/patterns.md +60 -48
- package/src/skills/react-view-transitions/references/troubleshooting.md +47 -0
- package/src/skills/research.md +2 -0
- package/src/skills/review-response.md +2 -0
- package/src/skills/rule.md +2 -0
- package/src/skills/ship.md +2 -0
- package/src/skills/skill-forge.md +1 -0
- package/src/skills/spring-boot/cloud.md +103 -0
- package/src/skills/spring-boot/java-modern.md +71 -0
- package/src/skills/spring-boot/logging.md +89 -0
- package/src/skills/spring-boot/security.md +112 -0
- package/src/skills/spring-boot/web.md +112 -0
- package/src/skills/spring-boot.md +57 -0
- package/src/skills/spring-data-jpa/patterns.md +68 -0
- package/src/skills/spring-data-jpa.md +81 -0
- package/src/skills/spring-testing.md +93 -0
- package/src/skills/wayfinder.md +2 -0
- package/src/upstreams.json +59 -0
- package/themes/_TEMPLATE.json +9 -0
- package/themes/biker.json +9 -0
- package/themes/commentator.json +9 -0
- package/themes/cyberpunk.json +9 -0
- package/themes/gamer.json +9 -0
- package/themes/imperial.json +9 -0
- package/themes/joker.json +9 -0
- package/themes/marvin.json +9 -0
- package/themes/mean.json +9 -0
- package/themes/military.json +9 -0
- package/themes/neutral.json +9 -0
- package/themes/pirate.json +9 -0
- package/themes/sports.json +9 -0
- package/themes/verstappen.json +9 -0
- package/themes/wizard.json +9 -0
- package/web/dist/assets/{Activity-C8sZVIUU.js → Activity-BjyJaf8e.js} +1 -1
- package/web/dist/assets/{ActivityDetail-Cc6vWUS4.js → ActivityDetail-Bo9nRDNV.js} +1 -1
- package/web/dist/assets/{Dashboard-C5ATa3ow.js → Dashboard-BQJGU7vE.js} +2 -2
- package/web/dist/assets/{Docs-tDW7i7mo.js → Docs-DLVckkew.js} +1 -1
- package/web/dist/assets/{GreenhouseView-zk2Uzc3A.js → GreenhouseView--DML7CW3.js} +1 -1
- package/web/dist/assets/{Installs-CB_rvbBP.js → Installs-CxCiln1F.js} +2 -2
- package/web/dist/assets/{JournalView-Bfj27XHC.js → JournalView-ymgZleHi.js} +1 -1
- package/web/dist/assets/{Laws-BA3CzUf5.js → Laws-BkKnZOBI.js} +1 -1
- package/web/dist/assets/Library-C8ercIZv.js +1 -0
- package/web/dist/assets/{Loops-DaPk-ckL.js → Loops-C95zhbFl.js} +1 -1
- package/web/dist/assets/{OperatorHudView-CyiGhiHk.js → OperatorHudView-B9qwc3w3.js} +1 -1
- package/web/dist/assets/{Personal-Bwd27CmT.js → Personal-DyR72AF-.js} +1 -1
- package/web/dist/assets/{Tabs-BOD7Kaz1.js → Tabs-ChCr_HqV.js} +1 -1
- package/web/dist/assets/api-BfpZmUMI.js +1 -0
- package/web/dist/assets/{drawable-CyLe1gtW.js → drawable-CgYi0Dtg.js} +1 -1
- package/web/dist/assets/index-B32EvfDT.js +2 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/Library-BWwiy9Vb.js +0 -1
- package/web/dist/assets/api-CiXlkbe0.js +0 -1
- package/web/dist/assets/index-DQXG_mqo.js +0 -2
package/js/inspect/status.mjs
CHANGED
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
59
59
|
import path from 'node:path';
|
|
60
60
|
|
|
61
|
-
import { GATE_SWITCHES, THEMES, ROOT, makeCfg } from '../build/source.mjs';
|
|
61
|
+
import { GATE_SWITCHES, THEMES, ROOT, loadKits, makeCfg } from '../build/source.mjs';
|
|
62
62
|
import { tuiInventory } from './inventory.mjs';
|
|
63
63
|
import { readVersion, sourceFingerprint } from '../build/version.mjs';
|
|
64
64
|
import {
|
|
65
65
|
claudeConfigDir, bobConfigDir, openclaudeConfigDir, opencodeConfigDir, opencodeShadowedInstall,
|
|
66
|
-
isHostGlobalDir, resolvePath, resolveMemoryDir, sovereignBypass, CLAUDE_STYLE, GATE_LEDGER,
|
|
66
|
+
isHostGlobalDir, resolvePath, resolveMemoryDir, sovereignBypass, CLAUDE_STYLE, GATE_LEDGER, HOSTS,
|
|
67
67
|
} from '../hosts/hosts.mjs';
|
|
68
68
|
// P5f moved the install DETECTORS out of this file — `diff` renders its expected copy in the
|
|
69
69
|
// deployed theme and footprint, and `rebuild-all` re-emits in the deployed everything, so
|
|
@@ -238,6 +238,40 @@ export function gateSummary(cfgDirs, platform = process.platform) {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/** `_harness_status._status_data`. */
|
|
241
|
+
/**
|
|
242
|
+
* Spec R6: the member files of every kit NOT on in this install that are still on disk — the ones
|
|
243
|
+
* `pruneOwned` kept because their owner edited them — as `{ kit: [rel, ...] }`, or `null` when
|
|
244
|
+
* there are none. No new state: the kit manifests say which rels a member owns (`skills/<m>.md`,
|
|
245
|
+
* `skills/<m>/...`, `agents/<m>.md`), the install's layer dir says where to look.
|
|
246
|
+
*/
|
|
247
|
+
export function keptKitFiles(host, scope, root, active) {
|
|
248
|
+
const marker = HOSTS.find((h) => h.host === host)?.projectMarker;
|
|
249
|
+
const dir = scope === 'global' || !marker ? root : path.join(root, marker);
|
|
250
|
+
const walk = (rel) => {
|
|
251
|
+
const abs = path.join(dir, rel);
|
|
252
|
+
let entries;
|
|
253
|
+
try { entries = readdirSync(abs, { withFileTypes: true }); } catch { return []; }
|
|
254
|
+
return entries.flatMap((e) => (e.isDirectory() ? walk(`${rel}/${e.name}`)
|
|
255
|
+
: e.isFile() ? [`${rel}/${e.name}`] : []));
|
|
256
|
+
};
|
|
257
|
+
const kept = {};
|
|
258
|
+
let kits;
|
|
259
|
+
try { kits = loadKits(); } catch (e) {
|
|
260
|
+
process.stderr.write(`warning: kit rows skipped — ${e.message}\n`);
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
for (const [kit, m] of kits) {
|
|
264
|
+
if (active.includes(kit)) continue;
|
|
265
|
+
const rels = [
|
|
266
|
+
...(m.skills ?? []).flatMap((n) => [`skills/${n}.md`].filter((r) => existsSync(path.join(dir, r)))
|
|
267
|
+
.concat(walk(`skills/${n}`))),
|
|
268
|
+
...(m.agents ?? []).map((n) => `agents/${n}.md`).filter((r) => existsSync(path.join(dir, r))),
|
|
269
|
+
].sort();
|
|
270
|
+
if (rels.length) kept[kit] = rels;
|
|
271
|
+
}
|
|
272
|
+
return Object.keys(kept).length ? kept : null;
|
|
273
|
+
}
|
|
274
|
+
|
|
241
275
|
export function statusData() {
|
|
242
276
|
const inst = installedDefaults();
|
|
243
277
|
const theme = inst.theme || defaultTheme();
|
|
@@ -304,10 +338,12 @@ export function statusData() {
|
|
|
304
338
|
installs: installTargets().map(([host, scope, root]) => {
|
|
305
339
|
try {
|
|
306
340
|
const p = installProfile(host, scope, root);
|
|
341
|
+
// Only when something is kept, so `--json` without kits stays the bytes it was.
|
|
342
|
+
const kept = keptKitFiles(host, scope, root, p.kits);
|
|
307
343
|
const rebuild = legacyProcessCarrierOfDir(root, host)
|
|
308
344
|
? "geneseed setup # pre-comms carrier (names 'process 8') — run setup for this install, not a rebuild"
|
|
309
345
|
: rebuildCommand(p.argv);
|
|
310
|
-
return { ...p, rebuild };
|
|
346
|
+
return { ...p, ...(kept ? { keptKitFiles: kept } : {}), rebuild };
|
|
311
347
|
} catch (e) {
|
|
312
348
|
return { host, scope, root, state: 'unreadable', error: String(e?.message ?? e) };
|
|
313
349
|
}
|
|
@@ -422,10 +458,15 @@ export function statusLines(d, color = false) {
|
|
|
422
458
|
const excl = p.excludeRules?.length ? `excluding ${p.excludeRules.join(',')}` : 'no rules excluded';
|
|
423
459
|
rows.push([`${p.host}:${p.scope}`, [p.state, p.theme, p.footprint, `${p.posture}/${p.mode}/${p.trust}`,
|
|
424
460
|
packs, excl, ...(p.excludeSkills?.length ? [`skills off: ${p.excludeSkills.join(',')}`] : []),
|
|
461
|
+
...(p.kits?.length ? [`kits: ${p.kits.join(',')}`] : []),
|
|
425
462
|
// Each elided when armed. The law's text still binds; only its tool-boundary gate is off.
|
|
426
463
|
...GATE_SWITCHES.filter((g) => p[g.key] === false)
|
|
427
464
|
.map((g) => `${g.marker.toLowerCase()}: off (Law ${g.law} text still binds)`)]
|
|
428
465
|
.join(` ${DOT} `) + where]);
|
|
466
|
+
// R6: a member its owner edited outlives its kit; name it, so turning the kit off is visible.
|
|
467
|
+
for (const [kit, rels] of Object.entries(p.keptKitFiles ?? {})) {
|
|
468
|
+
rows.push([`${p.host}:${p.scope}`, `kept from inactive kit ${kit}: ${rels.join(', ')}${where}`]);
|
|
469
|
+
}
|
|
429
470
|
}
|
|
430
471
|
|
|
431
472
|
const labelW = Math.max(...rows.map(([k]) => codePointLength(k)));
|
package/js/inspect/validate.mjs
CHANGED
|
@@ -34,8 +34,8 @@ export const VALIDATE_USAGE = [
|
|
|
34
34
|
'usage: geneseed validate [-h] [--theme THEME] [--emit EMIT] [--out OUT] [--root ROOT]',
|
|
35
35
|
' [--footprint {lean,full}] [--posture POSTURE] [--mode MODE]',
|
|
36
36
|
' [--trust TRUST] [--doctrines DOCTRINES] [--exclude-rules RULES]',
|
|
37
|
-
' [--exclude-skills SKILLS] [--
|
|
38
|
-
' [--no-deletion-gate] [-v]',
|
|
37
|
+
' [--exclude-skills SKILLS] [--kits KITS] [--no-secrets-gate]',
|
|
38
|
+
' [--no-done-gate] [--no-deletion-gate] [-v]',
|
|
39
39
|
'',
|
|
40
40
|
"Render and emit into a throwaway sandbox, run the doctor's checks, and write nothing real.",
|
|
41
41
|
"Takes the generator's render flags (see geneseed-build --help) except --sync-themes and",
|
|
@@ -130,7 +130,7 @@ export function cmdValidate(args) {
|
|
|
130
130
|
const opts = {
|
|
131
131
|
theme: args.theme, footprint: args.footprint, posture: args.posture, mode: args.mode,
|
|
132
132
|
trust: args.trust, doctrines: args.doctrines, excludeRules: args.excludeRules,
|
|
133
|
-
excludeSkills: args.excludeSkills,
|
|
133
|
+
excludeSkills: args.excludeSkills, kits: args.kits,
|
|
134
134
|
...Object.fromEntries(GATE_SWITCHES.map((g) => [g.key, args[g.arg] ? false : null])),
|
|
135
135
|
};
|
|
136
136
|
let scanDirs;
|
package/js/maintain/setup.mjs
CHANGED
|
@@ -46,7 +46,7 @@ import path from 'node:path';
|
|
|
46
46
|
|
|
47
47
|
import { main as driverMain, resolveOut } from '../build/driver.mjs';
|
|
48
48
|
import { playLine } from '../ui/anim.mjs';
|
|
49
|
-
import { discoverNames, GATE_SWITCHES, PACK_ORDER } from '../build/source.mjs';
|
|
49
|
+
import { discoverNames, GATE_SWITCHES, PACK_ORDER, loadKits } from '../build/source.mjs';
|
|
50
50
|
import { exportImprovements } from '../inspect/diff.mjs';
|
|
51
51
|
import { cmdDoctor } from '../inspect/doctor.mjs';
|
|
52
52
|
import { setupBuildArgs } from '../build/generate.mjs';
|
|
@@ -227,6 +227,28 @@ function askDoctrines(deployed = null) {
|
|
|
227
227
|
known === null || known.includes(name) ? 'yes' : 'no') === 'yes');
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* The Kits question (spec A5): one pick, a kit or `none`, defaulting to what the install has on
|
|
232
|
+
* (`deployed`, its `Active kits:` marker). Asked ONLY when this checkout ships a kit, so a kit-less
|
|
233
|
+
* wizard reads exactly the stdin it always did (and has no kit to carry: `kitsOfDir` drops an unshipped one).
|
|
234
|
+
* Returns the kit list, or null for none (`setupBuildArgs` omits `--kits`).
|
|
235
|
+
*/
|
|
236
|
+
export function askKits(deployed = null, kits = null) {
|
|
237
|
+
try { kits ??= loadKits(); } catch (e) {
|
|
238
|
+
process.stderr.write(`warning: kit question skipped — ${e.message}\n`);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const on = (deployed ?? []).filter((k) => kits.has(k));
|
|
242
|
+
if (!kits.size) return null;
|
|
243
|
+
const opts = [...kits].map(([k, m]) => [k, m.description ?? '']);
|
|
244
|
+
// shortcut: one pick per run, so several deployed kits are offered as one "keep" option; ask
|
|
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(',');
|
|
250
|
+
}
|
|
251
|
+
|
|
230
252
|
/**
|
|
231
253
|
* `_harness_setup._collect_setup_lines` — the six questions, the plan, the confirm.
|
|
232
254
|
*
|
|
@@ -260,13 +282,15 @@ function targetGates(emit, out) {
|
|
|
260
282
|
}
|
|
261
283
|
}
|
|
262
284
|
|
|
263
|
-
export function collectSetupLines() {
|
|
285
|
+
export function collectSetupLines(kitMap = null) {
|
|
264
286
|
printOut('Geneseed setup — answer a few questions; nothing is written until you confirm.\n');
|
|
265
287
|
const inst = installedDefaults();
|
|
266
288
|
const theme = askChoice('Theme', themeOptions(), inst.theme || defaultTheme());
|
|
267
289
|
const posture = askChoice('Posture', postureOptions(), inst.posture || defaultPosture());
|
|
268
290
|
const mode = askChoice('Mode', modeOptions(), inst.mode || defaultMode());
|
|
269
291
|
const doctrines = askDoctrines(inst.doctrines);
|
|
292
|
+
// Kits on stay on (spec A3); `kitsOfDir` already dropped a retired one.
|
|
293
|
+
const kits = askKits(inst.kits, kitMap);
|
|
270
294
|
const emit = askChoice('Install mode', EMIT_OPTIONS, inst.emit || 'opencode-global');
|
|
271
295
|
const footprint = askChoice('Footprint', FOOTPRINT_OPTIONS,
|
|
272
296
|
inst.footprint || 'lean');
|
|
@@ -304,13 +328,13 @@ export function collectSetupLines() {
|
|
|
304
328
|
const gated = Object.values(gates).some((on) => on === false);
|
|
305
329
|
printOut(`\nAbout to run: geneseed build ${
|
|
306
330
|
setupBuildArgs(theme, emit, out, root, footprint, posture, mode, doctrines, allPacks,
|
|
307
|
-
excludeRules, undefined, excludeSkills, gates).join(' ')}\n`);
|
|
331
|
+
excludeRules, undefined, excludeSkills, gates, kits).join(' ')}\n`);
|
|
308
332
|
if (!confirm('Proceed?', true)) return null;
|
|
309
333
|
// The key only when there is something to carry, so a selection that excludes no skill and
|
|
310
334
|
// switches off no gate is the same object it was before either existed.
|
|
311
335
|
return {
|
|
312
336
|
theme, posture, mode, doctrines, excludeRules, ...(excludeSkills ? { excludeSkills } : {}),
|
|
313
|
-
...(gated ? { gates } : {}),
|
|
337
|
+
...(gated ? { gates } : {}), ...(kits ? { kits } : {}),
|
|
314
338
|
emit, out, root, footprint,
|
|
315
339
|
};
|
|
316
340
|
}
|
|
@@ -494,7 +518,7 @@ export function setupLines() {
|
|
|
494
518
|
}
|
|
495
519
|
const {
|
|
496
520
|
theme, emit, out, root, footprint = 'lean', posture = 'peer', mode = 'direct',
|
|
497
|
-
doctrines = null, excludeRules = null, excludeSkills = null, gates = {},
|
|
521
|
+
doctrines = null, excludeRules = null, excludeSkills = null, gates = {}, kits = null,
|
|
498
522
|
} = sel;
|
|
499
523
|
if (emit === 'opencode-global') {
|
|
500
524
|
// The build below overwrites the deployed global harness, and the self-improvement loops
|
|
@@ -513,7 +537,7 @@ export function setupLines() {
|
|
|
513
537
|
// runs must carry the pack selection the plan line just previewed, or the wizard would print
|
|
514
538
|
// one command and run another.
|
|
515
539
|
const argv = setupBuildArgs(theme, emit, out, root, footprint, posture, mode, doctrines,
|
|
516
|
-
doctrineOptions().map(([n]) => n), excludeRules, undefined, excludeSkills, gates);
|
|
540
|
+
doctrineOptions().map(([n]) => n), excludeRules, undefined, excludeSkills, gates, kits);
|
|
517
541
|
printOut(`Running: geneseed build ${argv.join(' ')}\n`);
|
|
518
542
|
const rc = driverMain(argv);
|
|
519
543
|
if (rc !== 0) {
|
package/js/maintain/update.mjs
CHANGED
|
@@ -632,7 +632,7 @@ export async function installAxes(dir, { installProfile, setupBuildArgs }, notif
|
|
|
632
632
|
if (legacyProcessCarrierOfDir(String(dir), 'opencode')) return null;
|
|
633
633
|
const p = installProfile('opencode', 'global', String(dir), notify);
|
|
634
634
|
return setupBuildArgs('', 'files', null, null, p.footprint, p.posture, p.mode, p.doctrines,
|
|
635
|
-
undefined, p.excludeRules, p.trust, p.excludeSkills, p).slice(4);
|
|
635
|
+
undefined, p.excludeRules, p.trust, p.excludeSkills, p, p.kits).slice(4);
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
/**
|
package/js/web/actions.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import path from 'node:path';
|
|
|
34
34
|
|
|
35
35
|
import { emitGlobalInto } from '../build/driver.mjs';
|
|
36
36
|
import {
|
|
37
|
-
ROOT, discoverNames, knownRuleIds, PACK_ORDER,
|
|
37
|
+
ROOT, discoverNames, knownRuleIds, loadKits, PACK_ORDER,
|
|
38
38
|
} from '../build/source.mjs';
|
|
39
39
|
import { withStdoutSwallowed } from '../inspect/diff.mjs';
|
|
40
40
|
import { excludeAdd, excludeRemove } from '../inspect/excludes.mjs';
|
|
@@ -46,9 +46,10 @@ import {
|
|
|
46
46
|
opencodeConfigDir, resolvePath,
|
|
47
47
|
} from '../hosts/hosts.mjs';
|
|
48
48
|
import {
|
|
49
|
-
EMIT_HOST_SCOPE, doctrinesForBuild, excludedRulesOfDir, excludedSkillsOfDir,
|
|
49
|
+
EMIT_HOST_SCOPE, doctrinesForBuild, excludedRulesOfDir, excludedSkillsOfDir, kitsOfDir,
|
|
50
50
|
footprintOfDir, gatesOfDir,
|
|
51
|
-
installState, installTargets, legacyProcessCarrierOfDir, modeOfDir, postureOfDir,
|
|
51
|
+
installState, installTargets, legacyProcessCarrierOfDir, modeOfDir, postureOfDir, themeOfDir,
|
|
52
|
+
trustOfDir,
|
|
52
53
|
} from '../hosts/installs.mjs';
|
|
53
54
|
import {
|
|
54
55
|
isDict, mcpCommented, mcpInstallTargets, mcpLoad, mcpServerNames, mcpState,
|
|
@@ -131,6 +132,15 @@ function bodyExcludeRules(body) {
|
|
|
131
132
|
(n) => n.trim().replace(/[ \t]+/, '.'));
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
/**
|
|
136
|
+
* A request body's `kits` — REPLACES the install's kits (the Kits tab sends the whole set it
|
|
137
|
+
* wants): `[]`/`'none'` give `[]`, an omitted key gives `null` (keep the install's own), and an
|
|
138
|
+
* unknown name gives `null` too, which `apiInstallCmd` refuses for a body that HAS the key.
|
|
139
|
+
*/
|
|
140
|
+
function bodyKits(body) {
|
|
141
|
+
return bodyList(body, 'kits', () => [...loadKits().keys()]);
|
|
142
|
+
}
|
|
143
|
+
|
|
134
144
|
/**
|
|
135
145
|
* The gate switches across the trust boundary (`deletionGate`, `secretsGate`, `doneGate`), each
|
|
136
146
|
* on its own: only a real JSON boolean counts. Anything else — a string `"false"`, a number,
|
|
@@ -539,6 +549,8 @@ export function apiRestore(state, files) {
|
|
|
539
549
|
// re-states a rule whose hook gate the install deliberately left unwired.
|
|
540
550
|
excludeRules: excludedRulesOfDir(target),
|
|
541
551
|
excludeSkills: excludedSkillsOfDir(target),
|
|
552
|
+
// The kits: a restored AGENT.md keeps its Kits block and `Active kits:` line.
|
|
553
|
+
kits: kitsOfDir(target),
|
|
542
554
|
// The gate switches: a restored AGENT.md must keep the install's `<gate>: off` lines.
|
|
543
555
|
...gatesOfDir(target),
|
|
544
556
|
}));
|
|
@@ -607,8 +619,9 @@ export function apiInstallCmd(state, body) {
|
|
|
607
619
|
if (emit === undefined) return { error: `no install mode for ${host}:${scope}` };
|
|
608
620
|
// Every read-back below is narrowed to `host`'s OWN carrier (host-compat B1) — see the
|
|
609
621
|
// comment at `installProfile` in `js/build/generate.mjs`.
|
|
622
|
+
// The theme default is THIS install's own, not the viewed one's: the Kits tab acts on any row.
|
|
610
623
|
const { theme, footprint: fp, posture: pos, mode, trust } = bodyAxes(body, {
|
|
611
|
-
theme: state.theme, footprint: footprintOfDir(root),
|
|
624
|
+
theme: themeOfDir(root, host) || state.theme, footprint: footprintOfDir(root),
|
|
612
625
|
posture: postureOfDir(root, host) || 'peer',
|
|
613
626
|
mode: modeOfDir(root, host) || 'direct', trust: trustOfDir(root, host) || DEFAULT_PRESET });
|
|
614
627
|
// Unspecified means "keep what this install already has", exactly as theme, footprint,
|
|
@@ -626,9 +639,20 @@ export function apiInstallCmd(state, body) {
|
|
|
626
639
|
// The gate switches (the Constitution page's toggles): a strict boolean per gate in the body,
|
|
627
640
|
// else the install's own answer — so a pack Apply keeps every switch as it is.
|
|
628
641
|
const gates = bodyGates(body, root, host);
|
|
642
|
+
// The page sends `kits` deliberately, so an unknown name is refused: ignoring it would run a
|
|
643
|
+
// no-op rebuild and report success. An omitted key keeps the install's own kits.
|
|
644
|
+
let kits = null;
|
|
645
|
+
try {
|
|
646
|
+
kits = bodyKits(body);
|
|
647
|
+
if (kits === null && bget(body, 'kits') !== null) {
|
|
648
|
+
return { error: `unknown kit in ${formatRepr(bget(body, 'kits'))} — this Geneseed ships: `
|
|
649
|
+
+ [...loadKits().keys()].join(', ') };
|
|
650
|
+
}
|
|
651
|
+
} catch (e) { return { error: String(e?.message ?? e) }; }
|
|
629
652
|
const out = scope === 'global' ? null : String(root);
|
|
630
653
|
const argv = setupBuildArgs(theme || 'neutral', emit, out, out, fp, pos, mode, doctrines,
|
|
631
|
-
PACK_ORDER, excludeRules, trust, excludedSkillsOfDir(root, host), gates
|
|
654
|
+
PACK_ORDER, excludeRules, trust, excludedSkillsOfDir(root, host), gates,
|
|
655
|
+
kits ?? kitsOfDir(root, host));
|
|
632
656
|
return { cmd: [process.execPath, path.join(ROOT, 'bin', 'build-driver.mjs'), ...argv] };
|
|
633
657
|
}
|
|
634
658
|
|
|
@@ -730,6 +754,6 @@ export function apiDeployCmd(state, body) {
|
|
|
730
754
|
const gates = bodyGates(body, root, host);
|
|
731
755
|
// project-scope emit name == host name (opencode / claude / bob / openclaude)
|
|
732
756
|
const argv = setupBuildArgs(theme || 'neutral', host, root, root, fp, pos, mode, doctrines,
|
|
733
|
-
PACK_ORDER, excludeRules, trust, excludedSkillsOfDir(root, host), gates);
|
|
757
|
+
PACK_ORDER, excludeRules, trust, excludedSkillsOfDir(root, host), gates, kitsOfDir(root, host));
|
|
734
758
|
return { cmd: [process.execPath, path.join(ROOT, 'bin', 'build-driver.mjs'), ...argv] };
|
|
735
759
|
}
|
package/js/web/api.mjs
CHANGED
|
@@ -11,22 +11,24 @@
|
|
|
11
11
|
import { statSync } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
|
|
14
|
-
import { CONFIG, ROOT, THEMES, discoverNames, makeCfg } from '../build/source.mjs';
|
|
14
|
+
import { CONFIG, ROOT, THEMES, discoverNames, loadKits, makeCfg } 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
|
+
import { installProfile } from '../build/generate.mjs';
|
|
18
19
|
import { diffCollect, renderMemo } from '../inspect/diff.mjs';
|
|
19
20
|
import { excludesSnapshot } from '../inspect/excludes.mjs';
|
|
20
21
|
import { CLAUDE_STYLE, HOSTS, VERSION_MARKER, resolvePath } from '../hosts/hosts.mjs';
|
|
21
22
|
import {
|
|
22
|
-
footprintOfDir, gatesOfDir, installState, installTargets,
|
|
23
|
+
footprintOfDir, gatesOfDir, installState, installTargets, legacyProcessCarrierOfDir, modeOfDir,
|
|
24
|
+
trustOfDir,
|
|
23
25
|
postureOfDir, readJsonMaybe, themeOfDir,
|
|
24
26
|
} from '../hosts/installs.mjs';
|
|
25
27
|
import { PRESETS } from '../loop/score.mjs';
|
|
26
28
|
import { loadCatalog, catalogProblems } from '../loop/catalog.mjs';
|
|
27
29
|
import { activeLoops } from '../loop/registry.mjs';
|
|
28
30
|
import { EMIT_OPTIONS, themeOptions } from '../maintain/setup.mjs';
|
|
29
|
-
import { accentFor, statusData } from '../inspect/status.mjs';
|
|
31
|
+
import { accentFor, keptKitFiles, statusData } from '../inspect/status.mjs';
|
|
30
32
|
import { isFile } from '../lib/fs.mjs';
|
|
31
33
|
import { comparePaths, normcase } from '../lib/paths.mjs';
|
|
32
34
|
import {
|
|
@@ -231,6 +233,41 @@ export function apiFleet(state) {
|
|
|
231
233
|
return { installs, source_fp: sourceFp, doctor: state.doctor };
|
|
232
234
|
}
|
|
233
235
|
|
|
236
|
+
/**
|
|
237
|
+
* EVERY INSTALL'S KITS — the Kits tab. Its own route, like `apiFleet`: it re-reads each carrier
|
|
238
|
+
* and the kit manifests, which the sidebar's `/api/installs` refetch must not pay for.
|
|
239
|
+
*
|
|
240
|
+
* `kits` per row is `installProfile(...).kits`, the reader `rebuild-all` uses, so the tab shows
|
|
241
|
+
* what a rebuild would keep. A malformed manifest answers 200 with `error` and EMPTY lists, never
|
|
242
|
+
* rows without kits (which would read as "no kit on") and never a 500. `editable:false` carries
|
|
243
|
+
* the two refusals `apiInstallCmd` makes, so the page can say why before the click.
|
|
244
|
+
*/
|
|
245
|
+
export function apiKits() {
|
|
246
|
+
let known;
|
|
247
|
+
try { known = loadKits(); } catch (e) {
|
|
248
|
+
return { kits: [], installs: [], error: String(e?.message ?? e) };
|
|
249
|
+
}
|
|
250
|
+
const kits = [...known].map(([name, m]) => ({
|
|
251
|
+
name, description: m.description ?? '', skills: m.skills ?? [], agents: m.agents ?? [] }));
|
|
252
|
+
const installs = [];
|
|
253
|
+
for (const [host, scope, root] of installTargets()) {
|
|
254
|
+
const st = installState(root, host, scope);
|
|
255
|
+
if (st === 'absent') continue;
|
|
256
|
+
try {
|
|
257
|
+
const p = installProfile(host, scope, root);
|
|
258
|
+
const reason = st === 'disabled' ? 'install is disabled — reactivate it first'
|
|
259
|
+
: legacyProcessCarrierOfDir(root, host) ? 'predates the comms pack — run geneseed setup for it first'
|
|
260
|
+
: null;
|
|
261
|
+
installs.push({ ...installRowBase(host, scope, root, st), kits: p.kits,
|
|
262
|
+
keptKitFiles: keptKitFiles(host, scope, root, p.kits), editable: reason === null, reason });
|
|
263
|
+
} catch (e) {
|
|
264
|
+
installs.push({ ...installRowBase(host, scope, root, 'unreadable'),
|
|
265
|
+
error: String(e?.message ?? e) });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { kits, installs };
|
|
269
|
+
}
|
|
270
|
+
|
|
234
271
|
/** The dashboard aggregate. */
|
|
235
272
|
export function apiOverview(state) {
|
|
236
273
|
const inv = state.inventory;
|
package/js/web/routes.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { percentDecode } from '../lib/text.mjs';
|
|
|
21
21
|
import { apiExcludesMutate, apiInstallToggle, apiLoopsPresetMutate, apiMcp, apiMemoryDelete, apiSelectView } from './actions.mjs';
|
|
22
22
|
import { apiActivity, apiActivityDetail, apiActivityToggle } from './activity.mjs';
|
|
23
23
|
import {
|
|
24
|
-
apiDiff, apiDoctor, apiExcludes, apiFleet, apiInstalls, apiLoops, apiLoopsActive, apiOverview,
|
|
24
|
+
apiDiff, apiDoctor, apiExcludes, apiFleet, apiInstalls, apiKits, apiLoops, apiLoopsActive, apiOverview,
|
|
25
25
|
apiRecent, apiSetup, apiThemes, apiUpdate,
|
|
26
26
|
} from './api.mjs';
|
|
27
27
|
import { NotFound, apiCatalog, apiItem } from './catalog.mjs';
|
|
@@ -151,6 +151,7 @@ export const STATE_ROUTES = {
|
|
|
151
151
|
new URL(url || '', 'http://x').searchParams.has('fresh')),
|
|
152
152
|
'/api/installs': apiInstalls,
|
|
153
153
|
'/api/fleet': apiFleet,
|
|
154
|
+
'/api/kits': apiKits,
|
|
154
155
|
'/api/update': apiUpdate,
|
|
155
156
|
'/api/usage': apiUsage,
|
|
156
157
|
'/api/gates': apiGates,
|
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.10.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
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
"added": "2026-07-13",
|
|
23
23
|
"last_verified": ""
|
|
24
24
|
},
|
|
25
|
+
"agents/devops-engineer": {
|
|
26
|
+
"status": "experimental",
|
|
27
|
+
"version": "1.0.0",
|
|
28
|
+
"owner": "Guillaume Lemer",
|
|
29
|
+
"added": "2026-10-20",
|
|
30
|
+
"last_verified": ""
|
|
31
|
+
},
|
|
32
|
+
"agents/docker-expert": {
|
|
33
|
+
"status": "experimental",
|
|
34
|
+
"version": "1.0.0",
|
|
35
|
+
"owner": "Guillaume Lemer",
|
|
36
|
+
"added": "2026-10-20",
|
|
37
|
+
"last_verified": ""
|
|
38
|
+
},
|
|
25
39
|
"agents/docs": {
|
|
26
40
|
"status": "approved",
|
|
27
41
|
"version": "1.0.0",
|
|
@@ -57,6 +71,13 @@
|
|
|
57
71
|
"added": "2026-06-08",
|
|
58
72
|
"last_verified": ""
|
|
59
73
|
},
|
|
74
|
+
"agents/kubernetes-specialist": {
|
|
75
|
+
"status": "experimental",
|
|
76
|
+
"version": "1.0.0",
|
|
77
|
+
"owner": "Guillaume Lemer",
|
|
78
|
+
"added": "2026-10-20",
|
|
79
|
+
"last_verified": ""
|
|
80
|
+
},
|
|
60
81
|
"agents/memory-curator": {
|
|
61
82
|
"status": "experimental",
|
|
62
83
|
"version": "1.0.0",
|
|
@@ -113,6 +134,27 @@
|
|
|
113
134
|
"added": "2026-06-08",
|
|
114
135
|
"last_verified": ""
|
|
115
136
|
},
|
|
137
|
+
"agents/spring-boot-engineer": {
|
|
138
|
+
"status": "experimental",
|
|
139
|
+
"version": "1.0.0",
|
|
140
|
+
"owner": "Guillaume Lemer",
|
|
141
|
+
"added": "2026-10-20",
|
|
142
|
+
"last_verified": ""
|
|
143
|
+
},
|
|
144
|
+
"agents/spring-reviewer": {
|
|
145
|
+
"status": "experimental",
|
|
146
|
+
"version": "1.0.0",
|
|
147
|
+
"owner": "Guillaume Lemer",
|
|
148
|
+
"added": "2026-10-20",
|
|
149
|
+
"last_verified": ""
|
|
150
|
+
},
|
|
151
|
+
"agents/spring-security": {
|
|
152
|
+
"status": "experimental",
|
|
153
|
+
"version": "1.0.0",
|
|
154
|
+
"owner": "Guillaume Lemer",
|
|
155
|
+
"added": "2026-10-20",
|
|
156
|
+
"last_verified": ""
|
|
157
|
+
},
|
|
116
158
|
"agents/steward": {
|
|
117
159
|
"status": "approved",
|
|
118
160
|
"version": "1.0.0",
|
|
@@ -428,6 +470,27 @@
|
|
|
428
470
|
"added": "2026-09-06",
|
|
429
471
|
"last_verified": "2026-10-13"
|
|
430
472
|
},
|
|
473
|
+
"skills/spring-boot": {
|
|
474
|
+
"status": "experimental",
|
|
475
|
+
"version": "1.0.0",
|
|
476
|
+
"owner": "Guillaume Lemer",
|
|
477
|
+
"added": "2026-10-20",
|
|
478
|
+
"last_verified": ""
|
|
479
|
+
},
|
|
480
|
+
"skills/spring-data-jpa": {
|
|
481
|
+
"status": "experimental",
|
|
482
|
+
"version": "1.0.0",
|
|
483
|
+
"owner": "Guillaume Lemer",
|
|
484
|
+
"added": "2026-10-20",
|
|
485
|
+
"last_verified": ""
|
|
486
|
+
},
|
|
487
|
+
"skills/spring-testing": {
|
|
488
|
+
"status": "experimental",
|
|
489
|
+
"version": "1.0.0",
|
|
490
|
+
"owner": "Guillaume Lemer",
|
|
491
|
+
"added": "2026-10-20",
|
|
492
|
+
"last_verified": ""
|
|
493
|
+
},
|
|
431
494
|
"skills/teach": {
|
|
432
495
|
"status": "experimental",
|
|
433
496
|
"version": "1.0.0",
|
package/src/AGENT.md.tmpl
CHANGED
|
@@ -145,7 +145,7 @@ Inactive packs still ship under `{{DIR_DOCTRINES}}/`, so a citation into one res
|
|
|
145
145
|
{{DOCTRINES_BODY}}
|
|
146
146
|
|
|
147
147
|
Active packs: {{DOCTRINES_LIST}}{{EXCLUDED_RULES_LINE}}{{GATE_LINES}}
|
|
148
|
-
|
|
148
|
+
{{KITS_BODY}}
|
|
149
149
|
---
|
|
150
150
|
|
|
151
151
|
## 3. {{AGENTS}} — delegation by capability
|
|
@@ -288,7 +288,7 @@ inventory — that list is the catalogue and it is authoritative. Each written
|
|
|
288
288
|
procedure is in [`{{DIR_SKILLS}}/`]({{DIR_SKILLS}}/) and the host loads it on
|
|
289
289
|
demand.
|
|
290
290
|
<!-- CATALOG:end -->
|
|
291
|
-
{{EXCLUDED_SKILLS_LINE}}
|
|
291
|
+
{{EXCLUDED_SKILLS_LINE}}{{KITS_LINE}}
|
|
292
292
|
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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# {{AGENT}}: devops-engineer
|
|
2
|
+
|
|
3
|
+
> {{DESC_DEVOPS_ENGINEER}}
|
|
4
|
+
|
|
5
|
+
{{CREDIT:piomin-spring-boot:agents/devops-engineer.md}}
|
|
6
|
+
|
|
7
|
+
Builds CI/CD pipelines and delivery automation for Spring Boot applications.
|
|
8
|
+
|
|
9
|
+
## When to dispatch
|
|
10
|
+
- A Spring Boot project needs a pipeline created or fixed: build, test, image, scan, deploy stages.
|
|
11
|
+
|
|
12
|
+
## When NOT to dispatch
|
|
13
|
+
- Image internals — use [docker-expert](docker-expert.md). Cluster manifests — use [kubernetes-specialist](kubernetes-specialist.md).
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
- The repository, its existing CI provider and the target registry and environments.
|
|
17
|
+
|
|
18
|
+
## Allowed tools
|
|
19
|
+
- **Read + write to the assigned worktree.** May run the build and pipeline linters.
|
|
20
|
+
<!-- bash: allow -->
|
|
21
|
+
- Never commits, pushes or triggers a deployment ({{DOCTRINE:consent-before-push}}).
|
|
22
|
+
|
|
23
|
+
## Procedure
|
|
24
|
+
0. If `{{DIR_MEMORY}}/agents/<your-name>.md` exists, read it first — your durable lessons from prior dispatches ({{DOCTRINE:persist-insight}}).
|
|
25
|
+
1. Detect the CI the project already uses (GitHub Actions, GitLab CI, Jenkins, other) and extend it; do not switch providers. With none present, ask the user which to use.
|
|
26
|
+
2. Pipeline order: checkout, restore build cache, `./mvnw verify` (or `./gradlew build`), publish test results, build image, scan, push, deploy.
|
|
27
|
+
3. Cache the Maven or Gradle repository keyed on the build files.
|
|
28
|
+
4. Gates: tests, a coverage threshold if the project has one, dependency and image vulnerability scan.
|
|
29
|
+
5. Build images with the project's chosen method (`spring-boot:build-image` by default); tag with version or commit SHA, never `latest` in production.
|
|
30
|
+
6. Stages dev, staging, prod, with approval before prod.
|
|
31
|
+
7. Secrets come from the CI secret store or a vault, never from files in the repository.
|
|
32
|
+
8. Lint the pipeline file before handing off.
|
|
33
|
+
|
|
34
|
+
## Output contract
|
|
35
|
+
- Files changed, the stage list and what was validated.
|
|
36
|
+
|
|
37
|
+
## Self-improvement
|
|
38
|
+
|
|
39
|
+
If this spec misled you — an input you needed but were not given, a boundary
|
|
40
|
+
that proved wrong, a step you could not execute — end your report with one line:
|
|
41
|
+
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
42
|
+
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
43
|
+
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
44
|
+
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
45
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# {{AGENT}}: docker-expert
|
|
2
|
+
|
|
3
|
+
> {{DESC_DOCKER_EXPERT}}
|
|
4
|
+
|
|
5
|
+
{{CREDIT:piomin-spring-boot:agents/docker-expert.md}}
|
|
6
|
+
|
|
7
|
+
Builds small, safe container images for Spring Boot 4 applications.
|
|
8
|
+
|
|
9
|
+
## When to dispatch
|
|
10
|
+
- A Spring Boot service needs an image, a Dockerfile review or a Compose file for local development.
|
|
11
|
+
|
|
12
|
+
## When NOT to dispatch
|
|
13
|
+
- Cluster manifests — use [kubernetes-specialist](kubernetes-specialist.md). Pipelines — use [devops-engineer](devops-engineer.md).
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
- The build tool (Maven or Gradle), the Java version and any registry or base-image policy.
|
|
17
|
+
|
|
18
|
+
## Allowed tools
|
|
19
|
+
- **Read + write to the assigned worktree.** May run the build and local image builds.
|
|
20
|
+
<!-- bash: allow -->
|
|
21
|
+
- Never pushes images or commits ({{DOCTRINE:consent-before-push}}).
|
|
22
|
+
|
|
23
|
+
## Procedure
|
|
24
|
+
0. If `{{DIR_MEMORY}}/agents/<your-name>.md` exists, read it first — your durable lessons from prior dispatches ({{DOCTRINE:persist-insight}}).
|
|
25
|
+
1. Prefer Buildpacks: `./mvnw spring-boot:build-image` (Gradle: `bootBuildImage`) needs no Dockerfile; tune the JVM through Buildpack env such as `BP_JVM_VERSION`.
|
|
26
|
+
2. Use a Dockerfile only when Buildpacks do not fit. Build multi-stage and extract layers with `java -Djarmode=tools -jar app.jar extract --layers --destination extracted`; copy `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application` in that order for cache reuse.
|
|
27
|
+
3. Run as a non-root user; pin base image tags or digests, never `latest`.
|
|
28
|
+
4. Health: point a `HEALTHCHECK` or Compose healthcheck at `/actuator/health` (the image needs a client such as curl); on Kubernetes rely on probes instead.
|
|
29
|
+
5. Add a `.dockerignore` (`.git`, `target/`, IDE files).
|
|
30
|
+
6. No secrets in layers or build args; pass them at runtime.
|
|
31
|
+
7. Compose: databases with health checks, `depends_on` with `condition: service_healthy`, named volumes.
|
|
32
|
+
8. Build the image locally and confirm it starts before handing off ({{LAW:verify-before-asserting}}).
|
|
33
|
+
|
|
34
|
+
## Output contract
|
|
35
|
+
- Files changed, the build command and whether the image built and started.
|
|
36
|
+
|
|
37
|
+
## Self-improvement
|
|
38
|
+
|
|
39
|
+
If this spec misled you — an input you needed but were not given, a boundary
|
|
40
|
+
that proved wrong, a step you could not execute — end your report with one line:
|
|
41
|
+
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
42
|
+
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
43
|
+
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
44
|
+
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
45
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# {{AGENT}}: kubernetes-specialist
|
|
2
|
+
|
|
3
|
+
> {{DESC_KUBERNETES_SPECIALIST}}
|
|
4
|
+
|
|
5
|
+
{{CREDIT:piomin-spring-boot:agents/kubernetes-specialist.md}}
|
|
6
|
+
|
|
7
|
+
Deploys Spring Boot 4 applications on Kubernetes.
|
|
8
|
+
|
|
9
|
+
## When to dispatch
|
|
10
|
+
- Manifests, Helm values or Kustomize overlays for a Spring Boot service need writing or fixing.
|
|
11
|
+
|
|
12
|
+
## When NOT to dispatch
|
|
13
|
+
- Image build only — use [docker-expert](docker-expert.md). Pipelines — use [devops-engineer](devops-engineer.md).
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
- The service, its ports and dependencies, and the manifest tooling the project already uses.
|
|
17
|
+
|
|
18
|
+
## Allowed tools
|
|
19
|
+
- **Read + write to the assigned worktree.** May run `kubectl ... --dry-run`, linters and the build.
|
|
20
|
+
<!-- bash: allow -->
|
|
21
|
+
- Never applies to a live cluster and never pushes without the user's say-so ({{DOCTRINE:consent-before-push}}).
|
|
22
|
+
|
|
23
|
+
## Procedure
|
|
24
|
+
0. If `{{DIR_MEMORY}}/agents/<your-name>.md` exists, read it first — your durable lessons from prior dispatches ({{DOCTRINE:persist-insight}}).
|
|
25
|
+
1. Match the project's manifest style; do not introduce a new tool.
|
|
26
|
+
2. Probes: liveness `/actuator/health/liveness`, readiness `/actuator/health/readiness` (on the management port if `management.server.port` is set); add a startup probe for slow starts. Boot exposes these health groups by default; `management.endpoint.health.probes.enabled=false` turns them off.
|
|
27
|
+
3. Shutdown: `server.shutdown=graceful` with `spring.lifecycle.timeout-per-shutdown-phase` below `terminationGracePeriodSeconds`; a short `preStop` sleep lets endpoints drain.
|
|
28
|
+
4. Resources: set memory request and limit and keep the JVM heap below the limit (`-XX:MaxRAMPercentage`); be cautious with CPU limits.
|
|
29
|
+
5. Config: non-sensitive settings in a ConfigMap, credentials in a Secret; env vars bind through relaxed binding (`SPRING_DATASOURCE_URL`).
|
|
30
|
+
6. Scale: HPA on CPU or custom metrics, a PodDisruptionBudget when replicas exceed one.
|
|
31
|
+
7. Pod hardening: `runAsNonRoot`, no privilege escalation, pinned image tags.
|
|
32
|
+
8. Validate with a dry run or linter before handing off.
|
|
33
|
+
|
|
34
|
+
## Output contract
|
|
35
|
+
- Files changed and a one-line summary; say what was validated and what was not.
|
|
36
|
+
|
|
37
|
+
## Self-improvement
|
|
38
|
+
|
|
39
|
+
If this spec misled you — an input you needed but were not given, a boundary
|
|
40
|
+
that proved wrong, a step you could not execute — end your report with one line:
|
|
41
|
+
`spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
|
|
42
|
+
friction. The caller weighs the feedback, folds a real flaw back into this file
|
|
43
|
+
with the user's assent, and records it to {{MEMORY}} only if it clears
|
|
44
|
+
{{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
|
|
45
|
+
|
package/src/agents/reviewer.md
CHANGED