opencode-swarm 6.31.3 → 6.32.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
@@ -14591,7 +14591,10 @@ var init_schema = __esm(() => {
14591
14591
  enabled: exports_external.boolean().default(true),
14592
14592
  required_agents: exports_external.array(exports_external.enum(["coder", "reviewer", "test_engineer"])).default(["coder", "reviewer", "test_engineer"]),
14593
14593
  require_docs: exports_external.boolean().default(true),
14594
- policy: exports_external.enum(["enforce", "warn"]).default("enforce")
14594
+ policy: exports_external.enum(["enforce", "warn"]).default("enforce"),
14595
+ regression_sweep: exports_external.object({
14596
+ enforce: exports_external.boolean().default(false)
14597
+ }).optional()
14595
14598
  });
14596
14599
  SummaryConfigSchema = exports_external.object({
14597
14600
  enabled: exports_external.boolean().default(true),
@@ -14937,9 +14940,15 @@ var init_schema = __esm(() => {
14937
14940
  tool_output: exports_external.object({
14938
14941
  truncation_enabled: exports_external.boolean().default(true),
14939
14942
  max_lines: exports_external.number().min(10).max(500).default(150),
14940
- per_tool: exports_external.record(exports_external.string(), exports_external.number()).optional()
14943
+ per_tool: exports_external.record(exports_external.string(), exports_external.number()).optional(),
14944
+ truncation_tools: exports_external.array(exports_external.string()).optional().describe("Tools to apply output truncation to. Defaults to diff, symbols, bash, shell, test_runner, lint, pre_check_batch, complexity_hotspots, pkg_audit, sbom_generate, schema_drift.")
14941
14945
  }).optional(),
14942
14946
  slop_detector: SlopDetectorConfigSchema.optional(),
14947
+ todo_gate: exports_external.object({
14948
+ enabled: exports_external.boolean().default(true),
14949
+ max_high_priority: exports_external.number().int().min(-1).default(0).describe("Max new high-priority TODOs (FIXME/HACK/XXX) before warning. 0 = warn on any. Set to -1 to disable."),
14950
+ block_on_threshold: exports_external.boolean().default(false).describe("If true, block phase completion when threshold exceeded. Default: advisory only.")
14951
+ }).optional(),
14943
14952
  incremental_verify: IncrementalVerifyConfigSchema.optional(),
14944
14953
  compaction_service: CompactionConfigSchema.optional()
14945
14954
  });
@@ -33429,7 +33438,7 @@ function detectAdditionalLinter(cwd) {
33429
33438
  }
33430
33439
  async function detectAvailableLinter(directory) {
33431
33440
  const _DETECT_TIMEOUT = 2000;
33432
- const projectDir = directory || process.cwd();
33441
+ const projectDir = directory;
33433
33442
  const isWindows = process.platform === "win32";
33434
33443
  const biomeBin = isWindows ? path22.join(projectDir, "node_modules", ".bin", "biome.EXE") : path22.join(projectDir, "node_modules", ".bin", "biome");
33435
33444
  const eslintBin = isWindows ? path22.join(projectDir, "node_modules", ".bin", "eslint.cmd") : path22.join(projectDir, "node_modules", ".bin", "eslint");
@@ -40049,6 +40058,8 @@ Your message MUST NOT contain:
40049
40058
 
40050
40059
  Delegation is a handoff, not a negotiation. State facts, let agents decide.
40051
40060
 
40061
+ Before delegating to {{AGENT_PREFIX}}reviewer: call check_gate_status for the current task_id and include the gate results in the GATES field of the reviewer message. Format: GATES: lint=PASS/FAIL, sast_scan=PASS/FAIL, secretscan=PASS/FAIL (use PASS/FAIL/skipped for each gate). If no gates have been run yet, use GATES: none.
40062
+
40052
40063
  <!-- BEHAVIORAL_GUIDANCE_START -->
40053
40064
  PARTIAL GATE RATIONALIZATIONS \u2014 automated gates \u2260 agent review. Running SOME gates is NOT compliance:
40054
40065
  \u2717 "I ran pre_check_batch so the code is verified" \u2192 pre_check_batch does NOT replace {{AGENT_PREFIX}}reviewer or {{AGENT_PREFIX}}test_engineer
@@ -40138,6 +40149,7 @@ CONSTRAINT: Do not modify other functions
40138
40149
  TASK: Review login validation
40139
40150
  FILE: src/auth/login.ts
40140
40151
  CHECK: [security, correctness, edge-cases]
40152
+ GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
40141
40153
  OUTPUT: VERDICT + RISK + ISSUES
40142
40154
 
40143
40155
  {{AGENT_PREFIX}}test_engineer
@@ -40155,6 +40167,7 @@ OUTPUT: VERDICT + CONFIDENCE + ISSUES + SUMMARY
40155
40167
  TASK: Security-only review of login validation
40156
40168
  FILE: src/auth/login.ts
40157
40169
  CHECK: [security-only] \u2014 evaluate against OWASP Top 10, scan for hardcoded secrets, injection vectors, insecure crypto, missing input validation
40170
+ GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
40158
40171
  OUTPUT: VERDICT + RISK + SECURITY ISSUES ONLY
40159
40172
 
40160
40173
  {{AGENT_PREFIX}}test_engineer
@@ -40591,6 +40604,10 @@ Treating pre_check_batch as a substitute for {{AGENT_PREFIX}}reviewer is a PROCE
40591
40604
 
40592
40605
  IMPORTANT: The regression sweep runs test_runner DIRECTLY (architect calls the tool). Do NOT delegate to test_engineer for this \u2014 the test_engineer's EXECUTION BOUNDARY restricts it to its own test files. The architect has unrestricted test_runner access.
40593
40606
  \u2192 REQUIRED: Print "regression-sweep: [PASS N additional tests | SKIPPED \u2014 no related tests beyond task scope | SKIPPED \u2014 test_runner error | FAIL \u2014 REGRESSION DETECTED in files]"
40607
+
40608
+ 5n. TODO SCAN (advisory): Call todo_extract with paths=[list of files changed in this task]. If any results have priority HIGH \u2192 print "todo-scan: WARN \u2014 N high-priority TODOs in changed files: [list of TODO texts]". If no high-priority results \u2192 print "todo-scan: CLEAN". This is advisory only and does NOT block the pipeline.
40609
+ \u2192 REQUIRED: Print "todo-scan: [WARN \u2014 N high-priority TODOs | CLEAN]"
40610
+
40594
40611
  {{ADVERSARIAL_TEST_STEP}}
40595
40612
  5n. COVERAGE CHECK: If {{AGENT_PREFIX}}test_engineer reports coverage < 70% \u2192 delegate {{AGENT_PREFIX}}test_engineer for an additional test pass targeting uncovered paths. This is a soft guideline; use judgment for trivial tasks.
40596
40613
 
@@ -40873,6 +40890,9 @@ Before you report task completion, verify:
40873
40890
  [ ] I did not skip or stub any acceptance criterion
40874
40891
  [ ] I did not run tests, build commands, or validation tools \u2014 that is the reviewer's job
40875
40892
  [ ] My changes compile/parse without errors (syntax check only)
40893
+ [ ] I did not use vague identifier names (result, data, temp, value, item, info, stuff, obj, ret, val)
40894
+ [ ] I did not write empty or tautological comments (e.g., "// sets the value", "// constructor", "// handle error")
40895
+ [ ] I did not leave placeholder JSDoc/docstring @param descriptions blank or copy-paste identical descriptions across functions
40876
40896
  If ANY box is unchecked, fix it before reporting completion.
40877
40897
  Print this checklist with your completion report.
40878
40898
 
@@ -41606,7 +41626,11 @@ TIER 2: SAFETY (mandatory for MODERATE+, always for COMPLEX)
41606
41626
  Does the code introduce security vulnerabilities, data loss risks, or breaking changes? Check against: SAST findings, secret scan results, import analysis. Anti-rubber-stamp: "No issues found" requires evidence. State what you checked.
41607
41627
 
41608
41628
  TIER 3: QUALITY (run only for COMPLEX, and only if Tiers 1-2 pass)
41609
- Code style, naming, duplication, test coverage, documentation completeness. This tier is advisory \u2014 QUALITY findings do not block approval. Approval requires: Tier 1 PASS + Tier 2 PASS (where applicable). Tier 3 is informational.
41629
+ Code style, naming, duplication, test coverage, documentation completeness. This tier is advisory \u2014 QUALITY findings do not block approval. Approval requires: Tier 1 PASS + Tier 2 PASS (where applicable). Tier 3 is informational. Flag these slop patterns:
41630
+ - Vague identifiers (result, data, temp, value, item, info, stuff, obj, ret, val) \u2014 flag if a more descriptive name exists
41631
+ - Empty or tautological comments that describe syntax not intent (e.g., "// sets the value", "// constructor", "// handle error")
41632
+ - Copy-paste code blocks with only variable names changed
41633
+ - Blank or copy-pasted @param/@returns descriptions in JSDoc/docstrings
41610
41634
 
41611
41635
  VERDICT FORMAT:
41612
41636
  APPROVED: Tier 1 PASS, Tier 2 PASS [, Tier 3 notes if any]
@@ -41622,6 +41646,9 @@ FILE: [primary changed file or diff entry point]
41622
41646
  DIFF: [changed files/functions, or "infer from FILE" if omitted]
41623
41647
  AFFECTS: [callers/consumers/dependents to inspect, or "infer from diff"]
41624
41648
  CHECK: [list of dimensions to evaluate]
41649
+ GATES: [pre-completed gate results (lint, SAST, secretscan, etc.), or "none" if unavailable]
41650
+
41651
+ PROCESSING: If GATES is provided and includes passing results for lint, SAST, placeholder-scan, or secret-scan: skip the corresponding Tier 2 checks that those gates already cover. Focus Tier 2 time on checks NOT covered by automated gates.
41625
41652
 
41626
41653
  ## OUTPUT FORMAT (MANDATORY \u2014 deviations will be rejected)
41627
41654
  Begin directly with VERDICT. Do NOT prepend "Here's my review..." or any conversational preamble.
@@ -42780,9 +42807,9 @@ class PlanSyncWorker {
42780
42807
  disposed = false;
42781
42808
  constructor(options = {}) {
42782
42809
  if (!options.directory) {
42783
- console.warn("[plan-sync-worker] No directory provided, falling back to process.cwd()");
42810
+ throw new Error("[plan-sync-worker] No directory provided - options.directory is required");
42784
42811
  }
42785
- this.directory = options.directory || process.cwd();
42812
+ this.directory = options.directory;
42786
42813
  this.debounceMs = options.debounceMs ?? 300;
42787
42814
  this.pollIntervalMs = options.pollIntervalMs ?? 2000;
42788
42815
  this.syncTimeoutMs = options.syncTimeoutMs ?? 30000;
@@ -49183,6 +49210,8 @@ ${content.substring(endIndex + 1)}`;
49183
49210
  }
49184
49211
  // src/hooks/compaction-customizer.ts
49185
49212
  init_manager2();
49213
+ import * as fs17 from "fs";
49214
+ import { join as join25 } from "path";
49186
49215
  init_utils2();
49187
49216
  function createCompactionCustomizerHook(config3, directory) {
49188
49217
  const enabled = config3.hooks?.compaction !== false;
@@ -49227,6 +49256,15 @@ function createCompactionCustomizerHook(config3, directory) {
49227
49256
  output.context.push(`[SWARM PATTERNS] ${patterns}`);
49228
49257
  }
49229
49258
  }
49259
+ try {
49260
+ const summariesDir = join25(directory, ".swarm", "summaries");
49261
+ const files = await fs17.promises.readdir(summariesDir);
49262
+ if (files.length > 0) {
49263
+ const count = files.length;
49264
+ output.context.push(`[CONTEXT OPTIMIZATION] Tool outputs from earlier in this session have been stored to disk. When compacting, replace any large tool output blocks (bash, test_runner, lint, diff results) with a one-line reference: "[Output stored \u2014 use /swarm retrieve to access full content]". Preserve the tool name, exit status, and any error messages. Discard raw output lines.`);
49265
+ output.context.push(`[STORED OUTPUTS] ${count} tool output${count === 1 ? "" : "s"} stored in .swarm/summaries/. These are retrievable via /swarm retrieve <id>.`);
49266
+ }
49267
+ } catch {}
49230
49268
  output.context.push("[KNOWLEDGE TOOLS] You have persistent knowledge tools: knowledge_recall (search for relevant past decisions), knowledge_add (store a new lesson), knowledge_remove (delete outdated entries). Use knowledge_recall when past context would help.");
49231
49269
  })
49232
49270
  };
@@ -49705,7 +49743,7 @@ function maskToolOutput(msg, _threshold) {
49705
49743
  }
49706
49744
  // src/hooks/delegation-gate.ts
49707
49745
  init_schema();
49708
- import * as fs17 from "fs";
49746
+ import * as fs18 from "fs";
49709
49747
  import * as path31 from "path";
49710
49748
 
49711
49749
  // src/hooks/guardrails.ts
@@ -49884,15 +49922,14 @@ function isInDeclaredScope(filePath, scopeEntries) {
49884
49922
  return rel.length > 0 && !rel.startsWith("..") && !path29.isAbsolute(rel);
49885
49923
  });
49886
49924
  }
49887
- function createGuardrailsHooks(directoryOrConfig, config3) {
49888
- let directory;
49925
+ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
49889
49926
  let guardrailsConfig;
49890
- if (directoryOrConfig && typeof directoryOrConfig === "object" && "enabled" in directoryOrConfig) {
49927
+ if (directory && typeof directory === "object" && "enabled" in directory) {
49891
49928
  console.warn("[guardrails] Legacy call without directory, falling back to process.cwd()");
49892
- directory = process.cwd();
49929
+ guardrailsConfig = directory;
49930
+ } else if (directoryOrConfig && typeof directoryOrConfig === "object" && "enabled" in directoryOrConfig) {
49893
49931
  guardrailsConfig = directoryOrConfig;
49894
49932
  } else {
49895
- directory = directoryOrConfig || process.cwd();
49896
49933
  guardrailsConfig = config3;
49897
49934
  }
49898
49935
  if (guardrailsConfig?.enabled === false) {
@@ -50602,7 +50639,7 @@ function getEvidenceTaskId(session, directory) {
50602
50639
  if (!resolvedPlanPath.startsWith(resolvedDirectory + path31.sep) && resolvedPlanPath !== resolvedDirectory) {
50603
50640
  return null;
50604
50641
  }
50605
- const planContent = fs17.readFileSync(resolvedPlanPath, "utf-8");
50642
+ const planContent = fs18.readFileSync(resolvedPlanPath, "utf-8");
50606
50643
  const plan = JSON.parse(planContent);
50607
50644
  if (!plan || !Array.isArray(plan.phases)) {
50608
50645
  return null;
@@ -51079,7 +51116,7 @@ ${warningLines.join(`
51079
51116
  }
51080
51117
  // src/hooks/delegation-sanitizer.ts
51081
51118
  init_utils2();
51082
- import * as fs18 from "fs";
51119
+ import * as fs19 from "fs";
51083
51120
  var SANITIZATION_PATTERNS = [
51084
51121
  /\b\d+(st|nd|rd|th)\s+(attempt|try|time)\b/gi,
51085
51122
  /\b(5th|fifth|final|last)\s+attempt\b/gi,
@@ -51150,7 +51187,7 @@ function createDelegationSanitizerHook(directory) {
51150
51187
  stripped_patterns: result.stripped,
51151
51188
  timestamp: new Date().toISOString()
51152
51189
  };
51153
- fs18.appendFileSync(eventsPath, `${JSON.stringify(event)}
51190
+ fs19.appendFileSync(eventsPath, `${JSON.stringify(event)}
51154
51191
  `, "utf-8");
51155
51192
  } catch {}
51156
51193
  }
@@ -51383,7 +51420,7 @@ function createPipelineTrackerHook(config3, directory) {
51383
51420
  return;
51384
51421
  let phaseNumber = null;
51385
51422
  try {
51386
- const plan = await loadPlan(directory || process.cwd());
51423
+ const plan = await loadPlan(directory);
51387
51424
  if (plan) {
51388
51425
  const phaseString = extractCurrentPhaseFromPlan2(plan);
51389
51426
  phaseNumber = parsePhaseNumber(phaseString);
@@ -51407,12 +51444,12 @@ init_schema();
51407
51444
  init_manager();
51408
51445
  init_detector();
51409
51446
  init_manager2();
51410
- import * as fs20 from "fs";
51447
+ import * as fs21 from "fs";
51411
51448
 
51412
51449
  // src/services/decision-drift-analyzer.ts
51413
51450
  init_utils2();
51414
51451
  init_manager2();
51415
- import * as fs19 from "fs";
51452
+ import * as fs20 from "fs";
51416
51453
  import * as path33 from "path";
51417
51454
  var DEFAULT_DRIFT_CONFIG = {
51418
51455
  staleThresholdPhases: 1,
@@ -51570,8 +51607,8 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
51570
51607
  const contextPath = path33.join(directory, ".swarm", "context.md");
51571
51608
  let contextContent = "";
51572
51609
  try {
51573
- if (fs19.existsSync(contextPath)) {
51574
- contextContent = fs19.readFileSync(contextPath, "utf-8");
51610
+ if (fs20.existsSync(contextPath)) {
51611
+ contextContent = fs20.readFileSync(contextPath, "utf-8");
51575
51612
  }
51576
51613
  } catch {
51577
51614
  return {
@@ -52065,11 +52102,11 @@ function createSystemEnhancerHook(config3, directory) {
52065
52102
  if (handoffContent) {
52066
52103
  const handoffPath = validateSwarmPath(directory, "handoff.md");
52067
52104
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
52068
- if (fs20.existsSync(consumedPath)) {
52105
+ if (fs21.existsSync(consumedPath)) {
52069
52106
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
52070
- fs20.unlinkSync(consumedPath);
52107
+ fs21.unlinkSync(consumedPath);
52071
52108
  }
52072
- fs20.renameSync(handoffPath, consumedPath);
52109
+ fs21.renameSync(handoffPath, consumedPath);
52073
52110
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
52074
52111
  The previous model's session ended. Here is your starting context:
52075
52112
 
@@ -52349,11 +52386,11 @@ ${budgetWarning}`);
52349
52386
  if (handoffContent) {
52350
52387
  const handoffPath = validateSwarmPath(directory, "handoff.md");
52351
52388
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
52352
- if (fs20.existsSync(consumedPath)) {
52389
+ if (fs21.existsSync(consumedPath)) {
52353
52390
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
52354
- fs20.unlinkSync(consumedPath);
52391
+ fs21.unlinkSync(consumedPath);
52355
52392
  }
52356
- fs20.renameSync(handoffPath, consumedPath);
52393
+ fs21.renameSync(handoffPath, consumedPath);
52357
52394
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
52358
52395
  The previous model's session ended. Here is your starting context:
52359
52396
 
@@ -53122,7 +53159,7 @@ function isReadTool(toolName) {
53122
53159
  }
53123
53160
 
53124
53161
  // src/hooks/incremental-verify.ts
53125
- import * as fs21 from "fs";
53162
+ import * as fs22 from "fs";
53126
53163
  import * as path34 from "path";
53127
53164
 
53128
53165
  // src/hooks/spawn-helper.ts
@@ -53198,21 +53235,21 @@ function spawnAsync(command, cwd, timeoutMs) {
53198
53235
  // src/hooks/incremental-verify.ts
53199
53236
  var emittedSkipAdvisories = new Set;
53200
53237
  function detectPackageManager(projectDir) {
53201
- if (fs21.existsSync(path34.join(projectDir, "bun.lockb")))
53238
+ if (fs22.existsSync(path34.join(projectDir, "bun.lockb")))
53202
53239
  return "bun";
53203
- if (fs21.existsSync(path34.join(projectDir, "pnpm-lock.yaml")))
53240
+ if (fs22.existsSync(path34.join(projectDir, "pnpm-lock.yaml")))
53204
53241
  return "pnpm";
53205
- if (fs21.existsSync(path34.join(projectDir, "yarn.lock")))
53242
+ if (fs22.existsSync(path34.join(projectDir, "yarn.lock")))
53206
53243
  return "yarn";
53207
- if (fs21.existsSync(path34.join(projectDir, "package-lock.json")))
53244
+ if (fs22.existsSync(path34.join(projectDir, "package-lock.json")))
53208
53245
  return "npm";
53209
53246
  return "bun";
53210
53247
  }
53211
53248
  function detectTypecheckCommand(projectDir) {
53212
53249
  const pkgPath = path34.join(projectDir, "package.json");
53213
- if (fs21.existsSync(pkgPath)) {
53250
+ if (fs22.existsSync(pkgPath)) {
53214
53251
  try {
53215
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf8"));
53252
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf8"));
53216
53253
  const scripts = pkg.scripts;
53217
53254
  if (scripts?.typecheck) {
53218
53255
  const pm = detectPackageManager(projectDir);
@@ -53226,8 +53263,8 @@ function detectTypecheckCommand(projectDir) {
53226
53263
  ...pkg.dependencies,
53227
53264
  ...pkg.devDependencies
53228
53265
  };
53229
- if (!deps?.typescript && !fs21.existsSync(path34.join(projectDir, "tsconfig.json"))) {}
53230
- const hasTSMarkers = deps?.typescript || fs21.existsSync(path34.join(projectDir, "tsconfig.json"));
53266
+ if (!deps?.typescript && !fs22.existsSync(path34.join(projectDir, "tsconfig.json"))) {}
53267
+ const hasTSMarkers = deps?.typescript || fs22.existsSync(path34.join(projectDir, "tsconfig.json"));
53231
53268
  if (hasTSMarkers) {
53232
53269
  return { command: ["npx", "tsc", "--noEmit"], language: "typescript" };
53233
53270
  }
@@ -53235,17 +53272,17 @@ function detectTypecheckCommand(projectDir) {
53235
53272
  return null;
53236
53273
  }
53237
53274
  }
53238
- if (fs21.existsSync(path34.join(projectDir, "go.mod"))) {
53275
+ if (fs22.existsSync(path34.join(projectDir, "go.mod"))) {
53239
53276
  return { command: ["go", "vet", "./..."], language: "go" };
53240
53277
  }
53241
- if (fs21.existsSync(path34.join(projectDir, "Cargo.toml"))) {
53278
+ if (fs22.existsSync(path34.join(projectDir, "Cargo.toml"))) {
53242
53279
  return { command: ["cargo", "check"], language: "rust" };
53243
53280
  }
53244
- if (fs21.existsSync(path34.join(projectDir, "pyproject.toml")) || fs21.existsSync(path34.join(projectDir, "requirements.txt")) || fs21.existsSync(path34.join(projectDir, "setup.py"))) {
53281
+ if (fs22.existsSync(path34.join(projectDir, "pyproject.toml")) || fs22.existsSync(path34.join(projectDir, "requirements.txt")) || fs22.existsSync(path34.join(projectDir, "setup.py"))) {
53245
53282
  return { command: null, language: "python" };
53246
53283
  }
53247
53284
  try {
53248
- const entries = fs21.readdirSync(projectDir);
53285
+ const entries = fs22.readdirSync(projectDir);
53249
53286
  if (entries.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
53250
53287
  return {
53251
53288
  command: ["dotnet", "build", "--no-restore"],
@@ -53939,12 +53976,12 @@ Use this data to avoid repeating known failure patterns.`;
53939
53976
  // src/hooks/curator-drift.ts
53940
53977
  init_event_bus();
53941
53978
  init_utils2();
53942
- import * as fs22 from "fs";
53979
+ import * as fs23 from "fs";
53943
53980
  import * as path36 from "path";
53944
53981
  var DRIFT_REPORT_PREFIX = "drift-report-phase-";
53945
53982
  async function readPriorDriftReports(directory) {
53946
53983
  const swarmDir = path36.join(directory, ".swarm");
53947
- const entries = await fs22.promises.readdir(swarmDir).catch(() => null);
53984
+ const entries = await fs23.promises.readdir(swarmDir).catch(() => null);
53948
53985
  if (entries === null)
53949
53986
  return [];
53950
53987
  const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
@@ -53971,9 +54008,9 @@ async function writeDriftReport(directory, report) {
53971
54008
  const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
53972
54009
  const filePath = validateSwarmPath(directory, filename);
53973
54010
  const swarmDir = path36.dirname(filePath);
53974
- await fs22.promises.mkdir(swarmDir, { recursive: true });
54011
+ await fs23.promises.mkdir(swarmDir, { recursive: true });
53975
54012
  try {
53976
- await fs22.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
54013
+ await fs23.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
53977
54014
  } catch (err2) {
53978
54015
  throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
53979
54016
  }
@@ -54363,7 +54400,7 @@ function createSelfReviewHook(config3, injectAdvisory) {
54363
54400
  }
54364
54401
 
54365
54402
  // src/hooks/slop-detector.ts
54366
- import * as fs23 from "fs";
54403
+ import * as fs24 from "fs";
54367
54404
  import * as path38 from "path";
54368
54405
  var WRITE_EDIT_TOOLS = new Set([
54369
54406
  "write",
@@ -54409,7 +54446,7 @@ function checkBoilerplateExplosion(content, taskDescription, threshold) {
54409
54446
  function walkFiles(dir, exts, deadline) {
54410
54447
  const results = [];
54411
54448
  try {
54412
- for (const entry of fs23.readdirSync(dir, { withFileTypes: true })) {
54449
+ for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
54413
54450
  if (deadline !== undefined && Date.now() > deadline)
54414
54451
  break;
54415
54452
  if (entry.isSymbolicLink())
@@ -54429,7 +54466,7 @@ function walkFiles(dir, exts, deadline) {
54429
54466
  return results;
54430
54467
  }
54431
54468
  function checkDeadExports(content, projectDir, startTime) {
54432
- const hasPackageJson = fs23.existsSync(path38.join(projectDir, "package.json"));
54469
+ const hasPackageJson = fs24.existsSync(path38.join(projectDir, "package.json"));
54433
54470
  if (!hasPackageJson)
54434
54471
  return null;
54435
54472
  const exportMatches = content.matchAll(/^\+(?:export)\s+(?:function|class|const|type|interface)\s+(\w{3,})/gm);
@@ -54452,7 +54489,7 @@ function checkDeadExports(content, projectDir, startTime) {
54452
54489
  if (found || Date.now() - startTime > 480)
54453
54490
  break;
54454
54491
  try {
54455
- const text = fs23.readFileSync(file3, "utf-8");
54492
+ const text = fs24.readFileSync(file3, "utf-8");
54456
54493
  if (importPattern.test(text))
54457
54494
  found = true;
54458
54495
  importPattern.lastIndex = 0;
@@ -54585,7 +54622,7 @@ Review before proceeding.`;
54585
54622
 
54586
54623
  // src/hooks/steering-consumed.ts
54587
54624
  init_utils2();
54588
- import * as fs24 from "fs";
54625
+ import * as fs25 from "fs";
54589
54626
  function recordSteeringConsumed(directory, directiveId) {
54590
54627
  try {
54591
54628
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
@@ -54594,7 +54631,7 @@ function recordSteeringConsumed(directory, directiveId) {
54594
54631
  directiveId,
54595
54632
  timestamp: new Date().toISOString()
54596
54633
  };
54597
- fs24.appendFileSync(eventsPath, `${JSON.stringify(event)}
54634
+ fs25.appendFileSync(eventsPath, `${JSON.stringify(event)}
54598
54635
  `, "utf-8");
54599
54636
  } catch {}
54600
54637
  }
@@ -54951,7 +54988,7 @@ var build_check = createSwarmTool({
54951
54988
  // src/tools/check-gate-status.ts
54952
54989
  init_dist();
54953
54990
  init_create_tool();
54954
- import * as fs25 from "fs";
54991
+ import * as fs26 from "fs";
54955
54992
  import * as path39 from "path";
54956
54993
  var EVIDENCE_DIR = ".swarm/evidence";
54957
54994
  var TASK_ID_PATTERN2 = /^\d+\.\d+(\.\d+)*$/;
@@ -54975,12 +55012,12 @@ function isPathWithinSwarm(filePath, workspaceRoot) {
54975
55012
  return normalizedPath.startsWith(swarmPath);
54976
55013
  }
54977
55014
  function readEvidenceFile(evidencePath) {
54978
- if (!fs25.existsSync(evidencePath)) {
55015
+ if (!fs26.existsSync(evidencePath)) {
54979
55016
  return null;
54980
55017
  }
54981
55018
  let content;
54982
55019
  try {
54983
- content = fs25.readFileSync(evidencePath, "utf-8");
55020
+ content = fs26.readFileSync(evidencePath, "utf-8");
54984
55021
  } catch {
54985
55022
  return null;
54986
55023
  }
@@ -55016,7 +55053,8 @@ var check_gate_status = createSwarmTool({
55016
55053
  passed_gates: [],
55017
55054
  missing_gates: [],
55018
55055
  gates: {},
55019
- message: "Invalid task_id: task_id is required"
55056
+ message: "Invalid task_id: task_id is required",
55057
+ todo_scan: null
55020
55058
  };
55021
55059
  return JSON.stringify(errorResult, null, 2);
55022
55060
  }
@@ -55028,7 +55066,8 @@ var check_gate_status = createSwarmTool({
55028
55066
  passed_gates: [],
55029
55067
  missing_gates: [],
55030
55068
  gates: {},
55031
- message: `Invalid task_id format: "${taskIdInput}". Must match N.M or N.M.P (e.g. "1.1", "1.2.3", "1.2.3.4")`
55069
+ message: `Invalid task_id format: "${taskIdInput}". Must match N.M or N.M.P (e.g. "1.1", "1.2.3", "1.2.3.4")`,
55070
+ todo_scan: null
55032
55071
  };
55033
55072
  return JSON.stringify(errorResult, null, 2);
55034
55073
  }
@@ -55041,7 +55080,8 @@ var check_gate_status = createSwarmTool({
55041
55080
  passed_gates: [],
55042
55081
  missing_gates: [],
55043
55082
  gates: {},
55044
- message: "Invalid path: evidence path validation failed"
55083
+ message: "Invalid path: evidence path validation failed",
55084
+ todo_scan: null
55045
55085
  };
55046
55086
  return JSON.stringify(errorResult, null, 2);
55047
55087
  }
@@ -55054,7 +55094,8 @@ var check_gate_status = createSwarmTool({
55054
55094
  passed_gates: [],
55055
55095
  missing_gates: [],
55056
55096
  gates: {},
55057
- message: `No evidence file found for task "${taskIdInput}" at ${evidencePath}. Evidence file may be missing or invalid.`
55097
+ message: `No evidence file found for task "${taskIdInput}" at ${evidencePath}. Evidence file may be missing or invalid.`,
55098
+ todo_scan: null
55058
55099
  };
55059
55100
  return JSON.stringify(errorResult, null, 2);
55060
55101
  }
@@ -55076,6 +55117,7 @@ var check_gate_status = createSwarmTool({
55076
55117
  } else {
55077
55118
  message = `Task "${taskIdInput}" is incomplete. Missing gates: ${missingGates.join(", ")}.`;
55078
55119
  }
55120
+ const todoScan = evidenceData.todo_scan;
55079
55121
  const result = {
55080
55122
  taskId: taskIdInput,
55081
55123
  status,
@@ -55083,7 +55125,8 @@ var check_gate_status = createSwarmTool({
55083
55125
  passed_gates: passedGates,
55084
55126
  missing_gates: missingGates,
55085
55127
  gates: gatesMap,
55086
- message
55128
+ message,
55129
+ todo_scan: todoScan ?? null
55087
55130
  };
55088
55131
  return JSON.stringify(result, null, 2);
55089
55132
  }
@@ -55091,7 +55134,7 @@ var check_gate_status = createSwarmTool({
55091
55134
  // src/tools/complexity-hotspots.ts
55092
55135
  init_dist();
55093
55136
  init_create_tool();
55094
- import * as fs26 from "fs";
55137
+ import * as fs27 from "fs";
55095
55138
  import * as path40 from "path";
55096
55139
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
55097
55140
  var DEFAULT_DAYS = 90;
@@ -55221,11 +55264,11 @@ function estimateComplexity(content) {
55221
55264
  }
55222
55265
  function getComplexityForFile(filePath) {
55223
55266
  try {
55224
- const stat2 = fs26.statSync(filePath);
55267
+ const stat2 = fs27.statSync(filePath);
55225
55268
  if (stat2.size > MAX_FILE_SIZE_BYTES2) {
55226
55269
  return null;
55227
55270
  }
55228
- const content = fs26.readFileSync(filePath, "utf-8");
55271
+ const content = fs27.readFileSync(filePath, "utf-8");
55229
55272
  return estimateComplexity(content);
55230
55273
  } catch {
55231
55274
  return null;
@@ -55246,7 +55289,7 @@ async function analyzeHotspots(days, topN, extensions, directory) {
55246
55289
  let analyzedFiles = 0;
55247
55290
  for (const [file3, churnCount] of filteredChurn) {
55248
55291
  let fullPath = file3;
55249
- if (!fs26.existsSync(fullPath)) {
55292
+ if (!fs27.existsSync(fullPath)) {
55250
55293
  fullPath = path40.join(cwd, file3);
55251
55294
  }
55252
55295
  const complexity = getComplexityForFile(fullPath);
@@ -55455,7 +55498,7 @@ var curator_analyze = createSwarmTool({
55455
55498
  });
55456
55499
  // src/tools/declare-scope.ts
55457
55500
  init_tool();
55458
- import * as fs27 from "fs";
55501
+ import * as fs28 from "fs";
55459
55502
  import * as path41 from "path";
55460
55503
  init_create_tool();
55461
55504
  function validateTaskIdFormat(taskId) {
@@ -55548,9 +55591,9 @@ async function executeDeclareScope(args2, fallbackDir) {
55548
55591
  }
55549
55592
  const resolvedDir = path41.resolve(normalizedDir);
55550
55593
  try {
55551
- const realPath = fs27.realpathSync(resolvedDir);
55594
+ const realPath = fs28.realpathSync(resolvedDir);
55552
55595
  const planPath2 = path41.join(realPath, ".swarm", "plan.json");
55553
- if (!fs27.existsSync(planPath2)) {
55596
+ if (!fs28.existsSync(planPath2)) {
55554
55597
  return {
55555
55598
  success: false,
55556
55599
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -55572,9 +55615,9 @@ async function executeDeclareScope(args2, fallbackDir) {
55572
55615
  if (!fallbackDir) {
55573
55616
  console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
55574
55617
  }
55575
- const directory = normalizedDir || fallbackDir || process.cwd();
55618
+ const directory = normalizedDir || fallbackDir;
55576
55619
  const planPath = path41.resolve(directory, ".swarm", "plan.json");
55577
- if (!fs27.existsSync(planPath)) {
55620
+ if (!fs28.existsSync(planPath)) {
55578
55621
  return {
55579
55622
  success: false,
55580
55623
  message: "No plan found",
@@ -55583,7 +55626,7 @@ async function executeDeclareScope(args2, fallbackDir) {
55583
55626
  }
55584
55627
  let planContent;
55585
55628
  try {
55586
- planContent = JSON.parse(fs27.readFileSync(planPath, "utf-8"));
55629
+ planContent = JSON.parse(fs28.readFileSync(planPath, "utf-8"));
55587
55630
  } catch {
55588
55631
  return {
55589
55632
  success: false,
@@ -55988,7 +56031,7 @@ Use these as DOMAIN values when delegating to @sme.`;
55988
56031
  // src/tools/evidence-check.ts
55989
56032
  init_dist();
55990
56033
  init_create_tool();
55991
- import * as fs28 from "fs";
56034
+ import * as fs29 from "fs";
55992
56035
  import * as path42 from "path";
55993
56036
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
55994
56037
  var MAX_EVIDENCE_FILES = 1000;
@@ -56037,12 +56080,12 @@ function parseCompletedTasks(planContent) {
56037
56080
  }
56038
56081
  function readEvidenceFiles(evidenceDir, _cwd) {
56039
56082
  const evidence = [];
56040
- if (!fs28.existsSync(evidenceDir) || !fs28.statSync(evidenceDir).isDirectory()) {
56083
+ if (!fs29.existsSync(evidenceDir) || !fs29.statSync(evidenceDir).isDirectory()) {
56041
56084
  return evidence;
56042
56085
  }
56043
56086
  let files;
56044
56087
  try {
56045
- files = fs28.readdirSync(evidenceDir);
56088
+ files = fs29.readdirSync(evidenceDir);
56046
56089
  } catch {
56047
56090
  return evidence;
56048
56091
  }
@@ -56058,7 +56101,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
56058
56101
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
56059
56102
  continue;
56060
56103
  }
56061
- const stat2 = fs28.lstatSync(filePath);
56104
+ const stat2 = fs29.lstatSync(filePath);
56062
56105
  if (!stat2.isFile()) {
56063
56106
  continue;
56064
56107
  }
@@ -56067,7 +56110,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
56067
56110
  }
56068
56111
  let fileStat;
56069
56112
  try {
56070
- fileStat = fs28.statSync(filePath);
56113
+ fileStat = fs29.statSync(filePath);
56071
56114
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
56072
56115
  continue;
56073
56116
  }
@@ -56076,7 +56119,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
56076
56119
  }
56077
56120
  let content;
56078
56121
  try {
56079
- content = fs28.readFileSync(filePath, "utf-8");
56122
+ content = fs29.readFileSync(filePath, "utf-8");
56080
56123
  } catch {
56081
56124
  continue;
56082
56125
  }
@@ -56186,7 +56229,7 @@ var evidence_check = createSwarmTool({
56186
56229
  }
56187
56230
  let planContent;
56188
56231
  try {
56189
- planContent = fs28.readFileSync(planPath, "utf-8");
56232
+ planContent = fs29.readFileSync(planPath, "utf-8");
56190
56233
  } catch {
56191
56234
  const result2 = {
56192
56235
  message: "No completed tasks found in plan.",
@@ -56221,7 +56264,7 @@ var evidence_check = createSwarmTool({
56221
56264
  // src/tools/file-extractor.ts
56222
56265
  init_tool();
56223
56266
  init_create_tool();
56224
- import * as fs29 from "fs";
56267
+ import * as fs30 from "fs";
56225
56268
  import * as path43 from "path";
56226
56269
  var EXT_MAP = {
56227
56270
  python: ".py",
@@ -56284,8 +56327,8 @@ var extract_code_blocks = createSwarmTool({
56284
56327
  execute: async (args2, directory) => {
56285
56328
  const { content, output_dir, prefix } = args2;
56286
56329
  const targetDir = output_dir || directory;
56287
- if (!fs29.existsSync(targetDir)) {
56288
- fs29.mkdirSync(targetDir, { recursive: true });
56330
+ if (!fs30.existsSync(targetDir)) {
56331
+ fs30.mkdirSync(targetDir, { recursive: true });
56289
56332
  }
56290
56333
  if (!content) {
56291
56334
  return "Error: content is required";
@@ -56307,12 +56350,12 @@ var extract_code_blocks = createSwarmTool({
56307
56350
  const base = path43.basename(filepath, path43.extname(filepath));
56308
56351
  const ext = path43.extname(filepath);
56309
56352
  let counter = 1;
56310
- while (fs29.existsSync(filepath)) {
56353
+ while (fs30.existsSync(filepath)) {
56311
56354
  filepath = path43.join(targetDir, `${base}_${counter}${ext}`);
56312
56355
  counter++;
56313
56356
  }
56314
56357
  try {
56315
- fs29.writeFileSync(filepath, code.trim(), "utf-8");
56358
+ fs30.writeFileSync(filepath, code.trim(), "utf-8");
56316
56359
  savedFiles.push(filepath);
56317
56360
  } catch (error93) {
56318
56361
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -56428,7 +56471,7 @@ var gitingest = createSwarmTool({
56428
56471
  // src/tools/imports.ts
56429
56472
  init_dist();
56430
56473
  init_create_tool();
56431
- import * as fs30 from "fs";
56474
+ import * as fs31 from "fs";
56432
56475
  import * as path44 from "path";
56433
56476
  var MAX_FILE_PATH_LENGTH2 = 500;
56434
56477
  var MAX_SYMBOL_LENGTH = 256;
@@ -56597,7 +56640,7 @@ var SKIP_DIRECTORIES2 = new Set([
56597
56640
  function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
56598
56641
  let entries;
56599
56642
  try {
56600
- entries = fs30.readdirSync(dir);
56643
+ entries = fs31.readdirSync(dir);
56601
56644
  } catch (e) {
56602
56645
  stats.fileErrors.push({
56603
56646
  path: dir,
@@ -56614,7 +56657,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
56614
56657
  const fullPath = path44.join(dir, entry);
56615
56658
  let stat2;
56616
56659
  try {
56617
- stat2 = fs30.statSync(fullPath);
56660
+ stat2 = fs31.statSync(fullPath);
56618
56661
  } catch (e) {
56619
56662
  stats.fileErrors.push({
56620
56663
  path: fullPath,
@@ -56683,7 +56726,7 @@ var imports = createSwarmTool({
56683
56726
  }
56684
56727
  try {
56685
56728
  const targetFile = path44.resolve(file3);
56686
- if (!fs30.existsSync(targetFile)) {
56729
+ if (!fs31.existsSync(targetFile)) {
56687
56730
  const errorResult = {
56688
56731
  error: `target file not found: ${file3}`,
56689
56732
  target: file3,
@@ -56693,7 +56736,7 @@ var imports = createSwarmTool({
56693
56736
  };
56694
56737
  return JSON.stringify(errorResult, null, 2);
56695
56738
  }
56696
- const targetStat = fs30.statSync(targetFile);
56739
+ const targetStat = fs31.statSync(targetFile);
56697
56740
  if (!targetStat.isFile()) {
56698
56741
  const errorResult = {
56699
56742
  error: "target must be a file, not a directory",
@@ -56719,12 +56762,12 @@ var imports = createSwarmTool({
56719
56762
  if (consumers.length >= MAX_CONSUMERS)
56720
56763
  break;
56721
56764
  try {
56722
- const stat2 = fs30.statSync(filePath);
56765
+ const stat2 = fs31.statSync(filePath);
56723
56766
  if (stat2.size > MAX_FILE_SIZE_BYTES4) {
56724
56767
  skippedFileCount++;
56725
56768
  continue;
56726
56769
  }
56727
- const buffer = fs30.readFileSync(filePath);
56770
+ const buffer = fs31.readFileSync(filePath);
56728
56771
  if (isBinaryFile2(filePath, buffer)) {
56729
56772
  skippedFileCount++;
56730
56773
  continue;
@@ -57273,7 +57316,7 @@ init_dist();
57273
57316
  init_config();
57274
57317
  init_schema();
57275
57318
  init_manager();
57276
- import * as fs31 from "fs";
57319
+ import * as fs32 from "fs";
57277
57320
  import * as path45 from "path";
57278
57321
  init_utils2();
57279
57322
  init_create_tool();
@@ -57359,7 +57402,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57359
57402
  const phaseReferenceTimestamp = session.lastPhaseCompleteTimestamp ?? 0;
57360
57403
  const crossSessionResult = collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, sessionID);
57361
57404
  const agentsDispatched = Array.from(crossSessionResult.agents).sort();
57362
- const dir = workingDirectory || directory || process.cwd();
57405
+ const dir = workingDirectory || directory;
57363
57406
  const { config: config3 } = loadPluginConfigWithMeta(dir);
57364
57407
  let phaseCompleteConfig;
57365
57408
  try {
@@ -57532,7 +57575,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57532
57575
  if (agentsMissing.length > 0) {
57533
57576
  try {
57534
57577
  const planPath = validateSwarmPath(dir, "plan.json");
57535
- const planRaw = fs31.readFileSync(planPath, "utf-8");
57578
+ const planRaw = fs32.readFileSync(planPath, "utf-8");
57536
57579
  const plan = JSON.parse(planRaw);
57537
57580
  const targetPhase = plan.phases.find((p) => p.id === phase);
57538
57581
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -57560,6 +57603,34 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57560
57603
  warnings.push(`Warning: phase ${phase} missing required agents: ${agentsMissing.join(", ")}`);
57561
57604
  }
57562
57605
  }
57606
+ if (phaseCompleteConfig.regression_sweep?.enforce) {
57607
+ try {
57608
+ const planPath = validateSwarmPath(dir, "plan.json");
57609
+ const planRaw = fs32.readFileSync(planPath, "utf-8");
57610
+ const plan = JSON.parse(planRaw);
57611
+ const targetPhase = plan.phases.find((p) => p.id === phase);
57612
+ if (targetPhase) {
57613
+ let sweepFound = false;
57614
+ for (const task of targetPhase.tasks) {
57615
+ const taskEvidenceResult = await loadEvidence(dir, task.id);
57616
+ if (taskEvidenceResult.status === "found") {
57617
+ const entries = taskEvidenceResult.bundle.entries ?? [];
57618
+ for (const entry of entries) {
57619
+ if (entry.regression_sweep !== undefined) {
57620
+ sweepFound = true;
57621
+ break;
57622
+ }
57623
+ }
57624
+ }
57625
+ if (sweepFound)
57626
+ break;
57627
+ }
57628
+ if (!sweepFound) {
57629
+ warnings.push(`Warning: regression_sweep.enforce=true but no regression-sweep result found for any task in phase ${phase}. Run tests to populate regression-sweep results.`);
57630
+ }
57631
+ }
57632
+ } catch {}
57633
+ }
57563
57634
  const now = Date.now();
57564
57635
  const durationMs = now - phaseReferenceTimestamp;
57565
57636
  const event = {
@@ -57573,7 +57644,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57573
57644
  };
57574
57645
  try {
57575
57646
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
57576
- fs31.appendFileSync(eventsPath, `${JSON.stringify(event)}
57647
+ fs32.appendFileSync(eventsPath, `${JSON.stringify(event)}
57577
57648
  `, "utf-8");
57578
57649
  } catch (writeError) {
57579
57650
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -57592,12 +57663,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
57592
57663
  }
57593
57664
  try {
57594
57665
  const planPath = validateSwarmPath(dir, "plan.json");
57595
- const planJson = fs31.readFileSync(planPath, "utf-8");
57666
+ const planJson = fs32.readFileSync(planPath, "utf-8");
57596
57667
  const plan = JSON.parse(planJson);
57597
57668
  const phaseObj = plan.phases.find((p) => p.id === phase);
57598
57669
  if (phaseObj) {
57599
57670
  phaseObj.status = "completed";
57600
- fs31.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
57671
+ fs32.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
57601
57672
  `, "utf-8");
57602
57673
  }
57603
57674
  } catch (error93) {
@@ -57650,7 +57721,7 @@ init_dist();
57650
57721
  init_discovery();
57651
57722
  init_utils();
57652
57723
  init_create_tool();
57653
- import * as fs32 from "fs";
57724
+ import * as fs33 from "fs";
57654
57725
  import * as path46 from "path";
57655
57726
  var MAX_OUTPUT_BYTES5 = 52428800;
57656
57727
  var AUDIT_TIMEOUT_MS = 120000;
@@ -57669,28 +57740,28 @@ function validateArgs3(args2) {
57669
57740
  function detectEcosystems(directory) {
57670
57741
  const ecosystems = [];
57671
57742
  const cwd = directory;
57672
- if (fs32.existsSync(path46.join(cwd, "package.json"))) {
57743
+ if (fs33.existsSync(path46.join(cwd, "package.json"))) {
57673
57744
  ecosystems.push("npm");
57674
57745
  }
57675
- if (fs32.existsSync(path46.join(cwd, "pyproject.toml")) || fs32.existsSync(path46.join(cwd, "requirements.txt"))) {
57746
+ if (fs33.existsSync(path46.join(cwd, "pyproject.toml")) || fs33.existsSync(path46.join(cwd, "requirements.txt"))) {
57676
57747
  ecosystems.push("pip");
57677
57748
  }
57678
- if (fs32.existsSync(path46.join(cwd, "Cargo.toml"))) {
57749
+ if (fs33.existsSync(path46.join(cwd, "Cargo.toml"))) {
57679
57750
  ecosystems.push("cargo");
57680
57751
  }
57681
- if (fs32.existsSync(path46.join(cwd, "go.mod"))) {
57752
+ if (fs33.existsSync(path46.join(cwd, "go.mod"))) {
57682
57753
  ecosystems.push("go");
57683
57754
  }
57684
57755
  try {
57685
- const files = fs32.readdirSync(cwd);
57756
+ const files = fs33.readdirSync(cwd);
57686
57757
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
57687
57758
  ecosystems.push("dotnet");
57688
57759
  }
57689
57760
  } catch {}
57690
- if (fs32.existsSync(path46.join(cwd, "Gemfile")) || fs32.existsSync(path46.join(cwd, "Gemfile.lock"))) {
57761
+ if (fs33.existsSync(path46.join(cwd, "Gemfile")) || fs33.existsSync(path46.join(cwd, "Gemfile.lock"))) {
57691
57762
  ecosystems.push("ruby");
57692
57763
  }
57693
- if (fs32.existsSync(path46.join(cwd, "pubspec.yaml"))) {
57764
+ if (fs33.existsSync(path46.join(cwd, "pubspec.yaml"))) {
57694
57765
  ecosystems.push("dart");
57695
57766
  }
57696
57767
  return ecosystems;
@@ -58752,7 +58823,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
58752
58823
  ]);
58753
58824
  // src/tools/pre-check-batch.ts
58754
58825
  init_dist();
58755
- import * as fs35 from "fs";
58826
+ import * as fs36 from "fs";
58756
58827
  import * as path49 from "path";
58757
58828
 
58758
58829
  // node_modules/yocto-queue/index.js
@@ -58898,8 +58969,8 @@ function pLimit(concurrency) {
58898
58969
  },
58899
58970
  map: {
58900
58971
  async value(iterable, function_) {
58901
- const promises2 = Array.from(iterable, (value, index) => this(function_, value, index));
58902
- return Promise.all(promises2);
58972
+ const promises3 = Array.from(iterable, (value, index) => this(function_, value, index));
58973
+ return Promise.all(promises3);
58903
58974
  }
58904
58975
  }
58905
58976
  });
@@ -58920,7 +58991,7 @@ init_lint();
58920
58991
  init_manager();
58921
58992
 
58922
58993
  // src/quality/metrics.ts
58923
- import * as fs33 from "fs";
58994
+ import * as fs34 from "fs";
58924
58995
  import * as path47 from "path";
58925
58996
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
58926
58997
  var MIN_DUPLICATION_LINES = 10;
@@ -58959,11 +59030,11 @@ function estimateCyclomaticComplexity(content) {
58959
59030
  }
58960
59031
  function getComplexityForFile2(filePath) {
58961
59032
  try {
58962
- const stat2 = fs33.statSync(filePath);
59033
+ const stat2 = fs34.statSync(filePath);
58963
59034
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
58964
59035
  return null;
58965
59036
  }
58966
- const content = fs33.readFileSync(filePath, "utf-8");
59037
+ const content = fs34.readFileSync(filePath, "utf-8");
58967
59038
  return estimateCyclomaticComplexity(content);
58968
59039
  } catch {
58969
59040
  return null;
@@ -58974,7 +59045,7 @@ async function computeComplexityDelta(files, workingDir) {
58974
59045
  const analyzedFiles = [];
58975
59046
  for (const file3 of files) {
58976
59047
  const fullPath = path47.isAbsolute(file3) ? file3 : path47.join(workingDir, file3);
58977
- if (!fs33.existsSync(fullPath)) {
59048
+ if (!fs34.existsSync(fullPath)) {
58978
59049
  continue;
58979
59050
  }
58980
59051
  const complexity = getComplexityForFile2(fullPath);
@@ -59095,7 +59166,7 @@ function countGoExports(content) {
59095
59166
  }
59096
59167
  function getExportCountForFile(filePath) {
59097
59168
  try {
59098
- const content = fs33.readFileSync(filePath, "utf-8");
59169
+ const content = fs34.readFileSync(filePath, "utf-8");
59099
59170
  const ext = path47.extname(filePath).toLowerCase();
59100
59171
  switch (ext) {
59101
59172
  case ".ts":
@@ -59123,7 +59194,7 @@ async function computePublicApiDelta(files, workingDir) {
59123
59194
  const analyzedFiles = [];
59124
59195
  for (const file3 of files) {
59125
59196
  const fullPath = path47.isAbsolute(file3) ? file3 : path47.join(workingDir, file3);
59126
- if (!fs33.existsSync(fullPath)) {
59197
+ if (!fs34.existsSync(fullPath)) {
59127
59198
  continue;
59128
59199
  }
59129
59200
  const exports = getExportCountForFile(fullPath);
@@ -59157,15 +59228,15 @@ async function computeDuplicationRatio(files, workingDir) {
59157
59228
  const analyzedFiles = [];
59158
59229
  for (const file3 of files) {
59159
59230
  const fullPath = path47.isAbsolute(file3) ? file3 : path47.join(workingDir, file3);
59160
- if (!fs33.existsSync(fullPath)) {
59231
+ if (!fs34.existsSync(fullPath)) {
59161
59232
  continue;
59162
59233
  }
59163
59234
  try {
59164
- const stat2 = fs33.statSync(fullPath);
59235
+ const stat2 = fs34.statSync(fullPath);
59165
59236
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
59166
59237
  continue;
59167
59238
  }
59168
- const content = fs33.readFileSync(fullPath, "utf-8");
59239
+ const content = fs34.readFileSync(fullPath, "utf-8");
59169
59240
  const lines = content.split(`
59170
59241
  `).filter((line) => line.trim().length > 0);
59171
59242
  if (lines.length < MIN_DUPLICATION_LINES) {
@@ -59341,7 +59412,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
59341
59412
  let testLines = 0;
59342
59413
  let codeLines = 0;
59343
59414
  const srcDir = path47.join(workingDir, "src");
59344
- if (fs33.existsSync(srcDir)) {
59415
+ if (fs34.existsSync(srcDir)) {
59345
59416
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
59346
59417
  codeLines += lines;
59347
59418
  });
@@ -59349,14 +59420,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
59349
59420
  const possibleSrcDirs = ["lib", "app", "source", "core"];
59350
59421
  for (const dir of possibleSrcDirs) {
59351
59422
  const dirPath = path47.join(workingDir, dir);
59352
- if (fs33.existsSync(dirPath)) {
59423
+ if (fs34.existsSync(dirPath)) {
59353
59424
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
59354
59425
  codeLines += lines;
59355
59426
  });
59356
59427
  }
59357
59428
  }
59358
59429
  const testsDir = path47.join(workingDir, "tests");
59359
- if (fs33.existsSync(testsDir)) {
59430
+ if (fs34.existsSync(testsDir)) {
59360
59431
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
59361
59432
  testLines += lines;
59362
59433
  });
@@ -59364,7 +59435,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
59364
59435
  const possibleTestDirs = ["test", "__tests__", "specs"];
59365
59436
  for (const dir of possibleTestDirs) {
59366
59437
  const dirPath = path47.join(workingDir, dir);
59367
- if (fs33.existsSync(dirPath) && dirPath !== testsDir) {
59438
+ if (fs34.existsSync(dirPath) && dirPath !== testsDir) {
59368
59439
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
59369
59440
  testLines += lines;
59370
59441
  });
@@ -59376,7 +59447,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
59376
59447
  }
59377
59448
  async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
59378
59449
  try {
59379
- const entries = fs33.readdirSync(dirPath, { withFileTypes: true });
59450
+ const entries = fs34.readdirSync(dirPath, { withFileTypes: true });
59380
59451
  for (const entry of entries) {
59381
59452
  const fullPath = path47.join(dirPath, entry.name);
59382
59453
  if (entry.isDirectory()) {
@@ -59422,7 +59493,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
59422
59493
  continue;
59423
59494
  }
59424
59495
  try {
59425
- const content = fs33.readFileSync(fullPath, "utf-8");
59496
+ const content = fs34.readFileSync(fullPath, "utf-8");
59426
59497
  const lines = countCodeLines(content);
59427
59498
  callback(lines);
59428
59499
  } catch {}
@@ -59636,7 +59707,7 @@ async function qualityBudget(input, directory) {
59636
59707
  init_dist();
59637
59708
  init_manager();
59638
59709
  init_detector();
59639
- import * as fs34 from "fs";
59710
+ import * as fs35 from "fs";
59640
59711
  import * as path48 from "path";
59641
59712
  import { extname as extname9 } from "path";
59642
59713
 
@@ -60504,17 +60575,17 @@ var SEVERITY_ORDER = {
60504
60575
  };
60505
60576
  function shouldSkipFile(filePath) {
60506
60577
  try {
60507
- const stats = fs34.statSync(filePath);
60578
+ const stats = fs35.statSync(filePath);
60508
60579
  if (stats.size > MAX_FILE_SIZE_BYTES6) {
60509
60580
  return { skip: true, reason: "file too large" };
60510
60581
  }
60511
60582
  if (stats.size === 0) {
60512
60583
  return { skip: true, reason: "empty file" };
60513
60584
  }
60514
- const fd = fs34.openSync(filePath, "r");
60585
+ const fd = fs35.openSync(filePath, "r");
60515
60586
  const buffer = Buffer.alloc(8192);
60516
- const bytesRead = fs34.readSync(fd, buffer, 0, 8192, 0);
60517
- fs34.closeSync(fd);
60587
+ const bytesRead = fs35.readSync(fd, buffer, 0, 8192, 0);
60588
+ fs35.closeSync(fd);
60518
60589
  if (bytesRead > 0) {
60519
60590
  let nullCount = 0;
60520
60591
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -60553,7 +60624,7 @@ function countBySeverity(findings) {
60553
60624
  }
60554
60625
  function scanFileWithTierA(filePath, language) {
60555
60626
  try {
60556
- const content = fs34.readFileSync(filePath, "utf-8");
60627
+ const content = fs35.readFileSync(filePath, "utf-8");
60557
60628
  const findings = executeRulesSync(filePath, content, language);
60558
60629
  return findings.map((f) => ({
60559
60630
  rule_id: f.rule_id,
@@ -60601,7 +60672,7 @@ async function sastScan(input, directory, config3) {
60601
60672
  continue;
60602
60673
  }
60603
60674
  const resolvedPath = path48.isAbsolute(filePath) ? filePath : path48.resolve(directory, filePath);
60604
- if (!fs34.existsSync(resolvedPath)) {
60675
+ if (!fs35.existsSync(resolvedPath)) {
60605
60676
  _filesSkipped++;
60606
60677
  continue;
60607
60678
  }
@@ -61060,7 +61131,7 @@ async function runSecretscanWithFiles(files, directory) {
61060
61131
  }
61061
61132
  let stat2;
61062
61133
  try {
61063
- stat2 = fs35.statSync(file3);
61134
+ stat2 = fs36.statSync(file3);
61064
61135
  } catch {
61065
61136
  skippedFiles++;
61066
61137
  continue;
@@ -61071,7 +61142,7 @@ async function runSecretscanWithFiles(files, directory) {
61071
61142
  }
61072
61143
  let content;
61073
61144
  try {
61074
- const buffer = fs35.readFileSync(file3);
61145
+ const buffer = fs36.readFileSync(file3);
61075
61146
  if (buffer.includes(0)) {
61076
61147
  skippedFiles++;
61077
61148
  continue;
@@ -61471,7 +61542,7 @@ ${paginatedContent}`;
61471
61542
  init_tool();
61472
61543
  init_manager2();
61473
61544
  init_create_tool();
61474
- import * as fs36 from "fs";
61545
+ import * as fs37 from "fs";
61475
61546
  import * as path50 from "path";
61476
61547
  function detectPlaceholderContent(args2) {
61477
61548
  const issues = [];
@@ -61583,7 +61654,7 @@ async function executeSavePlan(args2, fallbackDir) {
61583
61654
  phases_count: plan.phases.length,
61584
61655
  tasks_count: tasksCount
61585
61656
  });
61586
- await fs36.promises.writeFile(markerPath, marker, "utf8");
61657
+ await fs37.promises.writeFile(markerPath, marker, "utf8");
61587
61658
  } catch {}
61588
61659
  return {
61589
61660
  success: true,
@@ -61626,7 +61697,7 @@ var save_plan = createSwarmTool({
61626
61697
  // src/tools/sbom-generate.ts
61627
61698
  init_dist();
61628
61699
  init_manager();
61629
- import * as fs37 from "fs";
61700
+ import * as fs38 from "fs";
61630
61701
  import * as path51 from "path";
61631
61702
 
61632
61703
  // src/sbom/detectors/index.ts
@@ -62473,7 +62544,7 @@ function findManifestFiles(rootDir) {
62473
62544
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
62474
62545
  function searchDir(dir) {
62475
62546
  try {
62476
- const entries = fs37.readdirSync(dir, { withFileTypes: true });
62547
+ const entries = fs38.readdirSync(dir, { withFileTypes: true });
62477
62548
  for (const entry of entries) {
62478
62549
  const fullPath = path51.join(dir, entry.name);
62479
62550
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
@@ -62500,7 +62571,7 @@ function findManifestFilesInDirs(directories, workingDir) {
62500
62571
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
62501
62572
  for (const dir of directories) {
62502
62573
  try {
62503
- const entries = fs37.readdirSync(dir, { withFileTypes: true });
62574
+ const entries = fs38.readdirSync(dir, { withFileTypes: true });
62504
62575
  for (const entry of entries) {
62505
62576
  const fullPath = path51.join(dir, entry.name);
62506
62577
  if (entry.isFile()) {
@@ -62537,7 +62608,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
62537
62608
  }
62538
62609
  function ensureOutputDir(outputDir) {
62539
62610
  try {
62540
- fs37.mkdirSync(outputDir, { recursive: true });
62611
+ fs38.mkdirSync(outputDir, { recursive: true });
62541
62612
  } catch (error93) {
62542
62613
  if (!error93 || error93.code !== "EEXIST") {
62543
62614
  throw error93;
@@ -62631,10 +62702,10 @@ var sbom_generate = createSwarmTool({
62631
62702
  for (const manifestFile of manifestFiles) {
62632
62703
  try {
62633
62704
  const fullPath = path51.isAbsolute(manifestFile) ? manifestFile : path51.join(workingDir, manifestFile);
62634
- if (!fs37.existsSync(fullPath)) {
62705
+ if (!fs38.existsSync(fullPath)) {
62635
62706
  continue;
62636
62707
  }
62637
- const content = fs37.readFileSync(fullPath, "utf-8");
62708
+ const content = fs38.readFileSync(fullPath, "utf-8");
62638
62709
  const components = detectComponents(manifestFile, content);
62639
62710
  processedFiles.push(manifestFile);
62640
62711
  if (components.length > 0) {
@@ -62648,7 +62719,7 @@ var sbom_generate = createSwarmTool({
62648
62719
  const bomJson = serializeCycloneDX(bom);
62649
62720
  const filename = generateSbomFilename();
62650
62721
  const outputPath = path51.join(outputDir, filename);
62651
- fs37.writeFileSync(outputPath, bomJson, "utf-8");
62722
+ fs38.writeFileSync(outputPath, bomJson, "utf-8");
62652
62723
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
62653
62724
  try {
62654
62725
  const timestamp = new Date().toISOString();
@@ -62690,7 +62761,7 @@ var sbom_generate = createSwarmTool({
62690
62761
  // src/tools/schema-drift.ts
62691
62762
  init_dist();
62692
62763
  init_create_tool();
62693
- import * as fs38 from "fs";
62764
+ import * as fs39 from "fs";
62694
62765
  import * as path52 from "path";
62695
62766
  var SPEC_CANDIDATES = [
62696
62767
  "openapi.json",
@@ -62732,19 +62803,19 @@ function discoverSpecFile(cwd, specFileArg) {
62732
62803
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
62733
62804
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
62734
62805
  }
62735
- const stats = fs38.statSync(resolvedPath);
62806
+ const stats = fs39.statSync(resolvedPath);
62736
62807
  if (stats.size > MAX_SPEC_SIZE) {
62737
62808
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
62738
62809
  }
62739
- if (!fs38.existsSync(resolvedPath)) {
62810
+ if (!fs39.existsSync(resolvedPath)) {
62740
62811
  throw new Error(`Spec file not found: ${resolvedPath}`);
62741
62812
  }
62742
62813
  return resolvedPath;
62743
62814
  }
62744
62815
  for (const candidate of SPEC_CANDIDATES) {
62745
62816
  const candidatePath = path52.resolve(cwd, candidate);
62746
- if (fs38.existsSync(candidatePath)) {
62747
- const stats = fs38.statSync(candidatePath);
62817
+ if (fs39.existsSync(candidatePath)) {
62818
+ const stats = fs39.statSync(candidatePath);
62748
62819
  if (stats.size <= MAX_SPEC_SIZE) {
62749
62820
  return candidatePath;
62750
62821
  }
@@ -62753,7 +62824,7 @@ function discoverSpecFile(cwd, specFileArg) {
62753
62824
  return null;
62754
62825
  }
62755
62826
  function parseSpec(specFile) {
62756
- const content = fs38.readFileSync(specFile, "utf-8");
62827
+ const content = fs39.readFileSync(specFile, "utf-8");
62757
62828
  const ext = path52.extname(specFile).toLowerCase();
62758
62829
  if (ext === ".json") {
62759
62830
  return parseJsonSpec(content);
@@ -62825,7 +62896,7 @@ function extractRoutes(cwd) {
62825
62896
  function walkDir(dir) {
62826
62897
  let entries;
62827
62898
  try {
62828
- entries = fs38.readdirSync(dir, { withFileTypes: true });
62899
+ entries = fs39.readdirSync(dir, { withFileTypes: true });
62829
62900
  } catch {
62830
62901
  return;
62831
62902
  }
@@ -62858,7 +62929,7 @@ function extractRoutes(cwd) {
62858
62929
  }
62859
62930
  function extractRoutesFromFile(filePath) {
62860
62931
  const routes = [];
62861
- const content = fs38.readFileSync(filePath, "utf-8");
62932
+ const content = fs39.readFileSync(filePath, "utf-8");
62862
62933
  const lines = content.split(/\r?\n/);
62863
62934
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
62864
62935
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -63009,7 +63080,7 @@ init_secretscan();
63009
63080
  // src/tools/symbols.ts
63010
63081
  init_tool();
63011
63082
  init_create_tool();
63012
- import * as fs39 from "fs";
63083
+ import * as fs40 from "fs";
63013
63084
  import * as path53 from "path";
63014
63085
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
63015
63086
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
@@ -63040,8 +63111,8 @@ function containsWindowsAttacks(str) {
63040
63111
  function isPathInWorkspace(filePath, workspace) {
63041
63112
  try {
63042
63113
  const resolvedPath = path53.resolve(workspace, filePath);
63043
- const realWorkspace = fs39.realpathSync(workspace);
63044
- const realResolvedPath = fs39.realpathSync(resolvedPath);
63114
+ const realWorkspace = fs40.realpathSync(workspace);
63115
+ const realResolvedPath = fs40.realpathSync(resolvedPath);
63045
63116
  const relativePath = path53.relative(realWorkspace, realResolvedPath);
63046
63117
  if (relativePath.startsWith("..") || path53.isAbsolute(relativePath)) {
63047
63118
  return false;
@@ -63061,11 +63132,11 @@ function extractTSSymbols(filePath, cwd) {
63061
63132
  }
63062
63133
  let content;
63063
63134
  try {
63064
- const stats = fs39.statSync(fullPath);
63135
+ const stats = fs40.statSync(fullPath);
63065
63136
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
63066
63137
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
63067
63138
  }
63068
- content = fs39.readFileSync(fullPath, "utf-8");
63139
+ content = fs40.readFileSync(fullPath, "utf-8");
63069
63140
  } catch {
63070
63141
  return [];
63071
63142
  }
@@ -63213,11 +63284,11 @@ function extractPythonSymbols(filePath, cwd) {
63213
63284
  }
63214
63285
  let content;
63215
63286
  try {
63216
- const stats = fs39.statSync(fullPath);
63287
+ const stats = fs40.statSync(fullPath);
63217
63288
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
63218
63289
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
63219
63290
  }
63220
- content = fs39.readFileSync(fullPath, "utf-8");
63291
+ content = fs40.readFileSync(fullPath, "utf-8");
63221
63292
  } catch {
63222
63293
  return [];
63223
63294
  }
@@ -63361,7 +63432,7 @@ init_test_runner();
63361
63432
  init_dist();
63362
63433
  init_utils();
63363
63434
  init_create_tool();
63364
- import * as fs40 from "fs";
63435
+ import * as fs41 from "fs";
63365
63436
  import * as path54 from "path";
63366
63437
  var MAX_TEXT_LENGTH = 200;
63367
63438
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
@@ -63457,7 +63528,7 @@ function isSupportedExtension(filePath) {
63457
63528
  function findSourceFiles2(dir, files = []) {
63458
63529
  let entries;
63459
63530
  try {
63460
- entries = fs40.readdirSync(dir);
63531
+ entries = fs41.readdirSync(dir);
63461
63532
  } catch {
63462
63533
  return files;
63463
63534
  }
@@ -63469,7 +63540,7 @@ function findSourceFiles2(dir, files = []) {
63469
63540
  const fullPath = path54.join(dir, entry);
63470
63541
  let stat2;
63471
63542
  try {
63472
- stat2 = fs40.statSync(fullPath);
63543
+ stat2 = fs41.statSync(fullPath);
63473
63544
  } catch {
63474
63545
  continue;
63475
63546
  }
@@ -63562,7 +63633,7 @@ var todo_extract = createSwarmTool({
63562
63633
  return JSON.stringify(errorResult, null, 2);
63563
63634
  }
63564
63635
  const scanPath = resolvedPath;
63565
- if (!fs40.existsSync(scanPath)) {
63636
+ if (!fs41.existsSync(scanPath)) {
63566
63637
  const errorResult = {
63567
63638
  error: `path not found: ${pathsInput}`,
63568
63639
  total: 0,
@@ -63572,7 +63643,7 @@ var todo_extract = createSwarmTool({
63572
63643
  return JSON.stringify(errorResult, null, 2);
63573
63644
  }
63574
63645
  const filesToScan = [];
63575
- const stat2 = fs40.statSync(scanPath);
63646
+ const stat2 = fs41.statSync(scanPath);
63576
63647
  if (stat2.isFile()) {
63577
63648
  if (isSupportedExtension(scanPath)) {
63578
63649
  filesToScan.push(scanPath);
@@ -63591,11 +63662,11 @@ var todo_extract = createSwarmTool({
63591
63662
  const allEntries = [];
63592
63663
  for (const filePath of filesToScan) {
63593
63664
  try {
63594
- const fileStat = fs40.statSync(filePath);
63665
+ const fileStat = fs41.statSync(filePath);
63595
63666
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
63596
63667
  continue;
63597
63668
  }
63598
- const content = fs40.readFileSync(filePath, "utf-8");
63669
+ const content = fs41.readFileSync(filePath, "utf-8");
63599
63670
  const entries = parseTodoComments(content, filePath, tagsSet);
63600
63671
  allEntries.push(...entries);
63601
63672
  } catch {}
@@ -63623,18 +63694,18 @@ var todo_extract = createSwarmTool({
63623
63694
  // src/tools/update-task-status.ts
63624
63695
  init_tool();
63625
63696
  init_schema();
63626
- import * as fs42 from "fs";
63697
+ import * as fs43 from "fs";
63627
63698
  import * as path56 from "path";
63628
63699
 
63629
63700
  // src/hooks/diff-scope.ts
63630
- import * as fs41 from "fs";
63701
+ import * as fs42 from "fs";
63631
63702
  import * as path55 from "path";
63632
63703
  function getDeclaredScope(taskId, directory) {
63633
63704
  try {
63634
63705
  const planPath = path55.join(directory, ".swarm", "plan.json");
63635
- if (!fs41.existsSync(planPath))
63706
+ if (!fs42.existsSync(planPath))
63636
63707
  return null;
63637
- const raw = fs41.readFileSync(planPath, "utf-8");
63708
+ const raw = fs42.readFileSync(planPath, "utf-8");
63638
63709
  const plan = JSON.parse(raw);
63639
63710
  for (const phase of plan.phases ?? []) {
63640
63711
  for (const task of phase.tasks ?? []) {
@@ -63766,10 +63837,10 @@ function hasActiveTurboMode2() {
63766
63837
  function checkReviewerGate(taskId, workingDirectory) {
63767
63838
  try {
63768
63839
  if (hasActiveTurboMode2()) {
63769
- const resolvedDir2 = workingDirectory ?? process.cwd();
63840
+ const resolvedDir2 = workingDirectory;
63770
63841
  try {
63771
63842
  const planPath = path56.join(resolvedDir2, ".swarm", "plan.json");
63772
- const planRaw = fs42.readFileSync(planPath, "utf-8");
63843
+ const planRaw = fs43.readFileSync(planPath, "utf-8");
63773
63844
  const plan = JSON.parse(planRaw);
63774
63845
  for (const planPhase of plan.phases ?? []) {
63775
63846
  for (const task of planPhase.tasks ?? []) {
@@ -63786,10 +63857,10 @@ function checkReviewerGate(taskId, workingDirectory) {
63786
63857
  }
63787
63858
  } catch {}
63788
63859
  }
63789
- const resolvedDir = workingDirectory ?? process.cwd();
63860
+ const resolvedDir = workingDirectory;
63790
63861
  try {
63791
63862
  const evidencePath = path56.join(resolvedDir, ".swarm", "evidence", `${taskId}.json`);
63792
- const raw = fs42.readFileSync(evidencePath, "utf-8");
63863
+ const raw = fs43.readFileSync(evidencePath, "utf-8");
63793
63864
  const evidence = JSON.parse(raw);
63794
63865
  if (evidence?.required_gates && Array.isArray(evidence.required_gates) && evidence?.gates) {
63795
63866
  const allGatesMet = evidence.required_gates.every((gate) => evidence.gates[gate] != null);
@@ -63828,9 +63899,9 @@ function checkReviewerGate(taskId, workingDirectory) {
63828
63899
  stateEntries.push(`${sessionId}: ${state2}`);
63829
63900
  }
63830
63901
  try {
63831
- const resolvedDir2 = workingDirectory ?? process.cwd();
63902
+ const resolvedDir2 = workingDirectory;
63832
63903
  const planPath = path56.join(resolvedDir2, ".swarm", "plan.json");
63833
- const planRaw = fs42.readFileSync(planPath, "utf-8");
63904
+ const planRaw = fs43.readFileSync(planPath, "utf-8");
63834
63905
  const plan = JSON.parse(planRaw);
63835
63906
  for (const planPhase of plan.phases ?? []) {
63836
63907
  for (const task of planPhase.tasks ?? []) {
@@ -63890,7 +63961,7 @@ function checkReviewerGate(taskId, workingDirectory) {
63890
63961
  }
63891
63962
  async function checkReviewerGateWithScope(taskId, workingDirectory) {
63892
63963
  const result = checkReviewerGate(taskId, workingDirectory);
63893
- const scopeWarning = await validateDiffScope(taskId, workingDirectory ?? process.cwd()).catch(() => null);
63964
+ const scopeWarning = await validateDiffScope(taskId, workingDirectory).catch(() => null);
63894
63965
  if (!scopeWarning)
63895
63966
  return result;
63896
63967
  return {
@@ -64024,9 +64095,9 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
64024
64095
  }
64025
64096
  const resolvedDir = path56.resolve(normalizedDir);
64026
64097
  try {
64027
- const realPath = fs42.realpathSync(resolvedDir);
64098
+ const realPath = fs43.realpathSync(resolvedDir);
64028
64099
  const planPath = path56.join(realPath, ".swarm", "plan.json");
64029
- if (!fs42.existsSync(planPath)) {
64100
+ if (!fs43.existsSync(planPath)) {
64030
64101
  return {
64031
64102
  success: false,
64032
64103
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -64047,9 +64118,9 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
64047
64118
  }
64048
64119
  } else {
64049
64120
  if (!fallbackDir) {
64050
- console.warn("[update-task-status] fallbackDir is undefined, falling back to process.cwd()");
64121
+ console.warn("[update-task-status] fallbackDir is undefined");
64051
64122
  }
64052
- directory = fallbackDir || process.cwd();
64123
+ directory = fallbackDir;
64053
64124
  }
64054
64125
  if (args2.status === "completed") {
64055
64126
  recoverTaskStateFromDelegations(args2.task_id);
@@ -64107,17 +64178,21 @@ var update_task_status = createSwarmTool({
64107
64178
  init_utils();
64108
64179
 
64109
64180
  // src/utils/tool-output.ts
64110
- function truncateToolOutput(output, maxLines, toolName) {
64181
+ function truncateToolOutput(output, maxLines, toolName, tailLines = 10) {
64111
64182
  if (!output) {
64112
64183
  return output;
64113
64184
  }
64185
+ if (tailLines >= maxLines) {
64186
+ tailLines = Math.floor(maxLines / 2);
64187
+ }
64114
64188
  const lines = output.split(`
64115
64189
  `);
64116
64190
  if (lines.length <= maxLines) {
64117
64191
  return output;
64118
64192
  }
64119
64193
  const omittedCount = lines.length - maxLines;
64120
- const truncated = lines.slice(0, maxLines);
64194
+ const headLines = lines.slice(0, maxLines - tailLines);
64195
+ const tailContent = lines.slice(-tailLines);
64121
64196
  const footerLines = [];
64122
64197
  footerLines.push("");
64123
64198
  footerLines.push(`[... ${omittedCount} line${omittedCount === 1 ? "" : "s"} omitted ...]`);
@@ -64125,7 +64200,9 @@ function truncateToolOutput(output, maxLines, toolName) {
64125
64200
  footerLines.push(`Tool: ${toolName}`);
64126
64201
  }
64127
64202
  footerLines.push("Use /swarm retrieve <id> to get the full content");
64128
- return `${truncated.join(`
64203
+ return `${headLines.join(`
64204
+ `)}
64205
+ ${tailContent.join(`
64129
64206
  `)}
64130
64207
  ${footerLines.join(`
64131
64208
  `)}`;
@@ -64166,7 +64243,7 @@ var OpenCodeSwarm = async (ctx) => {
64166
64243
  console.warn("");
64167
64244
  }
64168
64245
  const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
64169
- const guardrailsHooks = createGuardrailsHooks(ctx.directory, guardrailsConfig);
64246
+ const guardrailsHooks = createGuardrailsHooks(ctx.directory, undefined, guardrailsConfig);
64170
64247
  const watchdogConfig = WatchdogConfigSchema.parse(config3.watchdog ?? {});
64171
64248
  const advisoryInjector = (sessionId, message) => {
64172
64249
  const s = swarmState.agentSessions.get(sessionId);
@@ -64556,17 +64633,24 @@ var OpenCodeSwarm = async (ctx) => {
64556
64633
  await compactionServiceHook.toolAfter(input, output);
64557
64634
  const toolOutputConfig = config3.tool_output;
64558
64635
  if (toolOutputConfig && toolOutputConfig.truncation_enabled !== false && typeof output.output === "string") {
64559
- const skipTools = [
64636
+ const defaultTruncatableTools = new Set([
64637
+ "diff",
64638
+ "symbols",
64639
+ "bash",
64640
+ "shell",
64641
+ "test_runner",
64642
+ "lint",
64560
64643
  "pre_check_batch",
64644
+ "complexity_hotspots",
64561
64645
  "pkg_audit",
64562
- "schema_drift",
64563
- "sbom_generate"
64564
- ];
64565
- if (!skipTools.includes(input.tool)) {
64566
- const maxLines = toolOutputConfig.per_tool?.[input.tool] ?? toolOutputConfig.max_lines ?? 150;
64567
- if (input.tool === "diff" || input.tool === "symbols") {
64568
- output.output = truncateToolOutput(output.output, maxLines, input.tool);
64569
- }
64646
+ "sbom_generate",
64647
+ "schema_drift"
64648
+ ]);
64649
+ const configuredTools = toolOutputConfig.truncation_tools;
64650
+ const truncatableTools = configuredTools && configuredTools.length > 0 ? new Set(configuredTools) : defaultTruncatableTools;
64651
+ const maxLines = toolOutputConfig.per_tool?.[input.tool] ?? toolOutputConfig.max_lines ?? 150;
64652
+ if (truncatableTools.has(input.tool)) {
64653
+ output.output = truncateToolOutput(output.output, maxLines, input.tool, 10);
64570
64654
  }
64571
64655
  }
64572
64656
  const normalizedTool = input.tool.replace(/^[^:]+[:.]/, "");