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/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, options) {
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 { copyFileSync, existsSync as existsSync3, renameSync as renameSync2, unlinkSync } from "fs";
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 rebuilt = await replayFromLedger(directory);
16285
- if (rebuilt) {
16286
- await savePlan(directory, rebuilt);
16287
- return rebuilt;
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 anchorEvents = await readLedgerEvents(directory);
16291
- if (anchorEvents.length === 0) {
16292
- warn("[loadPlan] Ledger present but no events readable \u2014 refusing approved-snapshot recovery (cannot verify plan identity).");
16293
- return null;
16331
+ const rebuilt = await replayFromLedger(directory);
16332
+ if (rebuilt) {
16333
+ await savePlan(directory, rebuilt);
16334
+ return rebuilt;
16294
16335
  }
16295
- const expectedPlanId = anchorEvents[0].plan_id;
16296
- const approved = await loadLastApprovedPlan(directory, expectedPlanId);
16297
- if (approved) {
16298
- const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
16299
- 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.`);
16300
- await savePlan(directory, approved.plan);
16301
- try {
16302
- await takeSnapshotEvent(directory, approved.plan, {
16303
- source: "recovery_from_approved_snapshot",
16304
- approvalMetadata: approved.approval
16305
- });
16306
- } catch (healError) {
16307
- warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
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
- return approved.plan;
16358
+ } catch (recoveryError) {
16359
+ warn(`[loadPlan] Approved-snapshot recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
16310
16360
  }
16311
- } catch (recoveryError) {
16312
- warn(`[loadPlan] Approved-snapshot recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
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 Error(`Concurrent plan modification detected after retries: ${error49.message}. Please retry the operation.`);
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
- await Bun.write(mdTempPath, markdownWithHash);
16502
- renameSync2(mdTempPath, mdPath);
16503
- } finally {
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
- unlinkSync(mdTempPath);
16506
- } catch {}
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 updatedPhases = plan.phases.map((phase) => {
16569
- const updatedTasks = phase.tasks.map((task) => {
16570
- if (task.id === taskId) {
16571
- taskFound = true;
16572
- return { ...task, status };
16573
- }
16574
- return task;
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
- return {
16577
- ...phase,
16578
- status: derivePhaseStatusFromTasks(updatedTasks),
16579
- tasks: updatedTasks
16580
- };
16581
- });
16582
- if (!taskFound) {
16583
- throw new Error(`Task not found: ${taskId}`);
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
- const updatedPlan = { ...plan, phases: updatedPhases };
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/evidence/manager.ts
16866
- import { mkdirSync as mkdirSync2, readdirSync, rmSync, statSync as statSync2 } from "fs";
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
- throw new Error("Invalid task ID: empty string");
16960
+ return "Invalid task ID: empty string";
16878
16961
  }
16879
16962
  if (/\0/.test(taskId)) {
16880
- throw new Error("Invalid task ID: contains null bytes");
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
- throw new Error("Invalid task ID: contains control characters");
16967
+ return "Invalid task ID: contains control characters";
16885
16968
  }
16886
16969
  }
16887
- if (taskId.includes("..") || taskId.includes("../") || taskId.includes("..\\")) {
16888
- throw new Error("Invalid task ID: path traversal detected");
16970
+ if (taskId.includes("..") || taskId.includes("/") || taskId.includes("\\")) {
16971
+ return "Invalid task ID: path traversal detected";
16889
16972
  }
16890
- if (TASK_ID_REGEX.test(taskId)) {
16891
- return taskId;
16892
- }
16893
- if (RETRO_TASK_ID_REGEX.test(taskId)) {
16894
- return taskId;
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
- if (INTERNAL_TOOL_ID_REGEX.test(taskId)) {
16897
- return taskId;
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 = sanitizeTaskId(taskId);
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: [...bundle.entries, evidence],
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 = sanitizeTaskId(taskId);
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 = readdirSync(evidenceBasePath);
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
- sanitizeTaskId(entry);
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 = sanitizeTaskId(taskId);
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, TASK_ID_REGEX, RETRO_TASK_ID_REGEX, INTERNAL_TOOL_ID_REGEX, GENERAL_TASK_ID_REGEX, LEGACY_TASK_COMPLEXITY_MAP;
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
- TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
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
- if (!taskId)
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
- if (!isValidTaskId2(taskId)) {
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, TASK_ID_PATTERN;
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: { session_scope: "plan_free" }
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 archiveDir = path14.join(swarmDir, "archive", `swarm-${timestamp}`);
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 warningMsg = warnings.length > 0 ? `
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
- ${warnings.map((w) => `- ${w}`).join(`
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 readdirSync2, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
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 = readdirSync2(directory);
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) ? readdirSync2(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
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 readdirSync8, renameSync as renameSync8, rmSync as rmSync3, statSync as statSync7 } from "fs";
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
- if (!taskId)
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 validateTaskIdFormat(taskId) {
66167
- const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
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 = validateTaskIdFormat(args2.taskId);
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 = 5 * 1024 * 1024;
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 > MAX_FILE_SIZE2) {
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 taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
76261
- if (!taskIdPattern.test(taskId)) {
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,