@yemi33/minions 0.1.2286 → 0.1.2287
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/dashboard/js/render-inbox.js +32 -3
- package/dashboard/js/render-other.js +1 -1
- package/dashboard/js/render-plans.js +3 -3
- package/dashboard/js/render-prs.js +45 -4
- package/dashboard/js/render-schedules.js +2 -2
- package/dashboard/js/render-skills.js +3 -3
- package/dashboard/js/render-watches.js +2 -2
- package/dashboard/js/render-work-items.js +3 -1
- package/dashboard/js/settings.js +45 -0
- package/dashboard/pages/engine.html +3 -3
- package/dashboard/pages/meetings.html +2 -2
- package/dashboard/pages/pipelines.html +1 -1
- package/dashboard/pages/tools.html +3 -3
- package/dashboard/pages/watches.html +1 -1
- package/dashboard/slim/js/status.js +29 -15
- package/dashboard/styles.css +22 -4
- package/dashboard.js +51 -12
- package/docs/deprecated.json +35 -161
- package/docs/live-checkout-mode.md +54 -0
- package/docs/project-skills.md +0 -1
- package/engine/cleanup.js +51 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/features.js +14 -0
- package/engine/llm.js +1 -0
- package/engine/playbook.js +4 -4
- package/engine/prd-store.js +264 -0
- package/engine/queries.js +97 -59
- package/engine/runtimes/copilot.js +32 -5
- package/engine/shared.js +24 -8
- package/engine/watch-actions.js +4 -1
- package/engine.js +42 -7
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -37,6 +37,7 @@ const _dashboardVersion = {
|
|
|
37
37
|
};
|
|
38
38
|
const shared = require('./engine/shared');
|
|
39
39
|
const queries = require('./engine/queries');
|
|
40
|
+
const prdStore = require('./engine/prd-store');
|
|
40
41
|
const ado = require('./engine/ado');
|
|
41
42
|
const gh = require('./engine/github');
|
|
42
43
|
const ghToken = require('./engine/gh-token');
|
|
@@ -429,6 +430,21 @@ function mergeSettingsConfigUpdate(current, candidate, body, patch = {}) {
|
|
|
429
430
|
// `worktreeMode`. Drop any stale legacy key on every settings save so a
|
|
430
431
|
// migrated project never carries both fields.
|
|
431
432
|
delete currentProject.worktreeMode;
|
|
433
|
+
// M006 — mirror liveValidation: check both candidateProject (modified
|
|
434
|
+
// in-memory config) and the original body update. When handleSettingsUpdate
|
|
435
|
+
// deletes the field (null or empty type sent), candidateProject won't have
|
|
436
|
+
// it but the body update will — mirror the deletion to disk.
|
|
437
|
+
if (Object.prototype.hasOwnProperty.call(candidateProject, 'liveValidation')) {
|
|
438
|
+
if (candidateProject.liveValidation && typeof candidateProject.liveValidation === 'object' && candidateProject.liveValidation.type) {
|
|
439
|
+
currentProject.liveValidation = candidateProject.liveValidation;
|
|
440
|
+
} else {
|
|
441
|
+
delete currentProject.liveValidation;
|
|
442
|
+
}
|
|
443
|
+
} else if (Object.prototype.hasOwnProperty.call(update, 'liveValidation')) {
|
|
444
|
+
// Body sent liveValidation key but handleSettingsUpdate deleted it from
|
|
445
|
+
// the in-memory config (null / empty type) — propagate to disk.
|
|
446
|
+
delete currentProject.liveValidation;
|
|
447
|
+
}
|
|
432
448
|
}
|
|
433
449
|
}
|
|
434
450
|
shared.pruneDefaultClaudeConfig(current);
|
|
@@ -1299,7 +1315,7 @@ function collectArchivedWorkItems(minionsDir = MINIONS_DIR, projects = PROJECTS)
|
|
|
1299
1315
|
}
|
|
1300
1316
|
return archived;
|
|
1301
1317
|
}
|
|
1302
|
-
function linkPullRequestForTracking({ url, title, project: projectName, contextOnly,
|
|
1318
|
+
function linkPullRequestForTracking({ url, title, project: projectName, contextOnly, context, workItemId }, config = CONFIG, options = {}) {
|
|
1303
1319
|
if (!url) {
|
|
1304
1320
|
const err = new Error('url required');
|
|
1305
1321
|
err.statusCode = 400;
|
|
@@ -1315,12 +1331,7 @@ function linkPullRequestForTracking({ url, title, project: projectName, contextO
|
|
|
1315
1331
|
const linkedWorkItemId = getWorkItemIdFromPrLinkContext(context, workItemId);
|
|
1316
1332
|
const contextText = typeof context === 'string' ? context : (context == null ? '' : JSON.stringify(context));
|
|
1317
1333
|
const metadata = normalizePrMetadata(options.metadata);
|
|
1318
|
-
|
|
1319
|
-
// deprecated alias. Explicit canonical wins; otherwise fall back to the
|
|
1320
|
-
// alias (autoObserve:true → contextOnly:false); otherwise default false.
|
|
1321
|
-
const resolvedContextOnly = typeof contextOnly === 'boolean'
|
|
1322
|
-
? contextOnly
|
|
1323
|
-
: (autoObserve === undefined ? false : !autoObserve);
|
|
1334
|
+
const resolvedContextOnly = typeof contextOnly === 'boolean' ? contextOnly : false;
|
|
1324
1335
|
const result = shared.upsertPullRequestRecord(prPath, {
|
|
1325
1336
|
id: prId,
|
|
1326
1337
|
prNumber: parseInt(prNum, 10) || null,
|
|
@@ -6974,7 +6985,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
6974
6985
|
if (!fs.existsSync(PRD_DIR)) fs.mkdirSync(PRD_DIR, { recursive: true });
|
|
6975
6986
|
|
|
6976
6987
|
const planFile = 'manual-' + shared.uid() + '.json';
|
|
6977
|
-
|
|
6988
|
+
const manualPrdPath = path.join(PRD_DIR, planFile);
|
|
6989
|
+
safeWrite(manualPrdPath, manualPrd.plan);
|
|
6990
|
+
// Phase 10 step 2 — this create uses safeWrite (not mutateJsonFileLocked),
|
|
6991
|
+
// so mirror to SQL explicitly. Best-effort.
|
|
6992
|
+
prdStore.mirrorPrdToSql(manualPrdPath, manualPrd.plan);
|
|
6978
6993
|
invalidatePlansCache();
|
|
6979
6994
|
return jsonReply(res, 200, { ok: true, id: manualPrd.id, file: planFile });
|
|
6980
6995
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
@@ -8303,6 +8318,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
8303
8318
|
cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, getConfig());
|
|
8304
8319
|
} catch (e) { console.error('plan worktree cleanup:', e.message); }
|
|
8305
8320
|
safeUnlink(planPath);
|
|
8321
|
+
// Phase 10 step 2 — a deleted PRD must also leave SQL, or the read-flip
|
|
8322
|
+
// (step 3) would resurrect it from a stale mirror row. Best-effort; no-op
|
|
8323
|
+
// for non-PRD paths (parsePrdPath returns null for plan .md deletes).
|
|
8324
|
+
if (body.file.endsWith('.json')) prdStore.removePrdFromSql(planPath);
|
|
8306
8325
|
// Neutralize the `.backup` sidecar so `safeJson` auto-restore can't
|
|
8307
8326
|
// RESURRECT the PRD we just deleted (the live file is gone, but a stray
|
|
8308
8327
|
// `prd/<plan>.json.backup` would be auto-restored on the next safeJson read
|
|
@@ -9858,6 +9877,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9858
9877
|
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'cancelled-pre-stream');
|
|
9859
9878
|
return resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
9860
9879
|
}
|
|
9880
|
+
let poolSegmentId = 0;
|
|
9881
|
+
let segmentStart = 0;
|
|
9861
9882
|
await sessionHandle.stream(prompt, {
|
|
9862
9883
|
systemPromptText: systemPrompt,
|
|
9863
9884
|
onChunk: (delta) => {
|
|
@@ -9866,7 +9887,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9866
9887
|
accumulated += delta;
|
|
9867
9888
|
_touchCcLiveStream(liveState);
|
|
9868
9889
|
liveState.text = accumulated;
|
|
9869
|
-
|
|
9890
|
+
const segmentText = accumulated.slice(segmentStart);
|
|
9891
|
+
if (liveState.writer) liveState.writer({ type: 'chunk', text: segmentText, segmentId: poolSegmentId });
|
|
9870
9892
|
},
|
|
9871
9893
|
onToolUse: (name, input, toolCallId) => {
|
|
9872
9894
|
if (_tFirstTool == null) _tFirstTool = Date.now();
|
|
@@ -9877,6 +9899,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9877
9899
|
if (Array.isArray(toolUses)) toolUses.push(entry);
|
|
9878
9900
|
if (Array.isArray(liveState.tools)) liveState.tools.push(entry);
|
|
9879
9901
|
if (liveState.writer) liveState.writer({ type: 'tool', name, input: _lightToolInput(safeInput), id: toolCallId || null });
|
|
9902
|
+
// Advance segment tracking so the next onChunk creates a fresh segment
|
|
9903
|
+
poolSegmentId++;
|
|
9904
|
+
segmentStart = accumulated.length;
|
|
9880
9905
|
},
|
|
9881
9906
|
onToolUpdate: (toolCallId, status) => {
|
|
9882
9907
|
_touchCcLiveStream(liveState);
|
|
@@ -10865,6 +10890,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10865
10890
|
// the per-project dropdown. resolveCheckoutMode honors the legacy
|
|
10866
10891
|
// worktreeMode field; 'worktree' (default) or 'live'.
|
|
10867
10892
|
checkoutMode: shared.resolveCheckoutMode(p),
|
|
10893
|
+
// M006 — surface liveValidation so the Settings UI can pre-fill
|
|
10894
|
+
// the type input and autoDispatch toggle. Null when not configured.
|
|
10895
|
+
liveValidation: (p.liveValidation && typeof p.liveValidation === 'object' && p.liveValidation.type)
|
|
10896
|
+
? { type: p.liveValidation.type, autoDispatch: !!p.liveValidation.autoDispatch }
|
|
10897
|
+
: null,
|
|
10868
10898
|
workSources: {
|
|
10869
10899
|
pullRequests: { enabled: p.workSources?.pullRequests?.enabled !== false, cooldownMinutes: p.workSources?.pullRequests?.cooldownMinutes ?? 30 },
|
|
10870
10900
|
workItems: { enabled: p.workSources?.workItems?.enabled !== false, cooldownMinutes: p.workSources?.workItems?.cooldownMinutes ?? 0 }
|
|
@@ -11289,10 +11319,19 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11289
11319
|
// Drop the legacy field so a migrated project never carries both.
|
|
11290
11320
|
delete proj.worktreeMode;
|
|
11291
11321
|
}
|
|
11322
|
+
// M006 — per-project liveValidation: { type, autoDispatch }.
|
|
11323
|
+
// Only meaningful when checkoutMode is 'live'. Null / missing type
|
|
11324
|
+
// clears the field; explicit object with a non-empty type persists it.
|
|
11325
|
+
if (Object.prototype.hasOwnProperty.call(update, 'liveValidation')) {
|
|
11326
|
+
const lv = update.liveValidation;
|
|
11327
|
+
if (!lv || typeof lv !== 'object' || !lv.type || typeof lv.type !== 'string' || !lv.type.trim()) {
|
|
11328
|
+
delete proj.liveValidation;
|
|
11329
|
+
} else {
|
|
11330
|
+
proj.liveValidation = { type: lv.type.trim(), autoDispatch: !!lv.autoDispatch };
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11292
11333
|
}
|
|
11293
11334
|
}
|
|
11294
|
-
|
|
11295
|
-
shared.pruneDefaultClaudeConfig(config);
|
|
11296
11335
|
mutateDashboardConfig(current => mergeSettingsConfigUpdate(current, config, body, _configPatch));
|
|
11297
11336
|
// Refresh in-memory CONFIG so subsequent reads see the update
|
|
11298
11337
|
reloadConfig();
|
|
@@ -13357,7 +13396,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13357
13396
|
// /api/prd/regenerate removed — use /api/plans/approve which does diff-aware update
|
|
13358
13397
|
|
|
13359
13398
|
// Agents
|
|
13360
|
-
{ method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, contextOnly?,
|
|
13399
|
+
{ method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, contextOnly?, context?, workItemId?', handler: async (req, res) => {
|
|
13361
13400
|
const body = await readBody(req);
|
|
13362
13401
|
const { url } = body;
|
|
13363
13402
|
if (!url) return jsonReply(res, 400, { error: 'url required' });
|
package/docs/deprecated.json
CHANGED
|
@@ -3,26 +3,10 @@
|
|
|
3
3
|
"id": "agent-config-skills-field",
|
|
4
4
|
"description": "Legacy per-agent descriptive-metadata array `agents.<id>.skills` in config.json, renamed to `agents.<id>.expertise` to remove the name collision with executable runtime/harness skills (SKILL.md). The field is metadata only (capability tags like `architecture`, `bug-fixes`); nothing in the dispatch path reads it for behavior. A read-compat shim honors the old key so operator configs still carrying `skills` (and no `expertise`) keep working.",
|
|
5
5
|
"code": [
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"file": "engine/lifecycle.js",
|
|
13
|
-
"lines": "4620-4621",
|
|
14
|
-
"note": "pickReReviewAgentHints reads `agent.expertise` with an `agent.skills` array fallback"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"file": "engine/queries.js",
|
|
18
|
-
"lines": "731",
|
|
19
|
-
"note": "getAgents normalizes `expertise: a.expertise ?? a.skills ?? []` so the dashboard/settings UI always receives `expertise`"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"file": "dashboard.js",
|
|
23
|
-
"lines": "10708-10716",
|
|
24
|
-
"note": "settings POST accepts a legacy `updates.skills` key, persists as `config.agents[id].expertise`, and deletes the old `skills` key"
|
|
25
|
-
}
|
|
6
|
+
{ "file": "engine/playbook.js", "lines": "950", "note": "buildSystemPrompt reads `agent.expertise ?? agent.skills ?? []` for the `Expertise:` identity line" },
|
|
7
|
+
{ "file": "engine/lifecycle.js", "lines": "4620-4621", "note": "pickReReviewAgentHints reads `agent.expertise` with an `agent.skills` array fallback" },
|
|
8
|
+
{ "file": "engine/queries.js", "lines": "731", "note": "getAgents normalizes `expertise: a.expertise ?? a.skills ?? []` so the dashboard/settings UI always receives `expertise`" },
|
|
9
|
+
{ "file": "dashboard.js", "lines": "10708-10716", "note": "settings POST accepts a legacy `updates.skills` key, persists as `config.agents[id].expertise`, and deletes the old `skills` key" }
|
|
26
10
|
],
|
|
27
11
|
"removalGate": "Telemetry / a config sweep across all known engines must show no persisted `config.agents.<id>.skills` key (only `expertise`) for >=30 consecutive days, confirming every operator config has been re-saved through the dashboard (which drops the legacy key) or hand-migrated.",
|
|
28
12
|
"targetRemovalDate": "2026-09-17",
|
|
@@ -47,10 +31,7 @@
|
|
|
47
31
|
{
|
|
48
32
|
"id": "legacy-done-aliases",
|
|
49
33
|
"location": "engine/cleanup.js:1165-1166",
|
|
50
|
-
"constants": [
|
|
51
|
-
"LEGACY_DONE_ALIASES",
|
|
52
|
-
"LEGACY_NEEDS_REVIEW_STATUS"
|
|
53
|
-
],
|
|
34
|
+
"constants": ["LEGACY_DONE_ALIASES", "LEGACY_NEEDS_REVIEW_STATUS"],
|
|
54
35
|
"reason": "Read-side tolerance: cleanup sweep auto-migrates four obsolete work-item / PRD status strings ('in-pr', 'implemented', 'complete', 'needs-human-review') to the canonical 'done' / 'failed' values. The aliases are no longer written anywhere in the engine; the constants exist only to repair stale on-disk values from old engine versions.",
|
|
55
36
|
"targetRemovalDate": null,
|
|
56
37
|
"notes": "Keep indefinitely until telemetry / a sweep log shows zero migrations performed for 30 consecutive days across all known projects (work-items.json + prd/*.json). At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js (definitions at :1165-1166; usage at :1168-1183 for work items and :1269-1272 for PRD missing_features) can be deleted. Total cost on disk today: 4 strings."
|
|
@@ -59,21 +40,9 @@
|
|
|
59
40
|
"id": "config-claude-binary-override",
|
|
60
41
|
"description": "Legacy `config.claude.binary` runtime-resolution override. Older `minions init` versions persisted a `config.claude.binary` field that pointed the Claude runtime at a specific binary path. The runtime adapter still honors this override on every Claude spawn; the engine emits a `deprecated-config-claude` warning at config-load time but does NOT delete the override, so the override branch in claude.js is load-bearing for any install that still carries a non-default value.",
|
|
61
42
|
"code": [
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"note": "resolveBinary() respects config.claude.binary on every Claude spawn (probes npm package dir or direct binary path)"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"file": "engine/shared.js",
|
|
69
|
-
"lines": "2482-2496",
|
|
70
|
-
"note": "warnings.push({ id: 'deprecated-config-claude' }) — surface-only; never deletes the override"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"file": "engine/shared.js",
|
|
74
|
-
"lines": "3120-3124",
|
|
75
|
-
"note": "DEFAULT_CLAUDE.binary baseline that the warning + prune logic compares against"
|
|
76
|
-
}
|
|
43
|
+
{ "file": "engine/runtimes/claude.js", "lines": "82-86", "note": "resolveBinary() respects config.claude.binary on every Claude spawn (probes npm package dir or direct binary path)" },
|
|
44
|
+
{ "file": "engine/shared.js", "lines": "2482-2496", "note": "warnings.push({ id: 'deprecated-config-claude' }) — surface-only; never deletes the override" },
|
|
45
|
+
{ "file": "engine/shared.js", "lines": "3120-3124", "note": "DEFAULT_CLAUDE.binary baseline that the warning + prune logic compares against" }
|
|
77
46
|
],
|
|
78
47
|
"removalGate": "Telemetry: the `deprecated-config-claude` warning emitted at engine/shared.js:2492-2495 must report zero hits across all known engines for >=30 consecutive days, AND a sweep of every persisted config.json must show no `config.claude.binary` value that diverges from DEFAULT_CLAUDE.binary. Only then is the override branch in resolveBinary() (engine/runtimes/claude.js:82-86) removable, along with the `_deprecatedConfigClaudeFields` membership for `binary` and the warning emitter at engine/shared.js:2482-2496.",
|
|
79
48
|
"targetRemovalDate": null,
|
|
@@ -83,41 +52,13 @@
|
|
|
83
52
|
"id": "legacy-cc-model-migration",
|
|
84
53
|
"description": "applyLegacyCcModelMigration: in-memory shim that promotes legacy `engine.ccModel` to `engine.defaultModel` when defaultModel is unset, so single-model installs keep working after the runtime fleet refactor (P-3b8e5f1d). No on-disk rewrite — the persisted config.json continues to carry the legacy `ccModel` field. Called unconditionally on every engine boot from cli.start().",
|
|
85
54
|
"code": [
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
"lines": "477",
|
|
94
|
-
"note": "Boot call site inside start(); wrapped in try/catch so a migration failure cannot block startup"
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
"file": "CLAUDE.md",
|
|
98
|
-
"lines": "316",
|
|
99
|
-
"note": "Architectural documentation calling out the in-memory promotion contract"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"file": "docs/slim-ux/concepts.md",
|
|
103
|
-
"lines": "671",
|
|
104
|
-
"note": "Surface-level concepts doc cross-reference"
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"file": "test/unit.test.js",
|
|
108
|
-
"lines": "19801",
|
|
109
|
-
"note": "Source-inspection test pinning the CLAUDE.md description against the function name"
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"file": "test/unit/runtime-fleet-helpers.test.js",
|
|
113
|
-
"lines": "209-254",
|
|
114
|
-
"note": "Behavioural unit tests (promotion, no-op when defaultModel set, no-op when ccModel unset, empty-string handling, once-only logging, null-safety)"
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"file": "test/unit/runtime-fleet-helpers.test.js",
|
|
118
|
-
"lines": "500-505",
|
|
119
|
-
"note": "Source-inspection test pinning the cli.js boot call site"
|
|
120
|
-
}
|
|
55
|
+
{ "file": "engine/shared.js", "lines": "2407", "note": "applyLegacyCcModelMigration definition (function signature + once-per-process flag via _resetLegacyCcModelMigrationFlag)" },
|
|
56
|
+
{ "file": "engine/cli.js", "lines": "477", "note": "Boot call site inside start(); wrapped in try/catch so a migration failure cannot block startup" },
|
|
57
|
+
{ "file": "CLAUDE.md", "lines": "316", "note": "Architectural documentation calling out the in-memory promotion contract" },
|
|
58
|
+
{ "file": "docs/slim-ux/concepts.md", "lines": "671", "note": "Surface-level concepts doc cross-reference" },
|
|
59
|
+
{ "file": "test/unit.test.js", "lines": "19801", "note": "Source-inspection test pinning the CLAUDE.md description against the function name" },
|
|
60
|
+
{ "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "209-254", "note": "Behavioural unit tests (promotion, no-op when defaultModel set, no-op when ccModel unset, empty-string handling, once-only logging, null-safety)" },
|
|
61
|
+
{ "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "500-505", "note": "Source-inspection test pinning the cli.js boot call site" }
|
|
121
62
|
],
|
|
122
63
|
"removalGate": "Telemetry: the once-per-boot deprecation log line emitted by applyLegacyCcModelMigration (via the injected logger at engine/shared.js:2407) must show zero promotion events across all known engines for >=30 consecutive days, AND a sweep of every persisted config.json must confirm no `engine.ccModel` field remains. Once both conditions hold, removal deletes the function + _resetLegacyCcModelMigrationFlag export at engine/shared.js:4977, the boot call at engine/cli.js:477, the CLAUDE.md:316 paragraph and docs/slim-ux/concepts.md:671 reference, and the tests at runtime-fleet-helpers.test.js:209-254 + :500-505 + unit.test.js:19801.",
|
|
123
64
|
"targetRemovalDate": null,
|
|
@@ -127,39 +68,14 @@
|
|
|
127
68
|
"id": "sql-state-json-mirrors",
|
|
128
69
|
"description": "Phase X.5 follow-up to the SQL state migration (commits 62bd6a2c..1111cf54, phases 0–7). Every engine state file that previously used mutateJsonFileLocked now routes through a SQL store, but each store still writes a JSON dual-write mirror after every mutation because a handful of direct-readers (a few unit tests + a couple of inline safeJson calls) have not been migrated to the SQL read path. Once those readers are confirmed routed through the SQL store (or rewritten to use the store's read helper), the mirror writers can be deleted and the JSON files retired.",
|
|
129
70
|
"code": [
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
{
|
|
139
|
-
"file": "engine/pull-requests-store.js",
|
|
140
|
-
"note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope)"
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"file": "engine/logs-store.js",
|
|
144
|
-
"note": "engine/log.json mirror written by shared._flushLogBuffer's byJsonPath loop — Phase 4.5 will retire"
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
"file": "engine/metrics-store.js",
|
|
148
|
-
"note": "_mirrorJsonFromSql + _readJsonObjectFallback"
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"file": "engine/watches-store.js",
|
|
152
|
-
"note": "_mirrorJsonFromSql + _readJsonArrayFallback"
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
"file": "engine/small-state-store.js",
|
|
156
|
-
"note": "_mirrorScheduleRunsJson, _mirrorPipelineRunsJson, _mirrorManagedProcessesJson, _mirrorWorktreePoolJson + each store's _readJson fallback path"
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
"file": "CLAUDE.md",
|
|
160
|
-
"lines": "47-66, 240-265",
|
|
161
|
-
"note": "State Files + Concurrency sections still describe JSON files as the source of truth; they describe a layered SQLite-then-mirror reality in places but the headline contract still reads as JSON-primary. Rewrite these sections to make SQL-as-source-of-truth the headline and the JSON mirrors a transitional compatibility detail."
|
|
162
|
-
}
|
|
71
|
+
{ "file": "engine/dispatch-store.js", "note": "_mirrorJsonFromSql + _readDispatchJsonFallback — used when SQL is empty AND JSON has content (test seeding + first-time hydrate)" },
|
|
72
|
+
{ "file": "engine/work-items-store.js", "note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope) — same fallback contract as dispatch-store" },
|
|
73
|
+
{ "file": "engine/pull-requests-store.js", "note": "_mirrorJsonFromSql + _readJsonArrayFallback (per scope)" },
|
|
74
|
+
{ "file": "engine/logs-store.js", "note": "engine/log.json mirror written by shared._flushLogBuffer's byJsonPath loop — Phase 4.5 will retire" },
|
|
75
|
+
{ "file": "engine/metrics-store.js", "note": "_mirrorJsonFromSql + _readJsonObjectFallback" },
|
|
76
|
+
{ "file": "engine/watches-store.js", "note": "_mirrorJsonFromSql + _readJsonArrayFallback" },
|
|
77
|
+
{ "file": "engine/small-state-store.js", "note": "_mirrorScheduleRunsJson, _mirrorPipelineRunsJson, _mirrorManagedProcessesJson, _mirrorWorktreePoolJson + each store's _readJson fallback path" },
|
|
78
|
+
{ "file": "CLAUDE.md", "lines": "47-66, 240-265", "note": "State Files + Concurrency sections still describe JSON files as the source of truth; they describe a layered SQLite-then-mirror reality in places but the headline contract still reads as JSON-primary. Rewrite these sections to make SQL-as-source-of-truth the headline and the JSON mirrors a transitional compatibility detail." }
|
|
163
79
|
],
|
|
164
80
|
"removalGate": "All direct-readers of the mirror JSON files must be confirmed routed through their respective SQL store's read helper. Specifically: (a) grep the codebase for `safeJson`, `safeJsonArr`, `safeJsonObj`, `readFileSync(...work-items.json|pull-requests.json|metrics.json|watches.json|schedule-runs.json|pipeline-runs.json|managed-processes.json|worktree-pool.json|log.json|dispatch.json...)` and confirm every hit is either (i) a test fixture that can move to the SQL helper, or (ii) intentionally documented as bypassing SQL. (b) Run the full test suite with each store's _mirrorJsonFromSql temporarily neutered (returning early before safeWrite) and confirm 0 failures — that proves no production code path depends on the mirror. Once both conditions hold, removal deletes each store's _mirrorJsonFromSql call site in shared.js (mutateWorkItems/mutatePullRequests/etc.), the corresponding _readJsonArrayFallback paths, and the JSON file gitignore entries. CLAUDE.md update can ship independently as soon as someone has bandwidth.",
|
|
165
81
|
"targetRemovalDate": null,
|
|
@@ -169,51 +85,15 @@
|
|
|
169
85
|
"id": "prune-default-claude-config",
|
|
170
86
|
"description": "pruneDefaultClaudeConfig: active sanitizer that strips generated `config.claude.{binary,outputFormat,allowedTools,permissionMode}` defaults from persisted config.json so the `deprecated-config-claude` warning stops tripping on stale defaults left by older `minions init` versions. Sub-cluster of `config-claude-binary-override` — the prune deliberately preserves non-default user overrides (binary/allowedTools), which is what keeps the override branch in engine/runtimes/claude.js load-bearing.",
|
|
171
87
|
"code": [
|
|
172
|
-
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
"file": "dashboard.js",
|
|
184
|
-
"lines": "202",
|
|
185
|
-
"note": "Called when loading config for the dashboard UI"
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"file": "dashboard.js",
|
|
189
|
-
"lines": "9116",
|
|
190
|
-
"note": "Called during first config save handler"
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
"file": "dashboard.js",
|
|
194
|
-
"lines": "9331",
|
|
195
|
-
"note": "Called during second config save path"
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
"file": "dashboard.js",
|
|
199
|
-
"lines": "9450",
|
|
200
|
-
"note": "Called during third config save path"
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
"file": "minions.js",
|
|
204
|
-
"lines": "385",
|
|
205
|
-
"note": "Called during CLI init/update flow"
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
"file": "test/unit.test.js",
|
|
209
|
-
"lines": "2260-2303",
|
|
210
|
-
"note": "Behavioural unit tests (default strip, override preservation, outputFormat unconditional strip) + dashboard call-site source pin"
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
"file": "test/unit/runtime-fleet-helpers.test.js",
|
|
214
|
-
"lines": "546",
|
|
215
|
-
"note": "Source-inspection test pinning the dashboard handler call site"
|
|
216
|
-
}
|
|
88
|
+
{ "file": "engine/shared.js", "lines": "3126", "note": "pruneDefaultClaudeConfig definition: preserves non-default binary/allowedTools, always strips permissionMode + outputFormat" },
|
|
89
|
+
{ "file": "engine/shared.js", "lines": "5673", "note": "Module export entry" },
|
|
90
|
+
{ "file": "dashboard.js", "lines": "202", "note": "Called when loading config for the dashboard UI" },
|
|
91
|
+
{ "file": "dashboard.js", "lines": "9116", "note": "Called during first config save handler" },
|
|
92
|
+
{ "file": "dashboard.js", "lines": "9331", "note": "Called during second config save path" },
|
|
93
|
+
{ "file": "dashboard.js", "lines": "9450", "note": "Called during third config save path" },
|
|
94
|
+
{ "file": "minions.js", "lines": "385", "note": "Called during CLI init/update flow" },
|
|
95
|
+
{ "file": "test/unit.test.js", "lines": "2260-2303", "note": "Behavioural unit tests (default strip, override preservation, outputFormat unconditional strip) + dashboard call-site source pin" },
|
|
96
|
+
{ "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "546", "note": "Source-inspection test pinning the dashboard handler call site" }
|
|
217
97
|
],
|
|
218
98
|
"removalGate": "Telemetry: pruneDefaultClaudeConfig must return false (no mutation) for every call across all known engines for >=30 consecutive days (add an `_engine.pruneDefaultClaudeConfigStrips` counter if needed to observe this), AND the parent `config-claude-binary-override` entry must have already cleared its own gate. The dependency is strict: removing the prune while users still rely on the override branch would surface the `deprecated-config-claude` warning on every stale generated default. Once both conditions hold, removal is the function definition (engine/shared.js:3126), the export at :5673, all 5 call sites (dashboard.js:202, :9116, :9331, :9450; minions.js:385), and the tests at unit.test.js:2260-2303 + runtime-fleet-helpers.test.js:546.",
|
|
219
99
|
"targetRemovalDate": null,
|
|
@@ -226,10 +106,7 @@
|
|
|
226
106
|
"status": "removed",
|
|
227
107
|
"removedDate": "2026-06-25",
|
|
228
108
|
"code": [
|
|
229
|
-
{
|
|
230
|
-
"file": "engine/ado.js",
|
|
231
|
-
"note": "isAdoThrottled() arg-less branch and the global-OR fold over the per-org Map. Single call site to migrate: shared.getAdoOrgBase(project) is already in scope at every consumer."
|
|
232
|
-
}
|
|
109
|
+
{ "file": "engine/ado.js", "note": "isAdoThrottled() arg-less branch and the global-OR fold over the per-org Map. Single call site to migrate: shared.getAdoOrgBase(project) is already in scope at every consumer." }
|
|
233
110
|
],
|
|
234
111
|
"removalGate": "Two conditions must hold simultaneously: (a) grep `engine/ado.js` for `isAdoThrottled\\s*\\(\\s*\\)` and confirm zero arg-less call sites remain across the engine — every caller passes a concrete `orgBase` resolved via `shared.getAdoOrgBase(project)`; (b) `GET /api/diagnostics/ado-throttle` on a live engine has been observed for >=2 consecutive weeks reporting per-org keys (proves the per-org Map is populated under load and the global-OR isn't masking a regression). Once both hold, removal deletes the arg-less branch in isAdoThrottled and the global-OR fold; callers that still pass no argument become an immediate, surfaced bug rather than a silent over-throttle.",
|
|
235
112
|
"targetRemovalDate": "2026-08-03",
|
|
@@ -273,13 +150,10 @@
|
|
|
273
150
|
"id": "pr-observe-observe-body-param",
|
|
274
151
|
"description": "Legacy `observe` body parameter on `POST /api/pull-requests/observe`. The W-mq5s5ttx000j7ab8 endpoint sub-WI introduces canonical `contextOnly` as the inverse (`observe: false` ⇔ `contextOnly: true`) and keeps `observe` accepted for backward compat. Registering the deprecation here so the alias has a documented removal path; the WI explicitly notes this entry is the implementer's call (it is kept for backward compat and may live longer than the underscore-prefixed record fields).",
|
|
275
152
|
"code": [
|
|
276
|
-
{
|
|
277
|
-
"file": "dashboard.js",
|
|
278
|
-
"note": "POST /api/pull-requests/observe handler reads `body.contextOnly` first, then falls back to `!body.observe` for backwards compat."
|
|
279
|
-
}
|
|
153
|
+
{ "file": "dashboard.js", "note": "POST /api/pull-requests/observe handler reads `body.contextOnly` first, then falls back to `!body.observe` for backwards compat." }
|
|
280
154
|
],
|
|
281
155
|
"deprecated": "2026-06-08",
|
|
282
156
|
"targetRemovalDate": null,
|
|
283
157
|
"notes": "targetRemovalDate intentionally null — unlike the record-field aliases (`_contextOnly`, `_autoObserve`, `_manual`) which carry a 7-day clock, the `observe` body param is documented as a longer-lived back-compat alias. Set targetRemovalDate to a concrete future date once the dashboard UI + any client scripts are confirmed to POST `contextOnly` exclusively. Removal scope when the date is set: drop the `body.observe` fallback in dashboard.js, drop `observe` from the route registry params, and update any client still POSTing `observe`."
|
|
284
158
|
}
|
|
285
|
-
]
|
|
159
|
+
]
|
|
@@ -152,6 +152,60 @@ git for-each-ref --format '%(refname:short) %(upstream:track)' refs/heads \
|
|
|
152
152
|
|
|
153
153
|
The engine has no opinion about local branches; this hygiene is the operator's responsibility in live mode.
|
|
154
154
|
|
|
155
|
+
## Hybrid mode / deferred validation
|
|
156
|
+
|
|
157
|
+
> **Requires `checkoutMode: 'live'`** — `liveValidation` is ignored when `checkoutMode` is `'worktree'` (the default).
|
|
158
|
+
|
|
159
|
+
Hybrid mode lets you run *coding* work items (implement, fix, docs, …) in isolated worktrees while keeping *validation* work items (build-and-test, test, verify, …) serialized in live checkout — the setup where build caches, native toolchains, and test infrastructure only exist in one canonical checkout.
|
|
160
|
+
|
|
161
|
+
### Full flow
|
|
162
|
+
|
|
163
|
+
1. **Coding WI dispatched** — because `liveValidation.type` is set and this WI's type is *not* the validation type, `resolveCheckoutMode(project, workItem.type)` returns `'worktree'`. The engine creates an isolated worktree as normal, runs the agent, and pushes a PR branch.
|
|
164
|
+
2. **Coding WI completes** — if `liveValidation.autoDispatch: true`, the lifecycle hook (`engine/lifecycle.js`) auto-creates a validation WI of type `liveValidation.type` targeting the same PR branch.
|
|
165
|
+
3. **Validation WI dispatched** — `resolveCheckoutMode(project, 'build-and-test')` returns `'live'` (matches `liveValidation.type`). The engine runs `prepareLiveCheckout` in the operator's canonical checkout, which checks out the coding WI's branch in-place and runs the validation agent.
|
|
166
|
+
4. **Validation WI completes** — `restoreLiveCheckoutAtDispatchEnd` checks the operator's tree back to the original ref.
|
|
167
|
+
|
|
168
|
+
### Config snippet
|
|
169
|
+
|
|
170
|
+
```jsonc
|
|
171
|
+
{
|
|
172
|
+
"projects": [{
|
|
173
|
+
"name": "android-aosp",
|
|
174
|
+
"localPath": "/home/yemi/aosp",
|
|
175
|
+
"checkoutMode": "live",
|
|
176
|
+
"liveValidation": {
|
|
177
|
+
"type": "build-and-test",
|
|
178
|
+
"autoDispatch": true
|
|
179
|
+
}
|
|
180
|
+
}]
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Configure via Dashboard → Settings → Projects → **Live validation (deferred build/test)** section. The section is greyed-out when `checkoutMode` is not `'live'`.
|
|
185
|
+
|
|
186
|
+
### Agent behavior contract
|
|
187
|
+
|
|
188
|
+
When a coding agent is dispatched on a project with `liveValidation.autoDispatch: true`, the agent's playbook includes a note that inline build/test runs are not required:
|
|
189
|
+
|
|
190
|
+
> Skip inline build and test verification steps — a separate validation dispatch will run these in the live checkout after this coding WI completes. Push the branch and report success; the validation WI handles build/test confirmation.
|
|
191
|
+
|
|
192
|
+
This prevents coding agents (running in isolated worktrees) from attempting builds that may fail due to missing native toolchains, caches, or environment variables only present in the canonical checkout.
|
|
193
|
+
|
|
194
|
+
### Migration guidance
|
|
195
|
+
|
|
196
|
+
If your project is on `checkoutMode: live` and you want parallel coding WIs, add:
|
|
197
|
+
|
|
198
|
+
```jsonc
|
|
199
|
+
"liveValidation": { "type": "build-and-test", "autoDispatch": true }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
No other config changes needed. The engine automatically:
|
|
203
|
+
- Dispatches coding WIs into isolated worktrees (escaping the live-checkout cap of 1)
|
|
204
|
+
- Dispatches validation WIs serially in live checkout (preserving your build environment)
|
|
205
|
+
- Auto-creates validation WIs after each coding WI's PR is pushed
|
|
206
|
+
|
|
207
|
+
To opt back out, clear `liveValidation` from the project config (or set to `null` via Dashboard → Settings).
|
|
208
|
+
|
|
155
209
|
## Non-goals
|
|
156
210
|
|
|
157
211
|
Live-checkout mode is deliberately small. These are NOT supported and will not be added:
|
package/docs/project-skills.md
CHANGED
|
@@ -13,7 +13,6 @@ This document is the contract every playbook author and project teammate needs t
|
|
|
13
13
|
| `engine/discover-project-skills.js` | Bounded filesystem walk + classification heuristic. The crash-safe primitive. |
|
|
14
14
|
| `engine/playbook-intents.js` | Single source of truth for the playbook → intent set mapping. |
|
|
15
15
|
| `engine/playbook.js` | Calls discovery + filter at render time, injects `{{project_skills_block}}` into the rendered prompt. |
|
|
16
|
-
| `engine/discover-review-skills.js` | Thin re-export shim. Backward-compat only — new code uses `discover-project-skills.js`. |
|
|
17
16
|
|
|
18
17
|
## What gets discovered
|
|
19
18
|
|
package/engine/cleanup.js
CHANGED
|
@@ -458,6 +458,40 @@ function reapAgentScratch(worktreeRoot) {
|
|
|
458
458
|
return reaped;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
// Disposable agent-deliverable scratch that piles up in knowledge/: per-batch
|
|
462
|
+
// cleanup implementation/analysis notes and the regenerable consolidated digests.
|
|
463
|
+
// These are gitignored working notes nothing reads back — emitted by the
|
|
464
|
+
// simplify/KB-cleanup sweeps at daily cadence, they accumulate forever otherwise.
|
|
465
|
+
// The pattern is intentionally NARROW: curated KB entries and the per-agent
|
|
466
|
+
// memory files (knowledge/agents/<id>.md) never match it and are never touched.
|
|
467
|
+
const KNOWLEDGE_SCRATCH_RE = /(?:kb-cleanup-batch|consolidated-digest|consolidated-ci-health)/i;
|
|
468
|
+
const KNOWLEDGE_SCRATCH_TTL_MS = 48 * 60 * 60 * 1000; // 48h — keep a couple days for reference, reap older
|
|
469
|
+
const KNOWLEDGE_SCRATCH_MAX_SCAN = 5000;
|
|
470
|
+
|
|
471
|
+
function reapKnowledgeScratch(minionsDir = MINIONS_DIR) {
|
|
472
|
+
const dir = path.join(minionsDir, 'knowledge');
|
|
473
|
+
let names;
|
|
474
|
+
try { names = fs.readdirSync(dir); } catch { return 0; } // no knowledge dir → nothing to do
|
|
475
|
+
const cutoff = Date.now() - KNOWLEDGE_SCRATCH_TTL_MS;
|
|
476
|
+
let reaped = 0;
|
|
477
|
+
const limit = Math.min(names.length, KNOWLEDGE_SCRATCH_MAX_SCAN);
|
|
478
|
+
for (let i = 0; i < limit; i++) {
|
|
479
|
+
const name = names[i];
|
|
480
|
+
// Only top-level .md files whose name matches the disposable-scratch pattern.
|
|
481
|
+
if (!name.endsWith('.md') || !KNOWLEDGE_SCRATCH_RE.test(name)) continue;
|
|
482
|
+
const full = path.join(dir, name);
|
|
483
|
+
let stat;
|
|
484
|
+
try { stat = fs.statSync(full); } catch { continue; }
|
|
485
|
+
if (!stat.isFile() || stat.mtimeMs >= cutoff) continue; // recent — keep for reference
|
|
486
|
+
try { fs.unlinkSync(full); reaped++; }
|
|
487
|
+
catch { /* locked / in use — leave it for a later pass */ }
|
|
488
|
+
}
|
|
489
|
+
if (reaped > 0) {
|
|
490
|
+
log('info', `Reaped ${reaped} stale knowledge-scratch notes (>${Math.round(KNOWLEDGE_SCRATCH_TTL_MS / 3600000)}h) from ${dir}`);
|
|
491
|
+
}
|
|
492
|
+
return reaped;
|
|
493
|
+
}
|
|
494
|
+
|
|
461
495
|
// ─── Cleanup Orchestrator ────────────────────────────────────────────────────
|
|
462
496
|
|
|
463
497
|
async function runCleanup(config, verbose = false) {
|
|
@@ -742,7 +776,11 @@ async function runCleanup(config, verbose = false) {
|
|
|
742
776
|
}
|
|
743
777
|
|
|
744
778
|
// Get PRs for this project
|
|
745
|
-
|
|
779
|
+
// Array.isArray guard: safeJson(p) || [] only substitutes [] for falsy values;
|
|
780
|
+
// a truthy non-array (e.g. {}, 42, true) bypasses the fallback and causes a
|
|
781
|
+
// TypeError in the for-of loop below. Array.isArray covers that case.
|
|
782
|
+
const _prsRaw = safeJson(projectPrPath(project));
|
|
783
|
+
const prs = Array.isArray(_prsRaw) ? _prsRaw : [];
|
|
746
784
|
const mergedBranches = new Set();
|
|
747
785
|
for (const pr of prs) {
|
|
748
786
|
if (pr.status === shared.PR_STATUS.MERGED || pr.status === shared.PR_STATUS.ABANDONED) {
|
|
@@ -960,7 +998,10 @@ async function runCleanup(config, verbose = false) {
|
|
|
960
998
|
// Remove all marked worktrees
|
|
961
999
|
// Re-read PR status immediately before deletion — a PR can be reopened between
|
|
962
1000
|
// the initial status check and the actual deletion (Bug #15: TOCTOU race)
|
|
963
|
-
|
|
1001
|
+
// Array.isArray guard: same as the prs read above — non-array truthy JSON
|
|
1002
|
+
// bypasses || [] and would throw inside the try-catch, silently skipping deletions.
|
|
1003
|
+
const _freshPrsRaw = safeJson(projectPrPath(project));
|
|
1004
|
+
const freshPrs = Array.isArray(_freshPrsRaw) ? _freshPrsRaw : [];
|
|
964
1005
|
const freshMergedBranches = new Set();
|
|
965
1006
|
const freshMergedPrByBranch = new Map();
|
|
966
1007
|
for (const pr of freshPrs) {
|
|
@@ -1585,6 +1626,13 @@ async function runCleanup(config, verbose = false) {
|
|
|
1585
1626
|
}
|
|
1586
1627
|
} catch (e) { log('warn', `pruneStaleBackupSidecars: ${e.message}`); }
|
|
1587
1628
|
|
|
1629
|
+
// 17. Reap stale gitignored knowledge-scratch notes (kb-cleanup-batch / digests)
|
|
1630
|
+
// that the simplify/KB-cleanup sweeps emit into knowledge/ and never read back.
|
|
1631
|
+
cleaned.knowledgeScratch = 0;
|
|
1632
|
+
try {
|
|
1633
|
+
cleaned.knowledgeScratch = reapKnowledgeScratch();
|
|
1634
|
+
} catch (e) { log('warn', `reapKnowledgeScratch: ${e.message}`); }
|
|
1635
|
+
|
|
1588
1636
|
return cleaned;
|
|
1589
1637
|
}
|
|
1590
1638
|
|
|
@@ -1738,4 +1786,5 @@ module.exports = {
|
|
|
1738
1786
|
cleanupMergedPrLocalBranch, // exported for lifecycle cleanup and testing
|
|
1739
1787
|
collectPhantomBranchesForProject, // P-e0b4f7a5 — exported for testing
|
|
1740
1788
|
reapAgentScratch, // exported for testing
|
|
1789
|
+
reapKnowledgeScratch, // exported for testing
|
|
1741
1790
|
};
|
|
Binary file
|
package/engine/features.js
CHANGED
|
@@ -78,6 +78,20 @@ const FEATURES = {
|
|
|
78
78
|
addedIn: '0.1.1916',
|
|
79
79
|
requiredCcRuntime: 'copilot',
|
|
80
80
|
},
|
|
81
|
+
// prdReadsFromSql — Phase 10 step 3 read-flip. When ON, getPrdInfo sources its
|
|
82
|
+
// PRD list (existingPrds / verifyPrsByPlan / allPrdItems) from the SQL mirror
|
|
83
|
+
// (prds/prd_items/prd_verify_prs) after reconciling it from disk, instead of
|
|
84
|
+
// scanning prd/*.json directly. JSON stays canonical and the dual-write keeps
|
|
85
|
+
// SQL in sync; reconciliation catches PRDs written outside the chokepoint. The
|
|
86
|
+
// two code paths are proven output-equivalent by db-phase10-read-flip.test.js.
|
|
87
|
+
// Reversible: set `features.prdReadsFromSql: false` to fall back to the file
|
|
88
|
+
// scan instantly. Temporary migration gate — remove once the read-flip soaks.
|
|
89
|
+
'prdReadsFromSql': {
|
|
90
|
+
description: 'Source the dashboard PRD/plan read (getPrdInfo) from the SQL mirror instead of scanning prd/*.json. Reversible; the two paths are output-equivalent (Phase 10 step 3).',
|
|
91
|
+
default: true,
|
|
92
|
+
addedIn: '0.1.2090',
|
|
93
|
+
expires: '2026-12-01',
|
|
94
|
+
},
|
|
81
95
|
};
|
|
82
96
|
|
|
83
97
|
const ENV_TRUTHY = new Set(['1', 'true', 'on', 'yes']);
|
package/engine/llm.js
CHANGED
|
@@ -405,6 +405,7 @@ function _spawnProcess(promptText, sysPromptText, callOpts) {
|
|
|
405
405
|
maxBudget, bare, fallbackModel,
|
|
406
406
|
stream, disableBuiltinMcps, suppressAgentsMd, reasoningSummaries,
|
|
407
407
|
images,
|
|
408
|
+
tmpDir: llmTmpDir,
|
|
408
409
|
};
|
|
409
410
|
// Capability-gate per-flag opts before prompt construction so adapters can
|
|
410
411
|
// make resume-aware prompt decisions from the same opts used for argv.
|
package/engine/playbook.js
CHANGED
|
@@ -1007,11 +1007,11 @@ function renderPlaybook(type, vars) {
|
|
|
1007
1007
|
// ─── Playbook Section Validator ──────────────────────────────────────────────
|
|
1008
1008
|
|
|
1009
1009
|
// Required structural section patterns — warn (do not throw) when absent.
|
|
1010
|
-
//
|
|
1010
|
+
// NOTE: '## Tools' / '## Constraints' are NOT included because none of the
|
|
1011
|
+
// production playbooks use those headers. Only '## Your Task' is universally
|
|
1012
|
+
// present — it is injected by renderPlaybook via the playbook template.
|
|
1011
1013
|
const _REQUIRED_PROMPT_SECTIONS = [
|
|
1012
|
-
{ pattern: /^## Your Task\b/m,
|
|
1013
|
-
{ pattern: /^## Tools?\b/m, label: '## Tools' },
|
|
1014
|
-
{ pattern: /^## Constraints\b/m, label: '## Constraints' },
|
|
1014
|
+
{ pattern: /^## Your Task\b/m, label: '## Your Task' },
|
|
1015
1015
|
];
|
|
1016
1016
|
|
|
1017
1017
|
/**
|