@yemi33/minions 0.1.2285 → 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 +146 -72
- 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 }); }
|
|
@@ -8022,77 +8037,103 @@ const server = http.createServer(async (req, res) => {
|
|
|
8022
8037
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
8023
8038
|
}
|
|
8024
8039
|
|
|
8040
|
+
// Shared by pause + reject (and any future PRD-stop handler): stop a PRD's
|
|
8041
|
+
// materialized work. Kills any active dispatch for the PRD's items, transitions
|
|
8042
|
+
// every non-completed WI sourced from the PRD to `targetStatus`, AND cancels the
|
|
8043
|
+
// still-running plan-to-prd regeneration WI for the PRD's source plan so it can't
|
|
8044
|
+
// silently rebuild the PRD we just paused/rejected. Returns the count of WIs
|
|
8045
|
+
// transitioned. Lockless reads → cleanDispatchEntries (atomic kill + remove) →
|
|
8046
|
+
// mutateWorkItems, mirroring the original pause flow. (RC3 — reject had no
|
|
8047
|
+
// cleanup; pause never stopped regeneration.)
|
|
8048
|
+
function stopPlanMaterializedWork(prdFile, prdSourcePlan, opts) {
|
|
8049
|
+
const targetStatus = opts.targetStatus;
|
|
8050
|
+
const wiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
8051
|
+
for (const proj of PROJECTS) wiPaths.push(shared.projectWorkItemsPath(proj));
|
|
8052
|
+
|
|
8053
|
+
// Step 1: find dispatched item ids (read-only, no lock).
|
|
8054
|
+
const dispatchedItemIds = new Set();
|
|
8055
|
+
for (const wiPath of wiPaths) {
|
|
8056
|
+
try {
|
|
8057
|
+
for (const w of safeJsonArr(wiPath)) {
|
|
8058
|
+
if (w.sourcePlan !== prdFile) continue;
|
|
8059
|
+
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
8060
|
+
if (w.status === WI_STATUS.DISPATCHED && w.id) dispatchedItemIds.add(w.id);
|
|
8061
|
+
}
|
|
8062
|
+
} catch { /* file may not exist */ }
|
|
8063
|
+
}
|
|
8064
|
+
|
|
8065
|
+
// Step 2: kill active dispatches via the canonical primitive (resolves PIDs from
|
|
8066
|
+
// the pid sidecar, kills outside the dispatch lock, removes via mutateDispatch).
|
|
8067
|
+
if (dispatchedItemIds.size > 0) {
|
|
8068
|
+
cleanDispatchEntries((d) => {
|
|
8069
|
+
const itemId = d.meta?.item?.id;
|
|
8070
|
+
if (itemId && dispatchedItemIds.has(itemId)) return true;
|
|
8071
|
+
if (d.meta?.dispatchKey && [...dispatchedItemIds].some(id => d.meta.dispatchKey.includes(id))) return true;
|
|
8072
|
+
return false;
|
|
8073
|
+
});
|
|
8074
|
+
}
|
|
8075
|
+
|
|
8076
|
+
// Step 3: transition WIs per path (each lock held briefly, no nesting).
|
|
8077
|
+
let affected = 0;
|
|
8078
|
+
for (const wiPath of wiPaths) {
|
|
8079
|
+
try {
|
|
8080
|
+
mutateWorkItems(wiPath, items => {
|
|
8081
|
+
for (const w of items) {
|
|
8082
|
+
if (w.sourcePlan !== prdFile) continue;
|
|
8083
|
+
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
8084
|
+
if (w.status !== targetStatus) affected++;
|
|
8085
|
+
w.status = targetStatus;
|
|
8086
|
+
if (opts.stampField) w[opts.stampField] = opts.stampValue;
|
|
8087
|
+
delete w._resumedAt;
|
|
8088
|
+
delete w.dispatched_at;
|
|
8089
|
+
delete w.dispatched_to;
|
|
8090
|
+
delete w.failReason;
|
|
8091
|
+
delete w.failedAt;
|
|
8092
|
+
}
|
|
8093
|
+
});
|
|
8094
|
+
} catch (e) { console.error('stopPlanMaterializedWork work items:', e.message); }
|
|
8095
|
+
}
|
|
8096
|
+
|
|
8097
|
+
// Step 4: cancel the still-running plan-to-prd regeneration WI for this source
|
|
8098
|
+
// plan — otherwise a pending/dispatched plan-to-prd run rebuilds the PRD we just
|
|
8099
|
+
// stopped. (delete handles the DONE plan-to-prd WI separately to revert to draft.)
|
|
8100
|
+
if (prdSourcePlan) {
|
|
8101
|
+
try {
|
|
8102
|
+
const centralPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
8103
|
+
mutateWorkItems(centralPath, items => {
|
|
8104
|
+
for (const w of items) {
|
|
8105
|
+
if (w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === prdSourcePlan &&
|
|
8106
|
+
!DONE_STATUSES.has(w.status) && w.status !== WI_STATUS.CANCELLED) {
|
|
8107
|
+
w.status = WI_STATUS.CANCELLED;
|
|
8108
|
+
w._cancelledBy = opts.stampValue || 'prd-stopped';
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
});
|
|
8112
|
+
} catch (e) { console.error('stopPlanMaterializedWork plan-to-prd:', e.message); }
|
|
8113
|
+
}
|
|
8114
|
+
return affected;
|
|
8115
|
+
}
|
|
8116
|
+
|
|
8025
8117
|
async function handlePlansPause(req, res) {
|
|
8026
8118
|
try {
|
|
8027
8119
|
const body = await readBody(req);
|
|
8028
8120
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
8029
8121
|
if (!body.file.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename (got `' + body.file + '`). Pass prd/<plan>.json, not the source plans/<plan>.md.' });
|
|
8030
8122
|
const planPath = resolvePlanPath(body.file);
|
|
8031
|
-
|
|
8123
|
+
let prdSourcePlan = null;
|
|
8124
|
+
const updated = mutateJsonFileLocked(planPath, (plan) => {
|
|
8032
8125
|
if (!plan || Array.isArray(plan) || typeof plan !== 'object') plan = {};
|
|
8033
8126
|
plan.status = 'paused';
|
|
8034
8127
|
plan.pausedAt = new Date().toISOString();
|
|
8035
8128
|
return plan;
|
|
8036
8129
|
}, { defaultValue: {} });
|
|
8130
|
+
prdSourcePlan = updated?.source_plan || null;
|
|
8037
8131
|
|
|
8038
|
-
// Propagate pause to materialized work items across all projects
|
|
8039
|
-
//
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
wiPaths.push(shared.projectWorkItemsPath(proj));
|
|
8044
|
-
}
|
|
8045
|
-
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
8046
|
-
|
|
8047
|
-
// Step 1: Read work items (read-only, no lock) to find plan items that are dispatched.
|
|
8048
|
-
const dispatchedItemIds = new Set();
|
|
8049
|
-
for (const wiPath of wiPaths) {
|
|
8050
|
-
try {
|
|
8051
|
-
const items = safeJsonArr(wiPath);
|
|
8052
|
-
for (const w of items) {
|
|
8053
|
-
if (w.sourcePlan !== body.file) continue;
|
|
8054
|
-
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
8055
|
-
if (w.status === WI_STATUS.DISPATCHED && w.id) dispatchedItemIds.add(w.id);
|
|
8056
|
-
}
|
|
8057
|
-
} catch { /* file may not exist */ }
|
|
8058
|
-
}
|
|
8059
|
-
|
|
8060
|
-
// Step 2: Route PID resolution + kill + dispatch removal through the canonical primitive.
|
|
8061
|
-
// cleanDispatchEntries resolves PIDs from engine/tmp/dispatch-<id>-*/pid-<id>.pid
|
|
8062
|
-
// (see shared.findDispatchPidFile), kills outside the dispatch lock, and removes the
|
|
8063
|
-
// entries via mutateDispatch (SQL + JSON mirror in one atomic write).
|
|
8064
|
-
// The defunct per-agent status sidecar reads/writes that used to live here are gone
|
|
8065
|
-
// (engine/queries.js documents that file no longer exists).
|
|
8066
|
-
if (dispatchedItemIds.size > 0) {
|
|
8067
|
-
const matchFn = (d) => {
|
|
8068
|
-
const itemId = d.meta?.item?.id;
|
|
8069
|
-
if (itemId && dispatchedItemIds.has(itemId)) return true;
|
|
8070
|
-
if (d.meta?.dispatchKey && [...dispatchedItemIds].some(id => d.meta.dispatchKey.includes(id))) return true;
|
|
8071
|
-
return false;
|
|
8072
|
-
};
|
|
8073
|
-
cleanDispatchEntries(matchFn);
|
|
8074
|
-
}
|
|
8075
|
-
|
|
8076
|
-
// Step 3: Mutate work-items.json per path — pause items (each lock held briefly, no nesting).
|
|
8077
|
-
let reset = 0;
|
|
8078
|
-
for (const wiPath of wiPaths) {
|
|
8079
|
-
try {
|
|
8080
|
-
mutateWorkItems(wiPath, items => {
|
|
8081
|
-
for (const w of items) {
|
|
8082
|
-
if (w.sourcePlan !== body.file) continue;
|
|
8083
|
-
if (w.completedAt || DONE_STATUSES.has(w.status)) continue;
|
|
8084
|
-
if (w.status !== WI_STATUS.PAUSED) reset++;
|
|
8085
|
-
w.status = WI_STATUS.PAUSED;
|
|
8086
|
-
w._pausedBy = 'prd-pause';
|
|
8087
|
-
delete w._resumedAt;
|
|
8088
|
-
delete w.dispatched_at;
|
|
8089
|
-
delete w.dispatched_to;
|
|
8090
|
-
delete w.failReason;
|
|
8091
|
-
delete w.failedAt;
|
|
8092
|
-
}
|
|
8093
|
-
});
|
|
8094
|
-
} catch (e) { console.error('reset work items:', e.message); }
|
|
8095
|
-
}
|
|
8132
|
+
// Propagate pause to materialized work items across all projects + cancel the
|
|
8133
|
+
// plan-to-prd regeneration WI so a paused PRD can't be silently rebuilt.
|
|
8134
|
+
const reset = stopPlanMaterializedWork(body.file, prdSourcePlan, {
|
|
8135
|
+
targetStatus: WI_STATUS.PAUSED, stampField: '_pausedBy', stampValue: 'prd-pause',
|
|
8136
|
+
});
|
|
8096
8137
|
|
|
8097
8138
|
invalidateStatusCache();
|
|
8098
8139
|
invalidatePlansCache();
|
|
@@ -8149,8 +8190,17 @@ const server = http.createServer(async (req, res) => {
|
|
|
8149
8190
|
return data;
|
|
8150
8191
|
}, { defaultValue: {} });
|
|
8151
8192
|
|
|
8193
|
+
// RC3: reject used to flip only the PRD status — its materialized work items
|
|
8194
|
+
// kept dispatching and any active agent kept running, and a pending plan-to-prd
|
|
8195
|
+
// run could rebuild the PRD. Reject is terminal, so cancel the materialized WIs,
|
|
8196
|
+
// kill active dispatches, and cancel the plan-to-prd regeneration WI.
|
|
8197
|
+
const cancelled = stopPlanMaterializedWork(body.file, plan?.source_plan || null, {
|
|
8198
|
+
targetStatus: WI_STATUS.CANCELLED, stampField: '_cancelledBy', stampValue: 'prd-rejected',
|
|
8199
|
+
});
|
|
8200
|
+
|
|
8201
|
+
invalidateStatusCache();
|
|
8152
8202
|
invalidatePlansCache();
|
|
8153
|
-
return jsonReply(res, 200, { ok: true, status: 'rejected' });
|
|
8203
|
+
return jsonReply(res, 200, { ok: true, status: 'rejected', cancelledWorkItems: cancelled });
|
|
8154
8204
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
8155
8205
|
}
|
|
8156
8206
|
|
|
@@ -8268,6 +8318,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
8268
8318
|
cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, getConfig());
|
|
8269
8319
|
} catch (e) { console.error('plan worktree cleanup:', e.message); }
|
|
8270
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);
|
|
8271
8325
|
// Neutralize the `.backup` sidecar so `safeJson` auto-restore can't
|
|
8272
8326
|
// RESURRECT the PRD we just deleted (the live file is gone, but a stray
|
|
8273
8327
|
// `prd/<plan>.json.backup` would be auto-restored on the next safeJson read
|
|
@@ -9823,6 +9877,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9823
9877
|
_emitTimingLog(_lifecycle, _tSessionReady, Date.now(), 'cancelled-pre-stream');
|
|
9824
9878
|
return resolveResult({ text: accumulated, sessionId: sessionHandle.sessionId, code: 0, usage: {}, raw: accumulated, stderr: '' });
|
|
9825
9879
|
}
|
|
9880
|
+
let poolSegmentId = 0;
|
|
9881
|
+
let segmentStart = 0;
|
|
9826
9882
|
await sessionHandle.stream(prompt, {
|
|
9827
9883
|
systemPromptText: systemPrompt,
|
|
9828
9884
|
onChunk: (delta) => {
|
|
@@ -9831,7 +9887,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9831
9887
|
accumulated += delta;
|
|
9832
9888
|
_touchCcLiveStream(liveState);
|
|
9833
9889
|
liveState.text = accumulated;
|
|
9834
|
-
|
|
9890
|
+
const segmentText = accumulated.slice(segmentStart);
|
|
9891
|
+
if (liveState.writer) liveState.writer({ type: 'chunk', text: segmentText, segmentId: poolSegmentId });
|
|
9835
9892
|
},
|
|
9836
9893
|
onToolUse: (name, input, toolCallId) => {
|
|
9837
9894
|
if (_tFirstTool == null) _tFirstTool = Date.now();
|
|
@@ -9842,6 +9899,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9842
9899
|
if (Array.isArray(toolUses)) toolUses.push(entry);
|
|
9843
9900
|
if (Array.isArray(liveState.tools)) liveState.tools.push(entry);
|
|
9844
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;
|
|
9845
9905
|
},
|
|
9846
9906
|
onToolUpdate: (toolCallId, status) => {
|
|
9847
9907
|
_touchCcLiveStream(liveState);
|
|
@@ -10830,6 +10890,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10830
10890
|
// the per-project dropdown. resolveCheckoutMode honors the legacy
|
|
10831
10891
|
// worktreeMode field; 'worktree' (default) or 'live'.
|
|
10832
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,
|
|
10833
10898
|
workSources: {
|
|
10834
10899
|
pullRequests: { enabled: p.workSources?.pullRequests?.enabled !== false, cooldownMinutes: p.workSources?.pullRequests?.cooldownMinutes ?? 30 },
|
|
10835
10900
|
workItems: { enabled: p.workSources?.workItems?.enabled !== false, cooldownMinutes: p.workSources?.workItems?.cooldownMinutes ?? 0 }
|
|
@@ -11254,10 +11319,19 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11254
11319
|
// Drop the legacy field so a migrated project never carries both.
|
|
11255
11320
|
delete proj.worktreeMode;
|
|
11256
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
|
+
}
|
|
11257
11333
|
}
|
|
11258
11334
|
}
|
|
11259
|
-
|
|
11260
|
-
shared.pruneDefaultClaudeConfig(config);
|
|
11261
11335
|
mutateDashboardConfig(current => mergeSettingsConfigUpdate(current, config, body, _configPatch));
|
|
11262
11336
|
// Refresh in-memory CONFIG so subsequent reads see the update
|
|
11263
11337
|
reloadConfig();
|
|
@@ -13322,7 +13396,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13322
13396
|
// /api/prd/regenerate removed — use /api/plans/approve which does diff-aware update
|
|
13323
13397
|
|
|
13324
13398
|
// Agents
|
|
13325
|
-
{ 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) => {
|
|
13326
13400
|
const body = await readBody(req);
|
|
13327
13401
|
const { url } = body;
|
|
13328
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
|
+
]
|