opencode-swarm 7.33.0 → 7.33.2
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/README.md +1 -0
- package/dist/cli/index.js +1053 -519
- package/dist/evidence/gate-bridge.d.ts +15 -0
- package/dist/index.js +417 -163
- package/dist/plan/ledger.d.ts +9 -0
- package/dist/plan/manager.d.ts +28 -2
- package/dist/services/evidence-summary-service.d.ts +5 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/tools/co-change-analyzer.d.ts +2 -1
- package/dist/tools/save-plan.d.ts +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ var package_default;
|
|
|
48
48
|
var init_package = __esm(() => {
|
|
49
49
|
package_default = {
|
|
50
50
|
name: "opencode-swarm",
|
|
51
|
-
version: "7.33.
|
|
51
|
+
version: "7.33.2",
|
|
52
52
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
53
53
|
main: "dist/index.js",
|
|
54
54
|
types: "dist/index.d.ts",
|
|
@@ -17370,6 +17370,32 @@ async function appendLedgerEvent(directory, eventInput, options) {
|
|
|
17370
17370
|
fs4.renameSync(tempPath, ledgerPath);
|
|
17371
17371
|
return event;
|
|
17372
17372
|
}
|
|
17373
|
+
async function takeSnapshotWithRetry(directory, plan, options) {
|
|
17374
|
+
const MAX_RETRIES = 3;
|
|
17375
|
+
const TOTAL_ATTEMPTS = 1 + MAX_RETRIES;
|
|
17376
|
+
const telemetrySource = options?.source ?? "save_plan_tool";
|
|
17377
|
+
const snapshotOptions = { planHashAfter: options?.planHashAfter };
|
|
17378
|
+
let lastError;
|
|
17379
|
+
for (let attempt = 1;attempt <= TOTAL_ATTEMPTS; attempt++) {
|
|
17380
|
+
try {
|
|
17381
|
+
await takeSnapshotEvent(directory, plan, snapshotOptions);
|
|
17382
|
+
return;
|
|
17383
|
+
} catch (err2) {
|
|
17384
|
+
lastError = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
17385
|
+
if (attempt < TOTAL_ATTEMPTS) {
|
|
17386
|
+
await new Promise((r) => setTimeout(r, 10 * 2 ** (attempt - 1)));
|
|
17387
|
+
}
|
|
17388
|
+
}
|
|
17389
|
+
}
|
|
17390
|
+
console.warn(`[takeSnapshotWithRetry] Snapshot failed after ${MAX_RETRIES} retries (${TOTAL_ATTEMPTS} attempts): ${lastError.message}`);
|
|
17391
|
+
try {
|
|
17392
|
+
emit("snapshot_failed", {
|
|
17393
|
+
error: lastError.message,
|
|
17394
|
+
retries: MAX_RETRIES,
|
|
17395
|
+
source: telemetrySource
|
|
17396
|
+
});
|
|
17397
|
+
} catch {}
|
|
17398
|
+
}
|
|
17373
17399
|
async function takeSnapshotEvent(directory, plan, options) {
|
|
17374
17400
|
const payloadHash = computePlanHash(plan);
|
|
17375
17401
|
const snapshotPayload = {
|
|
@@ -17603,6 +17629,7 @@ async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
|
17603
17629
|
var LEDGER_SCHEMA_VERSION = "1.1.0", LEDGER_FILENAME = "plan-ledger.jsonl", PLAN_JSON_FILENAME = "plan.json", LedgerStaleWriterError;
|
|
17604
17630
|
var init_ledger = __esm(() => {
|
|
17605
17631
|
init_plan_schema();
|
|
17632
|
+
init_telemetry();
|
|
17606
17633
|
LedgerStaleWriterError = class LedgerStaleWriterError extends Error {
|
|
17607
17634
|
constructor(message) {
|
|
17608
17635
|
super(message);
|
|
@@ -17796,7 +17823,9 @@ async function loadPlan(directory) {
|
|
|
17796
17823
|
try {
|
|
17797
17824
|
const rebuilt = await replayFromLedger(directory);
|
|
17798
17825
|
if (rebuilt) {
|
|
17799
|
-
await rebuildPlan(directory, rebuilt
|
|
17826
|
+
await rebuildPlan(directory, rebuilt, {
|
|
17827
|
+
reason: "ledger_hash_mismatch_recovery"
|
|
17828
|
+
});
|
|
17800
17829
|
warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at .swarm/SWARM_PLAN.md if it exists.");
|
|
17801
17830
|
return rebuilt;
|
|
17802
17831
|
}
|
|
@@ -17804,7 +17833,9 @@ async function loadPlan(directory) {
|
|
|
17804
17833
|
try {
|
|
17805
17834
|
const approved = await loadLastApprovedPlan(directory, currentPlanId);
|
|
17806
17835
|
if (approved) {
|
|
17807
|
-
await rebuildPlan(directory, approved.plan
|
|
17836
|
+
await rebuildPlan(directory, approved.plan, {
|
|
17837
|
+
reason: "approved_snapshot_fallback"
|
|
17838
|
+
});
|
|
17808
17839
|
try {
|
|
17809
17840
|
await takeSnapshotEvent(directory, approved.plan, {
|
|
17810
17841
|
source: "recovery_from_approved_snapshot",
|
|
@@ -17881,7 +17912,9 @@ async function loadPlan(directory) {
|
|
|
17881
17912
|
} else if (catchFirstEvent !== null && rawPlanId !== null) {
|
|
17882
17913
|
const rebuilt = await replayFromLedger(directory);
|
|
17883
17914
|
if (rebuilt) {
|
|
17884
|
-
await rebuildPlan(directory, rebuilt
|
|
17915
|
+
await rebuildPlan(directory, rebuilt, {
|
|
17916
|
+
reason: "validation_failure_recovery"
|
|
17917
|
+
});
|
|
17885
17918
|
warn("[loadPlan] Rebuilt plan from ledger after validation failure. Projection was stale.");
|
|
17886
17919
|
return rebuilt;
|
|
17887
17920
|
}
|
|
@@ -18242,12 +18275,9 @@ async function savePlan(directory, plan, options) {
|
|
|
18242
18275
|
const SNAPSHOT_INTERVAL = 50;
|
|
18243
18276
|
const latestSeq = await getLatestLedgerSeq(directory);
|
|
18244
18277
|
if (latestSeq > 0 && latestSeq % SNAPSHOT_INTERVAL === 0) {
|
|
18245
|
-
await
|
|
18246
|
-
planHashAfter: hashAfter
|
|
18247
|
-
|
|
18248
|
-
if (process.env.DEBUG_SWARM) {
|
|
18249
|
-
warn(`[savePlan] Periodic snapshot write failed (non-fatal): ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
18250
|
-
}
|
|
18278
|
+
await takeSnapshotWithRetry(directory, validated, {
|
|
18279
|
+
planHashAfter: hashAfter,
|
|
18280
|
+
source: "savePlan_manager"
|
|
18251
18281
|
});
|
|
18252
18282
|
}
|
|
18253
18283
|
const swarmDir = path6.resolve(directory, ".swarm");
|
|
@@ -18261,6 +18291,17 @@ async function savePlan(directory, plan, options) {
|
|
|
18261
18291
|
unlinkSync(tempPath);
|
|
18262
18292
|
} catch {}
|
|
18263
18293
|
}
|
|
18294
|
+
try {
|
|
18295
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
18296
|
+
const inProgressMarker = JSON.stringify({
|
|
18297
|
+
source: "plan_manager",
|
|
18298
|
+
timestamp: new Date().toISOString(),
|
|
18299
|
+
phases_count: validated.phases.length,
|
|
18300
|
+
tasks_count: validated.phases.reduce((sum, p) => sum + p.tasks.length, 0),
|
|
18301
|
+
in_progress: true
|
|
18302
|
+
});
|
|
18303
|
+
await bunWrite(markerPath, inProgressMarker);
|
|
18304
|
+
} catch {}
|
|
18264
18305
|
try {
|
|
18265
18306
|
const contentHash = computePlanContentHash(validated);
|
|
18266
18307
|
const markdown = derivePlanMarkdown(validated);
|
|
@@ -18294,41 +18335,146 @@ ${markdown}`;
|
|
|
18294
18335
|
source: "plan_manager",
|
|
18295
18336
|
timestamp: new Date().toISOString(),
|
|
18296
18337
|
phases_count: validated.phases.length,
|
|
18297
|
-
tasks_count: tasksCount
|
|
18338
|
+
tasks_count: tasksCount,
|
|
18339
|
+
in_progress: false
|
|
18298
18340
|
});
|
|
18299
18341
|
await bunWrite(markerPath, marker);
|
|
18300
18342
|
} catch {}
|
|
18301
18343
|
}
|
|
18302
|
-
async function rebuildPlan(directory, plan) {
|
|
18344
|
+
async function rebuildPlan(directory, plan, options) {
|
|
18303
18345
|
const targetPlan = plan ?? await replayFromLedger(directory);
|
|
18304
18346
|
if (!targetPlan)
|
|
18305
18347
|
return null;
|
|
18306
18348
|
const swarmDir = path6.join(directory, ".swarm");
|
|
18307
18349
|
const planPath = path6.join(swarmDir, "plan.json");
|
|
18308
18350
|
const mdPath = path6.join(swarmDir, "plan.md");
|
|
18309
|
-
const tempPlanPath = path6.join(swarmDir, `plan.json.rebuild.${Date.now()}`);
|
|
18351
|
+
const tempPlanPath = path6.join(swarmDir, `plan.json.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
18310
18352
|
await bunWrite(tempPlanPath, JSON.stringify(targetPlan, null, 2));
|
|
18311
18353
|
renameSync3(tempPlanPath, planPath);
|
|
18312
|
-
const contentHash = computePlanContentHash(targetPlan);
|
|
18313
|
-
const markdown = derivePlanMarkdown(targetPlan);
|
|
18314
|
-
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
18315
|
-
${markdown}`;
|
|
18316
|
-
const tempMdPath = path6.join(swarmDir, `plan.md.rebuild.${Date.now()}`);
|
|
18317
|
-
await bunWrite(tempMdPath, markdownWithHash);
|
|
18318
|
-
renameSync3(tempMdPath, mdPath);
|
|
18319
18354
|
try {
|
|
18320
18355
|
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
18321
|
-
const
|
|
18322
|
-
const marker = JSON.stringify({
|
|
18356
|
+
const inProgressMarker = JSON.stringify({
|
|
18323
18357
|
source: "plan_manager",
|
|
18324
18358
|
timestamp: new Date().toISOString(),
|
|
18325
18359
|
phases_count: targetPlan.phases.length,
|
|
18326
|
-
tasks_count:
|
|
18360
|
+
tasks_count: targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0),
|
|
18361
|
+
in_progress: true
|
|
18327
18362
|
});
|
|
18328
|
-
await bunWrite(markerPath,
|
|
18363
|
+
await bunWrite(markerPath, inProgressMarker);
|
|
18364
|
+
} catch {}
|
|
18365
|
+
try {
|
|
18366
|
+
const contentHash = computePlanContentHash(targetPlan);
|
|
18367
|
+
const markdown = derivePlanMarkdown(targetPlan);
|
|
18368
|
+
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
18369
|
+
${markdown}`;
|
|
18370
|
+
const tempMdPath = path6.join(swarmDir, `plan.md.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
18371
|
+
await bunWrite(tempMdPath, markdownWithHash);
|
|
18372
|
+
renameSync3(tempMdPath, mdPath);
|
|
18373
|
+
} finally {
|
|
18374
|
+
try {
|
|
18375
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
18376
|
+
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
18377
|
+
const marker = JSON.stringify({
|
|
18378
|
+
source: "plan_manager",
|
|
18379
|
+
timestamp: new Date().toISOString(),
|
|
18380
|
+
phases_count: targetPlan.phases.length,
|
|
18381
|
+
tasks_count: tasksCount,
|
|
18382
|
+
in_progress: false
|
|
18383
|
+
});
|
|
18384
|
+
await bunWrite(markerPath, marker);
|
|
18385
|
+
} catch {}
|
|
18386
|
+
}
|
|
18387
|
+
try {
|
|
18388
|
+
const planId = derivePlanId(targetPlan);
|
|
18389
|
+
const planHashAfter = computePlanHash(targetPlan);
|
|
18390
|
+
await appendLedgerEvent(directory, {
|
|
18391
|
+
event_type: "plan_rebuilt",
|
|
18392
|
+
source: "rebuildPlan",
|
|
18393
|
+
plan_id: planId,
|
|
18394
|
+
payload: {
|
|
18395
|
+
reason: options?.reason ?? "ledger_replay_recovery",
|
|
18396
|
+
phases_count: targetPlan.phases.length,
|
|
18397
|
+
tasks_count: targetPlan.phases.reduce((sum, p) => sum + p.tasks.length, 0)
|
|
18398
|
+
}
|
|
18399
|
+
}, { planHashAfter });
|
|
18329
18400
|
} catch {}
|
|
18330
18401
|
return targetPlan;
|
|
18331
18402
|
}
|
|
18403
|
+
async function closePlanTerminalState(directory, plan, options) {
|
|
18404
|
+
const planId = derivePlanId(plan);
|
|
18405
|
+
const validated = PlanSchema.parse(plan);
|
|
18406
|
+
const hashAfter = computePlanHash(validated);
|
|
18407
|
+
for (const taskId of options.closedTaskIds) {
|
|
18408
|
+
let taskPhaseId;
|
|
18409
|
+
for (const phase of validated.phases) {
|
|
18410
|
+
if (phase.tasks.some((t) => t.id === taskId)) {
|
|
18411
|
+
taskPhaseId = phase.id;
|
|
18412
|
+
break;
|
|
18413
|
+
}
|
|
18414
|
+
}
|
|
18415
|
+
const fromStatus = options.originalStatuses?.get(taskId) ?? "in_progress";
|
|
18416
|
+
await appendLedgerEvent(directory, {
|
|
18417
|
+
plan_id: planId,
|
|
18418
|
+
event_type: "task_status_changed",
|
|
18419
|
+
task_id: taskId,
|
|
18420
|
+
phase_id: taskPhaseId,
|
|
18421
|
+
from_status: fromStatus,
|
|
18422
|
+
to_status: "closed",
|
|
18423
|
+
source: "close_terminal"
|
|
18424
|
+
}, { planHashAfter: hashAfter });
|
|
18425
|
+
}
|
|
18426
|
+
for (const phaseId of options.closedPhaseIds) {
|
|
18427
|
+
await appendLedgerEvent(directory, {
|
|
18428
|
+
plan_id: planId,
|
|
18429
|
+
event_type: "phase_completed",
|
|
18430
|
+
phase_id: phaseId,
|
|
18431
|
+
source: "close_terminal"
|
|
18432
|
+
}, { planHashAfter: hashAfter });
|
|
18433
|
+
}
|
|
18434
|
+
await takeSnapshotEvent(directory, validated, {
|
|
18435
|
+
planHashAfter: hashAfter,
|
|
18436
|
+
source: "close_terminal"
|
|
18437
|
+
});
|
|
18438
|
+
const swarmDir = path6.join(directory, ".swarm");
|
|
18439
|
+
const planPath = path6.join(swarmDir, "plan.json");
|
|
18440
|
+
const tempPlanPath = path6.join(swarmDir, `plan.json.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
18441
|
+
await bunWrite(tempPlanPath, JSON.stringify(validated, null, 2));
|
|
18442
|
+
renameSync3(tempPlanPath, planPath);
|
|
18443
|
+
try {
|
|
18444
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
18445
|
+
const inProgressMarker = JSON.stringify({
|
|
18446
|
+
source: "plan_manager_close",
|
|
18447
|
+
timestamp: new Date().toISOString(),
|
|
18448
|
+
phases_count: validated.phases.length,
|
|
18449
|
+
tasks_count: validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0),
|
|
18450
|
+
in_progress: true
|
|
18451
|
+
});
|
|
18452
|
+
await bunWrite(markerPath, inProgressMarker);
|
|
18453
|
+
} catch {}
|
|
18454
|
+
try {
|
|
18455
|
+
const mdPath = path6.join(swarmDir, "plan.md");
|
|
18456
|
+
const contentHash = computePlanContentHash(validated);
|
|
18457
|
+
const markdown = derivePlanMarkdown(validated);
|
|
18458
|
+
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
18459
|
+
${markdown}`;
|
|
18460
|
+
const mdTempPath = path6.join(swarmDir, `plan.md.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
18461
|
+
await bunWrite(mdTempPath, markdownWithHash);
|
|
18462
|
+
renameSync3(mdTempPath, mdPath);
|
|
18463
|
+
} finally {
|
|
18464
|
+
try {
|
|
18465
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
18466
|
+
const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
18467
|
+
const marker = JSON.stringify({
|
|
18468
|
+
source: "plan_manager_close",
|
|
18469
|
+
timestamp: new Date().toISOString(),
|
|
18470
|
+
phases_count: validated.phases.length,
|
|
18471
|
+
tasks_count: tasksCount,
|
|
18472
|
+
in_progress: false
|
|
18473
|
+
});
|
|
18474
|
+
await bunWrite(markerPath, marker);
|
|
18475
|
+
} catch {}
|
|
18476
|
+
}
|
|
18477
|
+
}
|
|
18332
18478
|
async function updateTaskStatus(directory, taskId, status) {
|
|
18333
18479
|
const derivePhaseStatusFromTasks = (tasks) => {
|
|
18334
18480
|
if (tasks.length > 0 && tasks.every((task) => task.status === "completed")) {
|
|
@@ -58844,6 +58990,12 @@ async function handleCloseCommand(directory, args2, options = {}) {
|
|
|
58844
58990
|
}
|
|
58845
58991
|
}
|
|
58846
58992
|
if (planExists) {
|
|
58993
|
+
const originalStatuses = new Map;
|
|
58994
|
+
for (const phase of planData.phases ?? []) {
|
|
58995
|
+
for (const task of phase.tasks ?? []) {
|
|
58996
|
+
originalStatuses.set(task.id, task.status);
|
|
58997
|
+
}
|
|
58998
|
+
}
|
|
58847
58999
|
const guaranteeResult = guaranteeAllPlansComplete(planData);
|
|
58848
59000
|
for (const phaseId of guaranteeResult.closedPhaseIds) {
|
|
58849
59001
|
if (!closedPhases.includes(phaseId)) {
|
|
@@ -58857,11 +59009,15 @@ async function handleCloseCommand(directory, args2, options = {}) {
|
|
|
58857
59009
|
}
|
|
58858
59010
|
if (!planAlreadyDone || guaranteeResult.closedPhaseIds.length > 0 || guaranteeResult.closedTaskIds.length > 0) {
|
|
58859
59011
|
try {
|
|
58860
|
-
await
|
|
59012
|
+
await closePlanTerminalState(directory, planData, {
|
|
59013
|
+
closedPhaseIds: guaranteeResult.closedPhaseIds,
|
|
59014
|
+
closedTaskIds: guaranteeResult.closedTaskIds,
|
|
59015
|
+
originalStatuses
|
|
59016
|
+
});
|
|
58861
59017
|
} catch (error93) {
|
|
58862
59018
|
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
58863
|
-
warnings.push(`Failed to persist terminal plan
|
|
58864
|
-
console.warn("[close-command] Failed to write plan
|
|
59019
|
+
warnings.push(`Failed to persist terminal plan state: ${msg}`);
|
|
59020
|
+
console.warn("[close-command] Failed to write terminal plan state:", error93);
|
|
58865
59021
|
}
|
|
58866
59022
|
}
|
|
58867
59023
|
}
|
|
@@ -59149,6 +59305,7 @@ var init_close = __esm(() => {
|
|
|
59149
59305
|
init_knowledge_curator();
|
|
59150
59306
|
init_knowledge_store();
|
|
59151
59307
|
init_utils2();
|
|
59308
|
+
init_manager();
|
|
59152
59309
|
init_scope_persistence();
|
|
59153
59310
|
init_skill_improver();
|
|
59154
59311
|
init_state();
|
|
@@ -59386,14 +59543,16 @@ async function parseGitLog(directory, maxCommits) {
|
|
|
59386
59543
|
}
|
|
59387
59544
|
return commitMap;
|
|
59388
59545
|
}
|
|
59389
|
-
function buildCoChangeMatrix(commitMap) {
|
|
59546
|
+
function buildCoChangeMatrix(commitMap, maxFilesPerCommit = 500) {
|
|
59390
59547
|
const matrix = new Map;
|
|
59391
59548
|
const fileCommitCount = new Map;
|
|
59392
59549
|
for (const files of commitMap.values()) {
|
|
59393
|
-
const
|
|
59394
|
-
for (const file3 of fileArray) {
|
|
59550
|
+
for (const file3 of files) {
|
|
59395
59551
|
fileCommitCount.set(file3, (fileCommitCount.get(file3) || 0) + 1);
|
|
59396
59552
|
}
|
|
59553
|
+
if (files.size > maxFilesPerCommit)
|
|
59554
|
+
continue;
|
|
59555
|
+
const fileArray = Array.from(files).sort();
|
|
59397
59556
|
for (let i2 = 0;i2 < fileArray.length; i2++) {
|
|
59398
59557
|
for (let j = i2 + 1;j < fileArray.length; j++) {
|
|
59399
59558
|
const fileA = fileArray[i2];
|
|
@@ -59556,6 +59715,7 @@ async function detectDarkMatter(directory, options) {
|
|
|
59556
59715
|
const minCoChanges = options?.minCoChanges ?? 3;
|
|
59557
59716
|
const npmiThreshold = options?.npmiThreshold ?? 0.5;
|
|
59558
59717
|
const maxCommitsToAnalyze = options?.maxCommitsToAnalyze ?? 500;
|
|
59718
|
+
const maxFilesPerCommit = options?.maxFilesPerCommit ?? 500;
|
|
59559
59719
|
try {
|
|
59560
59720
|
const { stdout } = await getExecFileAsync()("git", ["rev-list", "--count", "HEAD"], {
|
|
59561
59721
|
cwd: directory,
|
|
@@ -59569,7 +59729,7 @@ async function detectDarkMatter(directory, options) {
|
|
|
59569
59729
|
return [];
|
|
59570
59730
|
}
|
|
59571
59731
|
const commitMap = await _internals18.parseGitLog(directory, maxCommitsToAnalyze);
|
|
59572
|
-
const matrix = _internals18.buildCoChangeMatrix(commitMap);
|
|
59732
|
+
const matrix = _internals18.buildCoChangeMatrix(commitMap, maxFilesPerCommit);
|
|
59573
59733
|
const staticEdges = await _internals18.getStaticEdges(directory);
|
|
59574
59734
|
const results = [];
|
|
59575
59735
|
for (const entry of matrix.values()) {
|
|
@@ -59891,6 +60051,120 @@ function getPluginCachePaths() {
|
|
|
59891
60051
|
}
|
|
59892
60052
|
var init_cache_paths = () => {};
|
|
59893
60053
|
|
|
60054
|
+
// src/evidence/gate-bridge.ts
|
|
60055
|
+
async function readDurableGateEvidence(directory, taskId) {
|
|
60056
|
+
try {
|
|
60057
|
+
return await readTaskEvidence(directory, taskId);
|
|
60058
|
+
} catch {
|
|
60059
|
+
return null;
|
|
60060
|
+
}
|
|
60061
|
+
}
|
|
60062
|
+
function getDurableGateEvidenceStatus(evidence) {
|
|
60063
|
+
if (!evidence?.gates || typeof evidence.gates !== "object") {
|
|
60064
|
+
return {
|
|
60065
|
+
isComplete: false,
|
|
60066
|
+
missingGates: [],
|
|
60067
|
+
evidenceExists: evidence != null,
|
|
60068
|
+
invalid: false
|
|
60069
|
+
};
|
|
60070
|
+
}
|
|
60071
|
+
if (!Array.isArray(evidence.required_gates) || evidence.required_gates.length === 0) {
|
|
60072
|
+
return {
|
|
60073
|
+
isComplete: false,
|
|
60074
|
+
missingGates: ["required_gates"],
|
|
60075
|
+
evidenceExists: true,
|
|
60076
|
+
invalid: false
|
|
60077
|
+
};
|
|
60078
|
+
}
|
|
60079
|
+
const missingGates = evidence.required_gates.filter((gate) => evidence.gates[gate] == null);
|
|
60080
|
+
return {
|
|
60081
|
+
isComplete: missingGates.length === 0,
|
|
60082
|
+
missingGates,
|
|
60083
|
+
evidenceExists: true,
|
|
60084
|
+
invalid: false
|
|
60085
|
+
};
|
|
60086
|
+
}
|
|
60087
|
+
async function getDurableGateEvidenceStatusForTask(directory, taskId) {
|
|
60088
|
+
if (!isValidTaskId(taskId)) {
|
|
60089
|
+
return {
|
|
60090
|
+
isComplete: false,
|
|
60091
|
+
missingGates: [],
|
|
60092
|
+
evidenceExists: false,
|
|
60093
|
+
invalid: false
|
|
60094
|
+
};
|
|
60095
|
+
}
|
|
60096
|
+
try {
|
|
60097
|
+
return getDurableGateEvidenceStatus(readTaskEvidenceRaw(directory, taskId));
|
|
60098
|
+
} catch {
|
|
60099
|
+
return {
|
|
60100
|
+
isComplete: false,
|
|
60101
|
+
missingGates: ["invalid_gate_evidence"],
|
|
60102
|
+
evidenceExists: true,
|
|
60103
|
+
invalid: true
|
|
60104
|
+
};
|
|
60105
|
+
}
|
|
60106
|
+
}
|
|
60107
|
+
function gateEvidenceToEntry(taskId, gate, type, evidence) {
|
|
60108
|
+
const gateRecord = evidence.gates[gate];
|
|
60109
|
+
if (!gateRecord) {
|
|
60110
|
+
return null;
|
|
60111
|
+
}
|
|
60112
|
+
const base = {
|
|
60113
|
+
task_id: taskId,
|
|
60114
|
+
timestamp: gateRecord.timestamp,
|
|
60115
|
+
agent: gateRecord.agent || gate,
|
|
60116
|
+
verdict: "pass",
|
|
60117
|
+
summary: `Gate evidence recorded by ${gate}`,
|
|
60118
|
+
metadata: { source: "durable_gate_evidence", gate }
|
|
60119
|
+
};
|
|
60120
|
+
if (type === "review") {
|
|
60121
|
+
return {
|
|
60122
|
+
...base,
|
|
60123
|
+
type,
|
|
60124
|
+
risk: "low",
|
|
60125
|
+
issues: []
|
|
60126
|
+
};
|
|
60127
|
+
}
|
|
60128
|
+
if (type === "approval") {
|
|
60129
|
+
return {
|
|
60130
|
+
...base,
|
|
60131
|
+
type
|
|
60132
|
+
};
|
|
60133
|
+
}
|
|
60134
|
+
return {
|
|
60135
|
+
...base,
|
|
60136
|
+
type,
|
|
60137
|
+
tests_passed: 0,
|
|
60138
|
+
tests_failed: 0,
|
|
60139
|
+
failures: []
|
|
60140
|
+
};
|
|
60141
|
+
}
|
|
60142
|
+
function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
|
|
60143
|
+
if (!evidence?.gates) {
|
|
60144
|
+
return entries;
|
|
60145
|
+
}
|
|
60146
|
+
const merged = [...entries];
|
|
60147
|
+
for (const gate of Object.keys(evidence.gates).sort()) {
|
|
60148
|
+
const type = GATE_EVIDENCE_TYPE_BY_GATE[gate] ?? "approval";
|
|
60149
|
+
if ((type === "review" || type === "test") && merged.some((entry2) => entry2.type === type)) {
|
|
60150
|
+
continue;
|
|
60151
|
+
}
|
|
60152
|
+
const entry = gateEvidenceToEntry(taskId, gate, type, evidence);
|
|
60153
|
+
if (entry) {
|
|
60154
|
+
merged.push(entry);
|
|
60155
|
+
}
|
|
60156
|
+
}
|
|
60157
|
+
return merged;
|
|
60158
|
+
}
|
|
60159
|
+
var GATE_EVIDENCE_TYPE_BY_GATE;
|
|
60160
|
+
var init_gate_bridge = __esm(() => {
|
|
60161
|
+
init_gate_evidence();
|
|
60162
|
+
GATE_EVIDENCE_TYPE_BY_GATE = {
|
|
60163
|
+
reviewer: "review",
|
|
60164
|
+
test_engineer: "test"
|
|
60165
|
+
};
|
|
60166
|
+
});
|
|
60167
|
+
|
|
59894
60168
|
// src/services/version-check.ts
|
|
59895
60169
|
import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
59896
60170
|
import { homedir as homedir5 } from "node:os";
|
|
@@ -60038,7 +60312,21 @@ async function checkEvidenceCompleteness(directory, plan) {
|
|
|
60038
60312
|
}
|
|
60039
60313
|
if (completedTaskIds.length > 0) {
|
|
60040
60314
|
const evidenceTaskIds = new Set(await listEvidenceTaskIds(directory));
|
|
60041
|
-
const missingEvidence =
|
|
60315
|
+
const missingEvidence = [];
|
|
60316
|
+
for (const id of completedTaskIds) {
|
|
60317
|
+
const gateStatus = await getDurableGateEvidenceStatusForTask(directory, id);
|
|
60318
|
+
if (gateStatus.isComplete) {
|
|
60319
|
+
continue;
|
|
60320
|
+
}
|
|
60321
|
+
if (gateStatus.evidenceExists && gateStatus.missingGates.length > 0) {
|
|
60322
|
+
missingEvidence.push(id);
|
|
60323
|
+
continue;
|
|
60324
|
+
}
|
|
60325
|
+
if (evidenceTaskIds.has(id)) {
|
|
60326
|
+
continue;
|
|
60327
|
+
}
|
|
60328
|
+
missingEvidence.push(id);
|
|
60329
|
+
}
|
|
60042
60330
|
if (missingEvidence.length === 0) {
|
|
60043
60331
|
return {
|
|
60044
60332
|
name: "Evidence",
|
|
@@ -60793,6 +61081,7 @@ var init_diagnose_service = __esm(() => {
|
|
|
60793
61081
|
init_package();
|
|
60794
61082
|
init_cache_paths();
|
|
60795
61083
|
init_loader();
|
|
61084
|
+
init_gate_bridge();
|
|
60796
61085
|
init_manager2();
|
|
60797
61086
|
init_utils2();
|
|
60798
61087
|
init_manager();
|
|
@@ -63359,8 +63648,7 @@ function getTaskStatus(task, bundle) {
|
|
|
63359
63648
|
}
|
|
63360
63649
|
return "pending";
|
|
63361
63650
|
}
|
|
63362
|
-
function
|
|
63363
|
-
const entries = _internals20.normalizeBundleEntries(bundle);
|
|
63651
|
+
function evidenceCompleteFromEntries(entries) {
|
|
63364
63652
|
if (entries.length === 0) {
|
|
63365
63653
|
return {
|
|
63366
63654
|
isComplete: false,
|
|
@@ -63379,6 +63667,9 @@ function isEvidenceComplete(bundle) {
|
|
|
63379
63667
|
missingEvidence: missing
|
|
63380
63668
|
};
|
|
63381
63669
|
}
|
|
63670
|
+
function isEvidenceComplete(bundle) {
|
|
63671
|
+
return evidenceCompleteFromEntries(_internals20.normalizeBundleEntries(bundle));
|
|
63672
|
+
}
|
|
63382
63673
|
function getTaskBlockers(task, summary, status) {
|
|
63383
63674
|
const blockers = [];
|
|
63384
63675
|
if (task?.blocked_reason) {
|
|
@@ -63395,11 +63686,19 @@ function getTaskBlockers(task, summary, status) {
|
|
|
63395
63686
|
async function buildTaskSummary(directory, task, taskId) {
|
|
63396
63687
|
const result = await loadEvidence(directory, taskId);
|
|
63397
63688
|
const bundle = result.status === "found" ? result.bundle : null;
|
|
63689
|
+
const gateEvidence = await readDurableGateEvidence(directory, taskId);
|
|
63398
63690
|
const phase = task?.phase ?? 0;
|
|
63399
63691
|
const status = _internals20.getTaskStatus(task, bundle);
|
|
63400
|
-
const
|
|
63692
|
+
const entries = mergeDurableGateEntriesFromEvidence(taskId, _internals20.normalizeBundleEntries(bundle), gateEvidence);
|
|
63693
|
+
let evidenceCheck = _internals20.evidenceCompleteFromEntries(entries);
|
|
63694
|
+
if (gateEvidence) {
|
|
63695
|
+
const gateStatus = getDurableGateEvidenceStatus(gateEvidence);
|
|
63696
|
+
evidenceCheck = gateStatus.isComplete ? { isComplete: true, missingEvidence: [] } : {
|
|
63697
|
+
isComplete: false,
|
|
63698
|
+
missingEvidence: gateStatus.missingGates.map((gate) => `gate:${gate}`)
|
|
63699
|
+
};
|
|
63700
|
+
}
|
|
63401
63701
|
const blockers = _internals20.getTaskBlockers(task, evidenceCheck, status);
|
|
63402
|
-
const entries = _internals20.normalizeBundleEntries(bundle);
|
|
63403
63702
|
const hasReview = entries.some((e) => e.type === "review");
|
|
63404
63703
|
const hasTest = entries.some((e) => e.type === "test");
|
|
63405
63704
|
const hasApproval = entries.some((e) => e.type === "approval");
|
|
@@ -63577,6 +63876,7 @@ function isAutoSummaryEnabled(automationConfig) {
|
|
|
63577
63876
|
}
|
|
63578
63877
|
var VALID_EVIDENCE_TYPES2, REQUIRED_EVIDENCE_TYPES, EVIDENCE_SUMMARY_VERSION = "1.0.0", _internals20;
|
|
63579
63878
|
var init_evidence_summary_service = __esm(() => {
|
|
63879
|
+
init_gate_bridge();
|
|
63580
63880
|
init_manager2();
|
|
63581
63881
|
init_manager();
|
|
63582
63882
|
init_utils();
|
|
@@ -63594,6 +63894,7 @@ var init_evidence_summary_service = __esm(() => {
|
|
|
63594
63894
|
isAutoSummaryEnabled,
|
|
63595
63895
|
normalizeBundleEntries,
|
|
63596
63896
|
getTaskStatus,
|
|
63897
|
+
evidenceCompleteFromEntries,
|
|
63597
63898
|
isEvidenceComplete,
|
|
63598
63899
|
getTaskBlockers,
|
|
63599
63900
|
buildTaskSummary,
|
|
@@ -71204,7 +71505,22 @@ async function runEvidenceCheck(dir) {
|
|
|
71204
71505
|
};
|
|
71205
71506
|
}
|
|
71206
71507
|
const evidenceTaskIds = new Set(await listEvidenceTaskIds(dir));
|
|
71207
|
-
const missingEvidence =
|
|
71508
|
+
const missingEvidence = [];
|
|
71509
|
+
for (const id of completedTaskIds) {
|
|
71510
|
+
const gateStatus = await getDurableGateEvidenceStatusForTask(dir, id);
|
|
71511
|
+
if (gateStatus.isComplete) {
|
|
71512
|
+
continue;
|
|
71513
|
+
}
|
|
71514
|
+
if (gateStatus.evidenceExists && gateStatus.missingGates.length > 0) {
|
|
71515
|
+
missingEvidence.push(id);
|
|
71516
|
+
continue;
|
|
71517
|
+
}
|
|
71518
|
+
if (evidenceTaskIds.has(id)) {
|
|
71519
|
+
continue;
|
|
71520
|
+
}
|
|
71521
|
+
missingEvidence.push(id);
|
|
71522
|
+
}
|
|
71523
|
+
const completedWithEvidence = completedTaskIds.length - missingEvidence.length;
|
|
71208
71524
|
if (missingEvidence.length > 0) {
|
|
71209
71525
|
return {
|
|
71210
71526
|
type: "evidence",
|
|
@@ -71212,7 +71528,7 @@ async function runEvidenceCheck(dir) {
|
|
|
71212
71528
|
message: `${missingEvidence.length} completed task(s) missing evidence`,
|
|
71213
71529
|
details: {
|
|
71214
71530
|
totalCompleted: completedTaskIds.length,
|
|
71215
|
-
totalWithEvidence:
|
|
71531
|
+
totalWithEvidence: completedWithEvidence,
|
|
71216
71532
|
missingTasks: missingEvidence.slice(0, 10),
|
|
71217
71533
|
missingCount: missingEvidence.length
|
|
71218
71534
|
},
|
|
@@ -71225,7 +71541,7 @@ async function runEvidenceCheck(dir) {
|
|
|
71225
71541
|
message: `All ${completedTaskIds.length} completed tasks have evidence`,
|
|
71226
71542
|
details: {
|
|
71227
71543
|
totalCompleted: completedTaskIds.length,
|
|
71228
|
-
totalWithEvidence:
|
|
71544
|
+
totalWithEvidence: completedWithEvidence
|
|
71229
71545
|
},
|
|
71230
71546
|
durationMs: Date.now() - startTime
|
|
71231
71547
|
};
|
|
@@ -71456,6 +71772,7 @@ async function handlePreflightCommand(directory, _args) {
|
|
|
71456
71772
|
}
|
|
71457
71773
|
var MIN_CHECK_TIMEOUT_MS = 5000, MAX_CHECK_TIMEOUT_MS = 300000, DEFAULT_CONFIG, _internals34;
|
|
71458
71774
|
var init_preflight_service = __esm(() => {
|
|
71775
|
+
init_gate_bridge();
|
|
71459
71776
|
init_manager2();
|
|
71460
71777
|
init_manager();
|
|
71461
71778
|
init_lint();
|
|
@@ -76401,121 +76718,14 @@ Do NOT share other agents' responses at this stage.
|
|
|
76401
76718
|
### MODE: DEEP_DIVE
|
|
76402
76719
|
Activates when: architect receives \`[MODE: DEEP_DIVE profile=X max_explorers=N output=X update_main=X allow_dirty=X] <scope>\` signal from the deep-dive command handler.
|
|
76403
76720
|
|
|
76404
|
-
Purpose:
|
|
76405
|
-
|
|
76406
|
-
|
|
76407
|
-
|
|
76408
|
-
|
|
76409
|
-
- \`profile\`: one of standard | security | ux | architecture | full (default: standard)
|
|
76410
|
-
- \`max_explorers\`: integer 1..8 (default: 6, or 8 for full profile)
|
|
76411
|
-
- \`output\`: markdown | json (default: markdown)
|
|
76412
|
-
- \`update_main\`: boolean (default: true) — whether to fetch/ff-only main before starting
|
|
76413
|
-
- \`allow_dirty\`: boolean (default: false) — whether to proceed with uncommitted changes
|
|
76414
|
-
|
|
76415
|
-
If the header is malformed or missing required fields, report the error and stop.
|
|
76416
|
-
|
|
76417
|
-
#### STEP 1 — REPO READINESS
|
|
76418
|
-
1. Check git working tree status. If dirty and \`allow_dirty\` is false, warn the user and ask whether to proceed. Do NOT proceed automatically.
|
|
76419
|
-
2. If \`update_main\` is true and tree is clean: check current branch. If not on \`main\`, report current branch to user and ASK FOR CONFIRMATION before switching. Only after explicit user approval: \`git fetch origin main && git checkout main && git merge --ff-only origin/main\`. If ff-only fails, warn the user and ask before proceeding.
|
|
76420
|
-
3. Record the current HEAD commit hash for the report.
|
|
76421
|
-
|
|
76422
|
-
#### STEP 2 — SCOPE RESOLUTION
|
|
76423
|
-
Use the following tools to map the audit scope:
|
|
76424
|
-
1. \`repo_map\` with action "build" to establish the code graph
|
|
76425
|
-
2. \`repo_map\` with action "localization" for the scope target
|
|
76426
|
-
3. \`symbols\` and \`batch_symbols\` on key files identified by localization
|
|
76427
|
-
4. \`imports\` to trace dependency boundaries
|
|
76428
|
-
5. \`doc_scan\` if documentation coverage is relevant
|
|
76429
|
-
6. \`knowledge_recall\` with query matching the scope domain
|
|
76430
|
-
|
|
76431
|
-
Produce a SCOPE MAP: list of files, modules, and interfaces within the audit boundary. Cap at 50 files total.
|
|
76432
|
-
|
|
76433
|
-
#### STEP 3 — EXPLORER MISSIONS (Parallel Waves)
|
|
76434
|
-
Dispatch explorer waves using parallel Task calls. Each wave contains up to \`max_explorers\` missions.
|
|
76435
|
-
|
|
76436
|
-
**File caps per mission:**
|
|
76437
|
-
- 8 files maximum per mission
|
|
76438
|
-
- ~3500 total lines across all files in a mission
|
|
76439
|
-
- Group files by import proximity (files that import each other go in the same mission)
|
|
76440
|
-
|
|
76441
|
-
**Profile-based lane selection — each profile activates specific lanes:**
|
|
76442
|
-
|
|
76443
|
-
| Lane | Template | standard | security | ux | architecture | full |
|
|
76444
|
-
|------|----------|----------|----------|----|-------------|------|
|
|
76445
|
-
| SCOPE_MAP | Map structure, exports, boundaries | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
76446
|
-
| WIRING_DATAFLOW | Trace data flow, API contracts, state propagation | ✓ | ✓ | | ✓ | ✓ |
|
|
76447
|
-
| RUNTIME_BEHAVIOR | Error handling, edge cases, lifecycle, async patterns | ✓ | | | ✓ | ✓ |
|
|
76448
|
-
| UX_FLOW | User-facing behavior, accessibility, responsiveness | | | ✓ | | ✓ |
|
|
76449
|
-
| SECURITY_TRUST | Auth boundaries, input validation, trust transitions | | ✓ | | | ✓ |
|
|
76450
|
-
| TEST_COVERAGE | Coverage gaps, flaky tests, missing assertions | ✓ | | | | ✓ |
|
|
76451
|
-
| PERFORMANCE_RELIABILITY | Resource leaks, N+1 queries, race conditions | | | | ✓ | ✓ |
|
|
76452
|
-
| DOCS_CONFIG_DEPLOYMENT | Config consistency, docs accuracy, deployment drift | | | | | ✓ |
|
|
76453
|
-
|
|
76454
|
-
Each explorer mission receives:
|
|
76455
|
-
- Lane template name and description
|
|
76456
|
-
- Assigned files (8 max, grouped by import proximity)
|
|
76457
|
-
- The scope map context from Step 2
|
|
76458
|
-
- Instruction: "You are performing a [LANE] audit. Report findings as candidate observations with severity (INFO/LOW/MEDIUM/HIGH/CRITICAL), location, and evidence."
|
|
76459
|
-
|
|
76460
|
-
Explorer missions are dispatched in parallel waves. Wait for ALL missions in a wave to complete before dispatching the next wave.
|
|
76461
|
-
|
|
76462
|
-
Explorers generate CANDIDATE FINDINGS only — they do NOT make verdicts. All findings are unverified until Step 5.
|
|
76463
|
-
|
|
76464
|
-
#### STEP 4 — NORMALIZE CANDIDATES
|
|
76465
|
-
1. Collect all candidate findings from all explorer missions.
|
|
76466
|
-
2. Deduplicate: merge findings that reference the same location and issue.
|
|
76467
|
-
3. Assign DD-C001 through DD-CNNN identifiers to unique findings.
|
|
76468
|
-
4. Cap at 10 findings per shard (see Step 5 for sharding).
|
|
76469
|
-
5. Sort by severity (CRITICAL → HIGH → MEDIUM → LOW → INFO).
|
|
76470
|
-
|
|
76471
|
-
#### STEP 5 — ALWAYS 2 PARALLEL REVIEWERS
|
|
76472
|
-
Split the verified candidates into 2 shards of ≤10 candidates each. Dispatch 2 parallel \`{{AGENT_PREFIX}}reviewer\` calls.
|
|
76473
|
-
|
|
76474
|
-
Each reviewer receives:
|
|
76475
|
-
- Their shard of candidates (up to 10)
|
|
76476
|
-
- The scope map context
|
|
76477
|
-
- The original scope description
|
|
76478
|
-
- Instruction: "Verify or reject each candidate finding. For each: verdict (VERIFIED / REJECTED / NEEDS_MORE_EVIDENCE), confidence (0-1), and brief reasoning."
|
|
76479
|
-
|
|
76480
|
-
Reviewers MUST NOT suggest fixes — they verify findings only.
|
|
76481
|
-
|
|
76482
|
-
#### STEP 5b — REVIEWER MERGE/DEDUP
|
|
76483
|
-
After both reviewers return, perform a lightweight sync pass:
|
|
76484
|
-
1. Cross-reference findings between reviewers — flag correlations
|
|
76485
|
-
2. Deduplicate any findings both reviewers verified independently
|
|
76486
|
-
3. For NEEDS_MORE_EVIDENCE findings: if the other reviewer verified a related finding, merge
|
|
76487
|
-
4. Produce a unified findings list with verified/rejected status
|
|
76488
|
-
|
|
76489
|
-
#### STEP 6 — CRITIC CHALLENGE (HIGH/CRITICAL only)
|
|
76490
|
-
For verified findings rated HIGH or CRITICAL, dispatch sequential critic passes:
|
|
76491
|
-
|
|
76492
|
-
**Pass 1 — False-positive / root-cause challenge:**
|
|
76493
|
-
- \`{{AGENT_PREFIX}}critic\` receives each HIGH/CRITICAL finding
|
|
76494
|
-
- Challenge: "Is this a false positive? Is the root cause correctly identified? Provide verdict: SURVIVES / DOWNGRADE / REJECT"
|
|
76495
|
-
- Only findings that SURVIVE proceed to Pass 2
|
|
76496
|
-
|
|
76497
|
-
**Pass 2 — Impact / severity challenge:**
|
|
76498
|
-
- \`{{AGENT_PREFIX}}critic\` receives surviving findings
|
|
76499
|
-
- Challenge: "Is the severity correctly rated? Could this be lower impact than claimed? Provide verdict: SURVIVES / DOWNGRADE / REJECT"
|
|
76500
|
-
- Final severity is the critic's assessed severity
|
|
76501
|
-
|
|
76502
|
-
CRITICAL: Do NOT challenge MEDIUM/LOW/INFO findings. Only HIGH and CRITICAL go through critic review.
|
|
76503
|
-
|
|
76504
|
-
#### STEP 7 — FINAL REPORT
|
|
76505
|
-
Assemble and present the audit report:
|
|
76506
|
-
|
|
76507
|
-
1. **Wiring Map**: Visual summary of the scope's module structure and data flow
|
|
76508
|
-
2. **Functionality Assessment**: High-level summary of what the scope does and how well
|
|
76509
|
-
3. **Verified Findings Table**: DD-ID, severity, location, description, evidence
|
|
76510
|
-
4. **Rejected Candidates**: Brief list with rejection reasons
|
|
76511
|
-
5. **Enhancements**: Non-blocking improvement suggestions
|
|
76512
|
-
6. **Recommended Implementation Phases**: If findings suggest follow-up work, outline phases
|
|
76513
|
-
7. **JSON Block** (when output=json): Structured machine-readable findings
|
|
76514
|
-
|
|
76515
|
-
IMPORTANT CONSTRAINTS for MODE: DEEP_DIVE:
|
|
76516
|
-
- Do NOT mutate source code under any circumstances
|
|
76721
|
+
Purpose: Read-only deep audit of the specified codebase scope using parallel explorer waves, always 2 parallel reviewers, and sequential critic challenge. This mode does NOT mutate source code, does NOT delegate to coder, and does NOT call declare_scope.
|
|
76722
|
+
|
|
76723
|
+
ACTION: Load skill \`file:.opencode/skills/deep-dive/SKILL.md\` immediately and follow its protocol.
|
|
76724
|
+
|
|
76725
|
+
HARD CONSTRAINTS (apply regardless of skill load success):
|
|
76517
76726
|
- Do NOT delegate to coder
|
|
76518
76727
|
- Do NOT call declare_scope
|
|
76728
|
+
- Do NOT mutate source code
|
|
76519
76729
|
- Do NOT create or modify any files outside .swarm/
|
|
76520
76730
|
- No final finding may appear in the report without reviewer verification
|
|
76521
76731
|
- Explorers generate candidate findings only — reviewers verify or reject
|
|
@@ -84996,6 +85206,10 @@ class PlanSyncWorker {
|
|
|
84996
85206
|
const planMtimeMs = Math.floor(planStats.mtimeMs);
|
|
84997
85207
|
const markerContent = fs37.readFileSync(markerPath, "utf8");
|
|
84998
85208
|
const marker = JSON.parse(markerContent);
|
|
85209
|
+
if (marker.in_progress === true) {
|
|
85210
|
+
log("[PlanSyncWorker] Skipping unauthorized-write check - plan write in progress");
|
|
85211
|
+
return;
|
|
85212
|
+
}
|
|
84999
85213
|
const markerTimestampMs = new Date(marker.timestamp).getTime();
|
|
85000
85214
|
if (planMtimeMs > markerTimestampMs + 5000) {
|
|
85001
85215
|
log("[PlanSyncWorker] WARNING: plan.json may have been written outside save_plan/savePlan - unauthorized direct write suspected", { planMtimeMs, markerTimestampMs });
|
|
@@ -91802,10 +92016,13 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
91802
92016
|
minCommits: 20,
|
|
91803
92017
|
minCoChanges: 3
|
|
91804
92018
|
});
|
|
91805
|
-
|
|
91806
|
-
|
|
91807
|
-
|
|
91808
|
-
|
|
92019
|
+
await fs54.promises.mkdir(path81.dirname(darkMatterPath), {
|
|
92020
|
+
recursive: true
|
|
92021
|
+
});
|
|
92022
|
+
const darkMatterReport = formatDarkMatterOutput2(darkMatter);
|
|
92023
|
+
await fs54.promises.writeFile(darkMatterPath, darkMatterReport, "utf-8");
|
|
92024
|
+
warn(`[system-enhancer] Dark matter scan complete: ${darkMatter.length} co-change patterns found`);
|
|
92025
|
+
if (darkMatter.length > 0) {
|
|
91809
92026
|
try {
|
|
91810
92027
|
const projectName = path81.basename(path81.resolve(directory));
|
|
91811
92028
|
const knowledgeEntries = darkMatterToKnowledgeEntries2(darkMatter, projectName);
|
|
@@ -112516,6 +112733,9 @@ init_ledger();
|
|
|
112516
112733
|
init_manager();
|
|
112517
112734
|
init_state();
|
|
112518
112735
|
init_create_tool();
|
|
112736
|
+
function executionProfilesEqual(a, b) {
|
|
112737
|
+
return a.parallelization_enabled === b.parallelization_enabled && a.max_concurrent_tasks === b.max_concurrent_tasks && a.council_parallel === b.council_parallel && a.locked === b.locked;
|
|
112738
|
+
}
|
|
112519
112739
|
function detectPlaceholderContent(args2) {
|
|
112520
112740
|
const issues = [];
|
|
112521
112741
|
const placeholderPattern = /^\[\w[\w\s]*\]$/;
|
|
@@ -112680,18 +112900,51 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
112680
112900
|
}
|
|
112681
112901
|
}
|
|
112682
112902
|
}
|
|
112683
|
-
if (
|
|
112684
|
-
|
|
112903
|
+
if (args2.confirm_identity_change !== true) {
|
|
112904
|
+
const existingId = derivePlanId(existing);
|
|
112905
|
+
const incomingId = derivePlanId({
|
|
112906
|
+
swarm: args2.swarm_id,
|
|
112907
|
+
title: args2.title
|
|
112908
|
+
});
|
|
112909
|
+
if (existingId !== incomingId) {
|
|
112685
112910
|
return {
|
|
112686
112911
|
success: false,
|
|
112687
|
-
message: "
|
|
112912
|
+
message: "PLAN_IDENTITY_MISMATCH: The incoming plan identity does not match the existing plan. " + "To overwrite with a new identity, set confirm_identity_change: true.",
|
|
112688
112913
|
errors: [
|
|
112689
|
-
|
|
112914
|
+
`Existing plan identity: ${existingId} (swarm: "${existing.swarm}", title: "${existing.title}")`,
|
|
112915
|
+
`Incoming plan identity: ${incomingId} (swarm: "${args2.swarm_id}", title: "${args2.title}")`
|
|
112690
112916
|
],
|
|
112691
|
-
recovery_guidance: "
|
|
112917
|
+
recovery_guidance: "Verify the title and swarm_id match the intended plan. " + "If the identity change is intentional, retry with confirm_identity_change: true. " + "Never write .swarm/plan.json or .swarm/plan.md directly."
|
|
112692
112918
|
};
|
|
112693
112919
|
}
|
|
112694
|
-
|
|
112920
|
+
}
|
|
112921
|
+
if (existing.execution_profile?.locked) {
|
|
112922
|
+
if (args2.execution_profile !== undefined && !args2.reset_statuses) {
|
|
112923
|
+
const requestedProfile = ExecutionProfileSchema.safeParse({
|
|
112924
|
+
...existing.execution_profile,
|
|
112925
|
+
...args2.execution_profile
|
|
112926
|
+
});
|
|
112927
|
+
if (!requestedProfile.success) {
|
|
112928
|
+
return {
|
|
112929
|
+
success: false,
|
|
112930
|
+
message: "Invalid execution_profile: schema validation failed",
|
|
112931
|
+
errors: requestedProfile.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`),
|
|
112932
|
+
recovery_guidance: "Check execution_profile fields: parallelization_enabled (boolean), " + "max_concurrent_tasks (integer 1-64), council_parallel (boolean), locked (boolean)."
|
|
112933
|
+
};
|
|
112934
|
+
}
|
|
112935
|
+
if (executionProfilesEqual(existing.execution_profile, requestedProfile.data)) {
|
|
112936
|
+
preservedExecutionProfile = existing.execution_profile;
|
|
112937
|
+
} else {
|
|
112938
|
+
return {
|
|
112939
|
+
success: false,
|
|
112940
|
+
message: "EXECUTION_PROFILE_LOCKED: The execution_profile for this plan is locked and cannot be changed.",
|
|
112941
|
+
errors: [
|
|
112942
|
+
"execution_profile.locked is true — to change the profile you must first unlock it via a separate plan revision that explicitly sets locked: false, or reset the plan with reset_statuses."
|
|
112943
|
+
],
|
|
112944
|
+
recovery_guidance: "Remove the execution_profile field from this save_plan call to preserve the locked profile, " + "or use reset_statuses: true to start fresh (this clears the lock). " + "Never modify execution_profile directly in plan.json."
|
|
112945
|
+
};
|
|
112946
|
+
}
|
|
112947
|
+
} else if (!args2.reset_statuses) {
|
|
112695
112948
|
preservedExecutionProfile = existing.execution_profile;
|
|
112696
112949
|
}
|
|
112697
112950
|
} else {
|
|
@@ -112863,7 +113116,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
112863
113116
|
});
|
|
112864
113117
|
const savedPlan = await loadPlanJsonOnly(dir);
|
|
112865
113118
|
if (savedPlan) {
|
|
112866
|
-
await
|
|
113119
|
+
await takeSnapshotWithRetry(dir, savedPlan);
|
|
112867
113120
|
}
|
|
112868
113121
|
if (resolvedProfile !== undefined && savedPlan) {
|
|
112869
113122
|
const planId = derivePlanId(plan);
|
|
@@ -112961,6 +113214,7 @@ var save_plan = createSwarmTool({
|
|
|
112961
113214
|
removed_task_ids: exports_external.array(exports_external.string()).optional().describe("Task IDs that are present in the prior plan but intentionally being " + "removed by this save. Every task missing from `phases` MUST be enumerated " + "here, otherwise save_plan rejects with PLAN_TASK_REMOVAL_NOT_ACKNOWLEDGED. " + "Tasks not yet finished (status pending/in_progress/blocked) MUST NOT be " + "removed without explicit user confirmation."),
|
|
112962
113215
|
removal_reason: exports_external.string().optional().describe("Required when removed_task_ids is non-empty. Human-readable reason recorded " + "on each task_removed ledger event."),
|
|
112963
113216
|
confirm_destructive_reset: exports_external.boolean().optional().describe("Required when reset_statuses is true AND at least one task is missing from " + "the new plan. Set true to acknowledge that the destructive reset drops " + "unfinished work. When set together with reset_statuses, save_plan auto-" + "populates removed_task_ids from the missing set."),
|
|
113217
|
+
confirm_identity_change: exports_external.boolean().optional().describe("When true, allows overwriting an existing plan that has a different " + "identity (swarm_id + title). Without this flag, save_plan rejects " + "with PLAN_IDENTITY_MISMATCH if the identity differs."),
|
|
112964
113218
|
execution_profile: exports_external.object({
|
|
112965
113219
|
parallelization_enabled: exports_external.boolean().optional().describe("When true, enables parallel task dispatch for this plan. Default false (serial)."),
|
|
112966
113220
|
max_concurrent_tasks: exports_external.number().int().min(1).max(64).optional().describe("Maximum tasks that may run concurrently when parallelization is enabled. Default 1."),
|