opencode-swarm 6.58.0 → 6.59.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 +2 -2
- package/dist/agents/explorer-consumer-contract.test.d.ts +1 -0
- package/dist/agents/explorer-role-boundary.test.d.ts +1 -0
- package/dist/agents/explorer.d.ts +3 -3
- package/dist/cli/index.js +202 -8
- package/dist/hooks/curator.d.ts +21 -2
- package/dist/index.js +429 -72
- package/dist/plan/ledger.d.ts +91 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -389,7 +389,8 @@ var init_constants = __esm(() => {
|
|
|
389
389
|
retrieve_summary: "retrieve the full content of a stored tool output summary",
|
|
390
390
|
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.",
|
|
391
391
|
batch_symbols: "Batched symbol extraction across multiple files. Returns per-file symbol summaries with isolated error handling.",
|
|
392
|
-
suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch."
|
|
392
|
+
suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch.",
|
|
393
|
+
lint_spec: "validate .swarm/spec.md format and required fields"
|
|
393
394
|
};
|
|
394
395
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
395
396
|
const invalidTools = tools.filter((tool) => !TOOL_NAME_SET.has(tool));
|
|
@@ -15848,19 +15849,50 @@ async function appendLedgerEvent(directory, eventInput, options) {
|
|
|
15848
15849
|
fs3.renameSync(tempPath, ledgerPath);
|
|
15849
15850
|
return event;
|
|
15850
15851
|
}
|
|
15852
|
+
async function appendLedgerEventWithRetry(directory, eventInput, options) {
|
|
15853
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
15854
|
+
const backoffBase = options.backoffMs ?? 10;
|
|
15855
|
+
let currentExpected = options.expectedHash;
|
|
15856
|
+
let attempt = 0;
|
|
15857
|
+
while (true) {
|
|
15858
|
+
try {
|
|
15859
|
+
return await appendLedgerEvent(directory, eventInput, {
|
|
15860
|
+
expectedHash: currentExpected,
|
|
15861
|
+
planHashAfter: options.planHashAfter
|
|
15862
|
+
});
|
|
15863
|
+
} catch (error49) {
|
|
15864
|
+
if (!(error49 instanceof LedgerStaleWriterError) || attempt >= maxRetries) {
|
|
15865
|
+
throw error49;
|
|
15866
|
+
}
|
|
15867
|
+
attempt++;
|
|
15868
|
+
const delayMs = backoffBase * 2 ** (attempt - 1);
|
|
15869
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
15870
|
+
if (options.verifyValid) {
|
|
15871
|
+
const stillValid = await options.verifyValid();
|
|
15872
|
+
if (!stillValid) {
|
|
15873
|
+
return null;
|
|
15874
|
+
}
|
|
15875
|
+
}
|
|
15876
|
+
currentExpected = computeCurrentPlanHash(directory);
|
|
15877
|
+
}
|
|
15878
|
+
}
|
|
15879
|
+
}
|
|
15851
15880
|
async function takeSnapshotEvent(directory, plan, options) {
|
|
15852
15881
|
const payloadHash = computePlanHash(plan);
|
|
15853
15882
|
const snapshotPayload = {
|
|
15854
15883
|
plan,
|
|
15855
15884
|
payload_hash: payloadHash
|
|
15856
15885
|
};
|
|
15886
|
+
if (options?.approvalMetadata) {
|
|
15887
|
+
snapshotPayload.approval = options.approvalMetadata;
|
|
15888
|
+
}
|
|
15857
15889
|
const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
15858
15890
|
return appendLedgerEvent(directory, {
|
|
15859
15891
|
event_type: "snapshot",
|
|
15860
|
-
source: "takeSnapshotEvent",
|
|
15892
|
+
source: options?.source ?? "takeSnapshotEvent",
|
|
15861
15893
|
plan_id: planId,
|
|
15862
15894
|
payload: snapshotPayload
|
|
15863
|
-
}, options);
|
|
15895
|
+
}, { planHashAfter: options?.planHashAfter });
|
|
15864
15896
|
}
|
|
15865
15897
|
async function replayFromLedger(directory, options) {
|
|
15866
15898
|
const events = await readLedgerEvents(directory);
|
|
@@ -15949,6 +15981,40 @@ function applyEventToPlan(plan, event) {
|
|
|
15949
15981
|
throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
|
|
15950
15982
|
}
|
|
15951
15983
|
}
|
|
15984
|
+
async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
15985
|
+
const events = await readLedgerEvents(directory);
|
|
15986
|
+
if (events.length === 0) {
|
|
15987
|
+
return null;
|
|
15988
|
+
}
|
|
15989
|
+
for (let i2 = events.length - 1;i2 >= 0; i2--) {
|
|
15990
|
+
const event = events[i2];
|
|
15991
|
+
if (event.event_type !== "snapshot")
|
|
15992
|
+
continue;
|
|
15993
|
+
if (event.source !== "critic_approved")
|
|
15994
|
+
continue;
|
|
15995
|
+
if (expectedPlanId !== undefined && event.plan_id !== expectedPlanId) {
|
|
15996
|
+
continue;
|
|
15997
|
+
}
|
|
15998
|
+
const payload = event.payload;
|
|
15999
|
+
if (!payload || typeof payload !== "object" || !payload.plan) {
|
|
16000
|
+
continue;
|
|
16001
|
+
}
|
|
16002
|
+
if (expectedPlanId !== undefined) {
|
|
16003
|
+
const payloadPlanId = `${payload.plan.swarm}-${payload.plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16004
|
+
if (payloadPlanId !== expectedPlanId) {
|
|
16005
|
+
continue;
|
|
16006
|
+
}
|
|
16007
|
+
}
|
|
16008
|
+
return {
|
|
16009
|
+
plan: payload.plan,
|
|
16010
|
+
seq: event.seq,
|
|
16011
|
+
timestamp: event.timestamp,
|
|
16012
|
+
approval: payload.approval,
|
|
16013
|
+
payloadHash: payload.payload_hash
|
|
16014
|
+
};
|
|
16015
|
+
}
|
|
16016
|
+
return null;
|
|
16017
|
+
}
|
|
15952
16018
|
var LEDGER_SCHEMA_VERSION = "1.0.0", LEDGER_FILENAME = "plan-ledger.jsonl", PLAN_JSON_FILENAME = "plan.json", LedgerStaleWriterError;
|
|
15953
16019
|
var init_ledger = __esm(() => {
|
|
15954
16020
|
init_plan_schema();
|
|
@@ -16111,6 +16177,23 @@ async function loadPlan(directory) {
|
|
|
16111
16177
|
return rebuilt;
|
|
16112
16178
|
}
|
|
16113
16179
|
} catch (replayError) {
|
|
16180
|
+
try {
|
|
16181
|
+
const approved = await loadLastApprovedPlan(directory, currentPlanId);
|
|
16182
|
+
if (approved) {
|
|
16183
|
+
await rebuildPlan(directory, approved.plan);
|
|
16184
|
+
try {
|
|
16185
|
+
await takeSnapshotEvent(directory, approved.plan, {
|
|
16186
|
+
source: "recovery_from_approved_snapshot",
|
|
16187
|
+
approvalMetadata: approved.approval
|
|
16188
|
+
});
|
|
16189
|
+
} catch (healError) {
|
|
16190
|
+
warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
|
|
16191
|
+
}
|
|
16192
|
+
const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
|
|
16193
|
+
warn(`[loadPlan] Ledger replay failed (${replayError instanceof Error ? replayError.message : String(replayError)}) \u2014 recovered from critic-approved snapshot seq=${approved.seq} (approval phase=${approvedPhase ?? "unknown"}, timestamp=${approved.timestamp}). This may roll the plan back to an earlier phase \u2014 verify before continuing.`);
|
|
16194
|
+
return approved.plan;
|
|
16195
|
+
}
|
|
16196
|
+
} catch {}
|
|
16114
16197
|
warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
|
|
16115
16198
|
}
|
|
16116
16199
|
}
|
|
@@ -16201,6 +16284,31 @@ async function loadPlan(directory) {
|
|
|
16201
16284
|
await savePlan(directory, rebuilt);
|
|
16202
16285
|
return rebuilt;
|
|
16203
16286
|
}
|
|
16287
|
+
try {
|
|
16288
|
+
const anchorEvents = await readLedgerEvents(directory);
|
|
16289
|
+
if (anchorEvents.length === 0) {
|
|
16290
|
+
warn("[loadPlan] Ledger present but no events readable \u2014 refusing approved-snapshot recovery (cannot verify plan identity).");
|
|
16291
|
+
return null;
|
|
16292
|
+
}
|
|
16293
|
+
const expectedPlanId = anchorEvents[0].plan_id;
|
|
16294
|
+
const approved = await loadLastApprovedPlan(directory, expectedPlanId);
|
|
16295
|
+
if (approved) {
|
|
16296
|
+
const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
|
|
16297
|
+
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.`);
|
|
16298
|
+
await savePlan(directory, approved.plan);
|
|
16299
|
+
try {
|
|
16300
|
+
await takeSnapshotEvent(directory, approved.plan, {
|
|
16301
|
+
source: "recovery_from_approved_snapshot",
|
|
16302
|
+
approvalMetadata: approved.approval
|
|
16303
|
+
});
|
|
16304
|
+
} catch (healError) {
|
|
16305
|
+
warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
|
|
16306
|
+
}
|
|
16307
|
+
return approved.plan;
|
|
16308
|
+
}
|
|
16309
|
+
} catch (recoveryError) {
|
|
16310
|
+
warn(`[loadPlan] Approved-snapshot recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
16311
|
+
}
|
|
16204
16312
|
}
|
|
16205
16313
|
return null;
|
|
16206
16314
|
}
|
|
@@ -16334,16 +16442,31 @@ async function savePlan(directory, plan, options) {
|
|
|
16334
16442
|
to_status: task.status,
|
|
16335
16443
|
source: "savePlan"
|
|
16336
16444
|
};
|
|
16337
|
-
|
|
16445
|
+
const capturedFromStatus = oldTask.status;
|
|
16446
|
+
const capturedTaskId = task.id;
|
|
16447
|
+
await appendLedgerEventWithRetry(directory, eventInput, {
|
|
16338
16448
|
expectedHash: currentHash,
|
|
16339
|
-
planHashAfter: hashAfter
|
|
16449
|
+
planHashAfter: hashAfter,
|
|
16450
|
+
maxRetries: 3,
|
|
16451
|
+
verifyValid: async () => {
|
|
16452
|
+
const onDisk = await loadPlanJsonOnly(directory);
|
|
16453
|
+
if (!onDisk)
|
|
16454
|
+
return true;
|
|
16455
|
+
for (const p of onDisk.phases) {
|
|
16456
|
+
const t = p.tasks.find((x) => x.id === capturedTaskId);
|
|
16457
|
+
if (t) {
|
|
16458
|
+
return t.status === capturedFromStatus;
|
|
16459
|
+
}
|
|
16460
|
+
}
|
|
16461
|
+
return false;
|
|
16462
|
+
}
|
|
16340
16463
|
});
|
|
16341
16464
|
}
|
|
16342
16465
|
}
|
|
16343
16466
|
}
|
|
16344
16467
|
} catch (error49) {
|
|
16345
16468
|
if (error49 instanceof LedgerStaleWriterError) {
|
|
16346
|
-
throw new Error(`Concurrent plan modification detected: ${error49.message}. Please retry the operation.`);
|
|
16469
|
+
throw new Error(`Concurrent plan modification detected after retries: ${error49.message}. Please retry the operation.`);
|
|
16347
16470
|
}
|
|
16348
16471
|
throw error49;
|
|
16349
16472
|
}
|
|
@@ -47025,7 +47148,7 @@ async function executeWriteRetro(args2, directory) {
|
|
|
47025
47148
|
message: "Invalid task ID: path traversal detected"
|
|
47026
47149
|
}, null, 2);
|
|
47027
47150
|
}
|
|
47028
|
-
const VALID_TASK_ID = /^(retro
|
|
47151
|
+
const VALID_TASK_ID = /^(retro-[a-zA-Z0-9][a-zA-Z0-9_-]*|\d+\.\d+(\.\d+)*)$/;
|
|
47029
47152
|
if (!VALID_TASK_ID.test(tid)) {
|
|
47030
47153
|
return JSON.stringify({
|
|
47031
47154
|
success: false,
|
|
@@ -47173,6 +47296,7 @@ var write_retro = createSwarmTool({
|
|
|
47173
47296
|
var ARCHIVE_ARTIFACTS = [
|
|
47174
47297
|
"plan.json",
|
|
47175
47298
|
"plan.md",
|
|
47299
|
+
"plan-ledger.jsonl",
|
|
47176
47300
|
"context.md",
|
|
47177
47301
|
"events.jsonl",
|
|
47178
47302
|
"handoff.md",
|
|
@@ -47182,7 +47306,9 @@ var ARCHIVE_ARTIFACTS = [
|
|
|
47182
47306
|
"close-lessons.md"
|
|
47183
47307
|
];
|
|
47184
47308
|
var ACTIVE_STATE_TO_CLEAN = [
|
|
47309
|
+
"plan.json",
|
|
47185
47310
|
"plan.md",
|
|
47311
|
+
"plan-ledger.jsonl",
|
|
47186
47312
|
"events.jsonl",
|
|
47187
47313
|
"handoff.md",
|
|
47188
47314
|
"handoff-prompt.md",
|
|
@@ -47257,6 +47383,33 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47257
47383
|
}
|
|
47258
47384
|
}
|
|
47259
47385
|
}
|
|
47386
|
+
const wrotePhaseRetro = closedPhases.length > 0;
|
|
47387
|
+
if (!wrotePhaseRetro && !planExists) {
|
|
47388
|
+
try {
|
|
47389
|
+
const sessionRetroResult = await executeWriteRetro({
|
|
47390
|
+
phase: 1,
|
|
47391
|
+
task_id: "retro-session",
|
|
47392
|
+
summary: isForced ? "Plan-free session force-closed via /swarm close --force" : "Plan-free session closed via /swarm close",
|
|
47393
|
+
task_count: 1,
|
|
47394
|
+
task_complexity: "simple",
|
|
47395
|
+
total_tool_calls: 0,
|
|
47396
|
+
coder_revisions: 0,
|
|
47397
|
+
reviewer_rejections: 0,
|
|
47398
|
+
test_failures: 0,
|
|
47399
|
+
security_findings: 0,
|
|
47400
|
+
integration_issues: 0,
|
|
47401
|
+
metadata: { session_scope: "plan_free" }
|
|
47402
|
+
}, directory);
|
|
47403
|
+
try {
|
|
47404
|
+
const parsed = JSON.parse(sessionRetroResult);
|
|
47405
|
+
if (parsed.success !== true) {
|
|
47406
|
+
warnings.push(`Session retrospective write failed: ${parsed.message ?? "unknown"}`);
|
|
47407
|
+
}
|
|
47408
|
+
} catch {}
|
|
47409
|
+
} catch (retroError) {
|
|
47410
|
+
warnings.push(`Session retrospective write threw: ${retroError instanceof Error ? retroError.message : String(retroError)}`);
|
|
47411
|
+
}
|
|
47412
|
+
}
|
|
47260
47413
|
const lessonsFilePath = path14.join(swarmDir, "close-lessons.md");
|
|
47261
47414
|
let explicitLessons = [];
|
|
47262
47415
|
try {
|
|
@@ -47269,6 +47422,8 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47269
47422
|
await curateAndStoreSwarm(explicitLessons, projectName, { phase_number: 0 }, directory, config3);
|
|
47270
47423
|
curationSucceeded = true;
|
|
47271
47424
|
} catch (error93) {
|
|
47425
|
+
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
47426
|
+
warnings.push(`Lessons curation failed: ${msg}`);
|
|
47272
47427
|
console.warn("[close-command] curateAndStoreSwarm error:", error93);
|
|
47273
47428
|
}
|
|
47274
47429
|
if (curationSucceeded && explicitLessons.length > 0) {
|
|
@@ -47294,6 +47449,8 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47294
47449
|
try {
|
|
47295
47450
|
await fs9.writeFile(planPath, JSON.stringify(planData, null, 2), "utf-8");
|
|
47296
47451
|
} catch (error93) {
|
|
47452
|
+
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
47453
|
+
warnings.push(`Failed to persist terminal plan.json state: ${msg}`);
|
|
47297
47454
|
console.warn("[close-command] Failed to write plan.json:", error93);
|
|
47298
47455
|
}
|
|
47299
47456
|
}
|
|
@@ -47355,6 +47512,8 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47355
47512
|
try {
|
|
47356
47513
|
await archiveEvidence(directory, 30, 10);
|
|
47357
47514
|
} catch (error93) {
|
|
47515
|
+
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
47516
|
+
warnings.push(`Evidence retention archive failed: ${msg}`);
|
|
47358
47517
|
console.warn("[close-command] archiveEvidence error:", error93);
|
|
47359
47518
|
}
|
|
47360
47519
|
let configBackupsRemoved = 0;
|
|
@@ -47383,6 +47542,12 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47383
47542
|
configBackupsRemoved++;
|
|
47384
47543
|
} catch {}
|
|
47385
47544
|
}
|
|
47545
|
+
const ledgerSiblings = swarmFiles.filter((f) => (f.startsWith("plan-ledger.archived-") || f.startsWith("plan-ledger.backup-")) && f.endsWith(".jsonl"));
|
|
47546
|
+
for (const sibling of ledgerSiblings) {
|
|
47547
|
+
try {
|
|
47548
|
+
await fs9.unlink(path14.join(swarmDir, sibling));
|
|
47549
|
+
} catch {}
|
|
47550
|
+
}
|
|
47386
47551
|
} catch {}
|
|
47387
47552
|
const contextPath = path14.join(swarmDir, "context.md");
|
|
47388
47553
|
const contextContent = [
|
|
@@ -47399,6 +47564,8 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47399
47564
|
try {
|
|
47400
47565
|
await fs9.writeFile(contextPath, contextContent, "utf-8");
|
|
47401
47566
|
} catch (error93) {
|
|
47567
|
+
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
47568
|
+
warnings.push(`Failed to reset context.md: ${msg}`);
|
|
47402
47569
|
console.warn("[close-command] Failed to write context.md:", error93);
|
|
47403
47570
|
}
|
|
47404
47571
|
const pruneBranches = args2.includes("--prune-branches");
|
|
@@ -47528,16 +47695,27 @@ async function handleCloseCommand(directory, args2) {
|
|
|
47528
47695
|
try {
|
|
47529
47696
|
await fs9.writeFile(closeSummaryPath, summaryContent, "utf-8");
|
|
47530
47697
|
} catch (error93) {
|
|
47698
|
+
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
47699
|
+
warnings.push(`Failed to write close-summary.md: ${msg}`);
|
|
47531
47700
|
console.warn("[close-command] Failed to write close-summary.md:", error93);
|
|
47532
47701
|
}
|
|
47533
47702
|
try {
|
|
47534
47703
|
await flushPendingSnapshot(directory);
|
|
47535
47704
|
} catch (error93) {
|
|
47705
|
+
const msg = error93 instanceof Error ? error93.message : String(error93);
|
|
47706
|
+
warnings.push(`flushPendingSnapshot failed: ${msg}`);
|
|
47536
47707
|
console.warn("[close-command] flushPendingSnapshot error:", error93);
|
|
47537
47708
|
}
|
|
47538
47709
|
await writeCheckpoint(directory).catch(() => {});
|
|
47539
|
-
swarmState.
|
|
47540
|
-
swarmState.
|
|
47710
|
+
const preservedClient = swarmState.opencodeClient;
|
|
47711
|
+
const preservedFullAutoFlag = swarmState.fullAutoEnabledInConfig;
|
|
47712
|
+
const preservedCuratorInitNames = swarmState.curatorInitAgentNames;
|
|
47713
|
+
const preservedCuratorPhaseNames = swarmState.curatorPhaseAgentNames;
|
|
47714
|
+
resetSwarmState();
|
|
47715
|
+
swarmState.opencodeClient = preservedClient;
|
|
47716
|
+
swarmState.fullAutoEnabledInConfig = preservedFullAutoFlag;
|
|
47717
|
+
swarmState.curatorInitAgentNames = preservedCuratorInitNames;
|
|
47718
|
+
swarmState.curatorPhaseAgentNames = preservedCuratorPhaseNames;
|
|
47541
47719
|
if (pruneErrors.length > 0) {
|
|
47542
47720
|
warnings.push(`Could not prune ${pruneErrors.length} branch(es) (unmerged or checked out): ${pruneErrors.join(", ")}`);
|
|
47543
47721
|
}
|
|
@@ -47632,17 +47810,21 @@ When exploring a codebase area, systematically report all four dimensions:
|
|
|
47632
47810
|
- State management approach (global, module-level, passed through)
|
|
47633
47811
|
- Configuration pattern (env vars, config files, hardcoded)
|
|
47634
47812
|
|
|
47635
|
-
###
|
|
47636
|
-
-
|
|
47637
|
-
-
|
|
47638
|
-
-
|
|
47639
|
-
|
|
47813
|
+
### COMPLEXITY INDICATORS
|
|
47814
|
+
- High cyclomatic complexity, deep nesting, or complex control flow
|
|
47815
|
+
- Large files (>500 lines) with many exported symbols
|
|
47816
|
+
- Deep inheritance hierarchies or complex type hierarchies
|
|
47817
|
+
|
|
47818
|
+
### RUNTIME/BEHAVIORAL CONCERNS
|
|
47819
|
+
- Missing error handling paths or single-throw patterns
|
|
47640
47820
|
- Platform-specific assumptions (path separators, line endings, OS APIs)
|
|
47641
47821
|
|
|
47642
|
-
### RELEVANT
|
|
47643
|
-
-
|
|
47644
|
-
-
|
|
47645
|
-
-
|
|
47822
|
+
### RELEVANT CONSTRAINTS
|
|
47823
|
+
- Architectural patterns observed (layered architecture, event-driven, microservice, etc.)
|
|
47824
|
+
- Error handling coverage patterns observed in the codebase
|
|
47825
|
+
- Platform-specific assumptions observed in the codebase
|
|
47826
|
+
- Established conventions (naming patterns, error handling approaches, testing strategies)
|
|
47827
|
+
- Configuration management approaches (env vars, config files, feature flags)
|
|
47646
47828
|
|
|
47647
47829
|
OUTPUT FORMAT (MANDATORY \u2014 deviations will be rejected):
|
|
47648
47830
|
Begin directly with PROJECT. Do NOT prepend "Here's my analysis..." or any conversational preamble.
|
|
@@ -47653,16 +47835,41 @@ FRAMEWORK: [if any]
|
|
|
47653
47835
|
|
|
47654
47836
|
STRUCTURE:
|
|
47655
47837
|
[key directories, 5-10 lines max]
|
|
47838
|
+
Example:
|
|
47839
|
+
src/agents/ \u2014 agent factories and definitions
|
|
47840
|
+
src/tools/ \u2014 CLI tool implementations
|
|
47841
|
+
src/config/ \u2014 plan schema and constants
|
|
47656
47842
|
|
|
47657
47843
|
KEY FILES:
|
|
47658
47844
|
- [path]: [purpose]
|
|
47845
|
+
Example:
|
|
47846
|
+
src/agents/explorer.ts \u2014 explorer agent factory and all prompt definitions
|
|
47847
|
+
src/agents/architect.ts \u2014 architect orchestrator with all mode handlers
|
|
47659
47848
|
|
|
47660
47849
|
PATTERNS: [observations]
|
|
47850
|
+
Example: Factory pattern for agent creation; Result type for error handling; Module-level state via closure
|
|
47851
|
+
|
|
47852
|
+
COMPLEXITY INDICATORS:
|
|
47853
|
+
[structural complexity concerns: elevated cyclomatic complexity, deep nesting, large files, deep inheritance hierarchies, or similar \u2014 describe what is OBSERVED]
|
|
47854
|
+
Example: explorer.ts (289 lines, 12 exports); architect.ts (complex branching in mode handlers)
|
|
47855
|
+
|
|
47856
|
+
OBSERVED CHANGES:
|
|
47857
|
+
[if INPUT referenced specific files/changes: what changed in those targets; otherwise "none" or "general exploration"]
|
|
47858
|
+
|
|
47859
|
+
CONSUMERS_AFFECTED:
|
|
47860
|
+
[if integration impact mode: list files that import/use the changed symbols; otherwise "not applicable"]
|
|
47861
|
+
|
|
47862
|
+
RELEVANT CONSTRAINTS:
|
|
47863
|
+
[architectural patterns, error handling coverage patterns, platform-specific assumptions, established conventions observed in the codebase]
|
|
47864
|
+
Example: Layered architecture (agents \u2192 tools \u2192 filesystem); Bun-native path handling; Error-first callbacks in hooks
|
|
47661
47865
|
|
|
47662
47866
|
DOMAINS: [relevant SME domains: powershell, security, python, etc.]
|
|
47867
|
+
Example: typescript, nodejs, cli-tooling, powershell
|
|
47663
47868
|
|
|
47664
|
-
|
|
47665
|
-
- [path]: [
|
|
47869
|
+
FOLLOW-UP CANDIDATE AREAS:
|
|
47870
|
+
- [path]: [observable condition, relevant domain]
|
|
47871
|
+
Example:
|
|
47872
|
+
src/tools/declare-scope.ts \u2014 function has 12 parameters, consider splitting; tool-authoring
|
|
47666
47873
|
|
|
47667
47874
|
## INTEGRATION IMPACT ANALYSIS MODE
|
|
47668
47875
|
Activates when delegated with "Integration impact analysis" or INPUT lists contract changes.
|
|
@@ -47678,10 +47885,15 @@ OUTPUT FORMAT (MANDATORY \u2014 deviations will be rejected):
|
|
|
47678
47885
|
Begin directly with BREAKING_CHANGES. Do NOT prepend conversational preamble.
|
|
47679
47886
|
|
|
47680
47887
|
BREAKING_CHANGES: [list with affected consumer files, or "none"]
|
|
47888
|
+
Example: src/agents/explorer.ts \u2014 removed createExplorerAgent export (was used by 3 files)
|
|
47681
47889
|
COMPATIBLE_CHANGES: [list, or "none"]
|
|
47890
|
+
Example: src/config/constants.ts \u2014 added new optional field to Config interface
|
|
47682
47891
|
CONSUMERS_AFFECTED: [list of files that import/use changed exports, or "none"]
|
|
47683
|
-
|
|
47684
|
-
|
|
47892
|
+
Example: src/agents/coder.ts, src/agents/reviewer.ts, src/main.ts
|
|
47893
|
+
COMPATIBILITY SIGNALS: [COMPATIBLE | INCOMPATIBLE | UNCERTAIN \u2014 based on observable contract changes]
|
|
47894
|
+
Example: INCOMPATIBLE \u2014 removeExport changes function arity from 3 to 2
|
|
47895
|
+
MIGRATION_SURFACE: [yes \u2014 list of observable call signatures affected | no \u2014 no observable impact detected]
|
|
47896
|
+
Example: yes \u2014 createExplorerAgent(model, customPrompt?, customAppendPrompt?) \u2192 createExplorerAgent(model)
|
|
47685
47897
|
|
|
47686
47898
|
## DOCUMENTATION DISCOVERY MODE
|
|
47687
47899
|
Activates automatically during codebase reality check at plan ingestion.
|
|
@@ -47727,8 +47939,8 @@ PROJECT_CONTEXT: [context.md excerpt]
|
|
|
47727
47939
|
ACTIONS:
|
|
47728
47940
|
- Read the prior summary to understand session history
|
|
47729
47941
|
- Cross-reference knowledge entries against project context
|
|
47730
|
-
-
|
|
47731
|
-
-
|
|
47942
|
+
- Note contradictions (knowledge says X, project state shows Y)
|
|
47943
|
+
- Observe where lessons could be tighter or stale
|
|
47732
47944
|
- Produce a concise briefing for the architect
|
|
47733
47945
|
|
|
47734
47946
|
RULES:
|
|
@@ -47744,13 +47956,13 @@ BRIEFING:
|
|
|
47744
47956
|
CONTRADICTIONS:
|
|
47745
47957
|
- [entry_id]: [description] (or "None detected")
|
|
47746
47958
|
|
|
47747
|
-
|
|
47748
|
-
-
|
|
47749
|
-
-
|
|
47750
|
-
-
|
|
47751
|
-
-
|
|
47752
|
-
-
|
|
47753
|
-
Use the UUID from KNOWLEDGE_ENTRIES when
|
|
47959
|
+
OBSERVATIONS:
|
|
47960
|
+
- entry <uuid> appears high-confidence: [observable evidence] (suggests boost confidence, mark hive_eligible)
|
|
47961
|
+
- entry <uuid> appears stale: [observable evidence] (suggests archive \u2014 no longer injected)
|
|
47962
|
+
- entry <uuid> could be tighter: [what's verbose or duplicate] (suggests rewrite with tighter version, max 280 chars)
|
|
47963
|
+
- entry <uuid> contradicts project state: [observable conflict] (suggests tag as contradicted)
|
|
47964
|
+
- new candidate: [concise lesson text from observed patterns] (suggests new entry)
|
|
47965
|
+
Use the UUID from KNOWLEDGE_ENTRIES when observing about existing entries. Use "new candidate" only when observing a potential new entry.
|
|
47754
47966
|
|
|
47755
47967
|
KNOWLEDGE_STATS:
|
|
47756
47968
|
- Entries reviewed: [N]
|
|
@@ -47772,14 +47984,15 @@ KNOWLEDGE_ENTRIES: [JSON array of existing entries with UUIDs]
|
|
|
47772
47984
|
|
|
47773
47985
|
ACTIONS:
|
|
47774
47986
|
- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)
|
|
47775
|
-
-
|
|
47776
|
-
-
|
|
47987
|
+
- Observe workflow deviations: missing reviewer, missing retro, skipped test_engineer
|
|
47988
|
+
- Report knowledge update candidates with observable evidence: entries that appear promoted, archived, rewritten, or contradicted
|
|
47777
47989
|
- Summarize key decisions and blockers resolved
|
|
47778
47990
|
|
|
47779
47991
|
RULES:
|
|
47780
47992
|
- Output under 2000 chars
|
|
47781
47993
|
- No code modifications
|
|
47782
47994
|
- Compliance observations are READ-ONLY \u2014 report, do not enforce
|
|
47995
|
+
- OBSERVATIONS should not contain directives \u2014 report what is observed, do not instruct the architect what to do
|
|
47783
47996
|
- Extend the digest, never replace it
|
|
47784
47997
|
|
|
47785
47998
|
OUTPUT FORMAT:
|
|
@@ -47792,15 +48005,15 @@ key_decisions: [list]
|
|
|
47792
48005
|
blockers_resolved: [list]
|
|
47793
48006
|
|
|
47794
48007
|
COMPLIANCE:
|
|
47795
|
-
- [type]: [description] (or "No deviations observed")
|
|
48008
|
+
- [type] observed: [description] (or "No deviations observed")
|
|
47796
48009
|
|
|
47797
|
-
|
|
47798
|
-
-
|
|
47799
|
-
-
|
|
47800
|
-
-
|
|
47801
|
-
-
|
|
47802
|
-
-
|
|
47803
|
-
Use the UUID from KNOWLEDGE_ENTRIES when
|
|
48010
|
+
OBSERVATIONS:
|
|
48011
|
+
- entry <uuid> appears high-confidence: [observable evidence] (suggests boost confidence, mark hive_eligible)
|
|
48012
|
+
- entry <uuid> appears stale: [observable evidence] (suggests archive \u2014 no longer injected)
|
|
48013
|
+
- entry <uuid> could be tighter: [what's verbose or duplicate] (suggests rewrite with tighter version, max 280 chars)
|
|
48014
|
+
- entry <uuid> contradicts project state: [observable conflict] (suggests tag as contradicted)
|
|
48015
|
+
- new candidate: [concise lesson text from observed patterns] (suggests new entry)
|
|
48016
|
+
Use the UUID from KNOWLEDGE_ENTRIES when observing about existing entries. Use "new candidate" only when observing a potential new entry.
|
|
47804
48017
|
|
|
47805
48018
|
EXTENDED_DIGEST:
|
|
47806
48019
|
[the full running digest with this phase appended]
|
|
@@ -47816,7 +48029,7 @@ ${customAppendPrompt}`;
|
|
|
47816
48029
|
}
|
|
47817
48030
|
return {
|
|
47818
48031
|
name: "explorer",
|
|
47819
|
-
description: "Fast codebase discovery and analysis. Scans directory structure, identifies languages/frameworks, summarizes key files, and
|
|
48032
|
+
description: "Fast codebase discovery and analysis. Scans directory structure, identifies languages/frameworks, summarizes key files, and identifies areas where specialized domain knowledge may be beneficial.",
|
|
47820
48033
|
config: {
|
|
47821
48034
|
model,
|
|
47822
48035
|
temperature: 0.1,
|
|
@@ -47838,7 +48051,7 @@ init_utils2();
|
|
|
47838
48051
|
var DEFAULT_CURATOR_LLM_TIMEOUT_MS = 300000;
|
|
47839
48052
|
function parseKnowledgeRecommendations(llmOutput) {
|
|
47840
48053
|
const recommendations = [];
|
|
47841
|
-
const section = llmOutput.match(/
|
|
48054
|
+
const section = llmOutput.match(/OBSERVATIONS:\s*\n([\s\S]*?)(?:\n\n|\n[A-Z_]+:|$)/);
|
|
47842
48055
|
if (!section)
|
|
47843
48056
|
return recommendations;
|
|
47844
48057
|
const lines = section[1].split(`
|
|
@@ -47847,19 +48060,33 @@ function parseKnowledgeRecommendations(llmOutput) {
|
|
|
47847
48060
|
const trimmed = line.trim();
|
|
47848
48061
|
if (!trimmed.startsWith("-"))
|
|
47849
48062
|
continue;
|
|
47850
|
-
const match = trimmed.match(/^-\s+
|
|
47851
|
-
if (match)
|
|
47852
|
-
|
|
47853
|
-
|
|
47854
|
-
|
|
47855
|
-
|
|
47856
|
-
|
|
47857
|
-
|
|
47858
|
-
|
|
47859
|
-
|
|
47860
|
-
|
|
47861
|
-
|
|
47862
|
-
}
|
|
48063
|
+
const match = trimmed.match(/^-\s+entry\s+(\S+)\s+\(([^)]+)\):\s+(.+)$/i);
|
|
48064
|
+
if (!match)
|
|
48065
|
+
continue;
|
|
48066
|
+
const uuid8 = match[1];
|
|
48067
|
+
const parenthetical = match[2];
|
|
48068
|
+
const text = match[3].trim().replace(/\s+\([^)]+\)$/, "");
|
|
48069
|
+
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
48070
|
+
const entryId = uuid8 === "new" || !UUID_V4.test(uuid8) ? undefined : uuid8;
|
|
48071
|
+
let action = "rewrite";
|
|
48072
|
+
const lowerParenthetical = parenthetical.toLowerCase();
|
|
48073
|
+
if (lowerParenthetical.includes("suggests boost confidence") || lowerParenthetical.includes("mark hive_eligible") || lowerParenthetical.includes("appears high-confidence")) {
|
|
48074
|
+
action = "promote";
|
|
48075
|
+
} else if (lowerParenthetical.includes("suggests archive") || lowerParenthetical.includes("appears stale")) {
|
|
48076
|
+
action = "archive";
|
|
48077
|
+
} else if (lowerParenthetical.includes("contradicts project state")) {
|
|
48078
|
+
action = "flag_contradiction";
|
|
48079
|
+
} else if (lowerParenthetical.includes("suggests rewrite") || lowerParenthetical.includes("could be tighter")) {
|
|
48080
|
+
action = "rewrite";
|
|
48081
|
+
} else if (lowerParenthetical.includes("new candidate")) {
|
|
48082
|
+
action = "promote";
|
|
48083
|
+
}
|
|
48084
|
+
recommendations.push({
|
|
48085
|
+
action,
|
|
48086
|
+
entry_id: entryId,
|
|
48087
|
+
lesson: text,
|
|
48088
|
+
reason: text
|
|
48089
|
+
});
|
|
47863
48090
|
}
|
|
47864
48091
|
return recommendations;
|
|
47865
48092
|
}
|
|
@@ -52925,6 +53152,20 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
|
|
|
52925
53152
|
- Never pass raw files - summarize relevant parts
|
|
52926
53153
|
- Never assume subagents remember prior context
|
|
52927
53154
|
|
|
53155
|
+
## EXPLORER ROLE BOUNDARIES (Phase 2+)
|
|
53156
|
+
Explorer is strictly a FACTUAL MAPPER \u2014 it observes and reports. It does NOT make judgments, verdicts, routing decisions, or enforcement actions.
|
|
53157
|
+
|
|
53158
|
+
Explorer outputs (COMPLEXITY INDICATORS, FOLLOW-UP CANDIDATE AREAS, DOMAINS, etc.) are CANDIDATE EVIDENCE. As Architect, YOU decide what to use, how to route, and what to prioritize.
|
|
53159
|
+
|
|
53160
|
+
Explorer should NEVER be treated as:
|
|
53161
|
+
- A verdict authority (its signals are informational, not binding)
|
|
53162
|
+
- A routing oracle (SME nominations and domain hints are suggestions, not assignments)
|
|
53163
|
+
- A compliance enforcer (workflow observations are read-only reports)
|
|
53164
|
+
|
|
53165
|
+
The architect makes dispatch and routing decisions. Explorer provides facts.
|
|
53166
|
+
|
|
53167
|
+
SPEED PRESERVATION: This change improves explorer precision by narrowing its job to factual mapping \u2014 it does NOT reduce explorer usage. All existing explorer calls and workflows remain intact. The goal is better signal quality, not fewer calls.
|
|
53168
|
+
|
|
52928
53169
|
## RULES
|
|
52929
53170
|
|
|
52930
53171
|
NAMESPACE RULE: "Phase N" and "Task N.M" ALWAYS refer to the PROJECT PLAN in .swarm/plan.md.
|
|
@@ -53271,7 +53512,7 @@ OUTPUT: Test file + VERDICT: PASS/FAIL
|
|
|
53271
53512
|
{{AGENT_PREFIX}}explorer
|
|
53272
53513
|
TASK: Integration impact analysis
|
|
53273
53514
|
INPUT: Contract changes detected: [list from diff tool]
|
|
53274
|
-
OUTPUT: BREAKING_CHANGES + COMPATIBLE_CHANGES + CONSUMERS_AFFECTED +
|
|
53515
|
+
OUTPUT: BREAKING_CHANGES + COMPATIBLE_CHANGES + CONSUMERS_AFFECTED + COMPATIBILITY SIGNALS: [COMPATIBLE | INCOMPATIBLE | UNCERTAIN] + MIGRATION_SURFACE: [yes \u2014 list of affected call signatures | no]
|
|
53275
53516
|
CONSTRAINT: Read-only. use search to find imports/usages of changed exports.
|
|
53276
53517
|
|
|
53277
53518
|
{{AGENT_PREFIX}}docs
|
|
@@ -53661,7 +53902,7 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
|
|
|
53661
53902
|
5a-bis. **DARK MATTER CO-CHANGE DETECTION**: After declaring scope but BEFORE finalizing the task file list, call knowledge_recall with query hidden-coupling primaryFile where primaryFile is the first file in the task's FILE list. Extract primaryFile from the task's FILE list (first file = primary). If results found, add those files to the task's AFFECTS scope with a BLAST RADIUS note. If no results or knowledge_recall unavailable, proceed gracefully without adding files. This is advisory \u2014 the architect may exclude files from scope if they are unrelated to the current task. only after scope is declared.
|
|
53662
53903
|
|
|
53663
53904
|
5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
|
|
53664
|
-
5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. If
|
|
53905
|
+
5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. If COMPATIBILITY SIGNALS=INCOMPATIBLE or MIGRATION_SURFACE=yes \u2192 coder retry. If COMPATIBILITY SIGNALS=COMPATIBLE and MIGRATION_SURFACE=no \u2192 proceed.
|
|
53665
53906
|
\u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
|
|
53666
53907
|
5d. Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
|
|
53667
53908
|
\u2192 REQUIRED: Print "syntaxcheck: [PASS | FAIL \u2014 N errors]"
|
|
@@ -53757,6 +53998,14 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
|
|
|
53757
53998
|
If ANY box is unchecked: DO NOT COMMIT. Return to step 5b.
|
|
53758
53999
|
There is no override. A commit without a completed QA gate is a workflow violation.
|
|
53759
54000
|
|
|
54001
|
+
## ROLE-BOUNDARY CHANGE VALIDATION (mandatory for prompt changes)
|
|
54002
|
+
When a task modifies agent prompts (especially explorer, reviewer, critic, or any agent involved in the mapper/validator/challenge hierarchy), add an explicit test validation step:
|
|
54003
|
+
- If new prompt contract tests exist (e.g., explorer-role-boundary.test.ts, explorer-consumer-contract.test.ts): Run them via test_runner
|
|
54004
|
+
- If no specific tests exist for the changed prompt: Run test_runner with scope "convention" on the changed file
|
|
54005
|
+
- Verify the new tests pass before completing the task
|
|
54006
|
+
|
|
54007
|
+
This step supplements (not replaces) the existing regression-sweep and test-drift checks. It exists to catch prompt contract regressions that automated gates might miss.
|
|
54008
|
+
|
|
53760
54009
|
5o. \u26D4 TASK COMPLETION GATE \u2014 You MUST print this checklist with filled values before marking \u2713 in .swarm/plan.md:
|
|
53761
54010
|
[TOOL] diff: PASS / SKIP \u2014 value: ___
|
|
53762
54011
|
[TOOL] syntax_check: PASS \u2014 value: ___
|
|
@@ -54942,6 +55191,14 @@ DO NOT:
|
|
|
54942
55191
|
|
|
54943
55192
|
Your unique value is catching LOGIC ERRORS, EDGE CASES, and SECURITY FLAWS that automated tools cannot detect. If your review only catches things a linter would catch, you are not adding value.
|
|
54944
55193
|
|
|
55194
|
+
## EXPLORER FINDINGS \u2014 VALIDATE BEFORE REPORTING
|
|
55195
|
+
Explorer agent outputs (from @mega_explorer) may contain observations labeled as REVIEW NEEDED, RISKS, VERDICT, BREAKING, COMPATIBLE, or similar judgment language. Treat these as CANDIDATE OBSERVATIONS, not established facts.
|
|
55196
|
+
- BEFORE including any issue-like finding from explorer input in your final report: READ the relevant code yourself and verify the issue independently
|
|
55197
|
+
- Do NOT adopt the explorer's VERDICT, BREAKING, or COMPATIBLE labels as your own \u2014 you must reach your own conclusion
|
|
55198
|
+
- Explorer's RISKS section names potential concerns \u2014 you determine if they are actual issues through your own review
|
|
55199
|
+
- If explorer suggests "REVIEW NEEDED" for an area, treat it as a hint to look there, not as a confirmed problem
|
|
55200
|
+
- Your verdict must reflect YOUR verification, not the explorer's framing
|
|
55201
|
+
|
|
54945
55202
|
DO (explicitly):
|
|
54946
55203
|
- READ the changed files yourself \u2014 do not rely on the coder's self-report
|
|
54947
55204
|
- VERIFY imports exist: if the coder added a new import, use search to verify the export exists in the source
|
|
@@ -65609,15 +65866,13 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
65609
65866
|
errors: [`Task ${args2.taskId} does not exist in plan.json`]
|
|
65610
65867
|
};
|
|
65611
65868
|
}
|
|
65612
|
-
|
|
65613
|
-
|
|
65614
|
-
|
|
65615
|
-
|
|
65616
|
-
|
|
65617
|
-
|
|
65618
|
-
|
|
65619
|
-
};
|
|
65620
|
-
}
|
|
65869
|
+
const taskInPlan = allTasks.find((t) => t.id === args2.taskId);
|
|
65870
|
+
if (taskInPlan && taskInPlan.status === "completed") {
|
|
65871
|
+
return {
|
|
65872
|
+
success: false,
|
|
65873
|
+
message: `Task ${args2.taskId} is already completed`,
|
|
65874
|
+
errors: [`Cannot declare scope for completed task ${args2.taskId}`]
|
|
65875
|
+
};
|
|
65621
65876
|
}
|
|
65622
65877
|
const rawMergedFiles = [...args2.files, ...args2.whitelist ?? []];
|
|
65623
65878
|
const warnings = [];
|
|
@@ -69562,6 +69817,10 @@ var DEFAULT_STRING_PATTERNS = [
|
|
|
69562
69817
|
},
|
|
69563
69818
|
{ pattern: /`[^`]*\bstub\b[^`]*`/i, rule_id: "placeholder/text-placeholder" }
|
|
69564
69819
|
];
|
|
69820
|
+
var FILE_ALLOWLIST = [
|
|
69821
|
+
"src/tools/declare-scope.ts",
|
|
69822
|
+
"src/tools/placeholder-scan.ts"
|
|
69823
|
+
];
|
|
69565
69824
|
var DEFAULT_CODE_PATTERNS = [
|
|
69566
69825
|
{
|
|
69567
69826
|
pattern: /throw\s+new\s+Error\s*\(\s*["'][^"']*\bTODO\b[^"']*["']\s*\)/i,
|
|
@@ -69702,6 +69961,64 @@ function scanPlanFileForPlaceholders(content, filePath) {
|
|
|
69702
69961
|
}
|
|
69703
69962
|
return findings;
|
|
69704
69963
|
}
|
|
69964
|
+
function isValidationPattern(lines, currentLineIdx) {
|
|
69965
|
+
const currentLine = lines[currentLineIdx];
|
|
69966
|
+
if (!/return\s+undefined\s*;/.test(currentLine)) {
|
|
69967
|
+
return false;
|
|
69968
|
+
}
|
|
69969
|
+
const MAX_SEARCH_LINES = 50;
|
|
69970
|
+
let jsdocContent = "";
|
|
69971
|
+
let foundFunction = false;
|
|
69972
|
+
const functionKeywords = /^(?:export\s+)?(?:async\s+)?function\s+\w+|^(?:export\s+)?(?:async\s+)?(?:\w+\s+)?\w+\s*\([^)]*\)\s*(?::\s*\w+\s*)?(?:\{|$)/;
|
|
69973
|
+
for (let i2 = currentLineIdx - 1;i2 >= 0 && i2 >= currentLineIdx - MAX_SEARCH_LINES; i2--) {
|
|
69974
|
+
const line = lines[i2].trim();
|
|
69975
|
+
if (line.startsWith("*") || line.startsWith("*/")) {
|
|
69976
|
+
const jsdocLine = line.replace(/^\*?\s?/, "").replace(/^\*\//, "");
|
|
69977
|
+
jsdocContent = jsdocLine + `
|
|
69978
|
+
` + jsdocContent;
|
|
69979
|
+
} else if (line.includes("*/")) {
|
|
69980
|
+
break;
|
|
69981
|
+
} else if (functionKeywords.test(line) || line.startsWith("function ")) {
|
|
69982
|
+
foundFunction = true;
|
|
69983
|
+
break;
|
|
69984
|
+
} else if (line.length > 0 && !line.startsWith("//") && !line.startsWith("*")) {
|
|
69985
|
+
break;
|
|
69986
|
+
}
|
|
69987
|
+
}
|
|
69988
|
+
if (jsdocContent) {
|
|
69989
|
+
const returnsPattern = /@returns\s*(?:\{[^}]*\})?\s*(?:undefined|[A-Za-z_]\w*)/i;
|
|
69990
|
+
if (returnsPattern.test(jsdocContent)) {
|
|
69991
|
+
return true;
|
|
69992
|
+
}
|
|
69993
|
+
}
|
|
69994
|
+
let braceCount = 0;
|
|
69995
|
+
let inFunction = false;
|
|
69996
|
+
for (let i2 = currentLineIdx;i2 >= 0; i2--) {
|
|
69997
|
+
const line = lines[i2];
|
|
69998
|
+
for (const char of line) {
|
|
69999
|
+
if (char === "{") {
|
|
70000
|
+
braceCount++;
|
|
70001
|
+
inFunction = true;
|
|
70002
|
+
} else if (char === "}") {
|
|
70003
|
+
braceCount--;
|
|
70004
|
+
}
|
|
70005
|
+
}
|
|
70006
|
+
if (inFunction && braceCount === 0 && i2 < currentLineIdx) {
|
|
70007
|
+
break;
|
|
70008
|
+
}
|
|
70009
|
+
}
|
|
70010
|
+
const errorReturnPattern = /return\s+["'`][[:ascii:]]*["'`]\s*;/;
|
|
70011
|
+
for (let i2 = currentLineIdx - 1;i2 >= 0 && i2 >= currentLineIdx - MAX_SEARCH_LINES; i2--) {
|
|
70012
|
+
const line = lines[i2].trim();
|
|
70013
|
+
if (functionKeywords.test(line) || line.startsWith("function ")) {
|
|
70014
|
+
break;
|
|
70015
|
+
}
|
|
70016
|
+
if (errorReturnPattern.test(line)) {
|
|
70017
|
+
return true;
|
|
70018
|
+
}
|
|
70019
|
+
}
|
|
70020
|
+
return false;
|
|
70021
|
+
}
|
|
69705
70022
|
function scanWithRegex(content, filePath, denyPatterns) {
|
|
69706
70023
|
const findings = [];
|
|
69707
70024
|
const lines = content.split(`
|
|
@@ -69761,6 +70078,11 @@ function scanWithRegex(content, filePath, denyPatterns) {
|
|
|
69761
70078
|
for (const { pattern, rule_id } of denyPatterns.code) {
|
|
69762
70079
|
const isTestLike = line.includes("describe(") || line.includes("it(") || line.includes("test(") || line.includes("expect(");
|
|
69763
70080
|
if (!isTestLike && pattern.test(line)) {
|
|
70081
|
+
if (rule_id === "placeholder/code-stub-return" && /return\s+undefined\s*;/.test(line)) {
|
|
70082
|
+
if (isValidationPattern(lines, i2)) {
|
|
70083
|
+
continue;
|
|
70084
|
+
}
|
|
70085
|
+
}
|
|
69764
70086
|
findings.push({
|
|
69765
70087
|
path: filePath,
|
|
69766
70088
|
line: lineNumber,
|
|
@@ -69868,6 +70190,10 @@ async function placeholderScan(input, directory) {
|
|
|
69868
70190
|
if (isAllowedByGlobs(filePath, allow_globs)) {
|
|
69869
70191
|
continue;
|
|
69870
70192
|
}
|
|
70193
|
+
const relativeFilePath = path65.relative(directory, fullPath).replace(/\\/g, "/");
|
|
70194
|
+
if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
|
|
70195
|
+
continue;
|
|
70196
|
+
}
|
|
69871
70197
|
let content;
|
|
69872
70198
|
try {
|
|
69873
70199
|
const stat2 = fs52.statSync(fullPath);
|
|
@@ -71438,13 +71764,13 @@ function validatePath(inputPath, baseDir, workspaceDir) {
|
|
|
71438
71764
|
resolved = path67.resolve(baseDir, inputPath);
|
|
71439
71765
|
}
|
|
71440
71766
|
const workspaceResolved = path67.resolve(workspaceDir);
|
|
71441
|
-
let
|
|
71767
|
+
let relative12;
|
|
71442
71768
|
if (isWinAbs) {
|
|
71443
|
-
|
|
71769
|
+
relative12 = path67.win32.relative(workspaceResolved, resolved);
|
|
71444
71770
|
} else {
|
|
71445
|
-
|
|
71771
|
+
relative12 = path67.relative(workspaceResolved, resolved);
|
|
71446
71772
|
}
|
|
71447
|
-
if (
|
|
71773
|
+
if (relative12.startsWith("..")) {
|
|
71448
71774
|
return "path traversal detected";
|
|
71449
71775
|
}
|
|
71450
71776
|
return null;
|
|
@@ -76026,6 +76352,8 @@ var update_task_status = createSwarmTool({
|
|
|
76026
76352
|
// src/tools/write-drift-evidence.ts
|
|
76027
76353
|
init_tool();
|
|
76028
76354
|
init_utils2();
|
|
76355
|
+
init_ledger();
|
|
76356
|
+
init_manager();
|
|
76029
76357
|
init_create_tool();
|
|
76030
76358
|
import fs66 from "fs";
|
|
76031
76359
|
import path79 from "path";
|
|
@@ -76093,11 +76421,40 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
76093
76421
|
const tempPath = path79.join(evidenceDir, `.${filename}.tmp`);
|
|
76094
76422
|
await fs66.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
76095
76423
|
await fs66.promises.rename(tempPath, validatedPath);
|
|
76424
|
+
let snapshotInfo;
|
|
76425
|
+
let snapshotError;
|
|
76426
|
+
if (normalizedVerdict === "approved") {
|
|
76427
|
+
try {
|
|
76428
|
+
const currentPlan = await loadPlanJsonOnly(directory);
|
|
76429
|
+
if (currentPlan) {
|
|
76430
|
+
const snapshotEvent = await takeSnapshotEvent(directory, currentPlan, {
|
|
76431
|
+
source: "critic_approved",
|
|
76432
|
+
approvalMetadata: {
|
|
76433
|
+
phase,
|
|
76434
|
+
verdict: "APPROVED",
|
|
76435
|
+
summary: summary.trim(),
|
|
76436
|
+
approved_at: new Date().toISOString()
|
|
76437
|
+
}
|
|
76438
|
+
});
|
|
76439
|
+
snapshotInfo = {
|
|
76440
|
+
seq: snapshotEvent.seq,
|
|
76441
|
+
timestamp: snapshotEvent.timestamp
|
|
76442
|
+
};
|
|
76443
|
+
} else {
|
|
76444
|
+
snapshotError = "plan.json not available for snapshot";
|
|
76445
|
+
}
|
|
76446
|
+
} catch (err2) {
|
|
76447
|
+
snapshotError = err2 instanceof Error ? err2.message : String(err2);
|
|
76448
|
+
console.warn("[write_drift_evidence] critic-approved snapshot failed:", snapshotError);
|
|
76449
|
+
}
|
|
76450
|
+
}
|
|
76096
76451
|
return JSON.stringify({
|
|
76097
76452
|
success: true,
|
|
76098
76453
|
phase,
|
|
76099
76454
|
verdict: normalizedVerdict,
|
|
76100
|
-
message: `Drift evidence written to .swarm/evidence/${phase}/drift-verifier.json
|
|
76455
|
+
message: `Drift evidence written to .swarm/evidence/${phase}/drift-verifier.json`,
|
|
76456
|
+
approvedSnapshot: snapshotInfo,
|
|
76457
|
+
snapshotError
|
|
76101
76458
|
}, null, 2);
|
|
76102
76459
|
} catch (error93) {
|
|
76103
76460
|
return JSON.stringify({
|