opencode-swarm 6.73.0 → 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/cli/index.js +17 -13
- package/dist/config/evidence-schema.d.ts +115 -0
- package/dist/index.js +715 -261
- package/dist/tools/pre-check-batch.d.ts +8 -0
- package/dist/tools/sast-baseline.d.ts +126 -0
- package/dist/tools/sast-scan.d.ts +27 -0
- package/dist/tools/save-plan.d.ts +7 -0
- package/package.json +1 -1
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(
|
|
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"),
|
|
@@ -16734,7 +16738,7 @@ async function updateTaskStatus(directory, taskId, status) {
|
|
|
16734
16738
|
const updatedPlan = { ...plan, phases: updatedPhases };
|
|
16735
16739
|
try {
|
|
16736
16740
|
await savePlan(directory, updatedPlan, {
|
|
16737
|
-
preserveCompletedStatuses:
|
|
16741
|
+
preserveCompletedStatuses: false
|
|
16738
16742
|
});
|
|
16739
16743
|
return updatedPlan;
|
|
16740
16744
|
} catch (error49) {
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
76606
|
-
import * as
|
|
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
|
|
76742
|
-
import * as
|
|
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 =
|
|
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 =
|
|
77954
|
+
const fd = fs64.openSync(filePath, "r");
|
|
77650
77955
|
const buffer = Buffer.alloc(8192);
|
|
77651
|
-
const bytesRead =
|
|
77652
|
-
|
|
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 =
|
|
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 {
|
|
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 =
|
|
77739
|
-
const resolvedDirectory =
|
|
77740
|
-
if (!resolvedPath.startsWith(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 (!
|
|
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
|
-
|
|
77829
|
-
|
|
77830
|
-
|
|
77831
|
-
|
|
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
|
|
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
|
-
|
|
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).
|
|
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 =
|
|
77943
|
-
} else if (
|
|
77944
|
-
resolved =
|
|
78362
|
+
resolved = path79.win32.resolve(inputPath);
|
|
78363
|
+
} else if (path79.isAbsolute(inputPath)) {
|
|
78364
|
+
resolved = path79.resolve(inputPath);
|
|
77945
78365
|
} else {
|
|
77946
|
-
resolved =
|
|
78366
|
+
resolved = path79.resolve(baseDir, inputPath);
|
|
77947
78367
|
}
|
|
77948
|
-
const workspaceResolved =
|
|
77949
|
-
let
|
|
78368
|
+
const workspaceResolved = path79.resolve(workspaceDir);
|
|
78369
|
+
let relative19;
|
|
77950
78370
|
if (isWinAbs) {
|
|
77951
|
-
|
|
78371
|
+
relative19 = path79.win32.relative(workspaceResolved, resolved);
|
|
77952
78372
|
} else {
|
|
77953
|
-
|
|
78373
|
+
relative19 = path79.relative(workspaceResolved, resolved);
|
|
77954
78374
|
}
|
|
77955
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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({
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
78520
|
-
|
|
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 =
|
|
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
|
|
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 (
|
|
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 (
|
|
78725
|
-
const rel =
|
|
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
|
|
78870
|
-
import * as
|
|
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 (!
|
|
79378
|
+
if (!fs66.existsSync(evidenceDir) || !fs66.statSync(evidenceDir).isDirectory()) {
|
|
78930
79379
|
return [];
|
|
78931
79380
|
}
|
|
78932
79381
|
let entries;
|
|
78933
79382
|
try {
|
|
78934
|
-
entries =
|
|
79383
|
+
entries = fs66.readdirSync(evidenceDir);
|
|
78935
79384
|
} catch {
|
|
78936
79385
|
return [];
|
|
78937
79386
|
}
|
|
78938
79387
|
for (const entry of entries) {
|
|
78939
|
-
const entryPath =
|
|
79388
|
+
const entryPath = path81.join(evidenceDir, entry);
|
|
78940
79389
|
try {
|
|
78941
|
-
const stat3 =
|
|
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 =
|
|
79404
|
+
const evidenceFilePath = path81.join(entryPath, "evidence.json");
|
|
78956
79405
|
try {
|
|
78957
|
-
const resolvedPath =
|
|
78958
|
-
const evidenceDirResolved =
|
|
78959
|
-
if (!resolvedPath.startsWith(evidenceDirResolved +
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
79007
|
-
const cwdResolved =
|
|
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 =
|
|
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 =
|
|
79590
|
+
const specPath = path81.join(cwd, SPEC_FILE);
|
|
79142
79591
|
let specContent;
|
|
79143
79592
|
try {
|
|
79144
|
-
specContent =
|
|
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 =
|
|
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 =
|
|
79643
|
+
const reportPath = path81.join(evidenceDir, reportFilename);
|
|
79195
79644
|
try {
|
|
79196
|
-
if (!
|
|
79197
|
-
|
|
79645
|
+
if (!fs66.existsSync(evidenceDir)) {
|
|
79646
|
+
fs66.mkdirSync(evidenceDir, { recursive: true });
|
|
79198
79647
|
}
|
|
79199
|
-
|
|
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
|
|
79278
|
-
import * as
|
|
79279
|
-
import * as
|
|
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 =
|
|
79809
|
+
const specPath = path82.join(targetWorkspace, ".swarm", "spec.md");
|
|
79361
79810
|
try {
|
|
79362
|
-
const stat3 = await
|
|
79811
|
+
const stat3 = await fs67.promises.stat(specPath);
|
|
79363
79812
|
specMtime = stat3.mtime.toISOString();
|
|
79364
|
-
const content = await
|
|
79365
|
-
specHash =
|
|
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,
|
|
@@ -79374,16 +79823,18 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
79374
79823
|
}
|
|
79375
79824
|
const dir = targetWorkspace;
|
|
79376
79825
|
const existingStatusMap = new Map;
|
|
79377
|
-
|
|
79378
|
-
|
|
79379
|
-
|
|
79380
|
-
|
|
79381
|
-
for (const
|
|
79382
|
-
|
|
79826
|
+
if (!args2.reset_statuses) {
|
|
79827
|
+
try {
|
|
79828
|
+
const existing = await loadPlanJsonOnly(dir);
|
|
79829
|
+
if (existing) {
|
|
79830
|
+
for (const phase of existing.phases) {
|
|
79831
|
+
for (const task of phase.tasks) {
|
|
79832
|
+
existingStatusMap.set(task.id, task.status);
|
|
79833
|
+
}
|
|
79383
79834
|
}
|
|
79384
79835
|
}
|
|
79385
|
-
}
|
|
79386
|
-
}
|
|
79836
|
+
} catch {}
|
|
79837
|
+
}
|
|
79387
79838
|
const plan = {
|
|
79388
79839
|
schema_version: "1.0.0",
|
|
79389
79840
|
title: args2.title,
|
|
@@ -79428,21 +79879,23 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
79428
79879
|
};
|
|
79429
79880
|
}
|
|
79430
79881
|
try {
|
|
79431
|
-
await savePlan(dir, plan
|
|
79882
|
+
await savePlan(dir, plan, {
|
|
79883
|
+
preserveCompletedStatuses: !args2.reset_statuses
|
|
79884
|
+
});
|
|
79432
79885
|
const savedPlan = await loadPlanJsonOnly(dir);
|
|
79433
79886
|
if (savedPlan) {
|
|
79434
79887
|
await takeSnapshotEvent(dir, savedPlan).catch(() => {});
|
|
79435
79888
|
}
|
|
79436
79889
|
await writeCheckpoint(dir).catch(() => {});
|
|
79437
79890
|
try {
|
|
79438
|
-
const markerPath =
|
|
79891
|
+
const markerPath = path82.join(dir, ".swarm", ".plan-write-marker");
|
|
79439
79892
|
const marker = JSON.stringify({
|
|
79440
79893
|
source: "save_plan",
|
|
79441
79894
|
timestamp: new Date().toISOString(),
|
|
79442
79895
|
phases_count: plan.phases.length,
|
|
79443
79896
|
tasks_count: tasksCount
|
|
79444
79897
|
});
|
|
79445
|
-
await
|
|
79898
|
+
await fs67.promises.writeFile(markerPath, marker, "utf8");
|
|
79446
79899
|
} catch {}
|
|
79447
79900
|
const warnings = [];
|
|
79448
79901
|
let criticReviewFound = false;
|
|
@@ -79458,7 +79911,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
79458
79911
|
return {
|
|
79459
79912
|
success: true,
|
|
79460
79913
|
message: "Plan saved successfully",
|
|
79461
|
-
plan_path:
|
|
79914
|
+
plan_path: path82.join(dir, ".swarm", "plan.json"),
|
|
79462
79915
|
phases_count: plan.phases.length,
|
|
79463
79916
|
tasks_count: tasksCount,
|
|
79464
79917
|
...warnings.length > 0 ? { warnings } : {}
|
|
@@ -79493,7 +79946,8 @@ var save_plan = createSwarmTool({
|
|
|
79493
79946
|
acceptance: tool.schema.string().optional().describe("Acceptance criteria for this task")
|
|
79494
79947
|
})).min(1).describe("Tasks in this phase")
|
|
79495
79948
|
})).min(1).describe("Implementation phases"),
|
|
79496
|
-
working_directory: tool.schema.string().optional().describe("Working directory (explicit path, required - no fallback)")
|
|
79949
|
+
working_directory: tool.schema.string().optional().describe("Working directory (explicit path, required - no fallback)"),
|
|
79950
|
+
reset_statuses: tool.schema.boolean().optional().describe("When true, reset ALL task statuses to pending regardless of prior completion state. " + "Use only when deliberately re-planning a phase from scratch. " + "Default false (preserves existing task statuses across plan revisions).")
|
|
79497
79951
|
},
|
|
79498
79952
|
execute: async (args2, _directory) => {
|
|
79499
79953
|
return JSON.stringify(await executeSavePlan(args2, _directory), null, 2);
|
|
@@ -79502,8 +79956,8 @@ var save_plan = createSwarmTool({
|
|
|
79502
79956
|
// src/tools/sbom-generate.ts
|
|
79503
79957
|
init_dist();
|
|
79504
79958
|
init_manager2();
|
|
79505
|
-
import * as
|
|
79506
|
-
import * as
|
|
79959
|
+
import * as fs68 from "fs";
|
|
79960
|
+
import * as path83 from "path";
|
|
79507
79961
|
|
|
79508
79962
|
// src/sbom/detectors/index.ts
|
|
79509
79963
|
init_utils();
|
|
@@ -80351,9 +80805,9 @@ function findManifestFiles(rootDir) {
|
|
|
80351
80805
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
80352
80806
|
function searchDir(dir) {
|
|
80353
80807
|
try {
|
|
80354
|
-
const entries =
|
|
80808
|
+
const entries = fs68.readdirSync(dir, { withFileTypes: true });
|
|
80355
80809
|
for (const entry of entries) {
|
|
80356
|
-
const fullPath =
|
|
80810
|
+
const fullPath = path83.join(dir, entry.name);
|
|
80357
80811
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
80358
80812
|
continue;
|
|
80359
80813
|
}
|
|
@@ -80362,7 +80816,7 @@ function findManifestFiles(rootDir) {
|
|
|
80362
80816
|
} else if (entry.isFile()) {
|
|
80363
80817
|
for (const pattern of patterns) {
|
|
80364
80818
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
80365
|
-
manifestFiles.push(
|
|
80819
|
+
manifestFiles.push(path83.relative(rootDir, fullPath));
|
|
80366
80820
|
break;
|
|
80367
80821
|
}
|
|
80368
80822
|
}
|
|
@@ -80378,13 +80832,13 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
80378
80832
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
80379
80833
|
for (const dir of directories) {
|
|
80380
80834
|
try {
|
|
80381
|
-
const entries =
|
|
80835
|
+
const entries = fs68.readdirSync(dir, { withFileTypes: true });
|
|
80382
80836
|
for (const entry of entries) {
|
|
80383
|
-
const fullPath =
|
|
80837
|
+
const fullPath = path83.join(dir, entry.name);
|
|
80384
80838
|
if (entry.isFile()) {
|
|
80385
80839
|
for (const pattern of patterns) {
|
|
80386
80840
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
80387
|
-
found.push(
|
|
80841
|
+
found.push(path83.relative(workingDir, fullPath));
|
|
80388
80842
|
break;
|
|
80389
80843
|
}
|
|
80390
80844
|
}
|
|
@@ -80397,11 +80851,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
80397
80851
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
80398
80852
|
const dirs = new Set;
|
|
80399
80853
|
for (const file3 of changedFiles) {
|
|
80400
|
-
let currentDir =
|
|
80854
|
+
let currentDir = path83.dirname(file3);
|
|
80401
80855
|
while (true) {
|
|
80402
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
80403
|
-
dirs.add(
|
|
80404
|
-
const parent =
|
|
80856
|
+
if (currentDir && currentDir !== "." && currentDir !== path83.sep) {
|
|
80857
|
+
dirs.add(path83.join(workingDir, currentDir));
|
|
80858
|
+
const parent = path83.dirname(currentDir);
|
|
80405
80859
|
if (parent === currentDir)
|
|
80406
80860
|
break;
|
|
80407
80861
|
currentDir = parent;
|
|
@@ -80415,7 +80869,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
80415
80869
|
}
|
|
80416
80870
|
function ensureOutputDir(outputDir) {
|
|
80417
80871
|
try {
|
|
80418
|
-
|
|
80872
|
+
fs68.mkdirSync(outputDir, { recursive: true });
|
|
80419
80873
|
} catch (error93) {
|
|
80420
80874
|
if (!error93 || error93.code !== "EEXIST") {
|
|
80421
80875
|
throw error93;
|
|
@@ -80485,7 +80939,7 @@ var sbom_generate = createSwarmTool({
|
|
|
80485
80939
|
const changedFiles = obj.changed_files;
|
|
80486
80940
|
const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
|
|
80487
80941
|
const workingDir = directory;
|
|
80488
|
-
const outputDir =
|
|
80942
|
+
const outputDir = path83.isAbsolute(relativeOutputDir) ? relativeOutputDir : path83.join(workingDir, relativeOutputDir);
|
|
80489
80943
|
let manifestFiles = [];
|
|
80490
80944
|
if (scope === "all") {
|
|
80491
80945
|
manifestFiles = findManifestFiles(workingDir);
|
|
@@ -80508,11 +80962,11 @@ var sbom_generate = createSwarmTool({
|
|
|
80508
80962
|
const processedFiles = [];
|
|
80509
80963
|
for (const manifestFile of manifestFiles) {
|
|
80510
80964
|
try {
|
|
80511
|
-
const fullPath =
|
|
80512
|
-
if (!
|
|
80965
|
+
const fullPath = path83.isAbsolute(manifestFile) ? manifestFile : path83.join(workingDir, manifestFile);
|
|
80966
|
+
if (!fs68.existsSync(fullPath)) {
|
|
80513
80967
|
continue;
|
|
80514
80968
|
}
|
|
80515
|
-
const content =
|
|
80969
|
+
const content = fs68.readFileSync(fullPath, "utf-8");
|
|
80516
80970
|
const components = detectComponents(manifestFile, content);
|
|
80517
80971
|
processedFiles.push(manifestFile);
|
|
80518
80972
|
if (components.length > 0) {
|
|
@@ -80525,8 +80979,8 @@ var sbom_generate = createSwarmTool({
|
|
|
80525
80979
|
const bom = generateCycloneDX(allComponents);
|
|
80526
80980
|
const bomJson = serializeCycloneDX(bom);
|
|
80527
80981
|
const filename = generateSbomFilename();
|
|
80528
|
-
const outputPath =
|
|
80529
|
-
|
|
80982
|
+
const outputPath = path83.join(outputDir, filename);
|
|
80983
|
+
fs68.writeFileSync(outputPath, bomJson, "utf-8");
|
|
80530
80984
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
80531
80985
|
try {
|
|
80532
80986
|
const timestamp = new Date().toISOString();
|
|
@@ -80568,8 +81022,8 @@ var sbom_generate = createSwarmTool({
|
|
|
80568
81022
|
// src/tools/schema-drift.ts
|
|
80569
81023
|
init_dist();
|
|
80570
81024
|
init_create_tool();
|
|
80571
|
-
import * as
|
|
80572
|
-
import * as
|
|
81025
|
+
import * as fs69 from "fs";
|
|
81026
|
+
import * as path84 from "path";
|
|
80573
81027
|
var SPEC_CANDIDATES = [
|
|
80574
81028
|
"openapi.json",
|
|
80575
81029
|
"openapi.yaml",
|
|
@@ -80601,28 +81055,28 @@ function normalizePath3(p) {
|
|
|
80601
81055
|
}
|
|
80602
81056
|
function discoverSpecFile(cwd, specFileArg) {
|
|
80603
81057
|
if (specFileArg) {
|
|
80604
|
-
const resolvedPath =
|
|
80605
|
-
const normalizedCwd = cwd.endsWith(
|
|
81058
|
+
const resolvedPath = path84.resolve(cwd, specFileArg);
|
|
81059
|
+
const normalizedCwd = cwd.endsWith(path84.sep) ? cwd : cwd + path84.sep;
|
|
80606
81060
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
80607
81061
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
80608
81062
|
}
|
|
80609
|
-
const ext =
|
|
81063
|
+
const ext = path84.extname(resolvedPath).toLowerCase();
|
|
80610
81064
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
80611
81065
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
80612
81066
|
}
|
|
80613
|
-
const stats =
|
|
81067
|
+
const stats = fs69.statSync(resolvedPath);
|
|
80614
81068
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
80615
81069
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
80616
81070
|
}
|
|
80617
|
-
if (!
|
|
81071
|
+
if (!fs69.existsSync(resolvedPath)) {
|
|
80618
81072
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
80619
81073
|
}
|
|
80620
81074
|
return resolvedPath;
|
|
80621
81075
|
}
|
|
80622
81076
|
for (const candidate of SPEC_CANDIDATES) {
|
|
80623
|
-
const candidatePath =
|
|
80624
|
-
if (
|
|
80625
|
-
const stats =
|
|
81077
|
+
const candidatePath = path84.resolve(cwd, candidate);
|
|
81078
|
+
if (fs69.existsSync(candidatePath)) {
|
|
81079
|
+
const stats = fs69.statSync(candidatePath);
|
|
80626
81080
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
80627
81081
|
return candidatePath;
|
|
80628
81082
|
}
|
|
@@ -80631,8 +81085,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
80631
81085
|
return null;
|
|
80632
81086
|
}
|
|
80633
81087
|
function parseSpec(specFile) {
|
|
80634
|
-
const content =
|
|
80635
|
-
const ext =
|
|
81088
|
+
const content = fs69.readFileSync(specFile, "utf-8");
|
|
81089
|
+
const ext = path84.extname(specFile).toLowerCase();
|
|
80636
81090
|
if (ext === ".json") {
|
|
80637
81091
|
return parseJsonSpec(content);
|
|
80638
81092
|
}
|
|
@@ -80703,12 +81157,12 @@ function extractRoutes(cwd) {
|
|
|
80703
81157
|
function walkDir(dir) {
|
|
80704
81158
|
let entries;
|
|
80705
81159
|
try {
|
|
80706
|
-
entries =
|
|
81160
|
+
entries = fs69.readdirSync(dir, { withFileTypes: true });
|
|
80707
81161
|
} catch {
|
|
80708
81162
|
return;
|
|
80709
81163
|
}
|
|
80710
81164
|
for (const entry of entries) {
|
|
80711
|
-
const fullPath =
|
|
81165
|
+
const fullPath = path84.join(dir, entry.name);
|
|
80712
81166
|
if (entry.isSymbolicLink()) {
|
|
80713
81167
|
continue;
|
|
80714
81168
|
}
|
|
@@ -80718,7 +81172,7 @@ function extractRoutes(cwd) {
|
|
|
80718
81172
|
}
|
|
80719
81173
|
walkDir(fullPath);
|
|
80720
81174
|
} else if (entry.isFile()) {
|
|
80721
|
-
const ext =
|
|
81175
|
+
const ext = path84.extname(entry.name).toLowerCase();
|
|
80722
81176
|
const baseName = entry.name.toLowerCase();
|
|
80723
81177
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
80724
81178
|
continue;
|
|
@@ -80736,7 +81190,7 @@ function extractRoutes(cwd) {
|
|
|
80736
81190
|
}
|
|
80737
81191
|
function extractRoutesFromFile(filePath) {
|
|
80738
81192
|
const routes = [];
|
|
80739
|
-
const content =
|
|
81193
|
+
const content = fs69.readFileSync(filePath, "utf-8");
|
|
80740
81194
|
const lines = content.split(/\r?\n/);
|
|
80741
81195
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
80742
81196
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -80884,8 +81338,8 @@ var schema_drift = createSwarmTool({
|
|
|
80884
81338
|
init_tool();
|
|
80885
81339
|
init_path_security();
|
|
80886
81340
|
init_create_tool();
|
|
80887
|
-
import * as
|
|
80888
|
-
import * as
|
|
81341
|
+
import * as fs70 from "fs";
|
|
81342
|
+
import * as path85 from "path";
|
|
80889
81343
|
var DEFAULT_MAX_RESULTS = 100;
|
|
80890
81344
|
var DEFAULT_MAX_LINES = 200;
|
|
80891
81345
|
var REGEX_TIMEOUT_MS = 5000;
|
|
@@ -80921,11 +81375,11 @@ function containsWindowsAttacks3(str) {
|
|
|
80921
81375
|
}
|
|
80922
81376
|
function isPathInWorkspace3(filePath, workspace) {
|
|
80923
81377
|
try {
|
|
80924
|
-
const resolvedPath =
|
|
80925
|
-
const realWorkspace =
|
|
80926
|
-
const realResolvedPath =
|
|
80927
|
-
const relativePath =
|
|
80928
|
-
if (relativePath.startsWith("..") ||
|
|
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)) {
|
|
80929
81383
|
return false;
|
|
80930
81384
|
}
|
|
80931
81385
|
return true;
|
|
@@ -80938,12 +81392,12 @@ function validatePathForRead2(filePath, workspace) {
|
|
|
80938
81392
|
}
|
|
80939
81393
|
function findRgInEnvPath() {
|
|
80940
81394
|
const searchPath = process.env.PATH ?? "";
|
|
80941
|
-
for (const dir of searchPath.split(
|
|
81395
|
+
for (const dir of searchPath.split(path85.delimiter)) {
|
|
80942
81396
|
if (!dir)
|
|
80943
81397
|
continue;
|
|
80944
81398
|
const isWindows = process.platform === "win32";
|
|
80945
|
-
const candidate =
|
|
80946
|
-
if (
|
|
81399
|
+
const candidate = path85.join(dir, isWindows ? "rg.exe" : "rg");
|
|
81400
|
+
if (fs70.existsSync(candidate))
|
|
80947
81401
|
return candidate;
|
|
80948
81402
|
}
|
|
80949
81403
|
return null;
|
|
@@ -80997,7 +81451,7 @@ async function ripgrepSearch(opts) {
|
|
|
80997
81451
|
stderr: "pipe",
|
|
80998
81452
|
cwd: opts.workspace
|
|
80999
81453
|
});
|
|
81000
|
-
const timeout = new Promise((
|
|
81454
|
+
const timeout = new Promise((resolve36) => setTimeout(() => resolve36("timeout"), REGEX_TIMEOUT_MS));
|
|
81001
81455
|
const exitPromise = proc.exited;
|
|
81002
81456
|
const result = await Promise.race([exitPromise, timeout]);
|
|
81003
81457
|
if (result === "timeout") {
|
|
@@ -81070,10 +81524,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
|
|
|
81070
81524
|
return files;
|
|
81071
81525
|
}
|
|
81072
81526
|
try {
|
|
81073
|
-
const entries =
|
|
81527
|
+
const entries = fs70.readdirSync(dir, { withFileTypes: true });
|
|
81074
81528
|
for (const entry of entries) {
|
|
81075
|
-
const fullPath =
|
|
81076
|
-
const relativePath =
|
|
81529
|
+
const fullPath = path85.join(dir, entry.name);
|
|
81530
|
+
const relativePath = path85.relative(workspace, fullPath);
|
|
81077
81531
|
if (!validatePathForRead2(fullPath, workspace)) {
|
|
81078
81532
|
continue;
|
|
81079
81533
|
}
|
|
@@ -81114,13 +81568,13 @@ async function fallbackSearch(opts) {
|
|
|
81114
81568
|
const matches = [];
|
|
81115
81569
|
let total = 0;
|
|
81116
81570
|
for (const file3 of files) {
|
|
81117
|
-
const fullPath =
|
|
81571
|
+
const fullPath = path85.join(opts.workspace, file3);
|
|
81118
81572
|
if (!validatePathForRead2(fullPath, opts.workspace)) {
|
|
81119
81573
|
continue;
|
|
81120
81574
|
}
|
|
81121
81575
|
let stats;
|
|
81122
81576
|
try {
|
|
81123
|
-
stats =
|
|
81577
|
+
stats = fs70.statSync(fullPath);
|
|
81124
81578
|
if (stats.size > MAX_FILE_SIZE_BYTES10) {
|
|
81125
81579
|
continue;
|
|
81126
81580
|
}
|
|
@@ -81129,7 +81583,7 @@ async function fallbackSearch(opts) {
|
|
|
81129
81583
|
}
|
|
81130
81584
|
let content;
|
|
81131
81585
|
try {
|
|
81132
|
-
content =
|
|
81586
|
+
content = fs70.readFileSync(fullPath, "utf-8");
|
|
81133
81587
|
} catch {
|
|
81134
81588
|
continue;
|
|
81135
81589
|
}
|
|
@@ -81241,7 +81695,7 @@ var search = createSwarmTool({
|
|
|
81241
81695
|
message: "Exclude pattern contains invalid Windows-specific sequence"
|
|
81242
81696
|
}, null, 2);
|
|
81243
81697
|
}
|
|
81244
|
-
if (!
|
|
81698
|
+
if (!fs70.existsSync(directory)) {
|
|
81245
81699
|
return JSON.stringify({
|
|
81246
81700
|
error: true,
|
|
81247
81701
|
type: "unknown",
|
|
@@ -81364,8 +81818,8 @@ var set_qa_gates = createSwarmTool({
|
|
|
81364
81818
|
init_tool();
|
|
81365
81819
|
init_path_security();
|
|
81366
81820
|
init_create_tool();
|
|
81367
|
-
import * as
|
|
81368
|
-
import * as
|
|
81821
|
+
import * as fs71 from "fs";
|
|
81822
|
+
import * as path86 from "path";
|
|
81369
81823
|
var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
81370
81824
|
function containsWindowsAttacks4(str) {
|
|
81371
81825
|
if (/:[^\\/]/.test(str))
|
|
@@ -81379,14 +81833,14 @@ function containsWindowsAttacks4(str) {
|
|
|
81379
81833
|
}
|
|
81380
81834
|
function isPathInWorkspace4(filePath, workspace) {
|
|
81381
81835
|
try {
|
|
81382
|
-
const resolvedPath =
|
|
81383
|
-
if (!
|
|
81836
|
+
const resolvedPath = path86.resolve(workspace, filePath);
|
|
81837
|
+
if (!fs71.existsSync(resolvedPath)) {
|
|
81384
81838
|
return true;
|
|
81385
81839
|
}
|
|
81386
|
-
const realWorkspace =
|
|
81387
|
-
const realResolvedPath =
|
|
81388
|
-
const relativePath =
|
|
81389
|
-
if (relativePath.startsWith("..") ||
|
|
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)) {
|
|
81390
81844
|
return false;
|
|
81391
81845
|
}
|
|
81392
81846
|
return true;
|
|
@@ -81558,7 +82012,7 @@ var suggestPatch = createSwarmTool({
|
|
|
81558
82012
|
message: "changes cannot be empty"
|
|
81559
82013
|
}, null, 2);
|
|
81560
82014
|
}
|
|
81561
|
-
if (!
|
|
82015
|
+
if (!fs71.existsSync(directory)) {
|
|
81562
82016
|
return JSON.stringify({
|
|
81563
82017
|
success: false,
|
|
81564
82018
|
error: true,
|
|
@@ -81594,8 +82048,8 @@ var suggestPatch = createSwarmTool({
|
|
|
81594
82048
|
});
|
|
81595
82049
|
continue;
|
|
81596
82050
|
}
|
|
81597
|
-
const fullPath =
|
|
81598
|
-
if (!
|
|
82051
|
+
const fullPath = path86.resolve(directory, change.file);
|
|
82052
|
+
if (!fs71.existsSync(fullPath)) {
|
|
81599
82053
|
errors5.push({
|
|
81600
82054
|
success: false,
|
|
81601
82055
|
error: true,
|
|
@@ -81609,7 +82063,7 @@ var suggestPatch = createSwarmTool({
|
|
|
81609
82063
|
}
|
|
81610
82064
|
let content;
|
|
81611
82065
|
try {
|
|
81612
|
-
content =
|
|
82066
|
+
content = fs71.readFileSync(fullPath, "utf-8");
|
|
81613
82067
|
} catch (err3) {
|
|
81614
82068
|
errors5.push({
|
|
81615
82069
|
success: false,
|
|
@@ -81688,8 +82142,8 @@ var suggestPatch = createSwarmTool({
|
|
|
81688
82142
|
// src/tools/lint-spec.ts
|
|
81689
82143
|
init_spec_schema();
|
|
81690
82144
|
init_create_tool();
|
|
81691
|
-
import * as
|
|
81692
|
-
import * as
|
|
82145
|
+
import * as fs72 from "fs";
|
|
82146
|
+
import * as path87 from "path";
|
|
81693
82147
|
var SPEC_FILE_NAME = "spec.md";
|
|
81694
82148
|
var SWARM_DIR2 = ".swarm";
|
|
81695
82149
|
var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
|
|
@@ -81742,8 +82196,8 @@ var lint_spec = createSwarmTool({
|
|
|
81742
82196
|
async execute(_args, directory) {
|
|
81743
82197
|
const errors5 = [];
|
|
81744
82198
|
const warnings = [];
|
|
81745
|
-
const specPath =
|
|
81746
|
-
if (!
|
|
82199
|
+
const specPath = path87.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
|
|
82200
|
+
if (!fs72.existsSync(specPath)) {
|
|
81747
82201
|
const result2 = {
|
|
81748
82202
|
valid: false,
|
|
81749
82203
|
specMtime: null,
|
|
@@ -81762,12 +82216,12 @@ var lint_spec = createSwarmTool({
|
|
|
81762
82216
|
}
|
|
81763
82217
|
let specMtime = null;
|
|
81764
82218
|
try {
|
|
81765
|
-
const stats =
|
|
82219
|
+
const stats = fs72.statSync(specPath);
|
|
81766
82220
|
specMtime = stats.mtime.toISOString();
|
|
81767
82221
|
} catch {}
|
|
81768
82222
|
let content;
|
|
81769
82223
|
try {
|
|
81770
|
-
content =
|
|
82224
|
+
content = fs72.readFileSync(specPath, "utf-8");
|
|
81771
82225
|
} catch (e) {
|
|
81772
82226
|
const result2 = {
|
|
81773
82227
|
valid: false,
|
|
@@ -81812,13 +82266,13 @@ var lint_spec = createSwarmTool({
|
|
|
81812
82266
|
});
|
|
81813
82267
|
// src/tools/mutation-test.ts
|
|
81814
82268
|
init_dist();
|
|
81815
|
-
import * as
|
|
81816
|
-
import * as
|
|
82269
|
+
import * as fs73 from "fs";
|
|
82270
|
+
import * as path89 from "path";
|
|
81817
82271
|
|
|
81818
82272
|
// src/mutation/engine.ts
|
|
81819
82273
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
81820
|
-
import { unlinkSync as
|
|
81821
|
-
import * as
|
|
82274
|
+
import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
|
|
82275
|
+
import * as path88 from "path";
|
|
81822
82276
|
|
|
81823
82277
|
// src/mutation/equivalence.ts
|
|
81824
82278
|
function isStaticallyEquivalent(originalCode, mutatedCode) {
|
|
@@ -81953,9 +82407,9 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
|
|
|
81953
82407
|
let patchFile;
|
|
81954
82408
|
try {
|
|
81955
82409
|
const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
81956
|
-
patchFile =
|
|
82410
|
+
patchFile = path88.join(workingDir, `.mutation_patch_${safeId2}.diff`);
|
|
81957
82411
|
try {
|
|
81958
|
-
|
|
82412
|
+
writeFileSync18(patchFile, patch.patch);
|
|
81959
82413
|
} catch (writeErr) {
|
|
81960
82414
|
error93 = `Failed to write patch file: ${writeErr}`;
|
|
81961
82415
|
outcome = "error";
|
|
@@ -82051,7 +82505,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
|
|
|
82051
82505
|
revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
|
|
82052
82506
|
}
|
|
82053
82507
|
try {
|
|
82054
|
-
|
|
82508
|
+
unlinkSync12(patchFile);
|
|
82055
82509
|
} catch (_unlinkErr) {}
|
|
82056
82510
|
}
|
|
82057
82511
|
}
|
|
@@ -82347,8 +82801,8 @@ var mutation_test = createSwarmTool({
|
|
|
82347
82801
|
];
|
|
82348
82802
|
for (const filePath of uniquePaths) {
|
|
82349
82803
|
try {
|
|
82350
|
-
const resolvedPath =
|
|
82351
|
-
sourceFiles.set(filePath,
|
|
82804
|
+
const resolvedPath = path89.resolve(cwd, filePath);
|
|
82805
|
+
sourceFiles.set(filePath, fs73.readFileSync(resolvedPath, "utf-8"));
|
|
82352
82806
|
} catch {}
|
|
82353
82807
|
}
|
|
82354
82808
|
const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
|
|
@@ -82366,8 +82820,8 @@ var mutation_test = createSwarmTool({
|
|
|
82366
82820
|
init_dist();
|
|
82367
82821
|
init_manager2();
|
|
82368
82822
|
init_detector();
|
|
82369
|
-
import * as
|
|
82370
|
-
import * as
|
|
82823
|
+
import * as fs74 from "fs";
|
|
82824
|
+
import * as path90 from "path";
|
|
82371
82825
|
init_create_tool();
|
|
82372
82826
|
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
82373
82827
|
var BINARY_CHECK_BYTES = 8192;
|
|
@@ -82433,7 +82887,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
82433
82887
|
if (languages?.length) {
|
|
82434
82888
|
const lowerLangs = languages.map((l) => l.toLowerCase());
|
|
82435
82889
|
filesToCheck = filesToCheck.filter((file3) => {
|
|
82436
|
-
const ext =
|
|
82890
|
+
const ext = path90.extname(file3.path).toLowerCase();
|
|
82437
82891
|
const langDef = getLanguageForExtension(ext);
|
|
82438
82892
|
const fileProfile = getProfileForFile(file3.path);
|
|
82439
82893
|
const langId = fileProfile?.id || langDef?.id;
|
|
@@ -82446,7 +82900,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
82446
82900
|
let skippedCount = 0;
|
|
82447
82901
|
for (const fileInfo of filesToCheck) {
|
|
82448
82902
|
const { path: filePath } = fileInfo;
|
|
82449
|
-
const fullPath =
|
|
82903
|
+
const fullPath = path90.isAbsolute(filePath) ? filePath : path90.join(directory, filePath);
|
|
82450
82904
|
const result = {
|
|
82451
82905
|
path: filePath,
|
|
82452
82906
|
language: "",
|
|
@@ -82476,7 +82930,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
82476
82930
|
}
|
|
82477
82931
|
let content;
|
|
82478
82932
|
try {
|
|
82479
|
-
content =
|
|
82933
|
+
content = fs74.readFileSync(fullPath, "utf8");
|
|
82480
82934
|
} catch {
|
|
82481
82935
|
result.skipped_reason = "file_read_error";
|
|
82482
82936
|
skippedCount++;
|
|
@@ -82495,7 +82949,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
82495
82949
|
results.push(result);
|
|
82496
82950
|
continue;
|
|
82497
82951
|
}
|
|
82498
|
-
const ext =
|
|
82952
|
+
const ext = path90.extname(filePath).toLowerCase();
|
|
82499
82953
|
const langDef = getLanguageForExtension(ext);
|
|
82500
82954
|
result.language = profile?.id || langDef?.id || "unknown";
|
|
82501
82955
|
const errors5 = extractSyntaxErrors(parser, content);
|
|
@@ -82587,8 +83041,8 @@ init_dist();
|
|
|
82587
83041
|
init_utils();
|
|
82588
83042
|
init_create_tool();
|
|
82589
83043
|
init_path_security();
|
|
82590
|
-
import * as
|
|
82591
|
-
import * as
|
|
83044
|
+
import * as fs75 from "fs";
|
|
83045
|
+
import * as path91 from "path";
|
|
82592
83046
|
var MAX_TEXT_LENGTH = 200;
|
|
82593
83047
|
var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
|
|
82594
83048
|
var SUPPORTED_EXTENSIONS4 = new Set([
|
|
@@ -82654,9 +83108,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
82654
83108
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
82655
83109
|
}
|
|
82656
83110
|
try {
|
|
82657
|
-
const resolvedPath =
|
|
82658
|
-
const normalizedCwd =
|
|
82659
|
-
const normalizedResolved =
|
|
83111
|
+
const resolvedPath = path91.resolve(paths);
|
|
83112
|
+
const normalizedCwd = path91.resolve(cwd);
|
|
83113
|
+
const normalizedResolved = path91.resolve(resolvedPath);
|
|
82660
83114
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
82661
83115
|
return {
|
|
82662
83116
|
error: "paths must be within the current working directory",
|
|
@@ -82672,13 +83126,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
82672
83126
|
}
|
|
82673
83127
|
}
|
|
82674
83128
|
function isSupportedExtension(filePath) {
|
|
82675
|
-
const ext =
|
|
83129
|
+
const ext = path91.extname(filePath).toLowerCase();
|
|
82676
83130
|
return SUPPORTED_EXTENSIONS4.has(ext);
|
|
82677
83131
|
}
|
|
82678
83132
|
function findSourceFiles4(dir, files = []) {
|
|
82679
83133
|
let entries;
|
|
82680
83134
|
try {
|
|
82681
|
-
entries =
|
|
83135
|
+
entries = fs75.readdirSync(dir);
|
|
82682
83136
|
} catch {
|
|
82683
83137
|
return files;
|
|
82684
83138
|
}
|
|
@@ -82687,10 +83141,10 @@ function findSourceFiles4(dir, files = []) {
|
|
|
82687
83141
|
if (SKIP_DIRECTORIES5.has(entry)) {
|
|
82688
83142
|
continue;
|
|
82689
83143
|
}
|
|
82690
|
-
const fullPath =
|
|
83144
|
+
const fullPath = path91.join(dir, entry);
|
|
82691
83145
|
let stat3;
|
|
82692
83146
|
try {
|
|
82693
|
-
stat3 =
|
|
83147
|
+
stat3 = fs75.statSync(fullPath);
|
|
82694
83148
|
} catch {
|
|
82695
83149
|
continue;
|
|
82696
83150
|
}
|
|
@@ -82783,7 +83237,7 @@ var todo_extract = createSwarmTool({
|
|
|
82783
83237
|
return JSON.stringify(errorResult, null, 2);
|
|
82784
83238
|
}
|
|
82785
83239
|
const scanPath = resolvedPath;
|
|
82786
|
-
if (!
|
|
83240
|
+
if (!fs75.existsSync(scanPath)) {
|
|
82787
83241
|
const errorResult = {
|
|
82788
83242
|
error: `path not found: ${pathsInput}`,
|
|
82789
83243
|
total: 0,
|
|
@@ -82793,13 +83247,13 @@ var todo_extract = createSwarmTool({
|
|
|
82793
83247
|
return JSON.stringify(errorResult, null, 2);
|
|
82794
83248
|
}
|
|
82795
83249
|
const filesToScan = [];
|
|
82796
|
-
const stat3 =
|
|
83250
|
+
const stat3 = fs75.statSync(scanPath);
|
|
82797
83251
|
if (stat3.isFile()) {
|
|
82798
83252
|
if (isSupportedExtension(scanPath)) {
|
|
82799
83253
|
filesToScan.push(scanPath);
|
|
82800
83254
|
} else {
|
|
82801
83255
|
const errorResult = {
|
|
82802
|
-
error: `unsupported file extension: ${
|
|
83256
|
+
error: `unsupported file extension: ${path91.extname(scanPath)}`,
|
|
82803
83257
|
total: 0,
|
|
82804
83258
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
82805
83259
|
entries: []
|
|
@@ -82812,11 +83266,11 @@ var todo_extract = createSwarmTool({
|
|
|
82812
83266
|
const allEntries = [];
|
|
82813
83267
|
for (const filePath of filesToScan) {
|
|
82814
83268
|
try {
|
|
82815
|
-
const fileStat =
|
|
83269
|
+
const fileStat = fs75.statSync(filePath);
|
|
82816
83270
|
if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
|
|
82817
83271
|
continue;
|
|
82818
83272
|
}
|
|
82819
|
-
const content =
|
|
83273
|
+
const content = fs75.readFileSync(filePath, "utf-8");
|
|
82820
83274
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
82821
83275
|
allEntries.push(...entries);
|
|
82822
83276
|
} catch {}
|
|
@@ -82846,18 +83300,18 @@ init_tool();
|
|
|
82846
83300
|
init_loader();
|
|
82847
83301
|
init_schema();
|
|
82848
83302
|
init_gate_evidence();
|
|
82849
|
-
import * as
|
|
82850
|
-
import * as
|
|
83303
|
+
import * as fs77 from "fs";
|
|
83304
|
+
import * as path93 from "path";
|
|
82851
83305
|
|
|
82852
83306
|
// src/hooks/diff-scope.ts
|
|
82853
|
-
import * as
|
|
82854
|
-
import * as
|
|
83307
|
+
import * as fs76 from "fs";
|
|
83308
|
+
import * as path92 from "path";
|
|
82855
83309
|
function getDeclaredScope(taskId, directory) {
|
|
82856
83310
|
try {
|
|
82857
|
-
const planPath =
|
|
82858
|
-
if (!
|
|
83311
|
+
const planPath = path92.join(directory, ".swarm", "plan.json");
|
|
83312
|
+
if (!fs76.existsSync(planPath))
|
|
82859
83313
|
return null;
|
|
82860
|
-
const raw =
|
|
83314
|
+
const raw = fs76.readFileSync(planPath, "utf-8");
|
|
82861
83315
|
const plan = JSON.parse(raw);
|
|
82862
83316
|
for (const phase of plan.phases ?? []) {
|
|
82863
83317
|
for (const task of phase.tasks ?? []) {
|
|
@@ -82972,7 +83426,7 @@ var TIER_3_PATTERNS = [
|
|
|
82972
83426
|
];
|
|
82973
83427
|
function matchesTier3Pattern(files) {
|
|
82974
83428
|
for (const file3 of files) {
|
|
82975
|
-
const fileName =
|
|
83429
|
+
const fileName = path93.basename(file3);
|
|
82976
83430
|
for (const pattern of TIER_3_PATTERNS) {
|
|
82977
83431
|
if (pattern.test(fileName)) {
|
|
82978
83432
|
return true;
|
|
@@ -82986,8 +83440,8 @@ function checkReviewerGate(taskId, workingDirectory) {
|
|
|
82986
83440
|
if (hasActiveTurboMode()) {
|
|
82987
83441
|
const resolvedDir2 = workingDirectory;
|
|
82988
83442
|
try {
|
|
82989
|
-
const planPath =
|
|
82990
|
-
const planRaw =
|
|
83443
|
+
const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
|
|
83444
|
+
const planRaw = fs77.readFileSync(planPath, "utf-8");
|
|
82991
83445
|
const plan = JSON.parse(planRaw);
|
|
82992
83446
|
for (const planPhase of plan.phases ?? []) {
|
|
82993
83447
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -83053,8 +83507,8 @@ function checkReviewerGate(taskId, workingDirectory) {
|
|
|
83053
83507
|
}
|
|
83054
83508
|
try {
|
|
83055
83509
|
const resolvedDir2 = workingDirectory;
|
|
83056
|
-
const planPath =
|
|
83057
|
-
const planRaw =
|
|
83510
|
+
const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
|
|
83511
|
+
const planRaw = fs77.readFileSync(planPath, "utf-8");
|
|
83058
83512
|
const plan = JSON.parse(planRaw);
|
|
83059
83513
|
for (const planPhase of plan.phases ?? []) {
|
|
83060
83514
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -83271,8 +83725,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
83271
83725
|
};
|
|
83272
83726
|
}
|
|
83273
83727
|
}
|
|
83274
|
-
normalizedDir =
|
|
83275
|
-
const pathParts = normalizedDir.split(
|
|
83728
|
+
normalizedDir = path93.normalize(args2.working_directory);
|
|
83729
|
+
const pathParts = normalizedDir.split(path93.sep);
|
|
83276
83730
|
if (pathParts.includes("..")) {
|
|
83277
83731
|
return {
|
|
83278
83732
|
success: false,
|
|
@@ -83282,11 +83736,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
83282
83736
|
]
|
|
83283
83737
|
};
|
|
83284
83738
|
}
|
|
83285
|
-
const resolvedDir =
|
|
83739
|
+
const resolvedDir = path93.resolve(normalizedDir);
|
|
83286
83740
|
try {
|
|
83287
|
-
const realPath =
|
|
83288
|
-
const planPath =
|
|
83289
|
-
if (!
|
|
83741
|
+
const realPath = fs77.realpathSync(resolvedDir);
|
|
83742
|
+
const planPath = path93.join(realPath, ".swarm", "plan.json");
|
|
83743
|
+
if (!fs77.existsSync(planPath)) {
|
|
83290
83744
|
return {
|
|
83291
83745
|
success: false,
|
|
83292
83746
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -83317,12 +83771,12 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
83317
83771
|
}
|
|
83318
83772
|
if (args2.status === "in_progress") {
|
|
83319
83773
|
try {
|
|
83320
|
-
const evidencePath =
|
|
83321
|
-
|
|
83322
|
-
const fd =
|
|
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");
|
|
83323
83777
|
let writeOk = false;
|
|
83324
83778
|
try {
|
|
83325
|
-
|
|
83779
|
+
fs77.writeSync(fd, JSON.stringify({
|
|
83326
83780
|
task_id: args2.task_id,
|
|
83327
83781
|
required_gates: ["reviewer", "test_engineer"],
|
|
83328
83782
|
gates: {},
|
|
@@ -83330,10 +83784,10 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
83330
83784
|
}, null, 2));
|
|
83331
83785
|
writeOk = true;
|
|
83332
83786
|
} finally {
|
|
83333
|
-
|
|
83787
|
+
fs77.closeSync(fd);
|
|
83334
83788
|
if (!writeOk) {
|
|
83335
83789
|
try {
|
|
83336
|
-
|
|
83790
|
+
fs77.unlinkSync(evidencePath);
|
|
83337
83791
|
} catch {}
|
|
83338
83792
|
}
|
|
83339
83793
|
}
|
|
@@ -83343,8 +83797,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
83343
83797
|
recoverTaskStateFromDelegations(args2.task_id);
|
|
83344
83798
|
let phaseRequiresReviewer = true;
|
|
83345
83799
|
try {
|
|
83346
|
-
const planPath =
|
|
83347
|
-
const planRaw =
|
|
83800
|
+
const planPath = path93.join(directory, ".swarm", "plan.json");
|
|
83801
|
+
const planRaw = fs77.readFileSync(planPath, "utf-8");
|
|
83348
83802
|
const plan = JSON.parse(planRaw);
|
|
83349
83803
|
const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
|
|
83350
83804
|
if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
|
|
@@ -83453,8 +83907,8 @@ init_utils2();
|
|
|
83453
83907
|
init_ledger();
|
|
83454
83908
|
init_manager();
|
|
83455
83909
|
init_create_tool();
|
|
83456
|
-
import
|
|
83457
|
-
import
|
|
83910
|
+
import fs78 from "fs";
|
|
83911
|
+
import path94 from "path";
|
|
83458
83912
|
function derivePlanId5(plan) {
|
|
83459
83913
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
83460
83914
|
}
|
|
@@ -83505,7 +83959,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
83505
83959
|
entries: [evidenceEntry]
|
|
83506
83960
|
};
|
|
83507
83961
|
const filename = "drift-verifier.json";
|
|
83508
|
-
const relativePath =
|
|
83962
|
+
const relativePath = path94.join("evidence", String(phase), filename);
|
|
83509
83963
|
let validatedPath;
|
|
83510
83964
|
try {
|
|
83511
83965
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -83516,12 +83970,12 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
83516
83970
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
83517
83971
|
}, null, 2);
|
|
83518
83972
|
}
|
|
83519
|
-
const evidenceDir =
|
|
83973
|
+
const evidenceDir = path94.dirname(validatedPath);
|
|
83520
83974
|
try {
|
|
83521
|
-
await
|
|
83522
|
-
const tempPath =
|
|
83523
|
-
await
|
|
83524
|
-
await
|
|
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);
|
|
83525
83979
|
let snapshotInfo;
|
|
83526
83980
|
let snapshotError;
|
|
83527
83981
|
let qaProfileLocked;
|
|
@@ -83615,8 +84069,8 @@ var write_drift_evidence = createSwarmTool({
|
|
|
83615
84069
|
init_tool();
|
|
83616
84070
|
init_utils2();
|
|
83617
84071
|
init_create_tool();
|
|
83618
|
-
import
|
|
83619
|
-
import
|
|
84072
|
+
import fs79 from "fs";
|
|
84073
|
+
import path95 from "path";
|
|
83620
84074
|
function normalizeVerdict2(verdict) {
|
|
83621
84075
|
switch (verdict) {
|
|
83622
84076
|
case "APPROVED":
|
|
@@ -83664,7 +84118,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
83664
84118
|
entries: [evidenceEntry]
|
|
83665
84119
|
};
|
|
83666
84120
|
const filename = "hallucination-guard.json";
|
|
83667
|
-
const relativePath =
|
|
84121
|
+
const relativePath = path95.join("evidence", String(phase), filename);
|
|
83668
84122
|
let validatedPath;
|
|
83669
84123
|
try {
|
|
83670
84124
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -83675,12 +84129,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
83675
84129
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
83676
84130
|
}, null, 2);
|
|
83677
84131
|
}
|
|
83678
|
-
const evidenceDir =
|
|
84132
|
+
const evidenceDir = path95.dirname(validatedPath);
|
|
83679
84133
|
try {
|
|
83680
|
-
await
|
|
83681
|
-
const tempPath =
|
|
83682
|
-
await
|
|
83683
|
-
await
|
|
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);
|
|
83684
84138
|
return JSON.stringify({
|
|
83685
84139
|
success: true,
|
|
83686
84140
|
phase,
|
|
@@ -83889,7 +84343,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
83889
84343
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
83890
84344
|
preflightTriggerManager = new PTM(automationConfig);
|
|
83891
84345
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
83892
|
-
const swarmDir =
|
|
84346
|
+
const swarmDir = path96.resolve(ctx.directory, ".swarm");
|
|
83893
84347
|
statusArtifact = new ASA(swarmDir);
|
|
83894
84348
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
83895
84349
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|