opencode-swarm 6.61.0 → 6.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/critic.d.ts +1 -1
- package/dist/cli/index.js +208 -89
- package/dist/evidence/manager.d.ts +2 -12
- package/dist/gate-evidence.d.ts +2 -9
- package/dist/index.js +405 -154
- package/dist/plan/ledger.d.ts +2 -2
- package/dist/plan/manager.d.ts +8 -0
- package/dist/tools/get-approved-plan.d.ts +44 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/validation/task-id.d.ts +43 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,7 +75,8 @@ var init_tool_names = __esm(() => {
|
|
|
75
75
|
"search",
|
|
76
76
|
"batch_symbols",
|
|
77
77
|
"suggest_patch",
|
|
78
|
-
"req_coverage"
|
|
78
|
+
"req_coverage",
|
|
79
|
+
"get_approved_plan"
|
|
79
80
|
];
|
|
80
81
|
TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
81
82
|
});
|
|
@@ -307,7 +308,8 @@ var init_constants = __esm(() => {
|
|
|
307
308
|
"retrieve_summary",
|
|
308
309
|
"symbols",
|
|
309
310
|
"knowledge_recall",
|
|
310
|
-
"req_coverage"
|
|
311
|
+
"req_coverage",
|
|
312
|
+
"get_approved_plan"
|
|
311
313
|
],
|
|
312
314
|
critic_oversight: [
|
|
313
315
|
"complexity_hotspots",
|
|
@@ -391,7 +393,8 @@ var init_constants = __esm(() => {
|
|
|
391
393
|
search: "Workspace-scoped ripgrep-style text search with structured JSON output. Supports literal and regex modes, glob filtering, and result limits. NOTE: This is text search, not structural AST search \u2014 use symbols and imports tools for structural queries.",
|
|
392
394
|
batch_symbols: "Batched symbol extraction across multiple files. Returns per-file symbol summaries with isolated error handling.",
|
|
393
395
|
suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch.",
|
|
394
|
-
lint_spec: "validate .swarm/spec.md format and required fields"
|
|
396
|
+
lint_spec: "validate .swarm/spec.md format and required fields",
|
|
397
|
+
get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison"
|
|
395
398
|
};
|
|
396
399
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
397
400
|
const invalidTools = tools.filter((tool) => !TOOL_NAME_SET.has(tool));
|
|
@@ -15785,22 +15788,26 @@ async function readLedgerEvents(directory) {
|
|
|
15785
15788
|
return [];
|
|
15786
15789
|
}
|
|
15787
15790
|
}
|
|
15788
|
-
async function initLedger(directory, planId, initialPlanHash) {
|
|
15791
|
+
async function initLedger(directory, planId, initialPlanHash, initialPlan) {
|
|
15789
15792
|
const ledgerPath = getLedgerPath(directory);
|
|
15790
15793
|
const planJsonPath = getPlanJsonPath(directory);
|
|
15791
15794
|
if (fs3.existsSync(ledgerPath)) {
|
|
15792
15795
|
throw new Error("Ledger already initialized. Use appendLedgerEvent to add events.");
|
|
15793
15796
|
}
|
|
15794
15797
|
let planHashAfter = initialPlanHash ?? "";
|
|
15798
|
+
let embeddedPlan = initialPlan;
|
|
15795
15799
|
if (!initialPlanHash) {
|
|
15796
15800
|
try {
|
|
15797
15801
|
if (fs3.existsSync(planJsonPath)) {
|
|
15798
15802
|
const content = fs3.readFileSync(planJsonPath, "utf8");
|
|
15799
15803
|
const plan = JSON.parse(content);
|
|
15800
15804
|
planHashAfter = computePlanHash(plan);
|
|
15805
|
+
if (!embeddedPlan)
|
|
15806
|
+
embeddedPlan = plan;
|
|
15801
15807
|
}
|
|
15802
15808
|
} catch {}
|
|
15803
15809
|
}
|
|
15810
|
+
const payload = embeddedPlan ? { plan: embeddedPlan, payload_hash: planHashAfter } : undefined;
|
|
15804
15811
|
const event = {
|
|
15805
15812
|
seq: 1,
|
|
15806
15813
|
timestamp: new Date().toISOString(),
|
|
@@ -15809,7 +15816,8 @@ async function initLedger(directory, planId, initialPlanHash) {
|
|
|
15809
15816
|
source: "initLedger",
|
|
15810
15817
|
plan_hash_before: "",
|
|
15811
15818
|
plan_hash_after: planHashAfter,
|
|
15812
|
-
schema_version: LEDGER_SCHEMA_VERSION
|
|
15819
|
+
schema_version: LEDGER_SCHEMA_VERSION,
|
|
15820
|
+
...payload ? { payload } : {}
|
|
15813
15821
|
};
|
|
15814
15822
|
fs3.mkdirSync(path3.join(directory, ".swarm"), { recursive: true });
|
|
15815
15823
|
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
@@ -15896,7 +15904,7 @@ async function takeSnapshotEvent(directory, plan, options) {
|
|
|
15896
15904
|
payload: snapshotPayload
|
|
15897
15905
|
}, { planHashAfter: options?.planHashAfter });
|
|
15898
15906
|
}
|
|
15899
|
-
async function replayFromLedger(directory,
|
|
15907
|
+
async function replayFromLedger(directory, _options) {
|
|
15900
15908
|
const events = await readLedgerEvents(directory);
|
|
15901
15909
|
if (events.length === 0) {
|
|
15902
15910
|
return null;
|
|
@@ -15919,6 +15927,20 @@ async function replayFromLedger(directory, options) {
|
|
|
15919
15927
|
return plan2;
|
|
15920
15928
|
}
|
|
15921
15929
|
}
|
|
15930
|
+
const createdEvent = relevantEvents.find((e) => e.event_type === "plan_created");
|
|
15931
|
+
if (createdEvent?.payload && typeof createdEvent.payload === "object" && "plan" in createdEvent.payload) {
|
|
15932
|
+
const parseResult = PlanSchema.safeParse(createdEvent.payload.plan);
|
|
15933
|
+
if (parseResult.success) {
|
|
15934
|
+
let plan2 = parseResult.data;
|
|
15935
|
+
const eventsAfterCreated = relevantEvents.filter((e) => e.seq > createdEvent.seq);
|
|
15936
|
+
for (const event of eventsAfterCreated) {
|
|
15937
|
+
if (plan2 === null)
|
|
15938
|
+
return null;
|
|
15939
|
+
plan2 = applyEventToPlan(plan2, event);
|
|
15940
|
+
}
|
|
15941
|
+
return plan2;
|
|
15942
|
+
}
|
|
15943
|
+
}
|
|
15922
15944
|
const planJsonPath = getPlanJsonPath(directory);
|
|
15923
15945
|
if (!fs3.existsSync(planJsonPath)) {
|
|
15924
15946
|
return null;
|
|
@@ -15941,6 +15963,11 @@ async function replayFromLedger(directory, options) {
|
|
|
15941
15963
|
function applyEventToPlan(plan, event) {
|
|
15942
15964
|
switch (event.event_type) {
|
|
15943
15965
|
case "plan_created":
|
|
15966
|
+
if (event.payload && typeof event.payload === "object" && "plan" in event.payload) {
|
|
15967
|
+
const parsed = PlanSchema.safeParse(event.payload.plan);
|
|
15968
|
+
if (parsed.success)
|
|
15969
|
+
return parsed.data;
|
|
15970
|
+
}
|
|
15944
15971
|
return plan;
|
|
15945
15972
|
case "task_status_changed":
|
|
15946
15973
|
if (event.task_id && event.to_status) {
|
|
@@ -16029,7 +16056,13 @@ var init_ledger = __esm(() => {
|
|
|
16029
16056
|
});
|
|
16030
16057
|
|
|
16031
16058
|
// src/plan/manager.ts
|
|
16032
|
-
import {
|
|
16059
|
+
import {
|
|
16060
|
+
copyFileSync,
|
|
16061
|
+
existsSync as existsSync3,
|
|
16062
|
+
readdirSync,
|
|
16063
|
+
renameSync as renameSync2,
|
|
16064
|
+
unlinkSync
|
|
16065
|
+
} from "fs";
|
|
16033
16066
|
import * as fsPromises from "fs/promises";
|
|
16034
16067
|
import * as path4 from "path";
|
|
16035
16068
|
async function loadPlanJsonOnly(directory) {
|
|
@@ -16281,35 +16314,53 @@ async function loadPlan(directory) {
|
|
|
16281
16314
|
return migrated;
|
|
16282
16315
|
}
|
|
16283
16316
|
if (await ledgerExists(directory)) {
|
|
16284
|
-
const
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16317
|
+
const resolvedDir = path4.resolve(directory);
|
|
16318
|
+
const existingMutex = recoveryMutexes.get(resolvedDir);
|
|
16319
|
+
if (existingMutex) {
|
|
16320
|
+
await existingMutex;
|
|
16321
|
+
const postRecoveryPlan = await loadPlanJsonOnly(directory);
|
|
16322
|
+
if (postRecoveryPlan)
|
|
16323
|
+
return postRecoveryPlan;
|
|
16324
|
+
}
|
|
16325
|
+
let resolveRecovery;
|
|
16326
|
+
const mutex = new Promise((r) => {
|
|
16327
|
+
resolveRecovery = r;
|
|
16328
|
+
});
|
|
16329
|
+
recoveryMutexes.set(resolvedDir, mutex);
|
|
16289
16330
|
try {
|
|
16290
|
-
const
|
|
16291
|
-
if (
|
|
16292
|
-
|
|
16293
|
-
return
|
|
16331
|
+
const rebuilt = await replayFromLedger(directory);
|
|
16332
|
+
if (rebuilt) {
|
|
16333
|
+
await savePlan(directory, rebuilt);
|
|
16334
|
+
return rebuilt;
|
|
16294
16335
|
}
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
});
|
|
16306
|
-
|
|
16307
|
-
|
|
16336
|
+
try {
|
|
16337
|
+
const anchorEvents = await readLedgerEvents(directory);
|
|
16338
|
+
if (anchorEvents.length === 0) {
|
|
16339
|
+
warn("[loadPlan] Ledger present but no events readable \u2014 refusing approved-snapshot recovery (cannot verify plan identity).");
|
|
16340
|
+
return null;
|
|
16341
|
+
}
|
|
16342
|
+
const expectedPlanId = anchorEvents[0].plan_id;
|
|
16343
|
+
const approved = await loadLastApprovedPlan(directory, expectedPlanId);
|
|
16344
|
+
if (approved) {
|
|
16345
|
+
const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
|
|
16346
|
+
warn(`[loadPlan] Ledger replay returned no plan \u2014 recovered from critic-approved snapshot seq=${approved.seq} timestamp=${approved.timestamp} (approval phase=${approvedPhase ?? "unknown"}). This may roll the plan back to an earlier phase \u2014 verify before continuing.`);
|
|
16347
|
+
await savePlan(directory, approved.plan);
|
|
16348
|
+
try {
|
|
16349
|
+
await takeSnapshotEvent(directory, approved.plan, {
|
|
16350
|
+
source: "recovery_from_approved_snapshot",
|
|
16351
|
+
approvalMetadata: approved.approval
|
|
16352
|
+
});
|
|
16353
|
+
} catch (healError) {
|
|
16354
|
+
warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
|
|
16355
|
+
}
|
|
16356
|
+
return approved.plan;
|
|
16308
16357
|
}
|
|
16309
|
-
|
|
16358
|
+
} catch (recoveryError) {
|
|
16359
|
+
warn(`[loadPlan] Approved-snapshot recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
16310
16360
|
}
|
|
16311
|
-
}
|
|
16312
|
-
|
|
16361
|
+
} finally {
|
|
16362
|
+
resolveRecovery();
|
|
16363
|
+
recoveryMutexes.delete(resolvedDir);
|
|
16313
16364
|
}
|
|
16314
16365
|
}
|
|
16315
16366
|
return null;
|
|
@@ -16358,7 +16409,7 @@ async function savePlan(directory, plan, options) {
|
|
|
16358
16409
|
const planId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16359
16410
|
const planHashForInit = computePlanHash(validated);
|
|
16360
16411
|
if (!await ledgerExists(directory)) {
|
|
16361
|
-
await initLedger(directory, planId, planHashForInit);
|
|
16412
|
+
await initLedger(directory, planId, planHashForInit, validated);
|
|
16362
16413
|
} else {
|
|
16363
16414
|
const existingEvents = await readLedgerEvents(directory);
|
|
16364
16415
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
@@ -16377,7 +16428,7 @@ async function savePlan(directory, plan, options) {
|
|
|
16377
16428
|
let initSucceeded = false;
|
|
16378
16429
|
if (backupExists) {
|
|
16379
16430
|
try {
|
|
16380
|
-
await initLedger(directory, planId, planHashForInit);
|
|
16431
|
+
await initLedger(directory, planId, planHashForInit, validated);
|
|
16381
16432
|
initSucceeded = true;
|
|
16382
16433
|
} catch (initErr) {
|
|
16383
16434
|
const errorMessage = String(initErr);
|
|
@@ -16419,6 +16470,19 @@ async function savePlan(directory, plan, options) {
|
|
|
16419
16470
|
unlinkSync(oldLedgerBackupPath);
|
|
16420
16471
|
} catch {}
|
|
16421
16472
|
}
|
|
16473
|
+
const MAX_ARCHIVED_SIBLINGS = 5;
|
|
16474
|
+
try {
|
|
16475
|
+
const allFiles = readdirSync(swarmDir2);
|
|
16476
|
+
const archivedSiblings = allFiles.filter((f) => f.startsWith("plan-ledger.archived-") && f.endsWith(".jsonl")).sort();
|
|
16477
|
+
if (archivedSiblings.length > MAX_ARCHIVED_SIBLINGS) {
|
|
16478
|
+
const toRemove = archivedSiblings.slice(0, archivedSiblings.length - MAX_ARCHIVED_SIBLINGS);
|
|
16479
|
+
for (const file2 of toRemove) {
|
|
16480
|
+
try {
|
|
16481
|
+
unlinkSync(path4.join(swarmDir2, file2));
|
|
16482
|
+
} catch {}
|
|
16483
|
+
}
|
|
16484
|
+
}
|
|
16485
|
+
} catch {}
|
|
16422
16486
|
}
|
|
16423
16487
|
}
|
|
16424
16488
|
const currentHash = computeCurrentPlanHash(directory);
|
|
@@ -16468,7 +16532,7 @@ async function savePlan(directory, plan, options) {
|
|
|
16468
16532
|
}
|
|
16469
16533
|
} catch (error49) {
|
|
16470
16534
|
if (error49 instanceof LedgerStaleWriterError) {
|
|
16471
|
-
throw new
|
|
16535
|
+
throw new PlanConcurrentModificationError(`Concurrent plan modification detected after retries: ${error49.message}. Please retry the operation.`);
|
|
16472
16536
|
}
|
|
16473
16537
|
throw error49;
|
|
16474
16538
|
}
|
|
@@ -16478,7 +16542,11 @@ async function savePlan(directory, plan, options) {
|
|
|
16478
16542
|
if (latestSeq > 0 && latestSeq % SNAPSHOT_INTERVAL === 0) {
|
|
16479
16543
|
await takeSnapshotEvent(directory, validated, {
|
|
16480
16544
|
planHashAfter: hashAfter
|
|
16481
|
-
}).catch(() => {
|
|
16545
|
+
}).catch((err2) => {
|
|
16546
|
+
if (process.env.DEBUG_SWARM) {
|
|
16547
|
+
warn(`[savePlan] Periodic snapshot write failed (non-fatal): ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
16548
|
+
}
|
|
16549
|
+
});
|
|
16482
16550
|
}
|
|
16483
16551
|
const swarmDir = path4.resolve(directory, ".swarm");
|
|
16484
16552
|
const planPath = path4.join(swarmDir, "plan.json");
|
|
@@ -16491,19 +16559,23 @@ async function savePlan(directory, plan, options) {
|
|
|
16491
16559
|
unlinkSync(tempPath);
|
|
16492
16560
|
} catch {}
|
|
16493
16561
|
}
|
|
16494
|
-
const contentHash = computePlanContentHash(validated);
|
|
16495
|
-
const markdown = derivePlanMarkdown(validated);
|
|
16496
|
-
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
16497
|
-
${markdown}`;
|
|
16498
|
-
const mdPath = path4.join(swarmDir, "plan.md");
|
|
16499
|
-
const mdTempPath = path4.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
16500
16562
|
try {
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16563
|
+
const contentHash = computePlanContentHash(validated);
|
|
16564
|
+
const markdown = derivePlanMarkdown(validated);
|
|
16565
|
+
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
16566
|
+
${markdown}`;
|
|
16567
|
+
const mdPath = path4.join(swarmDir, "plan.md");
|
|
16568
|
+
const mdTempPath = path4.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
16504
16569
|
try {
|
|
16505
|
-
|
|
16506
|
-
|
|
16570
|
+
await Bun.write(mdTempPath, markdownWithHash);
|
|
16571
|
+
renameSync2(mdTempPath, mdPath);
|
|
16572
|
+
} finally {
|
|
16573
|
+
try {
|
|
16574
|
+
unlinkSync(mdTempPath);
|
|
16575
|
+
} catch {}
|
|
16576
|
+
}
|
|
16577
|
+
} catch (mdError) {
|
|
16578
|
+
warn(`[savePlan] plan.md write failed (non-fatal, plan.json is authoritative): ${mdError instanceof Error ? mdError.message : String(mdError)}`);
|
|
16507
16579
|
}
|
|
16508
16580
|
try {
|
|
16509
16581
|
const markerPath = path4.join(swarmDir, ".plan-write-marker");
|
|
@@ -16548,11 +16620,6 @@ ${markdown}`;
|
|
|
16548
16620
|
return targetPlan;
|
|
16549
16621
|
}
|
|
16550
16622
|
async function updateTaskStatus(directory, taskId, status) {
|
|
16551
|
-
const plan = await loadPlan(directory);
|
|
16552
|
-
if (plan === null) {
|
|
16553
|
-
throw new Error(`Plan not found in directory: ${directory}`);
|
|
16554
|
-
}
|
|
16555
|
-
let taskFound = false;
|
|
16556
16623
|
const derivePhaseStatusFromTasks = (tasks) => {
|
|
16557
16624
|
if (tasks.length > 0 && tasks.every((task) => task.status === "completed")) {
|
|
16558
16625
|
return "complete";
|
|
@@ -16565,26 +16632,44 @@ async function updateTaskStatus(directory, taskId, status) {
|
|
|
16565
16632
|
}
|
|
16566
16633
|
return "pending";
|
|
16567
16634
|
};
|
|
16568
|
-
const
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16635
|
+
const MAX_OUTER_RETRIES = 1;
|
|
16636
|
+
for (let attempt = 0;attempt <= MAX_OUTER_RETRIES; attempt++) {
|
|
16637
|
+
const plan = await loadPlan(directory);
|
|
16638
|
+
if (plan === null) {
|
|
16639
|
+
throw new Error(`Plan not found in directory: ${directory}`);
|
|
16640
|
+
}
|
|
16641
|
+
let taskFound = false;
|
|
16642
|
+
const updatedPhases = plan.phases.map((phase) => {
|
|
16643
|
+
const updatedTasks = phase.tasks.map((task) => {
|
|
16644
|
+
if (task.id === taskId) {
|
|
16645
|
+
taskFound = true;
|
|
16646
|
+
return { ...task, status };
|
|
16647
|
+
}
|
|
16648
|
+
return task;
|
|
16649
|
+
});
|
|
16650
|
+
return {
|
|
16651
|
+
...phase,
|
|
16652
|
+
status: derivePhaseStatusFromTasks(updatedTasks),
|
|
16653
|
+
tasks: updatedTasks
|
|
16654
|
+
};
|
|
16575
16655
|
});
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16656
|
+
if (!taskFound) {
|
|
16657
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
16658
|
+
}
|
|
16659
|
+
const updatedPlan = { ...plan, phases: updatedPhases };
|
|
16660
|
+
try {
|
|
16661
|
+
await savePlan(directory, updatedPlan, {
|
|
16662
|
+
preserveCompletedStatuses: true
|
|
16663
|
+
});
|
|
16664
|
+
return updatedPlan;
|
|
16665
|
+
} catch (error49) {
|
|
16666
|
+
if (error49 instanceof PlanConcurrentModificationError && attempt < MAX_OUTER_RETRIES) {
|
|
16667
|
+
continue;
|
|
16668
|
+
}
|
|
16669
|
+
throw error49;
|
|
16670
|
+
}
|
|
16584
16671
|
}
|
|
16585
|
-
|
|
16586
|
-
await savePlan(directory, updatedPlan, { preserveCompletedStatuses: true });
|
|
16587
|
-
return updatedPlan;
|
|
16672
|
+
throw new Error("updateTaskStatus: unexpected loop exit");
|
|
16588
16673
|
}
|
|
16589
16674
|
function derivePlanMarkdown(plan) {
|
|
16590
16675
|
const statusMap = {
|
|
@@ -16852,57 +16937,90 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
16852
16937
|
};
|
|
16853
16938
|
return plan;
|
|
16854
16939
|
}
|
|
16855
|
-
var startupLedgerCheckedWorkspaces;
|
|
16940
|
+
var PlanConcurrentModificationError, startupLedgerCheckedWorkspaces, recoveryMutexes;
|
|
16856
16941
|
var init_manager = __esm(() => {
|
|
16857
16942
|
init_plan_schema();
|
|
16858
16943
|
init_utils2();
|
|
16859
16944
|
init_utils();
|
|
16860
16945
|
init_spec_hash();
|
|
16861
16946
|
init_ledger();
|
|
16947
|
+
PlanConcurrentModificationError = class PlanConcurrentModificationError extends Error {
|
|
16948
|
+
constructor(message) {
|
|
16949
|
+
super(message);
|
|
16950
|
+
this.name = "PlanConcurrentModificationError";
|
|
16951
|
+
}
|
|
16952
|
+
};
|
|
16862
16953
|
startupLedgerCheckedWorkspaces = new Set;
|
|
16954
|
+
recoveryMutexes = new Map;
|
|
16863
16955
|
});
|
|
16864
16956
|
|
|
16865
|
-
// src/
|
|
16866
|
-
|
|
16867
|
-
import * as fs4 from "fs/promises";
|
|
16868
|
-
import * as path5 from "path";
|
|
16869
|
-
function isValidEvidenceType(type) {
|
|
16870
|
-
return VALID_EVIDENCE_TYPES.includes(type);
|
|
16871
|
-
}
|
|
16872
|
-
function isSecretscanEvidence(evidence) {
|
|
16873
|
-
return evidence.type === "secretscan";
|
|
16874
|
-
}
|
|
16875
|
-
function sanitizeTaskId(taskId) {
|
|
16957
|
+
// src/validation/task-id.ts
|
|
16958
|
+
function checkUnsafeChars(taskId) {
|
|
16876
16959
|
if (!taskId || taskId.length === 0) {
|
|
16877
|
-
|
|
16960
|
+
return "Invalid task ID: empty string";
|
|
16878
16961
|
}
|
|
16879
16962
|
if (/\0/.test(taskId)) {
|
|
16880
|
-
|
|
16963
|
+
return "Invalid task ID: contains null bytes";
|
|
16881
16964
|
}
|
|
16882
16965
|
for (let i2 = 0;i2 < taskId.length; i2++) {
|
|
16883
16966
|
if (taskId.charCodeAt(i2) < 32) {
|
|
16884
|
-
|
|
16967
|
+
return "Invalid task ID: contains control characters";
|
|
16885
16968
|
}
|
|
16886
16969
|
}
|
|
16887
|
-
if (taskId.includes("..") || taskId.includes("
|
|
16888
|
-
|
|
16970
|
+
if (taskId.includes("..") || taskId.includes("/") || taskId.includes("\\")) {
|
|
16971
|
+
return "Invalid task ID: path traversal detected";
|
|
16889
16972
|
}
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
if (
|
|
16894
|
-
return
|
|
16973
|
+
return;
|
|
16974
|
+
}
|
|
16975
|
+
function isStrictTaskId(taskId) {
|
|
16976
|
+
if (!taskId)
|
|
16977
|
+
return false;
|
|
16978
|
+
const unsafeMsg = checkUnsafeChars(taskId);
|
|
16979
|
+
if (unsafeMsg)
|
|
16980
|
+
return false;
|
|
16981
|
+
return STRICT_TASK_ID_PATTERN.test(taskId);
|
|
16982
|
+
}
|
|
16983
|
+
function assertStrictTaskId(taskId) {
|
|
16984
|
+
if (!isStrictTaskId(taskId)) {
|
|
16985
|
+
throw new Error(`Invalid taskId: "${taskId}". Must match N.M or N.M.P (e.g. "1.1", "1.2.3").`);
|
|
16895
16986
|
}
|
|
16896
|
-
|
|
16897
|
-
|
|
16987
|
+
}
|
|
16988
|
+
function sanitizeTaskId(taskId) {
|
|
16989
|
+
const unsafeMsg = checkUnsafeChars(taskId);
|
|
16990
|
+
if (unsafeMsg) {
|
|
16991
|
+
throw new Error(unsafeMsg);
|
|
16898
16992
|
}
|
|
16899
|
-
if (GENERAL_TASK_ID_REGEX.test(taskId)) {
|
|
16993
|
+
if (STRICT_TASK_ID_PATTERN.test(taskId) || RETRO_TASK_ID_REGEX.test(taskId) || INTERNAL_TOOL_ID_REGEX.test(taskId) || GENERAL_TASK_ID_REGEX.test(taskId)) {
|
|
16900
16994
|
return taskId;
|
|
16901
16995
|
}
|
|
16902
16996
|
throw new Error(`Invalid task ID: must be alphanumeric (ASCII) with optional hyphens, underscores, or dots, got "${taskId}"`);
|
|
16903
16997
|
}
|
|
16998
|
+
function validateTaskIdFormat(taskId) {
|
|
16999
|
+
if (!STRICT_TASK_ID_PATTERN.test(taskId)) {
|
|
17000
|
+
return `Invalid taskId "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
|
|
17001
|
+
}
|
|
17002
|
+
return;
|
|
17003
|
+
}
|
|
17004
|
+
var STRICT_TASK_ID_PATTERN, RETRO_TASK_ID_REGEX, INTERNAL_TOOL_ID_REGEX, GENERAL_TASK_ID_REGEX;
|
|
17005
|
+
var init_task_id = __esm(() => {
|
|
17006
|
+
STRICT_TASK_ID_PATTERN = /^\d+\.\d+(\.\d+)*$/;
|
|
17007
|
+
RETRO_TASK_ID_REGEX = /^retro-\d+$/;
|
|
17008
|
+
INTERNAL_TOOL_ID_REGEX = /^(?:sast_scan|quality_budget|syntax_check|placeholder_scan|sbom_generate|build|secretscan)$/;
|
|
17009
|
+
GENERAL_TASK_ID_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
17010
|
+
});
|
|
17011
|
+
|
|
17012
|
+
// src/evidence/manager.ts
|
|
17013
|
+
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync, statSync as statSync2 } from "fs";
|
|
17014
|
+
import * as fs4 from "fs/promises";
|
|
17015
|
+
import * as path5 from "path";
|
|
17016
|
+
function isValidEvidenceType(type) {
|
|
17017
|
+
return VALID_EVIDENCE_TYPES.includes(type);
|
|
17018
|
+
}
|
|
17019
|
+
function isSecretscanEvidence(evidence) {
|
|
17020
|
+
return evidence.type === "secretscan";
|
|
17021
|
+
}
|
|
16904
17022
|
async function saveEvidence(directory, taskId, evidence) {
|
|
16905
|
-
const sanitizedTaskId =
|
|
17023
|
+
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
16906
17024
|
const relativePath = path5.join("evidence", sanitizedTaskId, "evidence.json");
|
|
16907
17025
|
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
16908
17026
|
const evidenceDir = path5.dirname(evidencePath);
|
|
@@ -16933,9 +17051,14 @@ async function saveEvidence(directory, taskId, evidence) {
|
|
|
16933
17051
|
updated_at: now
|
|
16934
17052
|
};
|
|
16935
17053
|
}
|
|
17054
|
+
const MAX_BUNDLE_ENTRIES = 100;
|
|
17055
|
+
let entries = [...bundle.entries, evidence];
|
|
17056
|
+
if (entries.length > MAX_BUNDLE_ENTRIES) {
|
|
17057
|
+
entries = entries.slice(entries.length - MAX_BUNDLE_ENTRIES);
|
|
17058
|
+
}
|
|
16936
17059
|
const updatedBundle = {
|
|
16937
17060
|
...bundle,
|
|
16938
|
-
entries
|
|
17061
|
+
entries,
|
|
16939
17062
|
updated_at: new Date().toISOString()
|
|
16940
17063
|
};
|
|
16941
17064
|
const bundleJson = JSON.stringify(updatedBundle);
|
|
@@ -16980,7 +17103,7 @@ function wrapFlatRetrospective(flatEntry, taskId) {
|
|
|
16980
17103
|
};
|
|
16981
17104
|
}
|
|
16982
17105
|
async function loadEvidence(directory, taskId) {
|
|
16983
|
-
const sanitizedTaskId =
|
|
17106
|
+
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
16984
17107
|
const relativePath = path5.join("evidence", sanitizedTaskId, "evidence.json");
|
|
16985
17108
|
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
16986
17109
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
@@ -17034,7 +17157,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
17034
17157
|
}
|
|
17035
17158
|
let entries;
|
|
17036
17159
|
try {
|
|
17037
|
-
entries =
|
|
17160
|
+
entries = readdirSync2(evidenceBasePath);
|
|
17038
17161
|
} catch {
|
|
17039
17162
|
return [];
|
|
17040
17163
|
}
|
|
@@ -17046,7 +17169,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
17046
17169
|
if (!stats.isDirectory()) {
|
|
17047
17170
|
continue;
|
|
17048
17171
|
}
|
|
17049
|
-
|
|
17172
|
+
sanitizeTaskId2(entry);
|
|
17050
17173
|
taskIds.push(entry);
|
|
17051
17174
|
} catch (error49) {
|
|
17052
17175
|
if (error49 instanceof Error && !error49.message.startsWith("Invalid task ID")) {
|
|
@@ -17057,7 +17180,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
17057
17180
|
return taskIds.sort();
|
|
17058
17181
|
}
|
|
17059
17182
|
async function deleteEvidence(directory, taskId) {
|
|
17060
|
-
const sanitizedTaskId =
|
|
17183
|
+
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
17061
17184
|
const relativePath = path5.join("evidence", sanitizedTaskId);
|
|
17062
17185
|
const evidenceDir = validateSwarmPath(directory, relativePath);
|
|
17063
17186
|
try {
|
|
@@ -17119,12 +17242,13 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
|
|
|
17119
17242
|
}
|
|
17120
17243
|
return archived;
|
|
17121
17244
|
}
|
|
17122
|
-
var VALID_EVIDENCE_TYPES,
|
|
17245
|
+
var VALID_EVIDENCE_TYPES, sanitizeTaskId2, LEGACY_TASK_COMPLEXITY_MAP;
|
|
17123
17246
|
var init_manager2 = __esm(() => {
|
|
17124
17247
|
init_zod();
|
|
17125
17248
|
init_evidence_schema();
|
|
17126
17249
|
init_utils2();
|
|
17127
17250
|
init_utils();
|
|
17251
|
+
init_task_id();
|
|
17128
17252
|
VALID_EVIDENCE_TYPES = [
|
|
17129
17253
|
"review",
|
|
17130
17254
|
"test",
|
|
@@ -17140,10 +17264,7 @@ var init_manager2 = __esm(() => {
|
|
|
17140
17264
|
"quality_budget",
|
|
17141
17265
|
"secretscan"
|
|
17142
17266
|
];
|
|
17143
|
-
|
|
17144
|
-
RETRO_TASK_ID_REGEX = /^retro-\d+$/;
|
|
17145
|
-
INTERNAL_TOOL_ID_REGEX = /^(?:sast_scan|quality_budget|syntax_check|placeholder_scan|sbom_generate|build|secretscan)$/;
|
|
17146
|
-
GENERAL_TASK_ID_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
17267
|
+
sanitizeTaskId2 = sanitizeTaskId;
|
|
17147
17268
|
LEGACY_TASK_COMPLEXITY_MAP = {
|
|
17148
17269
|
low: "simple",
|
|
17149
17270
|
medium: "moderate",
|
|
@@ -40849,22 +40970,10 @@ __export(exports_gate_evidence, {
|
|
|
40849
40970
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync18, renameSync as renameSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
40850
40971
|
import * as path39 from "path";
|
|
40851
40972
|
function isValidTaskId2(taskId) {
|
|
40852
|
-
|
|
40853
|
-
return false;
|
|
40854
|
-
if (taskId.includes(".."))
|
|
40855
|
-
return false;
|
|
40856
|
-
if (taskId.includes("/"))
|
|
40857
|
-
return false;
|
|
40858
|
-
if (taskId.includes("\\"))
|
|
40859
|
-
return false;
|
|
40860
|
-
if (taskId.includes("\x00"))
|
|
40861
|
-
return false;
|
|
40862
|
-
return TASK_ID_PATTERN.test(taskId);
|
|
40973
|
+
return isStrictTaskId(taskId);
|
|
40863
40974
|
}
|
|
40864
40975
|
function assertValidTaskId(taskId) {
|
|
40865
|
-
|
|
40866
|
-
throw new Error(`Invalid taskId: "${taskId}". Must match N.M or N.M.P (e.g. "1.1", "1.2.3").`);
|
|
40867
|
-
}
|
|
40976
|
+
assertStrictTaskId(taskId);
|
|
40868
40977
|
}
|
|
40869
40978
|
function deriveRequiredGates(agentType) {
|
|
40870
40979
|
switch (agentType) {
|
|
@@ -40897,6 +41006,7 @@ function getEvidenceDir(directory) {
|
|
|
40897
41006
|
return path39.join(directory, ".swarm", "evidence");
|
|
40898
41007
|
}
|
|
40899
41008
|
function getEvidencePath(directory, taskId) {
|
|
41009
|
+
assertValidTaskId(taskId);
|
|
40900
41010
|
return path39.join(getEvidenceDir(directory), `${taskId}.json`);
|
|
40901
41011
|
}
|
|
40902
41012
|
function readExisting(evidencePath) {
|
|
@@ -40984,11 +41094,11 @@ async function hasPassedAllGates(directory, taskId) {
|
|
|
40984
41094
|
return false;
|
|
40985
41095
|
return evidence.required_gates.every((gate) => evidence.gates[gate] != null);
|
|
40986
41096
|
}
|
|
40987
|
-
var DEFAULT_REQUIRED_GATES
|
|
41097
|
+
var DEFAULT_REQUIRED_GATES;
|
|
40988
41098
|
var init_gate_evidence = __esm(() => {
|
|
40989
41099
|
init_telemetry();
|
|
41100
|
+
init_task_id();
|
|
40990
41101
|
DEFAULT_REQUIRED_GATES = ["reviewer", "test_engineer"];
|
|
40991
|
-
TASK_ID_PATTERN = /^\d+\.\d+(\.\d+)*$/;
|
|
40992
41102
|
});
|
|
40993
41103
|
|
|
40994
41104
|
// src/hooks/review-receipt.ts
|
|
@@ -47285,11 +47395,14 @@ async function executeWriteRetro(args2, directory) {
|
|
|
47285
47395
|
try {
|
|
47286
47396
|
const allTaskIds = await listEvidenceTaskIds(directory);
|
|
47287
47397
|
const phaseTaskIds = allTaskIds.filter((id) => id.startsWith(`${phase}.`));
|
|
47398
|
+
const sessionStart = args2.metadata && typeof args2.metadata.session_start === "string" ? args2.metadata.session_start : undefined;
|
|
47288
47399
|
for (const phaseTaskId of phaseTaskIds) {
|
|
47289
47400
|
const result = await loadEvidence(directory, phaseTaskId);
|
|
47290
47401
|
if (result.status !== "found")
|
|
47291
47402
|
continue;
|
|
47292
47403
|
const bundle = result.bundle;
|
|
47404
|
+
if (sessionStart && bundle.updated_at < sessionStart)
|
|
47405
|
+
continue;
|
|
47293
47406
|
for (const entry of bundle.entries) {
|
|
47294
47407
|
const e = entry;
|
|
47295
47408
|
if (e.type === "review" && e.verdict === "fail") {
|
|
@@ -47481,6 +47594,18 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47481
47594
|
}
|
|
47482
47595
|
}
|
|
47483
47596
|
}
|
|
47597
|
+
let sessionStart;
|
|
47598
|
+
{
|
|
47599
|
+
let earliest = Infinity;
|
|
47600
|
+
for (const [, session] of swarmState.agentSessions) {
|
|
47601
|
+
if (session.lastAgentEventTime > 0 && session.lastAgentEventTime < earliest) {
|
|
47602
|
+
earliest = session.lastAgentEventTime;
|
|
47603
|
+
}
|
|
47604
|
+
}
|
|
47605
|
+
if (earliest < Infinity) {
|
|
47606
|
+
sessionStart = new Date(earliest).toISOString();
|
|
47607
|
+
}
|
|
47608
|
+
}
|
|
47484
47609
|
const wrotePhaseRetro = closedPhases.length > 0;
|
|
47485
47610
|
if (!wrotePhaseRetro && !planExists) {
|
|
47486
47611
|
try {
|
|
@@ -47496,7 +47621,10 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47496
47621
|
test_failures: 0,
|
|
47497
47622
|
security_findings: 0,
|
|
47498
47623
|
integration_issues: 0,
|
|
47499
|
-
metadata: {
|
|
47624
|
+
metadata: {
|
|
47625
|
+
session_scope: "plan_free",
|
|
47626
|
+
...sessionStart ? { session_start: sessionStart } : {}
|
|
47627
|
+
}
|
|
47500
47628
|
}, directory);
|
|
47501
47629
|
try {
|
|
47502
47630
|
const parsed = JSON.parse(sessionRetroResult);
|
|
@@ -47553,7 +47681,8 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47553
47681
|
}
|
|
47554
47682
|
}
|
|
47555
47683
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
47556
|
-
const
|
|
47684
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
47685
|
+
const archiveDir = path14.join(swarmDir, "archive", `swarm-${timestamp}-${suffix}`);
|
|
47557
47686
|
let archiveResult = "";
|
|
47558
47687
|
let archivedFileCount = 0;
|
|
47559
47688
|
const archivedActiveStateFiles = new Set;
|
|
@@ -47817,11 +47946,23 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47817
47946
|
if (pruneErrors.length > 0) {
|
|
47818
47947
|
warnings.push(`Could not prune ${pruneErrors.length} branch(es) (unmerged or checked out): ${pruneErrors.join(", ")}`);
|
|
47819
47948
|
}
|
|
47820
|
-
const
|
|
47949
|
+
const retroWarnings = warnings.filter((w) => w.includes("Retrospective write") || w.includes("retrospective write") || w.includes("Session retrospective"));
|
|
47950
|
+
const otherWarnings = warnings.filter((w) => !w.includes("Retrospective write") && !w.includes("retrospective write") && !w.includes("Session retrospective"));
|
|
47951
|
+
let warningMsg = "";
|
|
47952
|
+
if (retroWarnings.length > 0) {
|
|
47953
|
+
warningMsg += `
|
|
47954
|
+
|
|
47955
|
+
**\u26A0 Retrospective evidence incomplete:**
|
|
47956
|
+
${retroWarnings.map((w) => `- ${w}`).join(`
|
|
47957
|
+
`)}`;
|
|
47958
|
+
}
|
|
47959
|
+
if (otherWarnings.length > 0) {
|
|
47960
|
+
warningMsg += `
|
|
47821
47961
|
|
|
47822
47962
|
**Warnings:**
|
|
47823
|
-
${
|
|
47824
|
-
`)}
|
|
47963
|
+
${otherWarnings.map((w) => `- ${w}`).join(`
|
|
47964
|
+
`)}`;
|
|
47965
|
+
}
|
|
47825
47966
|
if (planAlreadyDone) {
|
|
47826
47967
|
return `\u2705 Session finalized. Plan was already in a terminal state \u2014 cleanup and archive applied.
|
|
47827
47968
|
|
|
@@ -49071,7 +49212,7 @@ init_manager2();
|
|
|
49071
49212
|
init_utils2();
|
|
49072
49213
|
init_manager();
|
|
49073
49214
|
import * as child_process4 from "child_process";
|
|
49074
|
-
import { existsSync as existsSync8, readdirSync as
|
|
49215
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
49075
49216
|
import path19 from "path";
|
|
49076
49217
|
import { fileURLToPath } from "url";
|
|
49077
49218
|
function validateTaskDag(plan) {
|
|
@@ -49274,7 +49415,7 @@ async function checkPlanSync(directory, plan) {
|
|
|
49274
49415
|
}
|
|
49275
49416
|
async function checkConfigBackups(directory) {
|
|
49276
49417
|
try {
|
|
49277
|
-
const files =
|
|
49418
|
+
const files = readdirSync3(directory);
|
|
49278
49419
|
const backupCount = files.filter((f) => /\.opencode-swarm\.yaml\.bak/.test(f)).length;
|
|
49279
49420
|
if (backupCount <= 5) {
|
|
49280
49421
|
return {
|
|
@@ -49740,7 +49881,7 @@ async function getDiagnoseData(directory) {
|
|
|
49740
49881
|
checks5.push(await checkCurator(directory));
|
|
49741
49882
|
try {
|
|
49742
49883
|
const evidenceDir = path19.join(directory, ".swarm", "evidence");
|
|
49743
|
-
const snapshotFiles = existsSync8(evidenceDir) ?
|
|
49884
|
+
const snapshotFiles = existsSync8(evidenceDir) ? readdirSync3(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
|
|
49744
49885
|
if (snapshotFiles.length > 0) {
|
|
49745
49886
|
const latest = snapshotFiles.sort().pop();
|
|
49746
49887
|
checks5.push({
|
|
@@ -51972,7 +52113,7 @@ async function handleResetSessionCommand(directory, _args) {
|
|
|
51972
52113
|
// src/summaries/manager.ts
|
|
51973
52114
|
init_utils2();
|
|
51974
52115
|
init_utils();
|
|
51975
|
-
import { mkdirSync as mkdirSync9, readdirSync as
|
|
52116
|
+
import { mkdirSync as mkdirSync9, readdirSync as readdirSync9, renameSync as renameSync8, rmSync as rmSync3, statSync as statSync7 } from "fs";
|
|
51976
52117
|
import * as path31 from "path";
|
|
51977
52118
|
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
51978
52119
|
function sanitizeSummaryId(id) {
|
|
@@ -54910,6 +55051,20 @@ CRITICAL INSTRUCTIONS:
|
|
|
54910
55051
|
- If any task is MISSING, return NEEDS_REVISION.
|
|
54911
55052
|
- Do NOT rely on the Architect's implementation notes \u2014 verify independently.
|
|
54912
55053
|
|
|
55054
|
+
## BASELINE COMPARISON (mandatory before per-task review)
|
|
55055
|
+
|
|
55056
|
+
Before reviewing individual tasks, check whether the plan itself was silently mutated since it was last approved.
|
|
55057
|
+
|
|
55058
|
+
1. Call the \`get_approved_plan\` tool (no arguments required \u2014 it derives identity internally).
|
|
55059
|
+
2. Examine the response:
|
|
55060
|
+
- If \`success: false\` with \`reason: "no_approved_snapshot"\`: this is likely the first phase or no prior approval exists. Note this and proceed to per-task review.
|
|
55061
|
+
- If \`drift_detected: false\`: baseline integrity confirmed \u2014 the plan has not been mutated since the last critic approval. Proceed to per-task review.
|
|
55062
|
+
- If \`drift_detected: true\`: the plan was mutated after critic approval. Compare \`approved_plan\` vs \`current_plan\` to identify what changed (phases added/removed, tasks modified, scope changes). Report findings in a \`## BASELINE DRIFT\` section before the per-task rubric.
|
|
55063
|
+
- If \`drift_detected: "unknown"\`: current plan.json is unavailable. Flag this as a warning and proceed.
|
|
55064
|
+
3. If baseline drift is detected, this is a CRITICAL finding \u2014 plan mutations after approval bypass the quality gate.
|
|
55065
|
+
|
|
55066
|
+
Use \`summary_only: true\` if the plan is large and you only need structural comparison (phase/task counts).
|
|
55067
|
+
|
|
54913
55068
|
## PER-TASK 4-AXIS RUBRIC
|
|
54914
55069
|
Score each task independently:
|
|
54915
55070
|
|
|
@@ -54951,6 +55106,11 @@ TASK [id]: [VERIFIED|MISSING|DRIFTED]
|
|
|
54951
55106
|
- List of missing MUST requirements (if any)
|
|
54952
55107
|
- List of missing SHOULD requirements (if any)
|
|
54953
55108
|
|
|
55109
|
+
## BASELINE DRIFT (include only if get_approved_plan detected drift)
|
|
55110
|
+
Approved snapshot: seq=[N], timestamp=[ISO], phase=[N]
|
|
55111
|
+
Mutations detected: [list specific changes between approved plan and current plan \u2014 phases added/removed, tasks modified, scope changes]
|
|
55112
|
+
Severity: CRITICAL \u2014 plan was modified after critic approval without re-review
|
|
55113
|
+
|
|
54954
55114
|
## DRIFT REPORT
|
|
54955
55115
|
Unplanned additions: [list any code found that wasn't in the plan]
|
|
54956
55116
|
Dropped tasks: [list any tasks from the plan that were not implemented]
|
|
@@ -64675,24 +64835,14 @@ var build_check = createSwarmTool({
|
|
|
64675
64835
|
// src/tools/check-gate-status.ts
|
|
64676
64836
|
init_dist();
|
|
64677
64837
|
init_manager2();
|
|
64838
|
+
init_task_id();
|
|
64678
64839
|
init_create_tool();
|
|
64679
64840
|
init_resolve_working_directory();
|
|
64680
64841
|
import * as fs42 from "fs";
|
|
64681
64842
|
import * as path54 from "path";
|
|
64682
64843
|
var EVIDENCE_DIR = ".swarm/evidence";
|
|
64683
|
-
var TASK_ID_PATTERN2 = /^\d+\.\d+(\.\d+)*$/;
|
|
64684
64844
|
function isValidTaskId3(taskId) {
|
|
64685
|
-
|
|
64686
|
-
return false;
|
|
64687
|
-
if (taskId.includes(".."))
|
|
64688
|
-
return false;
|
|
64689
|
-
if (taskId.includes("/"))
|
|
64690
|
-
return false;
|
|
64691
|
-
if (taskId.includes("\\"))
|
|
64692
|
-
return false;
|
|
64693
|
-
if (taskId.includes("\x00"))
|
|
64694
|
-
return false;
|
|
64695
|
-
return TASK_ID_PATTERN2.test(taskId);
|
|
64845
|
+
return isStrictTaskId(taskId);
|
|
64696
64846
|
}
|
|
64697
64847
|
function isPathWithinSwarm(filePath, workspaceRoot) {
|
|
64698
64848
|
const normalizedWorkspace = path54.resolve(workspaceRoot);
|
|
@@ -66160,15 +66310,12 @@ var curator_analyze = createSwarmTool({
|
|
|
66160
66310
|
// src/tools/declare-scope.ts
|
|
66161
66311
|
init_tool();
|
|
66162
66312
|
init_state();
|
|
66313
|
+
init_task_id();
|
|
66163
66314
|
init_create_tool();
|
|
66164
66315
|
import * as fs46 from "fs";
|
|
66165
66316
|
import * as path58 from "path";
|
|
66166
|
-
function
|
|
66167
|
-
|
|
66168
|
-
if (!taskIdPattern.test(taskId)) {
|
|
66169
|
-
return `Invalid taskId "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
|
|
66170
|
-
}
|
|
66171
|
-
return;
|
|
66317
|
+
function validateTaskIdFormat2(taskId) {
|
|
66318
|
+
return validateTaskIdFormat(taskId);
|
|
66172
66319
|
}
|
|
66173
66320
|
function validateFiles(files) {
|
|
66174
66321
|
const errors5 = [];
|
|
@@ -66186,7 +66333,7 @@ function validateFiles(files) {
|
|
|
66186
66333
|
return errors5;
|
|
66187
66334
|
}
|
|
66188
66335
|
async function executeDeclareScope(args2, fallbackDir) {
|
|
66189
|
-
const taskIdError =
|
|
66336
|
+
const taskIdError = validateTaskIdFormat2(args2.taskId);
|
|
66190
66337
|
if (taskIdError) {
|
|
66191
66338
|
return {
|
|
66192
66339
|
success: false,
|
|
@@ -67380,6 +67527,95 @@ Errors:
|
|
|
67380
67527
|
return result;
|
|
67381
67528
|
}
|
|
67382
67529
|
});
|
|
67530
|
+
// src/tools/get-approved-plan.ts
|
|
67531
|
+
init_dist();
|
|
67532
|
+
init_ledger();
|
|
67533
|
+
init_manager();
|
|
67534
|
+
init_create_tool();
|
|
67535
|
+
function summarizePlan(plan) {
|
|
67536
|
+
return {
|
|
67537
|
+
title: plan.title,
|
|
67538
|
+
swarm: plan.swarm,
|
|
67539
|
+
current_phase: plan.current_phase ?? 0,
|
|
67540
|
+
phase_count: plan.phases.length,
|
|
67541
|
+
phases: plan.phases.map((p) => ({
|
|
67542
|
+
id: p.id,
|
|
67543
|
+
name: p.name,
|
|
67544
|
+
status: p.status,
|
|
67545
|
+
task_count: p.tasks.length
|
|
67546
|
+
}))
|
|
67547
|
+
};
|
|
67548
|
+
}
|
|
67549
|
+
function derivePlanId(plan) {
|
|
67550
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
67551
|
+
}
|
|
67552
|
+
async function executeGetApprovedPlan(args2, directory) {
|
|
67553
|
+
const currentPlan = await loadPlanJsonOnly(directory);
|
|
67554
|
+
if (!currentPlan) {
|
|
67555
|
+
const anySnapshot = await loadLastApprovedPlan(directory);
|
|
67556
|
+
if (anySnapshot) {
|
|
67557
|
+
return {
|
|
67558
|
+
success: true,
|
|
67559
|
+
approved_plan: undefined,
|
|
67560
|
+
current_plan: null,
|
|
67561
|
+
drift_detected: "unknown",
|
|
67562
|
+
current_plan_error: "plan.json not found or invalid"
|
|
67563
|
+
};
|
|
67564
|
+
}
|
|
67565
|
+
return {
|
|
67566
|
+
success: false,
|
|
67567
|
+
reason: "no_approved_snapshot"
|
|
67568
|
+
};
|
|
67569
|
+
}
|
|
67570
|
+
const expectedPlanId = derivePlanId(currentPlan);
|
|
67571
|
+
const approved = await loadLastApprovedPlan(directory, expectedPlanId);
|
|
67572
|
+
if (!approved) {
|
|
67573
|
+
const unscopedSnapshot = await loadLastApprovedPlan(directory);
|
|
67574
|
+
if (unscopedSnapshot) {
|
|
67575
|
+
return {
|
|
67576
|
+
success: true,
|
|
67577
|
+
approved_plan: undefined,
|
|
67578
|
+
current_plan: null,
|
|
67579
|
+
drift_detected: true,
|
|
67580
|
+
current_plan_error: "Plan identity (swarm/title) was mutated after approval \u2014 " + `expected plan_id '${expectedPlanId}' but approved snapshot has a different identity. ` + "This is a form of plan tampering."
|
|
67581
|
+
};
|
|
67582
|
+
}
|
|
67583
|
+
return {
|
|
67584
|
+
success: false,
|
|
67585
|
+
reason: "no_approved_snapshot"
|
|
67586
|
+
};
|
|
67587
|
+
}
|
|
67588
|
+
const summaryOnly = args2.summary_only === true;
|
|
67589
|
+
const approvedPayload = {
|
|
67590
|
+
plan: summaryOnly ? summarizePlan(approved.plan) : approved.plan,
|
|
67591
|
+
approval_metadata: approved.approval,
|
|
67592
|
+
snapshot_seq: approved.seq,
|
|
67593
|
+
snapshot_timestamp: approved.timestamp,
|
|
67594
|
+
payload_hash: approved.payloadHash
|
|
67595
|
+
};
|
|
67596
|
+
const currentHash = computePlanHash(currentPlan);
|
|
67597
|
+
const driftDetected = currentHash !== approved.payloadHash;
|
|
67598
|
+
const currentPayload = {
|
|
67599
|
+
plan: summaryOnly ? summarizePlan(currentPlan) : currentPlan,
|
|
67600
|
+
current_hash: currentHash
|
|
67601
|
+
};
|
|
67602
|
+
return {
|
|
67603
|
+
success: true,
|
|
67604
|
+
approved_plan: approvedPayload,
|
|
67605
|
+
current_plan: currentPayload,
|
|
67606
|
+
drift_detected: driftDetected
|
|
67607
|
+
};
|
|
67608
|
+
}
|
|
67609
|
+
var get_approved_plan = createSwarmTool({
|
|
67610
|
+
description: "Retrieve the last critic-approved immutable plan snapshot for baseline drift comparison. " + "Returns the approved plan, its approval metadata, and optionally compares against " + "the current plan.json to detect silent mutations. Read-only.",
|
|
67611
|
+
args: {
|
|
67612
|
+
summary_only: tool.schema.boolean().optional().describe("When true, returns only structural metadata (title, phases, task counts) " + "instead of full plan objects. Reduces output size for large plans.")
|
|
67613
|
+
},
|
|
67614
|
+
execute: async (args2, directory) => {
|
|
67615
|
+
const typedArgs = args2;
|
|
67616
|
+
return JSON.stringify(await executeGetApprovedPlan(typedArgs, directory), null, 2);
|
|
67617
|
+
}
|
|
67618
|
+
});
|
|
67383
67619
|
// src/tools/gitingest.ts
|
|
67384
67620
|
init_dist();
|
|
67385
67621
|
init_create_tool();
|
|
@@ -75720,7 +75956,7 @@ init_detector();
|
|
|
75720
75956
|
import * as fs62 from "fs";
|
|
75721
75957
|
import * as path75 from "path";
|
|
75722
75958
|
init_create_tool();
|
|
75723
|
-
var MAX_FILE_SIZE2 =
|
|
75959
|
+
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
75724
75960
|
var BINARY_CHECK_BYTES = 8192;
|
|
75725
75961
|
var BINARY_NULL_THRESHOLD3 = 0.1;
|
|
75726
75962
|
function isBinaryContent(content) {
|
|
@@ -75746,12 +75982,25 @@ function extractSyntaxErrors(parser, content) {
|
|
|
75746
75982
|
column: node.startPosition.column,
|
|
75747
75983
|
message: "Syntax error"
|
|
75748
75984
|
});
|
|
75985
|
+
} else if (node.isMissing) {
|
|
75986
|
+
errors5.push({
|
|
75987
|
+
line: node.startPosition.row + 1,
|
|
75988
|
+
column: node.startPosition.column,
|
|
75989
|
+
message: `Missing '${node.type}'`
|
|
75990
|
+
});
|
|
75749
75991
|
}
|
|
75750
75992
|
for (const child of node.children) {
|
|
75751
75993
|
walkNode(child);
|
|
75752
75994
|
}
|
|
75753
75995
|
}
|
|
75754
75996
|
walkNode(tree.rootNode);
|
|
75997
|
+
if (errors5.length === 0 && tree.rootNode.hasError) {
|
|
75998
|
+
errors5.push({
|
|
75999
|
+
line: 1,
|
|
76000
|
+
column: 0,
|
|
76001
|
+
message: "Syntax error detected (tree has errors)"
|
|
76002
|
+
});
|
|
76003
|
+
}
|
|
75755
76004
|
tree.delete();
|
|
75756
76005
|
return errors5;
|
|
75757
76006
|
}
|
|
@@ -75821,7 +76070,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
75821
76070
|
results.push(result);
|
|
75822
76071
|
continue;
|
|
75823
76072
|
}
|
|
75824
|
-
if (content.length
|
|
76073
|
+
if (content.length >= MAX_FILE_SIZE2) {
|
|
75825
76074
|
result.skipped_reason = "file_too_large";
|
|
75826
76075
|
skippedCount++;
|
|
75827
76076
|
results.push(result);
|
|
@@ -76243,6 +76492,7 @@ async function validateDiffScope(taskId, directory) {
|
|
|
76243
76492
|
init_manager();
|
|
76244
76493
|
init_state();
|
|
76245
76494
|
init_telemetry();
|
|
76495
|
+
init_task_id();
|
|
76246
76496
|
init_create_tool();
|
|
76247
76497
|
var VALID_STATUSES2 = [
|
|
76248
76498
|
"pending",
|
|
@@ -76257,8 +76507,8 @@ function validateStatus(status) {
|
|
|
76257
76507
|
return;
|
|
76258
76508
|
}
|
|
76259
76509
|
function validateTaskId(taskId) {
|
|
76260
|
-
const
|
|
76261
|
-
if (
|
|
76510
|
+
const result = validateTaskIdFormat(taskId);
|
|
76511
|
+
if (result) {
|
|
76262
76512
|
return `Invalid task_id "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
|
|
76263
76513
|
}
|
|
76264
76514
|
return;
|
|
@@ -77126,6 +77376,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
77126
77376
|
doc_scan,
|
|
77127
77377
|
evidence_check,
|
|
77128
77378
|
extract_code_blocks,
|
|
77379
|
+
get_approved_plan,
|
|
77129
77380
|
gitingest,
|
|
77130
77381
|
imports,
|
|
77131
77382
|
knowledge_query,
|