opencode-swarm 6.73.1 → 6.74.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
@@ -14365,7 +14365,7 @@ var init_zod = __esm(() => {
14365
14365
  });
14366
14366
 
14367
14367
  // src/config/evidence-schema.ts
14368
- var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
14368
+ var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastFindingSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
14369
14369
  var init_evidence_schema = __esm(() => {
14370
14370
  init_zod();
14371
14371
  EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
@@ -14504,19 +14504,20 @@ var init_evidence_schema = __esm(() => {
14504
14504
  files_with_findings: exports_external.number().int(),
14505
14505
  findings_count: exports_external.number().int()
14506
14506
  });
14507
+ SastFindingSchema = exports_external.object({
14508
+ rule_id: exports_external.string(),
14509
+ severity: exports_external.enum(["critical", "high", "medium", "low"]),
14510
+ message: exports_external.string(),
14511
+ location: exports_external.object({
14512
+ file: exports_external.string(),
14513
+ line: exports_external.number().int(),
14514
+ column: exports_external.number().int().optional()
14515
+ }),
14516
+ remediation: exports_external.string().optional()
14517
+ });
14507
14518
  SastEvidenceSchema = BaseEvidenceSchema.extend({
14508
14519
  type: exports_external.literal("sast"),
14509
- findings: exports_external.array(exports_external.object({
14510
- rule_id: exports_external.string(),
14511
- severity: exports_external.enum(["critical", "high", "medium", "low"]),
14512
- message: exports_external.string(),
14513
- location: exports_external.object({
14514
- file: exports_external.string(),
14515
- line: exports_external.number().int(),
14516
- column: exports_external.number().int().optional()
14517
- }),
14518
- remediation: exports_external.string().optional()
14519
- })).default([]),
14520
+ findings: exports_external.array(SastFindingSchema).default([]),
14520
14521
  engine: exports_external.enum(["tier_a", "tier_a+tier_b"]),
14521
14522
  files_scanned: exports_external.number().int(),
14522
14523
  findings_count: exports_external.number().int(),
@@ -14525,7 +14526,10 @@ var init_evidence_schema = __esm(() => {
14525
14526
  high: exports_external.number().int(),
14526
14527
  medium: exports_external.number().int(),
14527
14528
  low: exports_external.number().int()
14528
- })
14529
+ }),
14530
+ new_findings: exports_external.array(SastFindingSchema).optional(),
14531
+ pre_existing_findings: exports_external.array(SastFindingSchema).optional(),
14532
+ baseline_used: exports_external.boolean().optional()
14529
14533
  });
14530
14534
  SbomEvidenceSchema = BaseEvidenceSchema.extend({
14531
14535
  type: exports_external.literal("sbom"),
@@ -54529,6 +54533,9 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
54529
54533
  5a-bis. **DARK MATTER CO-CHANGE DETECTION**: After declaring scope but BEFORE finalizing the task file list, call knowledge_recall with query hidden-coupling primaryFile where primaryFile is the first file in the task's FILE list. Extract primaryFile from the task's FILE list (first file = primary). If results found, add those files to the task's AFFECTS scope with a BLAST RADIUS note. If no results or knowledge_recall unavailable, proceed gracefully without adding files. This is advisory \u2014 the architect may exclude files from scope if they are unrelated to the current task. Delegate to {{AGENT_PREFIX}}coder only after scope is declared.
54530
54534
 
54531
54535
  5b-PRE (required): Call \`declare_scope({ taskId, files })\` with the EXACT file list for this task \u2014 including any co-change files surfaced by 5a-bis. Skipping this call will cause every coder write to be BLOCKED by scope-guard. No \`declare_scope\` \u2192 no 5b delegation. See Rule 1a.
54536
+ 5b-BASE (required, once per task): Call \`sast_scan\` with \`{ capture_baseline: true, phase: <N>, changed_files: <files from 5b-PRE> }\` where \`<N>\` is the current phase number (extract from current task ID: task "3.2" \u2192 phase 3, task "1.5" \u2192 phase 1). The tool maintains \`.swarm/evidence/{phase}/sast-baseline.json\` as a phase-scoped, incrementally merged baseline of pre-existing SAST findings. Calling twice for the same files is safe (idempotent merge). Do NOT re-capture mid-task.
54537
+ \u2192 REQUIRED: Print "sast-baseline: [WRITTEN \u2014 N fingerprints | MERGED \u2014 N fingerprints | SKIPPED \u2014 gate disabled | ERROR \u2014 details]"
54538
+ \u2192 Subsequent \`pre_check_batch\` calls with \`phase: <N>\` will automatically diff against this baseline \u2014 only NEW findings (not in baseline) drive the fail verdict.
54532
54539
  5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
54533
54540
  5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. If COMPATIBILITY SIGNALS=INCOMPATIBLE or MIGRATION_SURFACE=yes \u2192 coder retry. If COMPATIBILITY SIGNALS=COMPATIBLE and MIGRATION_SURFACE=no \u2192 proceed.
54534
54541
  \u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
@@ -54542,18 +54549,19 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
54542
54549
  \u2192 REQUIRED: Print "lint: [PASS | FAIL \u2014 details]"
54543
54550
  5h. Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
54544
54551
  \u2192 REQUIRED: Print "buildcheck: [PASS | FAIL | SKIPPED \u2014 no toolchain]"
54545
- 5i. Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
54552
+ 5i. Run \`pre_check_batch\` tool with \`phase: <N>\` (same phase number used in 5b-BASE) \u2192 runs four verification tools in parallel (max 4 concurrent):
54546
54553
  - lint:check (code quality verification)
54547
54554
  - secretscan (secret detection)
54548
- - sast_scan (static security analysis)
54555
+ - sast_scan (static security analysis \u2014 diffs against phase baseline when phase provided)
54549
54556
  - quality_budget (maintainability metrics)
54550
54557
  \u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
54558
+ \u2192 sast_scan result may include { new_findings, pre_existing_findings, baseline_used } when baseline diff is active.
54551
54559
  \u2192 If ALL FOUR tools have ran === false (lint.ran === false && secretscan.ran === false && sast_scan.ran === false && quality_budget.ran === false):
54552
54560
  \u2192 This is a SKIP - no tools actually ran. Print "pre_check_batch: SKIP \u2014 all tools ran===false (no files to check or tools not available)" and proceed to {{AGENT_PREFIX}}reviewer.
54553
54561
  \u2192 Else if gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to {{AGENT_PREFIX}}coder with specific tool failures. Do NOT call {{AGENT_PREFIX}}reviewer.
54554
- \u2192 If gates_passed === true AND sast_preexisting_findings is present: proceed to {{AGENT_PREFIX}}reviewer. Include the pre-existing SAST findings in the reviewer delegation context with instruction: "SAST TRIAGE REQUIRED: The following HIGH/CRITICAL SAST findings exist on unchanged lines in this changeset. Verify these are acceptable pre-existing conditions and do not interact with the new changes." Do NOT return to coder for pre-existing findings on unchanged code.
54562
+ \u2192 If gates_passed === true AND sast_preexisting_findings is present: proceed to {{AGENT_PREFIX}}reviewer. Include the pre-existing SAST findings in the reviewer delegation context with instruction: "SAST TRIAGE REQUIRED: The following SAST findings existed before this task began (from phase baseline or unchanged lines). Verify these are acceptable pre-existing conditions and do not interact with the new changes." Do NOT return to coder for pre-existing findings.
54555
54563
  \u2192 If gates_passed === true (no sast_preexisting_findings): proceed to {{AGENT_PREFIX}}reviewer.
54556
- \u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | PASS \u2014 pre-existing SAST findings on unchanged lines (N findings, reviewer triage) | FAIL \u2014 [gate]: [details]]"
54564
+ \u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | PASS \u2014 pre-existing SAST findings (N findings, reviewer triage) | FAIL \u2014 [gate]: [details]]"
54557
54565
 
54558
54566
  \u26A0\uFE0F pre_check_batch SCOPE BOUNDARY:
54559
54567
  pre_check_batch runs FOUR automated tools: lint:check, secretscan, sast_scan, quality_budget.
@@ -61583,7 +61591,7 @@ var init_runtime = __esm(() => {
61583
61591
 
61584
61592
  // src/index.ts
61585
61593
  init_agents();
61586
- import * as path95 from "path";
61594
+ import * as path96 from "path";
61587
61595
 
61588
61596
  // src/background/index.ts
61589
61597
  init_event_bus();
@@ -76602,8 +76610,8 @@ var placeholder_scan = createSwarmTool({
76602
76610
  });
76603
76611
  // src/tools/pre-check-batch.ts
76604
76612
  init_dist();
76605
- import * as fs64 from "fs";
76606
- import * as path78 from "path";
76613
+ import * as fs65 from "fs";
76614
+ import * as path79 from "path";
76607
76615
  init_manager2();
76608
76616
  init_utils();
76609
76617
  init_create_tool();
@@ -76738,8 +76746,8 @@ var quality_budget = createSwarmTool({
76738
76746
  init_dist();
76739
76747
  init_manager2();
76740
76748
  init_detector();
76741
- import * as fs63 from "fs";
76742
- import * as path77 from "path";
76749
+ import * as fs64 from "fs";
76750
+ import * as path78 from "path";
76743
76751
  import { extname as extname18 } from "path";
76744
76752
 
76745
76753
  // src/sast/rules/c.ts
@@ -77628,6 +77636,303 @@ async function runSemgrep(options) {
77628
77636
  // src/tools/sast-scan.ts
77629
77637
  init_utils();
77630
77638
  init_create_tool();
77639
+
77640
+ // src/tools/sast-baseline.ts
77641
+ init_utils2();
77642
+ import * as crypto8 from "crypto";
77643
+ import * as fs63 from "fs";
77644
+ import * as path77 from "path";
77645
+ var BASELINE_SCHEMA_VERSION = "1.0.0";
77646
+ var MAX_BASELINE_FINDINGS = 2000;
77647
+ var MAX_BASELINE_BYTES = 2 * 1048576;
77648
+ var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
77649
+ function normalizeFindingPath(directory, file3) {
77650
+ const resolved = path77.isAbsolute(file3) ? file3 : path77.resolve(directory, file3);
77651
+ const rel = path77.relative(path77.resolve(directory), resolved);
77652
+ return rel.replace(/\\/g, "/");
77653
+ }
77654
+ function baselineRelPath(phase) {
77655
+ return path77.join("evidence", String(phase), "sast-baseline.json");
77656
+ }
77657
+ function tempRelPath(phase) {
77658
+ return path77.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
77659
+ }
77660
+ function lockRelPath(phase) {
77661
+ return path77.join("evidence", String(phase), "sast-baseline.json.lock");
77662
+ }
77663
+ function getLine(lines, idx) {
77664
+ if (idx < 0 || idx >= lines.length)
77665
+ return "";
77666
+ return (lines[idx] ?? "").trim();
77667
+ }
77668
+ function fingerprintFinding(finding, directory, occurrenceIndex) {
77669
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77670
+ if (relFile.startsWith("..")) {
77671
+ return {
77672
+ fingerprint: `${relFile}|${finding.rule_id}|L${finding.location.line}|UNSTABLE|#${occurrenceIndex}`,
77673
+ stable: false
77674
+ };
77675
+ }
77676
+ const lineNum = finding.location.line;
77677
+ try {
77678
+ const content = fs63.readFileSync(finding.location.file, "utf-8");
77679
+ const lines = content.split(`
77680
+ `);
77681
+ const idx = lineNum - 1;
77682
+ const window2 = [
77683
+ getLine(lines, idx - 1),
77684
+ getLine(lines, idx),
77685
+ getLine(lines, idx + 1)
77686
+ ].join(`
77687
+ `);
77688
+ const hash3 = crypto8.createHash("sha256").update(window2).digest("hex").slice(0, 16);
77689
+ return {
77690
+ fingerprint: `${relFile}|${finding.rule_id}|${hash3}|#${occurrenceIndex}`,
77691
+ stable: true
77692
+ };
77693
+ } catch {
77694
+ return {
77695
+ fingerprint: `${relFile}|${finding.rule_id}|L${lineNum}|UNSTABLE|#${occurrenceIndex}`,
77696
+ stable: false
77697
+ };
77698
+ }
77699
+ }
77700
+ function assignOccurrenceIndices(findings, directory) {
77701
+ const countMap = new Map;
77702
+ return findings.map((finding) => {
77703
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77704
+ const lineNum = finding.location.line;
77705
+ let baseKey;
77706
+ try {
77707
+ if (relFile.startsWith(".."))
77708
+ throw new Error("escapes workspace");
77709
+ const content = fs63.readFileSync(finding.location.file, "utf-8");
77710
+ const lines = content.split(`
77711
+ `);
77712
+ const idx = lineNum - 1;
77713
+ const window2 = [
77714
+ getLine(lines, idx - 1),
77715
+ getLine(lines, idx),
77716
+ getLine(lines, idx + 1)
77717
+ ].join(`
77718
+ `);
77719
+ const hash3 = crypto8.createHash("sha256").update(window2).digest("hex").slice(0, 16);
77720
+ baseKey = `${relFile}|${finding.rule_id}|${hash3}`;
77721
+ } catch {
77722
+ baseKey = `${relFile}|${finding.rule_id}|L${lineNum}|UNSTABLE`;
77723
+ }
77724
+ const occIdx = countMap.get(baseKey) ?? 0;
77725
+ countMap.set(baseKey, occIdx + 1);
77726
+ const fp = fingerprintFinding(finding, directory, occIdx);
77727
+ return {
77728
+ finding,
77729
+ index: occIdx,
77730
+ stable: fp.stable,
77731
+ fingerprint: fp.fingerprint
77732
+ };
77733
+ });
77734
+ }
77735
+ async function acquireLock(lockPath) {
77736
+ for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
77737
+ try {
77738
+ const fd = fs63.openSync(lockPath, "wx");
77739
+ fs63.closeSync(fd);
77740
+ return () => {
77741
+ try {
77742
+ fs63.unlinkSync(lockPath);
77743
+ } catch {}
77744
+ };
77745
+ } catch {
77746
+ if (attempt < LOCK_RETRY_DELAYS_MS.length) {
77747
+ await new Promise((resolve31) => setTimeout(resolve31, LOCK_RETRY_DELAYS_MS[attempt]));
77748
+ }
77749
+ }
77750
+ }
77751
+ return () => {};
77752
+ }
77753
+ function validatePhase(phase) {
77754
+ if (!Number.isInteger(phase) || phase < 1) {
77755
+ return "Invalid phase: must be a positive integer";
77756
+ }
77757
+ return null;
77758
+ }
77759
+ async function captureOrMergeBaseline(directory, phase, findings, engine, scannedFiles, opts) {
77760
+ const phaseError = validatePhase(phase);
77761
+ if (phaseError)
77762
+ return { status: "error", message: phaseError };
77763
+ if (!scannedFiles || scannedFiles.length === 0) {
77764
+ return {
77765
+ status: "error",
77766
+ message: "capture_baseline requires non-empty changed_files"
77767
+ };
77768
+ }
77769
+ let baselinePath;
77770
+ let tempPath;
77771
+ let lockPath;
77772
+ try {
77773
+ baselinePath = validateSwarmPath(directory, baselineRelPath(phase));
77774
+ tempPath = validateSwarmPath(directory, tempRelPath(phase));
77775
+ lockPath = validateSwarmPath(directory, lockRelPath(phase));
77776
+ } catch (e) {
77777
+ return {
77778
+ status: "error",
77779
+ message: e instanceof Error ? e.message : "Path validation failed"
77780
+ };
77781
+ }
77782
+ fs63.mkdirSync(path77.dirname(baselinePath), { recursive: true });
77783
+ const releaseLock = await acquireLock(lockPath);
77784
+ try {
77785
+ let existing = null;
77786
+ try {
77787
+ const raw = fs63.readFileSync(baselinePath, "utf-8");
77788
+ const parsed = JSON.parse(raw);
77789
+ if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
77790
+ existing = parsed;
77791
+ }
77792
+ } catch {}
77793
+ const scannedRelFiles = new Set(scannedFiles.map((f) => normalizeFindingPath(directory, f)));
77794
+ const indexed = assignOccurrenceIndices(findings, directory);
77795
+ if (existing && !opts?.force) {
77796
+ const prunedFingerprints = existing.fingerprints.filter((fp) => {
77797
+ const relFile = fp.slice(0, fp.indexOf("|"));
77798
+ return !scannedRelFiles.has(relFile);
77799
+ });
77800
+ const prunedSnapshot = existing.findings_snapshot.filter((f) => {
77801
+ return !scannedRelFiles.has(normalizeFindingPath(directory, f.location.file));
77802
+ });
77803
+ const prunedFilesIndexed = existing.files_indexed.filter((f) => !scannedRelFiles.has(f));
77804
+ const mergedFingerprints = [
77805
+ ...prunedFingerprints,
77806
+ ...indexed.map((i2) => i2.fingerprint)
77807
+ ];
77808
+ const mergedSnapshot = [
77809
+ ...prunedSnapshot,
77810
+ ...indexed.map((i2) => i2.finding)
77811
+ ];
77812
+ const mergedFilesIndexed = [
77813
+ ...prunedFilesIndexed,
77814
+ ...Array.from(scannedRelFiles)
77815
+ ];
77816
+ const truncated2 = mergedSnapshot.length > MAX_BASELINE_FINDINGS;
77817
+ const cappedSnapshot2 = truncated2 ? mergedSnapshot.slice(-MAX_BASELINE_FINDINGS) : mergedSnapshot;
77818
+ const cappedFingerprints2 = truncated2 ? mergedFingerprints.slice(-MAX_BASELINE_FINDINGS) : mergedFingerprints;
77819
+ let cappedFilesIndexed = mergedFilesIndexed;
77820
+ if (truncated2) {
77821
+ const survivingFiles = new Set;
77822
+ for (const finding of cappedSnapshot2) {
77823
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77824
+ survivingFiles.add(relFile);
77825
+ }
77826
+ cappedFilesIndexed = Array.from(survivingFiles);
77827
+ }
77828
+ const now2 = new Date().toISOString();
77829
+ const bundle2 = {
77830
+ schema_version: BASELINE_SCHEMA_VERSION,
77831
+ phase,
77832
+ created_at: existing.created_at,
77833
+ updated_at: now2,
77834
+ engine,
77835
+ files_indexed: cappedFilesIndexed,
77836
+ fingerprints: cappedFingerprints2,
77837
+ findings_snapshot: cappedSnapshot2,
77838
+ truncated: truncated2
77839
+ };
77840
+ const json4 = JSON.stringify(bundle2, null, 2);
77841
+ if (json4.length > MAX_BASELINE_BYTES) {
77842
+ return {
77843
+ status: "error",
77844
+ message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
77845
+ };
77846
+ }
77847
+ fs63.writeFileSync(tempPath, json4, "utf-8");
77848
+ fs63.renameSync(tempPath, baselinePath);
77849
+ return {
77850
+ status: "merged",
77851
+ path: baselinePath,
77852
+ fingerprint_count: cappedFingerprints2.length
77853
+ };
77854
+ }
77855
+ const newFingerprints = indexed.map((i2) => i2.fingerprint);
77856
+ const newSnapshot = indexed.map((i2) => i2.finding);
77857
+ const truncated = newSnapshot.length > MAX_BASELINE_FINDINGS;
77858
+ const cappedSnapshot = truncated ? newSnapshot.slice(0, MAX_BASELINE_FINDINGS) : newSnapshot;
77859
+ const cappedFingerprints = truncated ? newFingerprints.slice(0, MAX_BASELINE_FINDINGS) : newFingerprints;
77860
+ const now = new Date().toISOString();
77861
+ const bundle = {
77862
+ schema_version: BASELINE_SCHEMA_VERSION,
77863
+ phase,
77864
+ created_at: now,
77865
+ updated_at: now,
77866
+ engine,
77867
+ files_indexed: Array.from(scannedRelFiles),
77868
+ fingerprints: cappedFingerprints,
77869
+ findings_snapshot: cappedSnapshot,
77870
+ truncated
77871
+ };
77872
+ const json3 = JSON.stringify(bundle, null, 2);
77873
+ if (json3.length > MAX_BASELINE_BYTES) {
77874
+ return {
77875
+ status: "error",
77876
+ message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
77877
+ };
77878
+ }
77879
+ fs63.writeFileSync(tempPath, json3, "utf-8");
77880
+ fs63.renameSync(tempPath, baselinePath);
77881
+ return {
77882
+ status: "written",
77883
+ path: baselinePath,
77884
+ fingerprint_count: cappedFingerprints.length
77885
+ };
77886
+ } finally {
77887
+ releaseLock();
77888
+ }
77889
+ }
77890
+ function loadBaseline(directory, phase) {
77891
+ const phaseError = validatePhase(phase);
77892
+ if (phaseError) {
77893
+ return { status: "invalid_schema", errors: [phaseError] };
77894
+ }
77895
+ let baselinePath;
77896
+ try {
77897
+ baselinePath = validateSwarmPath(directory, baselineRelPath(phase));
77898
+ } catch (e) {
77899
+ return {
77900
+ status: "invalid_schema",
77901
+ errors: [e instanceof Error ? e.message : "Path validation failed"]
77902
+ };
77903
+ }
77904
+ try {
77905
+ const raw = fs63.readFileSync(baselinePath, "utf-8");
77906
+ const parsed = JSON.parse(raw);
77907
+ if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
77908
+ return {
77909
+ status: "invalid_schema",
77910
+ errors: [`Unknown schema version: ${String(parsed.schema_version)}`]
77911
+ };
77912
+ }
77913
+ if (!Array.isArray(parsed.fingerprints)) {
77914
+ return {
77915
+ status: "invalid_schema",
77916
+ errors: ["Missing or invalid fingerprints array"]
77917
+ };
77918
+ }
77919
+ return {
77920
+ status: "found",
77921
+ fingerprints: new Set(parsed.fingerprints),
77922
+ bundle: parsed
77923
+ };
77924
+ } catch (e) {
77925
+ if (e.code === "ENOENT") {
77926
+ return { status: "not_found" };
77927
+ }
77928
+ return {
77929
+ status: "invalid_schema",
77930
+ errors: [e instanceof Error ? e.message : "Failed to read baseline"]
77931
+ };
77932
+ }
77933
+ }
77934
+
77935
+ // src/tools/sast-scan.ts
77631
77936
  var MAX_FILE_SIZE_BYTES8 = 512 * 1024;
77632
77937
  var MAX_FILES_SCANNED2 = 1000;
77633
77938
  var MAX_FINDINGS2 = 100;
@@ -77639,17 +77944,17 @@ var SEVERITY_ORDER = {
77639
77944
  };
77640
77945
  function shouldSkipFile(filePath) {
77641
77946
  try {
77642
- const stats = fs63.statSync(filePath);
77947
+ const stats = fs64.statSync(filePath);
77643
77948
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
77644
77949
  return { skip: true, reason: "file too large" };
77645
77950
  }
77646
77951
  if (stats.size === 0) {
77647
77952
  return { skip: true, reason: "empty file" };
77648
77953
  }
77649
- const fd = fs63.openSync(filePath, "r");
77954
+ const fd = fs64.openSync(filePath, "r");
77650
77955
  const buffer = Buffer.alloc(8192);
77651
- const bytesRead = fs63.readSync(fd, buffer, 0, 8192, 0);
77652
- fs63.closeSync(fd);
77956
+ const bytesRead = fs64.readSync(fd, buffer, 0, 8192, 0);
77957
+ fs64.closeSync(fd);
77653
77958
  if (bytesRead > 0) {
77654
77959
  let nullCount = 0;
77655
77960
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -77688,7 +77993,7 @@ function countBySeverity(findings) {
77688
77993
  }
77689
77994
  function scanFileWithTierA(filePath, language) {
77690
77995
  try {
77691
- const content = fs63.readFileSync(filePath, "utf-8");
77996
+ const content = fs64.readFileSync(filePath, "utf-8");
77692
77997
  const findings = executeRulesSync(filePath, content, language);
77693
77998
  return findings.map((f) => ({
77694
77999
  rule_id: f.rule_id,
@@ -77706,7 +78011,12 @@ function scanFileWithTierA(filePath, language) {
77706
78011
  }
77707
78012
  }
77708
78013
  async function sastScan(input, directory, config3) {
77709
- const { changed_files, severity_threshold = "medium" } = input;
78014
+ const {
78015
+ changed_files,
78016
+ severity_threshold = "medium",
78017
+ capture_baseline = false,
78018
+ phase
78019
+ } = input;
77710
78020
  if (config3?.gates?.sast_scan?.enabled === false) {
77711
78021
  return {
77712
78022
  verdict: "pass",
@@ -77727,6 +78037,7 @@ async function sastScan(input, directory, config3) {
77727
78037
  const allFindings = [];
77728
78038
  let filesScanned = 0;
77729
78039
  let _filesSkipped = 0;
78040
+ const scannedFilePaths = [];
77730
78041
  const semgrepAvailable = isSemgrepAvailable();
77731
78042
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
77732
78043
  const filesByLanguage = new Map;
@@ -77735,13 +78046,13 @@ async function sastScan(input, directory, config3) {
77735
78046
  _filesSkipped++;
77736
78047
  continue;
77737
78048
  }
77738
- const resolvedPath = path77.isAbsolute(filePath) ? filePath : path77.resolve(directory, filePath);
77739
- const resolvedDirectory = path77.resolve(directory);
77740
- if (!resolvedPath.startsWith(resolvedDirectory + path77.sep) && resolvedPath !== resolvedDirectory) {
78049
+ const resolvedPath = path78.isAbsolute(filePath) ? filePath : path78.resolve(directory, filePath);
78050
+ const resolvedDirectory = path78.resolve(directory);
78051
+ if (!resolvedPath.startsWith(resolvedDirectory + path78.sep) && resolvedPath !== resolvedDirectory) {
77741
78052
  _filesSkipped++;
77742
78053
  continue;
77743
78054
  }
77744
- if (!fs63.existsSync(resolvedPath)) {
78055
+ if (!fs64.existsSync(resolvedPath)) {
77745
78056
  _filesSkipped++;
77746
78057
  continue;
77747
78058
  }
@@ -77776,6 +78087,7 @@ async function sastScan(input, directory, config3) {
77776
78087
  }
77777
78088
  }
77778
78089
  filesScanned++;
78090
+ scannedFilePaths.push(resolvedPath);
77779
78091
  if (filesScanned >= MAX_FILES_SCANNED2) {
77780
78092
  warn(`SAST Scan: Reached maximum files limit (${MAX_FILES_SCANNED2}), stopping`);
77781
78093
  break;
@@ -77825,14 +78137,97 @@ async function sastScan(input, directory, config3) {
77825
78137
  warn(`SAST Scan: Semgrep failed, falling back to Tier A: ${error93}`);
77826
78138
  }
77827
78139
  }
77828
- let finalFindings = allFindings;
77829
- if (allFindings.length > MAX_FINDINGS2) {
77830
- finalFindings = allFindings.slice(0, MAX_FINDINGS2);
77831
- warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
78140
+ if (capture_baseline) {
78141
+ if (phase === undefined || !Number.isInteger(phase) || phase < 1) {
78142
+ const errorResult = {
78143
+ verdict: "fail",
78144
+ findings: allFindings.slice(0, MAX_FINDINGS2),
78145
+ summary: {
78146
+ engine,
78147
+ files_scanned: filesScanned,
78148
+ findings_count: Math.min(allFindings.length, MAX_FINDINGS2),
78149
+ findings_by_severity: countBySeverity(allFindings.slice(0, MAX_FINDINGS2))
78150
+ }
78151
+ };
78152
+ return errorResult;
78153
+ }
78154
+ const captureFindings = allFindings.slice(0, MAX_BASELINE_FINDINGS);
78155
+ const captureResult = await captureOrMergeBaseline(directory, phase, captureFindings, engine, scannedFilePaths);
78156
+ const captureStatus = captureResult.status === "written" ? "baseline_captured" : captureResult.status === "merged" ? "baseline_merged" : undefined;
78157
+ if (captureResult.status === "error") {
78158
+ warn(`SAST Baseline: capture failed \u2014 ${captureResult.message}`);
78159
+ }
78160
+ const finalFindings2 = allFindings.slice(0, MAX_FINDINGS2);
78161
+ const summary2 = {
78162
+ engine,
78163
+ files_scanned: filesScanned,
78164
+ findings_count: finalFindings2.length,
78165
+ findings_by_severity: countBySeverity(finalFindings2)
78166
+ };
78167
+ await saveEvidence(directory, "sast_scan", {
78168
+ task_id: "sast_scan",
78169
+ type: "sast",
78170
+ timestamp: new Date().toISOString(),
78171
+ agent: "sast_scan",
78172
+ verdict: "pass",
78173
+ summary: `Baseline capture: scanned ${filesScanned} files, recorded ${captureFindings.length} finding(s)`,
78174
+ ...summary2,
78175
+ findings: finalFindings2,
78176
+ baseline_used: false
78177
+ });
78178
+ return {
78179
+ verdict: "pass",
78180
+ findings: finalFindings2,
78181
+ summary: summary2,
78182
+ status: captureStatus,
78183
+ finding_count: captureResult.status !== "error" ? captureResult.fingerprint_count : undefined,
78184
+ baseline_used: false
78185
+ };
78186
+ }
78187
+ let newFindings;
78188
+ let preExistingFindings;
78189
+ let baselineUsed = false;
78190
+ let truncatedPreExisting = false;
78191
+ if (phase !== undefined && Number.isInteger(phase) && phase >= 1) {
78192
+ const baselineResult = loadBaseline(directory, phase);
78193
+ if (baselineResult.status === "found") {
78194
+ baselineUsed = true;
78195
+ const baselineSet = baselineResult.fingerprints;
78196
+ const indexed = assignOccurrenceIndices(allFindings, directory);
78197
+ const rawNew = [];
78198
+ const rawPreExisting = [];
78199
+ for (const { finding, stable, fingerprint } of indexed) {
78200
+ if (!stable || !baselineSet.has(fingerprint)) {
78201
+ rawNew.push(finding);
78202
+ } else {
78203
+ rawPreExisting.push(finding);
78204
+ }
78205
+ }
78206
+ newFindings = rawNew.slice(0, MAX_FINDINGS2);
78207
+ const preExistingBudget = Math.max(0, MAX_FINDINGS2 - newFindings.length);
78208
+ preExistingFindings = rawPreExisting.slice(0, preExistingBudget);
78209
+ truncatedPreExisting = rawPreExisting.length > preExistingBudget;
78210
+ } else if (baselineResult.status === "invalid_schema") {
78211
+ warn(`SAST Baseline: could not load baseline for phase ${phase} \u2014 ${baselineResult.errors.join(", ")}. Falling back to legacy behavior.`);
78212
+ }
78213
+ }
78214
+ let finalFindings;
78215
+ if (!baselineUsed) {
78216
+ finalFindings = allFindings;
78217
+ if (allFindings.length > MAX_FINDINGS2) {
78218
+ finalFindings = allFindings.slice(0, MAX_FINDINGS2);
78219
+ warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
78220
+ }
78221
+ } else {
78222
+ finalFindings = [
78223
+ ...newFindings ?? [],
78224
+ ...preExistingFindings ?? []
78225
+ ].slice(0, MAX_FINDINGS2);
77832
78226
  }
77833
78227
  const findingsBySeverity = countBySeverity(finalFindings);
78228
+ const verdictSource = baselineUsed ? newFindings ?? [] : finalFindings;
77834
78229
  let verdict = "pass";
77835
- for (const finding of finalFindings) {
78230
+ for (const finding of verdictSource) {
77836
78231
  if (meetsThreshold(finding.severity, severity_threshold)) {
77837
78232
  verdict = "fail";
77838
78233
  break;
@@ -77855,42 +78250,65 @@ async function sastScan(input, directory, config3) {
77855
78250
  verdict,
77856
78251
  summary: `Scanned ${filesScanned} files, found ${finalFindings.length} finding(s) using ${engine}`,
77857
78252
  ...summary,
77858
- findings: finalFindings
78253
+ findings: finalFindings,
78254
+ ...baselineUsed && {
78255
+ new_findings: newFindings,
78256
+ pre_existing_findings: preExistingFindings,
78257
+ baseline_used: true
78258
+ }
77859
78259
  });
77860
- return {
78260
+ const result = {
77861
78261
  verdict,
77862
78262
  findings: finalFindings,
77863
78263
  summary
77864
78264
  };
78265
+ if (baselineUsed) {
78266
+ result.new_findings = newFindings;
78267
+ result.pre_existing_findings = preExistingFindings;
78268
+ result.baseline_used = true;
78269
+ if (truncatedPreExisting)
78270
+ result.truncated_pre_existing = true;
78271
+ }
78272
+ return result;
77865
78273
  }
77866
78274
  var sast_scan = createSwarmTool({
77867
- description: "Static Application Security Testing (SAST) scan. Scans files for security vulnerabilities using built-in rules (Tier A) and optional Semgrep (Tier B). Returns structured findings with severity levels.",
78275
+ description: "Static Application Security Testing (SAST) scan. Scans files for security vulnerabilities using built-in rules (Tier A) and optional Semgrep (Tier B). Supports phase-scoped baseline diffing: set capture_baseline:true before first coder delegation to snapshot pre-existing findings; subsequent scans with the same phase only fail on NEW findings.",
77868
78276
  args: {
77869
78277
  directory: tool.schema.string().describe("Directory to scan for security vulnerabilities"),
77870
78278
  changed_files: tool.schema.array(tool.schema.string()).optional().describe("List of files to scan (leave empty to scan none)"),
77871
- severity_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().default("medium").describe("Minimum severity that causes failure")
78279
+ severity_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().default("medium").describe("Minimum severity that causes failure"),
78280
+ capture_baseline: tool.schema.boolean().optional().describe("When true, capture/merge a phase-scoped baseline of pre-existing findings. Requires phase. Subsequent scans with phase only fail on NEW findings."),
78281
+ phase: tool.schema.number().int().min(1).optional().describe("Current phase number (positive integer >= 1). Required with capture_baseline. Enables baseline diff when provided on non-capture scans.")
77872
78282
  },
77873
78283
  execute: async (args2, directory) => {
77874
78284
  let safeArgs;
77875
78285
  try {
77876
78286
  if (args2 && typeof args2 === "object") {
78287
+ const rawPhase = args2.phase;
78288
+ const rawCapture = args2.capture_baseline;
77877
78289
  safeArgs = {
77878
78290
  directory: args2.directory,
77879
78291
  changed_files: args2.changed_files,
77880
- severity_threshold: args2.severity_threshold
78292
+ severity_threshold: args2.severity_threshold,
78293
+ phase: typeof rawPhase === "number" && Number.isInteger(rawPhase) && rawPhase >= 1 ? rawPhase : undefined,
78294
+ capture_baseline: typeof rawCapture === "boolean" ? rawCapture : undefined
77881
78295
  };
77882
78296
  } else {
77883
78297
  safeArgs = {
77884
78298
  directory: undefined,
77885
78299
  changed_files: undefined,
77886
- severity_threshold: undefined
78300
+ severity_threshold: undefined,
78301
+ capture_baseline: undefined,
78302
+ phase: undefined
77887
78303
  };
77888
78304
  }
77889
78305
  } catch {
77890
78306
  safeArgs = {
77891
78307
  directory: undefined,
77892
78308
  changed_files: undefined,
77893
- severity_threshold: undefined
78309
+ severity_threshold: undefined,
78310
+ capture_baseline: undefined,
78311
+ phase: undefined
77894
78312
  };
77895
78313
  }
77896
78314
  if (safeArgs.directory === undefined) {
@@ -77913,7 +78331,9 @@ var sast_scan = createSwarmTool({
77913
78331
  }
77914
78332
  const input = {
77915
78333
  changed_files: safeArgs.changed_files ?? [],
77916
- severity_threshold: safeArgs.severity_threshold ?? "medium"
78334
+ severity_threshold: safeArgs.severity_threshold ?? "medium",
78335
+ capture_baseline: safeArgs.capture_baseline,
78336
+ phase: safeArgs.phase
77917
78337
  };
77918
78338
  const result = await sastScan(input, directory);
77919
78339
  return JSON.stringify(result, null, 2);
@@ -77939,20 +78359,20 @@ function validatePath(inputPath, baseDir, workspaceDir) {
77939
78359
  let resolved;
77940
78360
  const isWinAbs = isWindowsAbsolutePath(inputPath);
77941
78361
  if (isWinAbs) {
77942
- resolved = path78.win32.resolve(inputPath);
77943
- } else if (path78.isAbsolute(inputPath)) {
77944
- resolved = path78.resolve(inputPath);
78362
+ resolved = path79.win32.resolve(inputPath);
78363
+ } else if (path79.isAbsolute(inputPath)) {
78364
+ resolved = path79.resolve(inputPath);
77945
78365
  } else {
77946
- resolved = path78.resolve(baseDir, inputPath);
78366
+ resolved = path79.resolve(baseDir, inputPath);
77947
78367
  }
77948
- const workspaceResolved = path78.resolve(workspaceDir);
77949
- let relative18;
78368
+ const workspaceResolved = path79.resolve(workspaceDir);
78369
+ let relative19;
77950
78370
  if (isWinAbs) {
77951
- relative18 = path78.win32.relative(workspaceResolved, resolved);
78371
+ relative19 = path79.win32.relative(workspaceResolved, resolved);
77952
78372
  } else {
77953
- relative18 = path78.relative(workspaceResolved, resolved);
78373
+ relative19 = path79.relative(workspaceResolved, resolved);
77954
78374
  }
77955
- if (relative18.startsWith("..")) {
78375
+ if (relative19.startsWith("..")) {
77956
78376
  return "path traversal detected";
77957
78377
  }
77958
78378
  return null;
@@ -78015,7 +78435,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
78015
78435
  if (typeof file3 !== "string") {
78016
78436
  continue;
78017
78437
  }
78018
- const resolvedPath = path78.resolve(file3);
78438
+ const resolvedPath = path79.resolve(file3);
78019
78439
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
78020
78440
  if (validationError) {
78021
78441
  continue;
@@ -78172,7 +78592,7 @@ async function runSecretscanWithFiles(files, directory) {
78172
78592
  skippedFiles++;
78173
78593
  continue;
78174
78594
  }
78175
- const resolvedPath = path78.resolve(file3);
78595
+ const resolvedPath = path79.resolve(file3);
78176
78596
  const validationError = validatePath(resolvedPath, directory, directory);
78177
78597
  if (validationError) {
78178
78598
  skippedFiles++;
@@ -78190,14 +78610,14 @@ async function runSecretscanWithFiles(files, directory) {
78190
78610
  };
78191
78611
  }
78192
78612
  for (const file3 of validatedFiles) {
78193
- const ext = path78.extname(file3).toLowerCase();
78613
+ const ext = path79.extname(file3).toLowerCase();
78194
78614
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
78195
78615
  skippedFiles++;
78196
78616
  continue;
78197
78617
  }
78198
78618
  let stat3;
78199
78619
  try {
78200
- stat3 = fs64.statSync(file3);
78620
+ stat3 = fs65.statSync(file3);
78201
78621
  } catch {
78202
78622
  skippedFiles++;
78203
78623
  continue;
@@ -78208,7 +78628,7 @@ async function runSecretscanWithFiles(files, directory) {
78208
78628
  }
78209
78629
  let content;
78210
78630
  try {
78211
- const buffer = fs64.readFileSync(file3);
78631
+ const buffer = fs65.readFileSync(file3);
78212
78632
  if (buffer.includes(0)) {
78213
78633
  skippedFiles++;
78214
78634
  continue;
@@ -78274,10 +78694,14 @@ async function runSecretscanWithFiles(files, directory) {
78274
78694
  return errorResult;
78275
78695
  }
78276
78696
  }
78277
- async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
78697
+ async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3, phase) {
78278
78698
  const start2 = process.hrtime.bigint();
78279
78699
  try {
78280
- const result = await runWithTimeout2(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
78700
+ const result = await runWithTimeout2(sastScan({
78701
+ changed_files: changedFiles,
78702
+ severity_threshold: severityThreshold,
78703
+ phase
78704
+ }, directory, config3), TOOL_TIMEOUT_MS);
78281
78705
  return {
78282
78706
  ran: true,
78283
78707
  result,
@@ -78309,6 +78733,15 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
78309
78733
  }
78310
78734
  }
78311
78735
  var GATE_SEVERITIES = new Set(["high", "critical"]);
78736
+ var SEVERITY_ORDER_PCB = {
78737
+ low: 0,
78738
+ medium: 1,
78739
+ high: 2,
78740
+ critical: 3
78741
+ };
78742
+ function meetsThresholdForTriage(severity, threshold) {
78743
+ return (SEVERITY_ORDER_PCB[severity] ?? 0) >= (SEVERITY_ORDER_PCB[threshold] ?? 1);
78744
+ }
78312
78745
  async function runGitDiff(args2, directory) {
78313
78746
  try {
78314
78747
  const proc = Bun.spawn(["git", "diff", ...args2], {
@@ -78396,7 +78829,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
78396
78829
  const preexistingFindings = [];
78397
78830
  for (const finding of findings) {
78398
78831
  const filePath = finding.location.file;
78399
- const normalised = path78.relative(directory, filePath).replace(/\\/g, "/");
78832
+ const normalised = path79.relative(directory, filePath).replace(/\\/g, "/");
78400
78833
  const changedLines = changedLineRanges.get(normalised);
78401
78834
  if (changedLines?.has(finding.location.line)) {
78402
78835
  newFindings.push(finding);
@@ -78408,7 +78841,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
78408
78841
  }
78409
78842
  async function runPreCheckBatch(input, workspaceDir, contextDir) {
78410
78843
  const effectiveWorkspaceDir = workspaceDir || input.directory || contextDir;
78411
- const { files, directory, sast_threshold = "medium", config: config3 } = input;
78844
+ const { files, directory, sast_threshold = "medium", config: config3, phase } = input;
78412
78845
  const dirError = validateDirectory2(directory, effectiveWorkspaceDir);
78413
78846
  if (dirError) {
78414
78847
  warn(`pre_check_batch: Invalid directory: ${dirError}`);
@@ -78447,7 +78880,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78447
78880
  warn(`pre_check_batch: Invalid file path: ${file3}`);
78448
78881
  continue;
78449
78882
  }
78450
- changedFiles.push(path78.resolve(directory, file3));
78883
+ changedFiles.push(path79.resolve(directory, file3));
78451
78884
  }
78452
78885
  if (changedFiles.length === 0) {
78453
78886
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -78471,7 +78904,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78471
78904
  const [lintResult, secretscanResult, sastScanResult, qualityBudgetResult] = await Promise.all([
78472
78905
  limit(() => runLintWrapped(changedFiles, directory, config3)),
78473
78906
  limit(() => runSecretscanWrapped(changedFiles, directory, config3)),
78474
- limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3)),
78907
+ limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3, phase)),
78475
78908
  limit(() => runQualityBudgetWrapped(changedFiles, directory, config3))
78476
78909
  ]);
78477
78910
  const totalDuration = lintResult.duration_ms + secretscanResult.duration_ms + sastScanResult.duration_ms + qualityBudgetResult.duration_ms;
@@ -78516,8 +78949,20 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78516
78949
  }
78517
78950
  let sastPreexistingFindings;
78518
78951
  if (sastScanResult.ran && sastScanResult.result) {
78519
- if (sastScanResult.result.verdict === "fail") {
78520
- const gateFindings = sastScanResult.result.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
78952
+ const sastResult = sastScanResult.result;
78953
+ if (sastResult.baseline_used) {
78954
+ if (sastResult.pre_existing_findings && sastResult.pre_existing_findings.length > 0) {
78955
+ sastPreexistingFindings = sastResult.pre_existing_findings.filter((f) => meetsThresholdForTriage(f.severity, sast_threshold));
78956
+ if (sastPreexistingFindings.length > 0) {
78957
+ warn(`pre_check_batch: SAST baseline diff found ${sastPreexistingFindings.length} pre-existing finding(s) - passing to reviewer for triage`);
78958
+ }
78959
+ }
78960
+ if (sastResult.verdict === "fail") {
78961
+ gatesPassed = false;
78962
+ warn(`pre_check_batch: SAST scan found new findings above threshold - GATE FAILED`);
78963
+ }
78964
+ } else if (sastResult.verdict === "fail") {
78965
+ const gateFindings = sastResult.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
78521
78966
  if (gateFindings.length > 0) {
78522
78967
  const changedLineRanges = await getChangedLineRanges(directory);
78523
78968
  const { newFindings, preexistingFindings } = classifySastFindings(gateFindings, changedLineRanges, directory);
@@ -78566,7 +79011,8 @@ var pre_check_batch = createSwarmTool({
78566
79011
  args: {
78567
79012
  files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to check (optional, scans directory if not provided)"),
78568
79013
  directory: tool.schema.string().describe('Directory to run checks in (e.g., "." or "./src")'),
78569
- sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)")
79014
+ sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)"),
79015
+ phase: tool.schema.number().int().min(1).optional().describe("Current phase number (positive integer >= 1). When provided, enables SAST baseline diffing: only findings absent from the phase-scoped baseline fail the gate.")
78570
79016
  },
78571
79017
  async execute(args2, directory) {
78572
79018
  if (!args2 || typeof args2 !== "object") {
@@ -78635,7 +79081,7 @@ var pre_check_batch = createSwarmTool({
78635
79081
  };
78636
79082
  return JSON.stringify(errorResult, null, 2);
78637
79083
  }
78638
- const resolvedDirectory = path78.resolve(typedArgs.directory);
79084
+ const resolvedDirectory = path79.resolve(typedArgs.directory);
78639
79085
  const workspaceAnchor = resolvedDirectory;
78640
79086
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
78641
79087
  if (dirError) {
@@ -78650,11 +79096,14 @@ var pre_check_batch = createSwarmTool({
78650
79096
  return JSON.stringify(errorResult, null, 2);
78651
79097
  }
78652
79098
  try {
79099
+ const rawPhase = typedArgs.phase;
79100
+ const safePhase = typeof rawPhase === "number" && Number.isInteger(rawPhase) && rawPhase >= 1 ? rawPhase : undefined;
78653
79101
  const result = await runPreCheckBatch({
78654
79102
  files: typedArgs.files,
78655
79103
  directory: resolvedDirectory,
78656
79104
  sast_threshold: typedArgs.sast_threshold,
78657
- config: typedArgs.config
79105
+ config: typedArgs.config,
79106
+ phase: safePhase
78658
79107
  }, workspaceAnchor, directory);
78659
79108
  return JSON.stringify(result, null, 2);
78660
79109
  } catch (error93) {
@@ -78673,7 +79122,7 @@ var pre_check_batch = createSwarmTool({
78673
79122
  });
78674
79123
  // src/tools/repo-map.ts
78675
79124
  init_dist();
78676
- import * as path79 from "path";
79125
+ import * as path80 from "path";
78677
79126
  init_path_security();
78678
79127
  init_create_tool();
78679
79128
  var VALID_ACTIONS = [
@@ -78698,7 +79147,7 @@ function validateFile(p) {
78698
79147
  return "file contains control characters";
78699
79148
  if (containsPathTraversal(p))
78700
79149
  return "file contains path traversal";
78701
- if (path79.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
79150
+ if (path80.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
78702
79151
  return "file must be a workspace-relative path, not absolute";
78703
79152
  }
78704
79153
  return null;
@@ -78721,8 +79170,8 @@ function ok(action, payload) {
78721
79170
  }
78722
79171
  function toRelativeGraphPath(input, workspaceRoot) {
78723
79172
  const normalized = input.replace(/\\/g, "/");
78724
- if (path79.isAbsolute(normalized)) {
78725
- const rel = path79.relative(workspaceRoot, normalized).replace(/\\/g, "/");
79173
+ if (path80.isAbsolute(normalized)) {
79174
+ const rel = path80.relative(workspaceRoot, normalized).replace(/\\/g, "/");
78726
79175
  return normalizeGraphPath2(rel);
78727
79176
  }
78728
79177
  return normalizeGraphPath2(normalized);
@@ -78866,8 +79315,8 @@ var repo_map = createSwarmTool({
78866
79315
  // src/tools/req-coverage.ts
78867
79316
  init_dist();
78868
79317
  init_create_tool();
78869
- import * as fs65 from "fs";
78870
- import * as path80 from "path";
79318
+ import * as fs66 from "fs";
79319
+ import * as path81 from "path";
78871
79320
  var SPEC_FILE = ".swarm/spec.md";
78872
79321
  var EVIDENCE_DIR4 = ".swarm/evidence";
78873
79322
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -78926,19 +79375,19 @@ function extractObligationAndText(id, lineText) {
78926
79375
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
78927
79376
  function readTouchedFiles(evidenceDir, phase, cwd) {
78928
79377
  const touchedFiles = new Set;
78929
- if (!fs65.existsSync(evidenceDir) || !fs65.statSync(evidenceDir).isDirectory()) {
79378
+ if (!fs66.existsSync(evidenceDir) || !fs66.statSync(evidenceDir).isDirectory()) {
78930
79379
  return [];
78931
79380
  }
78932
79381
  let entries;
78933
79382
  try {
78934
- entries = fs65.readdirSync(evidenceDir);
79383
+ entries = fs66.readdirSync(evidenceDir);
78935
79384
  } catch {
78936
79385
  return [];
78937
79386
  }
78938
79387
  for (const entry of entries) {
78939
- const entryPath = path80.join(evidenceDir, entry);
79388
+ const entryPath = path81.join(evidenceDir, entry);
78940
79389
  try {
78941
- const stat3 = fs65.statSync(entryPath);
79390
+ const stat3 = fs66.statSync(entryPath);
78942
79391
  if (!stat3.isDirectory()) {
78943
79392
  continue;
78944
79393
  }
@@ -78952,14 +79401,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78952
79401
  if (entryPhase !== String(phase)) {
78953
79402
  continue;
78954
79403
  }
78955
- const evidenceFilePath = path80.join(entryPath, "evidence.json");
79404
+ const evidenceFilePath = path81.join(entryPath, "evidence.json");
78956
79405
  try {
78957
- const resolvedPath = path80.resolve(evidenceFilePath);
78958
- const evidenceDirResolved = path80.resolve(evidenceDir);
78959
- if (!resolvedPath.startsWith(evidenceDirResolved + path80.sep)) {
79406
+ const resolvedPath = path81.resolve(evidenceFilePath);
79407
+ const evidenceDirResolved = path81.resolve(evidenceDir);
79408
+ if (!resolvedPath.startsWith(evidenceDirResolved + path81.sep)) {
78960
79409
  continue;
78961
79410
  }
78962
- const stat3 = fs65.lstatSync(evidenceFilePath);
79411
+ const stat3 = fs66.lstatSync(evidenceFilePath);
78963
79412
  if (!stat3.isFile()) {
78964
79413
  continue;
78965
79414
  }
@@ -78971,7 +79420,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78971
79420
  }
78972
79421
  let content;
78973
79422
  try {
78974
- content = fs65.readFileSync(evidenceFilePath, "utf-8");
79423
+ content = fs66.readFileSync(evidenceFilePath, "utf-8");
78975
79424
  } catch {
78976
79425
  continue;
78977
79426
  }
@@ -78990,7 +79439,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78990
79439
  if (Array.isArray(diffEntry.files_changed)) {
78991
79440
  for (const file3 of diffEntry.files_changed) {
78992
79441
  if (typeof file3 === "string") {
78993
- touchedFiles.add(path80.resolve(cwd, file3));
79442
+ touchedFiles.add(path81.resolve(cwd, file3));
78994
79443
  }
78995
79444
  }
78996
79445
  }
@@ -79003,12 +79452,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
79003
79452
  }
79004
79453
  function searchFileForKeywords(filePath, keywords, cwd) {
79005
79454
  try {
79006
- const resolvedPath = path80.resolve(filePath);
79007
- const cwdResolved = path80.resolve(cwd);
79455
+ const resolvedPath = path81.resolve(filePath);
79456
+ const cwdResolved = path81.resolve(cwd);
79008
79457
  if (!resolvedPath.startsWith(cwdResolved)) {
79009
79458
  return false;
79010
79459
  }
79011
- const content = fs65.readFileSync(resolvedPath, "utf-8");
79460
+ const content = fs66.readFileSync(resolvedPath, "utf-8");
79012
79461
  for (const keyword of keywords) {
79013
79462
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
79014
79463
  if (regex.test(content)) {
@@ -79138,10 +79587,10 @@ var req_coverage = createSwarmTool({
79138
79587
  }, null, 2);
79139
79588
  }
79140
79589
  const cwd = inputDirectory || directory;
79141
- const specPath = path80.join(cwd, SPEC_FILE);
79590
+ const specPath = path81.join(cwd, SPEC_FILE);
79142
79591
  let specContent;
79143
79592
  try {
79144
- specContent = fs65.readFileSync(specPath, "utf-8");
79593
+ specContent = fs66.readFileSync(specPath, "utf-8");
79145
79594
  } catch (readError) {
79146
79595
  return JSON.stringify({
79147
79596
  success: false,
@@ -79165,7 +79614,7 @@ var req_coverage = createSwarmTool({
79165
79614
  message: "No FR requirements found in spec.md"
79166
79615
  }, null, 2);
79167
79616
  }
79168
- const evidenceDir = path80.join(cwd, EVIDENCE_DIR4);
79617
+ const evidenceDir = path81.join(cwd, EVIDENCE_DIR4);
79169
79618
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
79170
79619
  const analyzedRequirements = [];
79171
79620
  let coveredCount = 0;
@@ -79191,12 +79640,12 @@ var req_coverage = createSwarmTool({
79191
79640
  requirements: analyzedRequirements
79192
79641
  };
79193
79642
  const reportFilename = `req-coverage-phase-${phase}.json`;
79194
- const reportPath = path80.join(evidenceDir, reportFilename);
79643
+ const reportPath = path81.join(evidenceDir, reportFilename);
79195
79644
  try {
79196
- if (!fs65.existsSync(evidenceDir)) {
79197
- fs65.mkdirSync(evidenceDir, { recursive: true });
79645
+ if (!fs66.existsSync(evidenceDir)) {
79646
+ fs66.mkdirSync(evidenceDir, { recursive: true });
79198
79647
  }
79199
- fs65.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
79648
+ fs66.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
79200
79649
  } catch (writeError) {
79201
79650
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
79202
79651
  }
@@ -79274,9 +79723,9 @@ ${paginatedContent}`;
79274
79723
  });
79275
79724
  // src/tools/save-plan.ts
79276
79725
  init_tool();
79277
- import * as crypto8 from "crypto";
79278
- import * as fs66 from "fs";
79279
- import * as path81 from "path";
79726
+ import * as crypto9 from "crypto";
79727
+ import * as fs67 from "fs";
79728
+ import * as path82 from "path";
79280
79729
  init_checkpoint3();
79281
79730
  init_ledger();
79282
79731
  init_manager();
@@ -79357,12 +79806,12 @@ async function executeSavePlan(args2, fallbackDir) {
79357
79806
  let specMtime;
79358
79807
  let specHash;
79359
79808
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
79360
- const specPath = path81.join(targetWorkspace, ".swarm", "spec.md");
79809
+ const specPath = path82.join(targetWorkspace, ".swarm", "spec.md");
79361
79810
  try {
79362
- const stat3 = await fs66.promises.stat(specPath);
79811
+ const stat3 = await fs67.promises.stat(specPath);
79363
79812
  specMtime = stat3.mtime.toISOString();
79364
- const content = await fs66.promises.readFile(specPath, "utf8");
79365
- specHash = crypto8.createHash("sha256").update(content).digest("hex");
79813
+ const content = await fs67.promises.readFile(specPath, "utf8");
79814
+ specHash = crypto9.createHash("sha256").update(content).digest("hex");
79366
79815
  } catch {
79367
79816
  return {
79368
79817
  success: false,
@@ -79439,14 +79888,14 @@ async function executeSavePlan(args2, fallbackDir) {
79439
79888
  }
79440
79889
  await writeCheckpoint(dir).catch(() => {});
79441
79890
  try {
79442
- const markerPath = path81.join(dir, ".swarm", ".plan-write-marker");
79891
+ const markerPath = path82.join(dir, ".swarm", ".plan-write-marker");
79443
79892
  const marker = JSON.stringify({
79444
79893
  source: "save_plan",
79445
79894
  timestamp: new Date().toISOString(),
79446
79895
  phases_count: plan.phases.length,
79447
79896
  tasks_count: tasksCount
79448
79897
  });
79449
- await fs66.promises.writeFile(markerPath, marker, "utf8");
79898
+ await fs67.promises.writeFile(markerPath, marker, "utf8");
79450
79899
  } catch {}
79451
79900
  const warnings = [];
79452
79901
  let criticReviewFound = false;
@@ -79462,7 +79911,7 @@ async function executeSavePlan(args2, fallbackDir) {
79462
79911
  return {
79463
79912
  success: true,
79464
79913
  message: "Plan saved successfully",
79465
- plan_path: path81.join(dir, ".swarm", "plan.json"),
79914
+ plan_path: path82.join(dir, ".swarm", "plan.json"),
79466
79915
  phases_count: plan.phases.length,
79467
79916
  tasks_count: tasksCount,
79468
79917
  ...warnings.length > 0 ? { warnings } : {}
@@ -79507,8 +79956,8 @@ var save_plan = createSwarmTool({
79507
79956
  // src/tools/sbom-generate.ts
79508
79957
  init_dist();
79509
79958
  init_manager2();
79510
- import * as fs67 from "fs";
79511
- import * as path82 from "path";
79959
+ import * as fs68 from "fs";
79960
+ import * as path83 from "path";
79512
79961
 
79513
79962
  // src/sbom/detectors/index.ts
79514
79963
  init_utils();
@@ -80356,9 +80805,9 @@ function findManifestFiles(rootDir) {
80356
80805
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
80357
80806
  function searchDir(dir) {
80358
80807
  try {
80359
- const entries = fs67.readdirSync(dir, { withFileTypes: true });
80808
+ const entries = fs68.readdirSync(dir, { withFileTypes: true });
80360
80809
  for (const entry of entries) {
80361
- const fullPath = path82.join(dir, entry.name);
80810
+ const fullPath = path83.join(dir, entry.name);
80362
80811
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
80363
80812
  continue;
80364
80813
  }
@@ -80367,7 +80816,7 @@ function findManifestFiles(rootDir) {
80367
80816
  } else if (entry.isFile()) {
80368
80817
  for (const pattern of patterns) {
80369
80818
  if (simpleGlobToRegex(pattern).test(entry.name)) {
80370
- manifestFiles.push(path82.relative(rootDir, fullPath));
80819
+ manifestFiles.push(path83.relative(rootDir, fullPath));
80371
80820
  break;
80372
80821
  }
80373
80822
  }
@@ -80383,13 +80832,13 @@ function findManifestFilesInDirs(directories, workingDir) {
80383
80832
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
80384
80833
  for (const dir of directories) {
80385
80834
  try {
80386
- const entries = fs67.readdirSync(dir, { withFileTypes: true });
80835
+ const entries = fs68.readdirSync(dir, { withFileTypes: true });
80387
80836
  for (const entry of entries) {
80388
- const fullPath = path82.join(dir, entry.name);
80837
+ const fullPath = path83.join(dir, entry.name);
80389
80838
  if (entry.isFile()) {
80390
80839
  for (const pattern of patterns) {
80391
80840
  if (simpleGlobToRegex(pattern).test(entry.name)) {
80392
- found.push(path82.relative(workingDir, fullPath));
80841
+ found.push(path83.relative(workingDir, fullPath));
80393
80842
  break;
80394
80843
  }
80395
80844
  }
@@ -80402,11 +80851,11 @@ function findManifestFilesInDirs(directories, workingDir) {
80402
80851
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
80403
80852
  const dirs = new Set;
80404
80853
  for (const file3 of changedFiles) {
80405
- let currentDir = path82.dirname(file3);
80854
+ let currentDir = path83.dirname(file3);
80406
80855
  while (true) {
80407
- if (currentDir && currentDir !== "." && currentDir !== path82.sep) {
80408
- dirs.add(path82.join(workingDir, currentDir));
80409
- const parent = path82.dirname(currentDir);
80856
+ if (currentDir && currentDir !== "." && currentDir !== path83.sep) {
80857
+ dirs.add(path83.join(workingDir, currentDir));
80858
+ const parent = path83.dirname(currentDir);
80410
80859
  if (parent === currentDir)
80411
80860
  break;
80412
80861
  currentDir = parent;
@@ -80420,7 +80869,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
80420
80869
  }
80421
80870
  function ensureOutputDir(outputDir) {
80422
80871
  try {
80423
- fs67.mkdirSync(outputDir, { recursive: true });
80872
+ fs68.mkdirSync(outputDir, { recursive: true });
80424
80873
  } catch (error93) {
80425
80874
  if (!error93 || error93.code !== "EEXIST") {
80426
80875
  throw error93;
@@ -80490,7 +80939,7 @@ var sbom_generate = createSwarmTool({
80490
80939
  const changedFiles = obj.changed_files;
80491
80940
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
80492
80941
  const workingDir = directory;
80493
- const outputDir = path82.isAbsolute(relativeOutputDir) ? relativeOutputDir : path82.join(workingDir, relativeOutputDir);
80942
+ const outputDir = path83.isAbsolute(relativeOutputDir) ? relativeOutputDir : path83.join(workingDir, relativeOutputDir);
80494
80943
  let manifestFiles = [];
80495
80944
  if (scope === "all") {
80496
80945
  manifestFiles = findManifestFiles(workingDir);
@@ -80513,11 +80962,11 @@ var sbom_generate = createSwarmTool({
80513
80962
  const processedFiles = [];
80514
80963
  for (const manifestFile of manifestFiles) {
80515
80964
  try {
80516
- const fullPath = path82.isAbsolute(manifestFile) ? manifestFile : path82.join(workingDir, manifestFile);
80517
- if (!fs67.existsSync(fullPath)) {
80965
+ const fullPath = path83.isAbsolute(manifestFile) ? manifestFile : path83.join(workingDir, manifestFile);
80966
+ if (!fs68.existsSync(fullPath)) {
80518
80967
  continue;
80519
80968
  }
80520
- const content = fs67.readFileSync(fullPath, "utf-8");
80969
+ const content = fs68.readFileSync(fullPath, "utf-8");
80521
80970
  const components = detectComponents(manifestFile, content);
80522
80971
  processedFiles.push(manifestFile);
80523
80972
  if (components.length > 0) {
@@ -80530,8 +80979,8 @@ var sbom_generate = createSwarmTool({
80530
80979
  const bom = generateCycloneDX(allComponents);
80531
80980
  const bomJson = serializeCycloneDX(bom);
80532
80981
  const filename = generateSbomFilename();
80533
- const outputPath = path82.join(outputDir, filename);
80534
- fs67.writeFileSync(outputPath, bomJson, "utf-8");
80982
+ const outputPath = path83.join(outputDir, filename);
80983
+ fs68.writeFileSync(outputPath, bomJson, "utf-8");
80535
80984
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
80536
80985
  try {
80537
80986
  const timestamp = new Date().toISOString();
@@ -80573,8 +81022,8 @@ var sbom_generate = createSwarmTool({
80573
81022
  // src/tools/schema-drift.ts
80574
81023
  init_dist();
80575
81024
  init_create_tool();
80576
- import * as fs68 from "fs";
80577
- import * as path83 from "path";
81025
+ import * as fs69 from "fs";
81026
+ import * as path84 from "path";
80578
81027
  var SPEC_CANDIDATES = [
80579
81028
  "openapi.json",
80580
81029
  "openapi.yaml",
@@ -80606,28 +81055,28 @@ function normalizePath3(p) {
80606
81055
  }
80607
81056
  function discoverSpecFile(cwd, specFileArg) {
80608
81057
  if (specFileArg) {
80609
- const resolvedPath = path83.resolve(cwd, specFileArg);
80610
- const normalizedCwd = cwd.endsWith(path83.sep) ? cwd : cwd + path83.sep;
81058
+ const resolvedPath = path84.resolve(cwd, specFileArg);
81059
+ const normalizedCwd = cwd.endsWith(path84.sep) ? cwd : cwd + path84.sep;
80611
81060
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
80612
81061
  throw new Error("Invalid spec_file: path traversal detected");
80613
81062
  }
80614
- const ext = path83.extname(resolvedPath).toLowerCase();
81063
+ const ext = path84.extname(resolvedPath).toLowerCase();
80615
81064
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
80616
81065
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
80617
81066
  }
80618
- const stats = fs68.statSync(resolvedPath);
81067
+ const stats = fs69.statSync(resolvedPath);
80619
81068
  if (stats.size > MAX_SPEC_SIZE) {
80620
81069
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
80621
81070
  }
80622
- if (!fs68.existsSync(resolvedPath)) {
81071
+ if (!fs69.existsSync(resolvedPath)) {
80623
81072
  throw new Error(`Spec file not found: ${resolvedPath}`);
80624
81073
  }
80625
81074
  return resolvedPath;
80626
81075
  }
80627
81076
  for (const candidate of SPEC_CANDIDATES) {
80628
- const candidatePath = path83.resolve(cwd, candidate);
80629
- if (fs68.existsSync(candidatePath)) {
80630
- const stats = fs68.statSync(candidatePath);
81077
+ const candidatePath = path84.resolve(cwd, candidate);
81078
+ if (fs69.existsSync(candidatePath)) {
81079
+ const stats = fs69.statSync(candidatePath);
80631
81080
  if (stats.size <= MAX_SPEC_SIZE) {
80632
81081
  return candidatePath;
80633
81082
  }
@@ -80636,8 +81085,8 @@ function discoverSpecFile(cwd, specFileArg) {
80636
81085
  return null;
80637
81086
  }
80638
81087
  function parseSpec(specFile) {
80639
- const content = fs68.readFileSync(specFile, "utf-8");
80640
- const ext = path83.extname(specFile).toLowerCase();
81088
+ const content = fs69.readFileSync(specFile, "utf-8");
81089
+ const ext = path84.extname(specFile).toLowerCase();
80641
81090
  if (ext === ".json") {
80642
81091
  return parseJsonSpec(content);
80643
81092
  }
@@ -80708,12 +81157,12 @@ function extractRoutes(cwd) {
80708
81157
  function walkDir(dir) {
80709
81158
  let entries;
80710
81159
  try {
80711
- entries = fs68.readdirSync(dir, { withFileTypes: true });
81160
+ entries = fs69.readdirSync(dir, { withFileTypes: true });
80712
81161
  } catch {
80713
81162
  return;
80714
81163
  }
80715
81164
  for (const entry of entries) {
80716
- const fullPath = path83.join(dir, entry.name);
81165
+ const fullPath = path84.join(dir, entry.name);
80717
81166
  if (entry.isSymbolicLink()) {
80718
81167
  continue;
80719
81168
  }
@@ -80723,7 +81172,7 @@ function extractRoutes(cwd) {
80723
81172
  }
80724
81173
  walkDir(fullPath);
80725
81174
  } else if (entry.isFile()) {
80726
- const ext = path83.extname(entry.name).toLowerCase();
81175
+ const ext = path84.extname(entry.name).toLowerCase();
80727
81176
  const baseName = entry.name.toLowerCase();
80728
81177
  if (![".ts", ".js", ".mjs"].includes(ext)) {
80729
81178
  continue;
@@ -80741,7 +81190,7 @@ function extractRoutes(cwd) {
80741
81190
  }
80742
81191
  function extractRoutesFromFile(filePath) {
80743
81192
  const routes = [];
80744
- const content = fs68.readFileSync(filePath, "utf-8");
81193
+ const content = fs69.readFileSync(filePath, "utf-8");
80745
81194
  const lines = content.split(/\r?\n/);
80746
81195
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
80747
81196
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -80889,8 +81338,8 @@ var schema_drift = createSwarmTool({
80889
81338
  init_tool();
80890
81339
  init_path_security();
80891
81340
  init_create_tool();
80892
- import * as fs69 from "fs";
80893
- import * as path84 from "path";
81341
+ import * as fs70 from "fs";
81342
+ import * as path85 from "path";
80894
81343
  var DEFAULT_MAX_RESULTS = 100;
80895
81344
  var DEFAULT_MAX_LINES = 200;
80896
81345
  var REGEX_TIMEOUT_MS = 5000;
@@ -80926,11 +81375,11 @@ function containsWindowsAttacks3(str) {
80926
81375
  }
80927
81376
  function isPathInWorkspace3(filePath, workspace) {
80928
81377
  try {
80929
- const resolvedPath = path84.resolve(workspace, filePath);
80930
- const realWorkspace = fs69.realpathSync(workspace);
80931
- const realResolvedPath = fs69.realpathSync(resolvedPath);
80932
- const relativePath = path84.relative(realWorkspace, realResolvedPath);
80933
- if (relativePath.startsWith("..") || path84.isAbsolute(relativePath)) {
81378
+ const resolvedPath = path85.resolve(workspace, filePath);
81379
+ const realWorkspace = fs70.realpathSync(workspace);
81380
+ const realResolvedPath = fs70.realpathSync(resolvedPath);
81381
+ const relativePath = path85.relative(realWorkspace, realResolvedPath);
81382
+ if (relativePath.startsWith("..") || path85.isAbsolute(relativePath)) {
80934
81383
  return false;
80935
81384
  }
80936
81385
  return true;
@@ -80943,12 +81392,12 @@ function validatePathForRead2(filePath, workspace) {
80943
81392
  }
80944
81393
  function findRgInEnvPath() {
80945
81394
  const searchPath = process.env.PATH ?? "";
80946
- for (const dir of searchPath.split(path84.delimiter)) {
81395
+ for (const dir of searchPath.split(path85.delimiter)) {
80947
81396
  if (!dir)
80948
81397
  continue;
80949
81398
  const isWindows = process.platform === "win32";
80950
- const candidate = path84.join(dir, isWindows ? "rg.exe" : "rg");
80951
- if (fs69.existsSync(candidate))
81399
+ const candidate = path85.join(dir, isWindows ? "rg.exe" : "rg");
81400
+ if (fs70.existsSync(candidate))
80952
81401
  return candidate;
80953
81402
  }
80954
81403
  return null;
@@ -81002,7 +81451,7 @@ async function ripgrepSearch(opts) {
81002
81451
  stderr: "pipe",
81003
81452
  cwd: opts.workspace
81004
81453
  });
81005
- const timeout = new Promise((resolve35) => setTimeout(() => resolve35("timeout"), REGEX_TIMEOUT_MS));
81454
+ const timeout = new Promise((resolve36) => setTimeout(() => resolve36("timeout"), REGEX_TIMEOUT_MS));
81006
81455
  const exitPromise = proc.exited;
81007
81456
  const result = await Promise.race([exitPromise, timeout]);
81008
81457
  if (result === "timeout") {
@@ -81075,10 +81524,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
81075
81524
  return files;
81076
81525
  }
81077
81526
  try {
81078
- const entries = fs69.readdirSync(dir, { withFileTypes: true });
81527
+ const entries = fs70.readdirSync(dir, { withFileTypes: true });
81079
81528
  for (const entry of entries) {
81080
- const fullPath = path84.join(dir, entry.name);
81081
- const relativePath = path84.relative(workspace, fullPath);
81529
+ const fullPath = path85.join(dir, entry.name);
81530
+ const relativePath = path85.relative(workspace, fullPath);
81082
81531
  if (!validatePathForRead2(fullPath, workspace)) {
81083
81532
  continue;
81084
81533
  }
@@ -81119,13 +81568,13 @@ async function fallbackSearch(opts) {
81119
81568
  const matches = [];
81120
81569
  let total = 0;
81121
81570
  for (const file3 of files) {
81122
- const fullPath = path84.join(opts.workspace, file3);
81571
+ const fullPath = path85.join(opts.workspace, file3);
81123
81572
  if (!validatePathForRead2(fullPath, opts.workspace)) {
81124
81573
  continue;
81125
81574
  }
81126
81575
  let stats;
81127
81576
  try {
81128
- stats = fs69.statSync(fullPath);
81577
+ stats = fs70.statSync(fullPath);
81129
81578
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
81130
81579
  continue;
81131
81580
  }
@@ -81134,7 +81583,7 @@ async function fallbackSearch(opts) {
81134
81583
  }
81135
81584
  let content;
81136
81585
  try {
81137
- content = fs69.readFileSync(fullPath, "utf-8");
81586
+ content = fs70.readFileSync(fullPath, "utf-8");
81138
81587
  } catch {
81139
81588
  continue;
81140
81589
  }
@@ -81246,7 +81695,7 @@ var search = createSwarmTool({
81246
81695
  message: "Exclude pattern contains invalid Windows-specific sequence"
81247
81696
  }, null, 2);
81248
81697
  }
81249
- if (!fs69.existsSync(directory)) {
81698
+ if (!fs70.existsSync(directory)) {
81250
81699
  return JSON.stringify({
81251
81700
  error: true,
81252
81701
  type: "unknown",
@@ -81369,8 +81818,8 @@ var set_qa_gates = createSwarmTool({
81369
81818
  init_tool();
81370
81819
  init_path_security();
81371
81820
  init_create_tool();
81372
- import * as fs70 from "fs";
81373
- import * as path85 from "path";
81821
+ import * as fs71 from "fs";
81822
+ import * as path86 from "path";
81374
81823
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
81375
81824
  function containsWindowsAttacks4(str) {
81376
81825
  if (/:[^\\/]/.test(str))
@@ -81384,14 +81833,14 @@ function containsWindowsAttacks4(str) {
81384
81833
  }
81385
81834
  function isPathInWorkspace4(filePath, workspace) {
81386
81835
  try {
81387
- const resolvedPath = path85.resolve(workspace, filePath);
81388
- if (!fs70.existsSync(resolvedPath)) {
81836
+ const resolvedPath = path86.resolve(workspace, filePath);
81837
+ if (!fs71.existsSync(resolvedPath)) {
81389
81838
  return true;
81390
81839
  }
81391
- const realWorkspace = fs70.realpathSync(workspace);
81392
- const realResolvedPath = fs70.realpathSync(resolvedPath);
81393
- const relativePath = path85.relative(realWorkspace, realResolvedPath);
81394
- if (relativePath.startsWith("..") || path85.isAbsolute(relativePath)) {
81840
+ const realWorkspace = fs71.realpathSync(workspace);
81841
+ const realResolvedPath = fs71.realpathSync(resolvedPath);
81842
+ const relativePath = path86.relative(realWorkspace, realResolvedPath);
81843
+ if (relativePath.startsWith("..") || path86.isAbsolute(relativePath)) {
81395
81844
  return false;
81396
81845
  }
81397
81846
  return true;
@@ -81563,7 +82012,7 @@ var suggestPatch = createSwarmTool({
81563
82012
  message: "changes cannot be empty"
81564
82013
  }, null, 2);
81565
82014
  }
81566
- if (!fs70.existsSync(directory)) {
82015
+ if (!fs71.existsSync(directory)) {
81567
82016
  return JSON.stringify({
81568
82017
  success: false,
81569
82018
  error: true,
@@ -81599,8 +82048,8 @@ var suggestPatch = createSwarmTool({
81599
82048
  });
81600
82049
  continue;
81601
82050
  }
81602
- const fullPath = path85.resolve(directory, change.file);
81603
- if (!fs70.existsSync(fullPath)) {
82051
+ const fullPath = path86.resolve(directory, change.file);
82052
+ if (!fs71.existsSync(fullPath)) {
81604
82053
  errors5.push({
81605
82054
  success: false,
81606
82055
  error: true,
@@ -81614,7 +82063,7 @@ var suggestPatch = createSwarmTool({
81614
82063
  }
81615
82064
  let content;
81616
82065
  try {
81617
- content = fs70.readFileSync(fullPath, "utf-8");
82066
+ content = fs71.readFileSync(fullPath, "utf-8");
81618
82067
  } catch (err3) {
81619
82068
  errors5.push({
81620
82069
  success: false,
@@ -81693,8 +82142,8 @@ var suggestPatch = createSwarmTool({
81693
82142
  // src/tools/lint-spec.ts
81694
82143
  init_spec_schema();
81695
82144
  init_create_tool();
81696
- import * as fs71 from "fs";
81697
- import * as path86 from "path";
82145
+ import * as fs72 from "fs";
82146
+ import * as path87 from "path";
81698
82147
  var SPEC_FILE_NAME = "spec.md";
81699
82148
  var SWARM_DIR2 = ".swarm";
81700
82149
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -81747,8 +82196,8 @@ var lint_spec = createSwarmTool({
81747
82196
  async execute(_args, directory) {
81748
82197
  const errors5 = [];
81749
82198
  const warnings = [];
81750
- const specPath = path86.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
81751
- if (!fs71.existsSync(specPath)) {
82199
+ const specPath = path87.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
82200
+ if (!fs72.existsSync(specPath)) {
81752
82201
  const result2 = {
81753
82202
  valid: false,
81754
82203
  specMtime: null,
@@ -81767,12 +82216,12 @@ var lint_spec = createSwarmTool({
81767
82216
  }
81768
82217
  let specMtime = null;
81769
82218
  try {
81770
- const stats = fs71.statSync(specPath);
82219
+ const stats = fs72.statSync(specPath);
81771
82220
  specMtime = stats.mtime.toISOString();
81772
82221
  } catch {}
81773
82222
  let content;
81774
82223
  try {
81775
- content = fs71.readFileSync(specPath, "utf-8");
82224
+ content = fs72.readFileSync(specPath, "utf-8");
81776
82225
  } catch (e) {
81777
82226
  const result2 = {
81778
82227
  valid: false,
@@ -81817,13 +82266,13 @@ var lint_spec = createSwarmTool({
81817
82266
  });
81818
82267
  // src/tools/mutation-test.ts
81819
82268
  init_dist();
81820
- import * as fs72 from "fs";
81821
- import * as path88 from "path";
82269
+ import * as fs73 from "fs";
82270
+ import * as path89 from "path";
81822
82271
 
81823
82272
  // src/mutation/engine.ts
81824
82273
  import { spawnSync as spawnSync3 } from "child_process";
81825
- import { unlinkSync as unlinkSync11, writeFileSync as writeFileSync17 } from "fs";
81826
- import * as path87 from "path";
82274
+ import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
82275
+ import * as path88 from "path";
81827
82276
 
81828
82277
  // src/mutation/equivalence.ts
81829
82278
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -81958,9 +82407,9 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
81958
82407
  let patchFile;
81959
82408
  try {
81960
82409
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
81961
- patchFile = path87.join(workingDir, `.mutation_patch_${safeId2}.diff`);
82410
+ patchFile = path88.join(workingDir, `.mutation_patch_${safeId2}.diff`);
81962
82411
  try {
81963
- writeFileSync17(patchFile, patch.patch);
82412
+ writeFileSync18(patchFile, patch.patch);
81964
82413
  } catch (writeErr) {
81965
82414
  error93 = `Failed to write patch file: ${writeErr}`;
81966
82415
  outcome = "error";
@@ -82056,7 +82505,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
82056
82505
  revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
82057
82506
  }
82058
82507
  try {
82059
- unlinkSync11(patchFile);
82508
+ unlinkSync12(patchFile);
82060
82509
  } catch (_unlinkErr) {}
82061
82510
  }
82062
82511
  }
@@ -82352,8 +82801,8 @@ var mutation_test = createSwarmTool({
82352
82801
  ];
82353
82802
  for (const filePath of uniquePaths) {
82354
82803
  try {
82355
- const resolvedPath = path88.resolve(cwd, filePath);
82356
- sourceFiles.set(filePath, fs72.readFileSync(resolvedPath, "utf-8"));
82804
+ const resolvedPath = path89.resolve(cwd, filePath);
82805
+ sourceFiles.set(filePath, fs73.readFileSync(resolvedPath, "utf-8"));
82357
82806
  } catch {}
82358
82807
  }
82359
82808
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -82371,8 +82820,8 @@ var mutation_test = createSwarmTool({
82371
82820
  init_dist();
82372
82821
  init_manager2();
82373
82822
  init_detector();
82374
- import * as fs73 from "fs";
82375
- import * as path89 from "path";
82823
+ import * as fs74 from "fs";
82824
+ import * as path90 from "path";
82376
82825
  init_create_tool();
82377
82826
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
82378
82827
  var BINARY_CHECK_BYTES = 8192;
@@ -82438,7 +82887,7 @@ async function syntaxCheck(input, directory, config3) {
82438
82887
  if (languages?.length) {
82439
82888
  const lowerLangs = languages.map((l) => l.toLowerCase());
82440
82889
  filesToCheck = filesToCheck.filter((file3) => {
82441
- const ext = path89.extname(file3.path).toLowerCase();
82890
+ const ext = path90.extname(file3.path).toLowerCase();
82442
82891
  const langDef = getLanguageForExtension(ext);
82443
82892
  const fileProfile = getProfileForFile(file3.path);
82444
82893
  const langId = fileProfile?.id || langDef?.id;
@@ -82451,7 +82900,7 @@ async function syntaxCheck(input, directory, config3) {
82451
82900
  let skippedCount = 0;
82452
82901
  for (const fileInfo of filesToCheck) {
82453
82902
  const { path: filePath } = fileInfo;
82454
- const fullPath = path89.isAbsolute(filePath) ? filePath : path89.join(directory, filePath);
82903
+ const fullPath = path90.isAbsolute(filePath) ? filePath : path90.join(directory, filePath);
82455
82904
  const result = {
82456
82905
  path: filePath,
82457
82906
  language: "",
@@ -82481,7 +82930,7 @@ async function syntaxCheck(input, directory, config3) {
82481
82930
  }
82482
82931
  let content;
82483
82932
  try {
82484
- content = fs73.readFileSync(fullPath, "utf8");
82933
+ content = fs74.readFileSync(fullPath, "utf8");
82485
82934
  } catch {
82486
82935
  result.skipped_reason = "file_read_error";
82487
82936
  skippedCount++;
@@ -82500,7 +82949,7 @@ async function syntaxCheck(input, directory, config3) {
82500
82949
  results.push(result);
82501
82950
  continue;
82502
82951
  }
82503
- const ext = path89.extname(filePath).toLowerCase();
82952
+ const ext = path90.extname(filePath).toLowerCase();
82504
82953
  const langDef = getLanguageForExtension(ext);
82505
82954
  result.language = profile?.id || langDef?.id || "unknown";
82506
82955
  const errors5 = extractSyntaxErrors(parser, content);
@@ -82592,8 +83041,8 @@ init_dist();
82592
83041
  init_utils();
82593
83042
  init_create_tool();
82594
83043
  init_path_security();
82595
- import * as fs74 from "fs";
82596
- import * as path90 from "path";
83044
+ import * as fs75 from "fs";
83045
+ import * as path91 from "path";
82597
83046
  var MAX_TEXT_LENGTH = 200;
82598
83047
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
82599
83048
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -82659,9 +83108,9 @@ function validatePathsInput(paths, cwd) {
82659
83108
  return { error: "paths contains path traversal", resolvedPath: null };
82660
83109
  }
82661
83110
  try {
82662
- const resolvedPath = path90.resolve(paths);
82663
- const normalizedCwd = path90.resolve(cwd);
82664
- const normalizedResolved = path90.resolve(resolvedPath);
83111
+ const resolvedPath = path91.resolve(paths);
83112
+ const normalizedCwd = path91.resolve(cwd);
83113
+ const normalizedResolved = path91.resolve(resolvedPath);
82665
83114
  if (!normalizedResolved.startsWith(normalizedCwd)) {
82666
83115
  return {
82667
83116
  error: "paths must be within the current working directory",
@@ -82677,13 +83126,13 @@ function validatePathsInput(paths, cwd) {
82677
83126
  }
82678
83127
  }
82679
83128
  function isSupportedExtension(filePath) {
82680
- const ext = path90.extname(filePath).toLowerCase();
83129
+ const ext = path91.extname(filePath).toLowerCase();
82681
83130
  return SUPPORTED_EXTENSIONS4.has(ext);
82682
83131
  }
82683
83132
  function findSourceFiles4(dir, files = []) {
82684
83133
  let entries;
82685
83134
  try {
82686
- entries = fs74.readdirSync(dir);
83135
+ entries = fs75.readdirSync(dir);
82687
83136
  } catch {
82688
83137
  return files;
82689
83138
  }
@@ -82692,10 +83141,10 @@ function findSourceFiles4(dir, files = []) {
82692
83141
  if (SKIP_DIRECTORIES5.has(entry)) {
82693
83142
  continue;
82694
83143
  }
82695
- const fullPath = path90.join(dir, entry);
83144
+ const fullPath = path91.join(dir, entry);
82696
83145
  let stat3;
82697
83146
  try {
82698
- stat3 = fs74.statSync(fullPath);
83147
+ stat3 = fs75.statSync(fullPath);
82699
83148
  } catch {
82700
83149
  continue;
82701
83150
  }
@@ -82788,7 +83237,7 @@ var todo_extract = createSwarmTool({
82788
83237
  return JSON.stringify(errorResult, null, 2);
82789
83238
  }
82790
83239
  const scanPath = resolvedPath;
82791
- if (!fs74.existsSync(scanPath)) {
83240
+ if (!fs75.existsSync(scanPath)) {
82792
83241
  const errorResult = {
82793
83242
  error: `path not found: ${pathsInput}`,
82794
83243
  total: 0,
@@ -82798,13 +83247,13 @@ var todo_extract = createSwarmTool({
82798
83247
  return JSON.stringify(errorResult, null, 2);
82799
83248
  }
82800
83249
  const filesToScan = [];
82801
- const stat3 = fs74.statSync(scanPath);
83250
+ const stat3 = fs75.statSync(scanPath);
82802
83251
  if (stat3.isFile()) {
82803
83252
  if (isSupportedExtension(scanPath)) {
82804
83253
  filesToScan.push(scanPath);
82805
83254
  } else {
82806
83255
  const errorResult = {
82807
- error: `unsupported file extension: ${path90.extname(scanPath)}`,
83256
+ error: `unsupported file extension: ${path91.extname(scanPath)}`,
82808
83257
  total: 0,
82809
83258
  byPriority: { high: 0, medium: 0, low: 0 },
82810
83259
  entries: []
@@ -82817,11 +83266,11 @@ var todo_extract = createSwarmTool({
82817
83266
  const allEntries = [];
82818
83267
  for (const filePath of filesToScan) {
82819
83268
  try {
82820
- const fileStat = fs74.statSync(filePath);
83269
+ const fileStat = fs75.statSync(filePath);
82821
83270
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
82822
83271
  continue;
82823
83272
  }
82824
- const content = fs74.readFileSync(filePath, "utf-8");
83273
+ const content = fs75.readFileSync(filePath, "utf-8");
82825
83274
  const entries = parseTodoComments(content, filePath, tagsSet);
82826
83275
  allEntries.push(...entries);
82827
83276
  } catch {}
@@ -82851,18 +83300,18 @@ init_tool();
82851
83300
  init_loader();
82852
83301
  init_schema();
82853
83302
  init_gate_evidence();
82854
- import * as fs76 from "fs";
82855
- import * as path92 from "path";
83303
+ import * as fs77 from "fs";
83304
+ import * as path93 from "path";
82856
83305
 
82857
83306
  // src/hooks/diff-scope.ts
82858
- import * as fs75 from "fs";
82859
- import * as path91 from "path";
83307
+ import * as fs76 from "fs";
83308
+ import * as path92 from "path";
82860
83309
  function getDeclaredScope(taskId, directory) {
82861
83310
  try {
82862
- const planPath = path91.join(directory, ".swarm", "plan.json");
82863
- if (!fs75.existsSync(planPath))
83311
+ const planPath = path92.join(directory, ".swarm", "plan.json");
83312
+ if (!fs76.existsSync(planPath))
82864
83313
  return null;
82865
- const raw = fs75.readFileSync(planPath, "utf-8");
83314
+ const raw = fs76.readFileSync(planPath, "utf-8");
82866
83315
  const plan = JSON.parse(raw);
82867
83316
  for (const phase of plan.phases ?? []) {
82868
83317
  for (const task of phase.tasks ?? []) {
@@ -82977,7 +83426,7 @@ var TIER_3_PATTERNS = [
82977
83426
  ];
82978
83427
  function matchesTier3Pattern(files) {
82979
83428
  for (const file3 of files) {
82980
- const fileName = path92.basename(file3);
83429
+ const fileName = path93.basename(file3);
82981
83430
  for (const pattern of TIER_3_PATTERNS) {
82982
83431
  if (pattern.test(fileName)) {
82983
83432
  return true;
@@ -82991,8 +83440,8 @@ function checkReviewerGate(taskId, workingDirectory) {
82991
83440
  if (hasActiveTurboMode()) {
82992
83441
  const resolvedDir2 = workingDirectory;
82993
83442
  try {
82994
- const planPath = path92.join(resolvedDir2, ".swarm", "plan.json");
82995
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83443
+ const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
83444
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
82996
83445
  const plan = JSON.parse(planRaw);
82997
83446
  for (const planPhase of plan.phases ?? []) {
82998
83447
  for (const task of planPhase.tasks ?? []) {
@@ -83058,8 +83507,8 @@ function checkReviewerGate(taskId, workingDirectory) {
83058
83507
  }
83059
83508
  try {
83060
83509
  const resolvedDir2 = workingDirectory;
83061
- const planPath = path92.join(resolvedDir2, ".swarm", "plan.json");
83062
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83510
+ const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
83511
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
83063
83512
  const plan = JSON.parse(planRaw);
83064
83513
  for (const planPhase of plan.phases ?? []) {
83065
83514
  for (const task of planPhase.tasks ?? []) {
@@ -83276,8 +83725,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83276
83725
  };
83277
83726
  }
83278
83727
  }
83279
- normalizedDir = path92.normalize(args2.working_directory);
83280
- const pathParts = normalizedDir.split(path92.sep);
83728
+ normalizedDir = path93.normalize(args2.working_directory);
83729
+ const pathParts = normalizedDir.split(path93.sep);
83281
83730
  if (pathParts.includes("..")) {
83282
83731
  return {
83283
83732
  success: false,
@@ -83287,11 +83736,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83287
83736
  ]
83288
83737
  };
83289
83738
  }
83290
- const resolvedDir = path92.resolve(normalizedDir);
83739
+ const resolvedDir = path93.resolve(normalizedDir);
83291
83740
  try {
83292
- const realPath = fs76.realpathSync(resolvedDir);
83293
- const planPath = path92.join(realPath, ".swarm", "plan.json");
83294
- if (!fs76.existsSync(planPath)) {
83741
+ const realPath = fs77.realpathSync(resolvedDir);
83742
+ const planPath = path93.join(realPath, ".swarm", "plan.json");
83743
+ if (!fs77.existsSync(planPath)) {
83295
83744
  return {
83296
83745
  success: false,
83297
83746
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -83322,12 +83771,12 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83322
83771
  }
83323
83772
  if (args2.status === "in_progress") {
83324
83773
  try {
83325
- const evidencePath = path92.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
83326
- fs76.mkdirSync(path92.dirname(evidencePath), { recursive: true });
83327
- const fd = fs76.openSync(evidencePath, "wx");
83774
+ const evidencePath = path93.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
83775
+ fs77.mkdirSync(path93.dirname(evidencePath), { recursive: true });
83776
+ const fd = fs77.openSync(evidencePath, "wx");
83328
83777
  let writeOk = false;
83329
83778
  try {
83330
- fs76.writeSync(fd, JSON.stringify({
83779
+ fs77.writeSync(fd, JSON.stringify({
83331
83780
  task_id: args2.task_id,
83332
83781
  required_gates: ["reviewer", "test_engineer"],
83333
83782
  gates: {},
@@ -83335,10 +83784,10 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83335
83784
  }, null, 2));
83336
83785
  writeOk = true;
83337
83786
  } finally {
83338
- fs76.closeSync(fd);
83787
+ fs77.closeSync(fd);
83339
83788
  if (!writeOk) {
83340
83789
  try {
83341
- fs76.unlinkSync(evidencePath);
83790
+ fs77.unlinkSync(evidencePath);
83342
83791
  } catch {}
83343
83792
  }
83344
83793
  }
@@ -83348,8 +83797,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83348
83797
  recoverTaskStateFromDelegations(args2.task_id);
83349
83798
  let phaseRequiresReviewer = true;
83350
83799
  try {
83351
- const planPath = path92.join(directory, ".swarm", "plan.json");
83352
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83800
+ const planPath = path93.join(directory, ".swarm", "plan.json");
83801
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
83353
83802
  const plan = JSON.parse(planRaw);
83354
83803
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
83355
83804
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -83458,8 +83907,8 @@ init_utils2();
83458
83907
  init_ledger();
83459
83908
  init_manager();
83460
83909
  init_create_tool();
83461
- import fs77 from "fs";
83462
- import path93 from "path";
83910
+ import fs78 from "fs";
83911
+ import path94 from "path";
83463
83912
  function derivePlanId5(plan) {
83464
83913
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
83465
83914
  }
@@ -83510,7 +83959,7 @@ async function executeWriteDriftEvidence(args2, directory) {
83510
83959
  entries: [evidenceEntry]
83511
83960
  };
83512
83961
  const filename = "drift-verifier.json";
83513
- const relativePath = path93.join("evidence", String(phase), filename);
83962
+ const relativePath = path94.join("evidence", String(phase), filename);
83514
83963
  let validatedPath;
83515
83964
  try {
83516
83965
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -83521,12 +83970,12 @@ async function executeWriteDriftEvidence(args2, directory) {
83521
83970
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
83522
83971
  }, null, 2);
83523
83972
  }
83524
- const evidenceDir = path93.dirname(validatedPath);
83973
+ const evidenceDir = path94.dirname(validatedPath);
83525
83974
  try {
83526
- await fs77.promises.mkdir(evidenceDir, { recursive: true });
83527
- const tempPath = path93.join(evidenceDir, `.${filename}.tmp`);
83528
- await fs77.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83529
- await fs77.promises.rename(tempPath, validatedPath);
83975
+ await fs78.promises.mkdir(evidenceDir, { recursive: true });
83976
+ const tempPath = path94.join(evidenceDir, `.${filename}.tmp`);
83977
+ await fs78.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83978
+ await fs78.promises.rename(tempPath, validatedPath);
83530
83979
  let snapshotInfo;
83531
83980
  let snapshotError;
83532
83981
  let qaProfileLocked;
@@ -83620,8 +84069,8 @@ var write_drift_evidence = createSwarmTool({
83620
84069
  init_tool();
83621
84070
  init_utils2();
83622
84071
  init_create_tool();
83623
- import fs78 from "fs";
83624
- import path94 from "path";
84072
+ import fs79 from "fs";
84073
+ import path95 from "path";
83625
84074
  function normalizeVerdict2(verdict) {
83626
84075
  switch (verdict) {
83627
84076
  case "APPROVED":
@@ -83669,7 +84118,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
83669
84118
  entries: [evidenceEntry]
83670
84119
  };
83671
84120
  const filename = "hallucination-guard.json";
83672
- const relativePath = path94.join("evidence", String(phase), filename);
84121
+ const relativePath = path95.join("evidence", String(phase), filename);
83673
84122
  let validatedPath;
83674
84123
  try {
83675
84124
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -83680,12 +84129,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
83680
84129
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
83681
84130
  }, null, 2);
83682
84131
  }
83683
- const evidenceDir = path94.dirname(validatedPath);
84132
+ const evidenceDir = path95.dirname(validatedPath);
83684
84133
  try {
83685
- await fs78.promises.mkdir(evidenceDir, { recursive: true });
83686
- const tempPath = path94.join(evidenceDir, `.${filename}.tmp`);
83687
- await fs78.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83688
- await fs78.promises.rename(tempPath, validatedPath);
84134
+ await fs79.promises.mkdir(evidenceDir, { recursive: true });
84135
+ const tempPath = path95.join(evidenceDir, `.${filename}.tmp`);
84136
+ await fs79.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
84137
+ await fs79.promises.rename(tempPath, validatedPath);
83689
84138
  return JSON.stringify({
83690
84139
  success: true,
83691
84140
  phase,
@@ -83894,7 +84343,7 @@ var OpenCodeSwarm = async (ctx) => {
83894
84343
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
83895
84344
  preflightTriggerManager = new PTM(automationConfig);
83896
84345
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
83897
- const swarmDir = path95.resolve(ctx.directory, ".swarm");
84346
+ const swarmDir = path96.resolve(ctx.directory, ".swarm");
83898
84347
  statusArtifact = new ASA(swarmDir);
83899
84348
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
83900
84349
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {