opencode-swarm 6.19.6 → 6.19.8
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/README.md +115 -0
- package/dist/cli/index.js +766 -3375
- package/dist/commands/handoff.d.ts +1 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/index.js +1337 -393
- package/dist/services/context-budget-service.d.ts +101 -0
- package/dist/services/handoff-service.d.ts +61 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/run-memory.d.ts +66 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/plugin-registration-adversarial.test.d.ts +1 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/tools/update-task-status.d.ts +47 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14490,16 +14490,68 @@ async function saveEvidence(directory, taskId, evidence) {
|
|
|
14490
14490
|
}
|
|
14491
14491
|
return updatedBundle;
|
|
14492
14492
|
}
|
|
14493
|
+
function isFlatRetrospective(parsed) {
|
|
14494
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && parsed.type === "retrospective" && !parsed.schema_version;
|
|
14495
|
+
}
|
|
14496
|
+
function remapLegacyTaskComplexity(entry) {
|
|
14497
|
+
const taskComplexity = entry.task_complexity;
|
|
14498
|
+
if (typeof taskComplexity === "string" && taskComplexity in LEGACY_TASK_COMPLEXITY_MAP) {
|
|
14499
|
+
return {
|
|
14500
|
+
...entry,
|
|
14501
|
+
task_complexity: LEGACY_TASK_COMPLEXITY_MAP[taskComplexity]
|
|
14502
|
+
};
|
|
14503
|
+
}
|
|
14504
|
+
return entry;
|
|
14505
|
+
}
|
|
14506
|
+
function wrapFlatRetrospective(flatEntry, taskId) {
|
|
14507
|
+
const now = new Date().toISOString();
|
|
14508
|
+
const remappedEntry = remapLegacyTaskComplexity(flatEntry);
|
|
14509
|
+
return {
|
|
14510
|
+
schema_version: "1.0.0",
|
|
14511
|
+
task_id: remappedEntry.task_id ?? taskId,
|
|
14512
|
+
created_at: remappedEntry.timestamp ?? now,
|
|
14513
|
+
updated_at: remappedEntry.timestamp ?? now,
|
|
14514
|
+
entries: [remappedEntry]
|
|
14515
|
+
};
|
|
14516
|
+
}
|
|
14493
14517
|
async function loadEvidence(directory, taskId) {
|
|
14494
14518
|
const sanitizedTaskId = sanitizeTaskId(taskId);
|
|
14495
14519
|
const relativePath = path3.join("evidence", sanitizedTaskId, "evidence.json");
|
|
14496
|
-
validateSwarmPath(directory, relativePath);
|
|
14520
|
+
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
14497
14521
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
14498
14522
|
if (content === null) {
|
|
14499
14523
|
return { status: "not_found" };
|
|
14500
14524
|
}
|
|
14525
|
+
let parsed;
|
|
14526
|
+
try {
|
|
14527
|
+
parsed = JSON.parse(content);
|
|
14528
|
+
} catch {
|
|
14529
|
+
return { status: "invalid_schema", errors: ["Invalid JSON"] };
|
|
14530
|
+
}
|
|
14531
|
+
if (isFlatRetrospective(parsed)) {
|
|
14532
|
+
const wrappedBundle = wrapFlatRetrospective(parsed, sanitizedTaskId);
|
|
14533
|
+
try {
|
|
14534
|
+
const validated = EvidenceBundleSchema.parse(wrappedBundle);
|
|
14535
|
+
const evidenceDir = path3.dirname(evidencePath);
|
|
14536
|
+
const bundleJson = JSON.stringify(validated);
|
|
14537
|
+
const tempPath = path3.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
14538
|
+
try {
|
|
14539
|
+
await Bun.write(tempPath, bundleJson);
|
|
14540
|
+
renameSync(tempPath, evidencePath);
|
|
14541
|
+
} catch (writeError) {
|
|
14542
|
+
try {
|
|
14543
|
+
rmSync(tempPath, { force: true });
|
|
14544
|
+
} catch {}
|
|
14545
|
+
warn(`Failed to persist repaired flat retrospective for task ${sanitizedTaskId}: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
14546
|
+
}
|
|
14547
|
+
return { status: "found", bundle: validated };
|
|
14548
|
+
} catch (error49) {
|
|
14549
|
+
warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
14550
|
+
const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => e.path.join(".") + ": " + e.message) : [String(error49)];
|
|
14551
|
+
return { status: "invalid_schema", errors: errors3 };
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14501
14554
|
try {
|
|
14502
|
-
const parsed = JSON.parse(content);
|
|
14503
14555
|
const validated = EvidenceBundleSchema.parse(parsed);
|
|
14504
14556
|
return { status: "found", bundle: validated };
|
|
14505
14557
|
} catch (error49) {
|
|
@@ -14592,7 +14644,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
|
|
|
14592
14644
|
}
|
|
14593
14645
|
return archived;
|
|
14594
14646
|
}
|
|
14595
|
-
var VALID_EVIDENCE_TYPES, TASK_ID_REGEX;
|
|
14647
|
+
var VALID_EVIDENCE_TYPES, TASK_ID_REGEX, LEGACY_TASK_COMPLEXITY_MAP;
|
|
14596
14648
|
var init_manager = __esm(() => {
|
|
14597
14649
|
init_zod();
|
|
14598
14650
|
init_evidence_schema();
|
|
@@ -14613,6 +14665,11 @@ var init_manager = __esm(() => {
|
|
|
14613
14665
|
"quality_budget"
|
|
14614
14666
|
];
|
|
14615
14667
|
TASK_ID_REGEX = /^[\w-]+(\.[\w-]+)*$/;
|
|
14668
|
+
LEGACY_TASK_COMPLEXITY_MAP = {
|
|
14669
|
+
low: "simple",
|
|
14670
|
+
medium: "moderate",
|
|
14671
|
+
high: "complex"
|
|
14672
|
+
};
|
|
14616
14673
|
});
|
|
14617
14674
|
|
|
14618
14675
|
// src/plan/manager.ts
|
|
@@ -14783,6 +14840,29 @@ ${markdown}`;
|
|
|
14783
14840
|
} catch {}
|
|
14784
14841
|
}
|
|
14785
14842
|
}
|
|
14843
|
+
async function updateTaskStatus(directory, taskId, status) {
|
|
14844
|
+
const plan = await loadPlan(directory);
|
|
14845
|
+
if (plan === null) {
|
|
14846
|
+
throw new Error(`Plan not found in directory: ${directory}`);
|
|
14847
|
+
}
|
|
14848
|
+
let taskFound = false;
|
|
14849
|
+
const updatedPhases = plan.phases.map((phase) => {
|
|
14850
|
+
const updatedTasks = phase.tasks.map((task) => {
|
|
14851
|
+
if (task.id === taskId) {
|
|
14852
|
+
taskFound = true;
|
|
14853
|
+
return { ...task, status };
|
|
14854
|
+
}
|
|
14855
|
+
return task;
|
|
14856
|
+
});
|
|
14857
|
+
return { ...phase, tasks: updatedTasks };
|
|
14858
|
+
});
|
|
14859
|
+
if (!taskFound) {
|
|
14860
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
14861
|
+
}
|
|
14862
|
+
const updatedPlan = { ...plan, phases: updatedPhases };
|
|
14863
|
+
await savePlan(directory, updatedPlan);
|
|
14864
|
+
return updatedPlan;
|
|
14865
|
+
}
|
|
14786
14866
|
function derivePlanMarkdown(plan) {
|
|
14787
14867
|
const statusMap = {
|
|
14788
14868
|
pending: "PENDING",
|
|
@@ -31812,7 +31892,7 @@ var init_detector = __esm(() => {
|
|
|
31812
31892
|
|
|
31813
31893
|
// src/build/discovery.ts
|
|
31814
31894
|
import * as fs6 from "fs";
|
|
31815
|
-
import * as
|
|
31895
|
+
import * as path17 from "path";
|
|
31816
31896
|
function isCommandAvailable(command) {
|
|
31817
31897
|
if (toolchainCache.has(command)) {
|
|
31818
31898
|
return toolchainCache.get(command);
|
|
@@ -31847,11 +31927,11 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
31847
31927
|
return regex.test(f);
|
|
31848
31928
|
});
|
|
31849
31929
|
if (matches.length > 0) {
|
|
31850
|
-
return
|
|
31930
|
+
return path17.join(dir, matches[0]);
|
|
31851
31931
|
}
|
|
31852
31932
|
} catch {}
|
|
31853
31933
|
} else {
|
|
31854
|
-
const filePath =
|
|
31934
|
+
const filePath = path17.join(workingDir, pattern);
|
|
31855
31935
|
if (fs6.existsSync(filePath)) {
|
|
31856
31936
|
return filePath;
|
|
31857
31937
|
}
|
|
@@ -31860,7 +31940,7 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
31860
31940
|
return null;
|
|
31861
31941
|
}
|
|
31862
31942
|
function getRepoDefinedScripts(workingDir, scripts) {
|
|
31863
|
-
const packageJsonPath =
|
|
31943
|
+
const packageJsonPath = path17.join(workingDir, "package.json");
|
|
31864
31944
|
if (!fs6.existsSync(packageJsonPath)) {
|
|
31865
31945
|
return [];
|
|
31866
31946
|
}
|
|
@@ -31901,7 +31981,7 @@ function findAllBuildFiles(workingDir) {
|
|
|
31901
31981
|
const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
|
|
31902
31982
|
findFilesRecursive(workingDir, regex, allBuildFiles);
|
|
31903
31983
|
} else {
|
|
31904
|
-
const filePath =
|
|
31984
|
+
const filePath = path17.join(workingDir, pattern);
|
|
31905
31985
|
if (fs6.existsSync(filePath)) {
|
|
31906
31986
|
allBuildFiles.add(filePath);
|
|
31907
31987
|
}
|
|
@@ -31914,7 +31994,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
31914
31994
|
try {
|
|
31915
31995
|
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
31916
31996
|
for (const entry of entries) {
|
|
31917
|
-
const fullPath =
|
|
31997
|
+
const fullPath = path17.join(dir, entry.name);
|
|
31918
31998
|
if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
|
|
31919
31999
|
findFilesRecursive(fullPath, regex, results);
|
|
31920
32000
|
} else if (entry.isFile() && regex.test(entry.name)) {
|
|
@@ -31937,7 +32017,7 @@ async function discoverBuildCommandsFromProfiles(workingDir) {
|
|
|
31937
32017
|
let foundCommand = false;
|
|
31938
32018
|
for (const cmd of sortedCommands) {
|
|
31939
32019
|
if (cmd.detectFile) {
|
|
31940
|
-
const detectFilePath =
|
|
32020
|
+
const detectFilePath = path17.join(workingDir, cmd.detectFile);
|
|
31941
32021
|
if (!fs6.existsSync(detectFilePath)) {
|
|
31942
32022
|
continue;
|
|
31943
32023
|
}
|
|
@@ -32147,7 +32227,7 @@ var init_discovery = __esm(() => {
|
|
|
32147
32227
|
|
|
32148
32228
|
// src/tools/lint.ts
|
|
32149
32229
|
import * as fs7 from "fs";
|
|
32150
|
-
import * as
|
|
32230
|
+
import * as path18 from "path";
|
|
32151
32231
|
function validateArgs(args2) {
|
|
32152
32232
|
if (typeof args2 !== "object" || args2 === null)
|
|
32153
32233
|
return false;
|
|
@@ -32158,9 +32238,9 @@ function validateArgs(args2) {
|
|
|
32158
32238
|
}
|
|
32159
32239
|
function getLinterCommand(linter, mode) {
|
|
32160
32240
|
const isWindows = process.platform === "win32";
|
|
32161
|
-
const binDir =
|
|
32162
|
-
const biomeBin = isWindows ?
|
|
32163
|
-
const eslintBin = isWindows ?
|
|
32241
|
+
const binDir = path18.join(process.cwd(), "node_modules", ".bin");
|
|
32242
|
+
const biomeBin = isWindows ? path18.join(binDir, "biome.EXE") : path18.join(binDir, "biome");
|
|
32243
|
+
const eslintBin = isWindows ? path18.join(binDir, "eslint.cmd") : path18.join(binDir, "eslint");
|
|
32164
32244
|
switch (linter) {
|
|
32165
32245
|
case "biome":
|
|
32166
32246
|
if (mode === "fix") {
|
|
@@ -32176,7 +32256,7 @@ function getLinterCommand(linter, mode) {
|
|
|
32176
32256
|
}
|
|
32177
32257
|
function getAdditionalLinterCommand(linter, mode, cwd) {
|
|
32178
32258
|
const gradlewName = process.platform === "win32" ? "gradlew.bat" : "gradlew";
|
|
32179
|
-
const gradlew = fs7.existsSync(
|
|
32259
|
+
const gradlew = fs7.existsSync(path18.join(cwd, gradlewName)) ? path18.join(cwd, gradlewName) : null;
|
|
32180
32260
|
switch (linter) {
|
|
32181
32261
|
case "ruff":
|
|
32182
32262
|
return mode === "fix" ? ["ruff", "check", "--fix", "."] : ["ruff", "check", "."];
|
|
@@ -32210,10 +32290,10 @@ function getAdditionalLinterCommand(linter, mode, cwd) {
|
|
|
32210
32290
|
}
|
|
32211
32291
|
}
|
|
32212
32292
|
function detectRuff(cwd) {
|
|
32213
|
-
if (fs7.existsSync(
|
|
32293
|
+
if (fs7.existsSync(path18.join(cwd, "ruff.toml")))
|
|
32214
32294
|
return isCommandAvailable("ruff");
|
|
32215
32295
|
try {
|
|
32216
|
-
const pyproject =
|
|
32296
|
+
const pyproject = path18.join(cwd, "pyproject.toml");
|
|
32217
32297
|
if (fs7.existsSync(pyproject)) {
|
|
32218
32298
|
const content = fs7.readFileSync(pyproject, "utf-8");
|
|
32219
32299
|
if (content.includes("[tool.ruff]"))
|
|
@@ -32223,19 +32303,19 @@ function detectRuff(cwd) {
|
|
|
32223
32303
|
return false;
|
|
32224
32304
|
}
|
|
32225
32305
|
function detectClippy(cwd) {
|
|
32226
|
-
return fs7.existsSync(
|
|
32306
|
+
return fs7.existsSync(path18.join(cwd, "Cargo.toml")) && isCommandAvailable("cargo");
|
|
32227
32307
|
}
|
|
32228
32308
|
function detectGolangciLint(cwd) {
|
|
32229
|
-
return fs7.existsSync(
|
|
32309
|
+
return fs7.existsSync(path18.join(cwd, "go.mod")) && isCommandAvailable("golangci-lint");
|
|
32230
32310
|
}
|
|
32231
32311
|
function detectCheckstyle(cwd) {
|
|
32232
|
-
const hasMaven = fs7.existsSync(
|
|
32233
|
-
const hasGradle = fs7.existsSync(
|
|
32234
|
-
const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs7.existsSync(
|
|
32312
|
+
const hasMaven = fs7.existsSync(path18.join(cwd, "pom.xml"));
|
|
32313
|
+
const hasGradle = fs7.existsSync(path18.join(cwd, "build.gradle")) || fs7.existsSync(path18.join(cwd, "build.gradle.kts"));
|
|
32314
|
+
const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs7.existsSync(path18.join(cwd, "gradlew")) || isCommandAvailable("gradle"));
|
|
32235
32315
|
return (hasMaven || hasGradle) && hasBinary;
|
|
32236
32316
|
}
|
|
32237
32317
|
function detectKtlint(cwd) {
|
|
32238
|
-
const hasKotlin = fs7.existsSync(
|
|
32318
|
+
const hasKotlin = fs7.existsSync(path18.join(cwd, "build.gradle.kts")) || fs7.existsSync(path18.join(cwd, "build.gradle")) || (() => {
|
|
32239
32319
|
try {
|
|
32240
32320
|
return fs7.readdirSync(cwd).some((f) => f.endsWith(".kt") || f.endsWith(".kts"));
|
|
32241
32321
|
} catch {
|
|
@@ -32254,11 +32334,11 @@ function detectDotnetFormat(cwd) {
|
|
|
32254
32334
|
}
|
|
32255
32335
|
}
|
|
32256
32336
|
function detectCppcheck(cwd) {
|
|
32257
|
-
if (fs7.existsSync(
|
|
32337
|
+
if (fs7.existsSync(path18.join(cwd, "CMakeLists.txt"))) {
|
|
32258
32338
|
return isCommandAvailable("cppcheck");
|
|
32259
32339
|
}
|
|
32260
32340
|
try {
|
|
32261
|
-
const dirsToCheck = [cwd,
|
|
32341
|
+
const dirsToCheck = [cwd, path18.join(cwd, "src")];
|
|
32262
32342
|
const hasCpp = dirsToCheck.some((dir) => {
|
|
32263
32343
|
try {
|
|
32264
32344
|
return fs7.readdirSync(dir).some((f) => /\.(c|cpp|cc|cxx|h|hpp)$/.test(f));
|
|
@@ -32272,13 +32352,13 @@ function detectCppcheck(cwd) {
|
|
|
32272
32352
|
}
|
|
32273
32353
|
}
|
|
32274
32354
|
function detectSwiftlint(cwd) {
|
|
32275
|
-
return fs7.existsSync(
|
|
32355
|
+
return fs7.existsSync(path18.join(cwd, "Package.swift")) && isCommandAvailable("swiftlint");
|
|
32276
32356
|
}
|
|
32277
32357
|
function detectDartAnalyze(cwd) {
|
|
32278
|
-
return fs7.existsSync(
|
|
32358
|
+
return fs7.existsSync(path18.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
32279
32359
|
}
|
|
32280
32360
|
function detectRubocop(cwd) {
|
|
32281
|
-
return (fs7.existsSync(
|
|
32361
|
+
return (fs7.existsSync(path18.join(cwd, "Gemfile")) || fs7.existsSync(path18.join(cwd, "gems.rb")) || fs7.existsSync(path18.join(cwd, ".rubocop.yml"))) && (isCommandAvailable("rubocop") || isCommandAvailable("bundle"));
|
|
32282
32362
|
}
|
|
32283
32363
|
function detectAdditionalLinter(cwd) {
|
|
32284
32364
|
if (detectRuff(cwd))
|
|
@@ -32498,7 +32578,7 @@ For Rust: rustup component add clippy`
|
|
|
32498
32578
|
|
|
32499
32579
|
// src/tools/secretscan.ts
|
|
32500
32580
|
import * as fs8 from "fs";
|
|
32501
|
-
import * as
|
|
32581
|
+
import * as path19 from "path";
|
|
32502
32582
|
function calculateShannonEntropy(str) {
|
|
32503
32583
|
if (str.length === 0)
|
|
32504
32584
|
return 0;
|
|
@@ -32525,7 +32605,7 @@ function isHighEntropyString(str) {
|
|
|
32525
32605
|
function containsPathTraversal(str) {
|
|
32526
32606
|
if (/\.\.[/\\]/.test(str))
|
|
32527
32607
|
return true;
|
|
32528
|
-
const normalized =
|
|
32608
|
+
const normalized = path19.normalize(str);
|
|
32529
32609
|
if (/\.\.[/\\]/.test(normalized))
|
|
32530
32610
|
return true;
|
|
32531
32611
|
if (str.includes("%2e%2e") || str.includes("%2E%2E"))
|
|
@@ -32553,7 +32633,7 @@ function validateDirectoryInput(dir) {
|
|
|
32553
32633
|
return null;
|
|
32554
32634
|
}
|
|
32555
32635
|
function isBinaryFile(filePath, buffer) {
|
|
32556
|
-
const ext =
|
|
32636
|
+
const ext = path19.extname(filePath).toLowerCase();
|
|
32557
32637
|
if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
32558
32638
|
return true;
|
|
32559
32639
|
}
|
|
@@ -32689,9 +32769,9 @@ function isSymlinkLoop(realPath, visited) {
|
|
|
32689
32769
|
return false;
|
|
32690
32770
|
}
|
|
32691
32771
|
function isPathWithinScope(realPath, scanDir) {
|
|
32692
|
-
const resolvedScanDir =
|
|
32693
|
-
const resolvedRealPath =
|
|
32694
|
-
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir +
|
|
32772
|
+
const resolvedScanDir = path19.resolve(scanDir);
|
|
32773
|
+
const resolvedRealPath = path19.resolve(realPath);
|
|
32774
|
+
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path19.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
|
|
32695
32775
|
}
|
|
32696
32776
|
function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
32697
32777
|
skippedDirs: 0,
|
|
@@ -32721,7 +32801,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
32721
32801
|
stats.skippedDirs++;
|
|
32722
32802
|
continue;
|
|
32723
32803
|
}
|
|
32724
|
-
const fullPath =
|
|
32804
|
+
const fullPath = path19.join(dir, entry);
|
|
32725
32805
|
let lstat;
|
|
32726
32806
|
try {
|
|
32727
32807
|
lstat = fs8.lstatSync(fullPath);
|
|
@@ -32752,7 +32832,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
32752
32832
|
const subFiles = findScannableFiles(fullPath, excludeDirs, scanDir, visited, stats);
|
|
32753
32833
|
files.push(...subFiles);
|
|
32754
32834
|
} else if (lstat.isFile()) {
|
|
32755
|
-
const ext =
|
|
32835
|
+
const ext = path19.extname(fullPath).toLowerCase();
|
|
32756
32836
|
if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
32757
32837
|
files.push(fullPath);
|
|
32758
32838
|
} else {
|
|
@@ -33018,7 +33098,7 @@ var init_secretscan = __esm(() => {
|
|
|
33018
33098
|
}
|
|
33019
33099
|
}
|
|
33020
33100
|
try {
|
|
33021
|
-
const scanDir =
|
|
33101
|
+
const scanDir = path19.resolve(directory);
|
|
33022
33102
|
if (!fs8.existsSync(scanDir)) {
|
|
33023
33103
|
const errorResult = {
|
|
33024
33104
|
error: "directory not found",
|
|
@@ -33147,7 +33227,7 @@ var init_secretscan = __esm(() => {
|
|
|
33147
33227
|
|
|
33148
33228
|
// src/tools/test-runner.ts
|
|
33149
33229
|
import * as fs9 from "fs";
|
|
33150
|
-
import * as
|
|
33230
|
+
import * as path20 from "path";
|
|
33151
33231
|
function containsPathTraversal2(str) {
|
|
33152
33232
|
if (/\.\.[/\\]/.test(str))
|
|
33153
33233
|
return true;
|
|
@@ -33240,14 +33320,14 @@ function hasDevDependency(devDeps, ...patterns) {
|
|
|
33240
33320
|
return hasPackageJsonDependency(devDeps, ...patterns);
|
|
33241
33321
|
}
|
|
33242
33322
|
function detectGoTest(cwd) {
|
|
33243
|
-
return fs9.existsSync(
|
|
33323
|
+
return fs9.existsSync(path20.join(cwd, "go.mod")) && isCommandAvailable("go");
|
|
33244
33324
|
}
|
|
33245
33325
|
function detectJavaMaven(cwd) {
|
|
33246
|
-
return fs9.existsSync(
|
|
33326
|
+
return fs9.existsSync(path20.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
|
|
33247
33327
|
}
|
|
33248
33328
|
function detectGradle(cwd) {
|
|
33249
|
-
const hasBuildFile = fs9.existsSync(
|
|
33250
|
-
const hasGradlew = fs9.existsSync(
|
|
33329
|
+
const hasBuildFile = fs9.existsSync(path20.join(cwd, "build.gradle")) || fs9.existsSync(path20.join(cwd, "build.gradle.kts"));
|
|
33330
|
+
const hasGradlew = fs9.existsSync(path20.join(cwd, "gradlew")) || fs9.existsSync(path20.join(cwd, "gradlew.bat"));
|
|
33251
33331
|
return hasBuildFile && (hasGradlew || isCommandAvailable("gradle"));
|
|
33252
33332
|
}
|
|
33253
33333
|
function detectDotnetTest(cwd) {
|
|
@@ -33260,30 +33340,30 @@ function detectDotnetTest(cwd) {
|
|
|
33260
33340
|
}
|
|
33261
33341
|
}
|
|
33262
33342
|
function detectCTest(cwd) {
|
|
33263
|
-
const hasSource = fs9.existsSync(
|
|
33264
|
-
const hasBuildCache = fs9.existsSync(
|
|
33343
|
+
const hasSource = fs9.existsSync(path20.join(cwd, "CMakeLists.txt"));
|
|
33344
|
+
const hasBuildCache = fs9.existsSync(path20.join(cwd, "CMakeCache.txt")) || fs9.existsSync(path20.join(cwd, "build", "CMakeCache.txt"));
|
|
33265
33345
|
return (hasSource || hasBuildCache) && isCommandAvailable("ctest");
|
|
33266
33346
|
}
|
|
33267
33347
|
function detectSwiftTest(cwd) {
|
|
33268
|
-
return fs9.existsSync(
|
|
33348
|
+
return fs9.existsSync(path20.join(cwd, "Package.swift")) && isCommandAvailable("swift");
|
|
33269
33349
|
}
|
|
33270
33350
|
function detectDartTest(cwd) {
|
|
33271
|
-
return fs9.existsSync(
|
|
33351
|
+
return fs9.existsSync(path20.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
33272
33352
|
}
|
|
33273
33353
|
function detectRSpec(cwd) {
|
|
33274
|
-
const hasRSpecFile = fs9.existsSync(
|
|
33275
|
-
const hasGemfile = fs9.existsSync(
|
|
33276
|
-
const hasSpecDir = fs9.existsSync(
|
|
33354
|
+
const hasRSpecFile = fs9.existsSync(path20.join(cwd, ".rspec"));
|
|
33355
|
+
const hasGemfile = fs9.existsSync(path20.join(cwd, "Gemfile"));
|
|
33356
|
+
const hasSpecDir = fs9.existsSync(path20.join(cwd, "spec"));
|
|
33277
33357
|
const hasRSpec = hasRSpecFile || hasGemfile && hasSpecDir;
|
|
33278
33358
|
return hasRSpec && (isCommandAvailable("bundle") || isCommandAvailable("rspec"));
|
|
33279
33359
|
}
|
|
33280
33360
|
function detectMinitest(cwd) {
|
|
33281
|
-
return fs9.existsSync(
|
|
33361
|
+
return fs9.existsSync(path20.join(cwd, "test")) && (fs9.existsSync(path20.join(cwd, "Gemfile")) || fs9.existsSync(path20.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
|
|
33282
33362
|
}
|
|
33283
33363
|
async function detectTestFramework(cwd) {
|
|
33284
33364
|
const baseDir = cwd || process.cwd();
|
|
33285
33365
|
try {
|
|
33286
|
-
const packageJsonPath =
|
|
33366
|
+
const packageJsonPath = path20.join(baseDir, "package.json");
|
|
33287
33367
|
if (fs9.existsSync(packageJsonPath)) {
|
|
33288
33368
|
const content = fs9.readFileSync(packageJsonPath, "utf-8");
|
|
33289
33369
|
const pkg = JSON.parse(content);
|
|
@@ -33304,16 +33384,16 @@ async function detectTestFramework(cwd) {
|
|
|
33304
33384
|
return "jest";
|
|
33305
33385
|
if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
|
|
33306
33386
|
return "mocha";
|
|
33307
|
-
if (fs9.existsSync(
|
|
33387
|
+
if (fs9.existsSync(path20.join(baseDir, "bun.lockb")) || fs9.existsSync(path20.join(baseDir, "bun.lock"))) {
|
|
33308
33388
|
if (scripts.test?.includes("bun"))
|
|
33309
33389
|
return "bun";
|
|
33310
33390
|
}
|
|
33311
33391
|
}
|
|
33312
33392
|
} catch {}
|
|
33313
33393
|
try {
|
|
33314
|
-
const pyprojectTomlPath =
|
|
33315
|
-
const setupCfgPath =
|
|
33316
|
-
const requirementsTxtPath =
|
|
33394
|
+
const pyprojectTomlPath = path20.join(baseDir, "pyproject.toml");
|
|
33395
|
+
const setupCfgPath = path20.join(baseDir, "setup.cfg");
|
|
33396
|
+
const requirementsTxtPath = path20.join(baseDir, "requirements.txt");
|
|
33317
33397
|
if (fs9.existsSync(pyprojectTomlPath)) {
|
|
33318
33398
|
const content = fs9.readFileSync(pyprojectTomlPath, "utf-8");
|
|
33319
33399
|
if (content.includes("[tool.pytest"))
|
|
@@ -33333,7 +33413,7 @@ async function detectTestFramework(cwd) {
|
|
|
33333
33413
|
}
|
|
33334
33414
|
} catch {}
|
|
33335
33415
|
try {
|
|
33336
|
-
const cargoTomlPath =
|
|
33416
|
+
const cargoTomlPath = path20.join(baseDir, "Cargo.toml");
|
|
33337
33417
|
if (fs9.existsSync(cargoTomlPath)) {
|
|
33338
33418
|
const content = fs9.readFileSync(cargoTomlPath, "utf-8");
|
|
33339
33419
|
if (content.includes("[dev-dependencies]")) {
|
|
@@ -33344,9 +33424,9 @@ async function detectTestFramework(cwd) {
|
|
|
33344
33424
|
}
|
|
33345
33425
|
} catch {}
|
|
33346
33426
|
try {
|
|
33347
|
-
const pesterConfigPath =
|
|
33348
|
-
const pesterConfigJsonPath =
|
|
33349
|
-
const pesterPs1Path =
|
|
33427
|
+
const pesterConfigPath = path20.join(baseDir, "pester.config.ps1");
|
|
33428
|
+
const pesterConfigJsonPath = path20.join(baseDir, "pester.config.ps1.json");
|
|
33429
|
+
const pesterPs1Path = path20.join(baseDir, "tests.ps1");
|
|
33350
33430
|
if (fs9.existsSync(pesterConfigPath) || fs9.existsSync(pesterConfigJsonPath) || fs9.existsSync(pesterPs1Path)) {
|
|
33351
33431
|
return "pester";
|
|
33352
33432
|
}
|
|
@@ -33379,8 +33459,8 @@ function getTestFilesFromConvention(sourceFiles) {
|
|
|
33379
33459
|
const testFiles = [];
|
|
33380
33460
|
for (const file3 of sourceFiles) {
|
|
33381
33461
|
const normalizedPath = file3.replace(/\\/g, "/");
|
|
33382
|
-
const basename4 =
|
|
33383
|
-
const
|
|
33462
|
+
const basename4 = path20.basename(file3);
|
|
33463
|
+
const dirname8 = path20.dirname(file3);
|
|
33384
33464
|
if (hasCompoundTestExtension(basename4) || basename4.includes(".spec.") || basename4.includes(".test.") || normalizedPath.includes("/__tests__/") || normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
|
|
33385
33465
|
if (!testFiles.includes(file3)) {
|
|
33386
33466
|
testFiles.push(file3);
|
|
@@ -33389,13 +33469,13 @@ function getTestFilesFromConvention(sourceFiles) {
|
|
|
33389
33469
|
}
|
|
33390
33470
|
for (const _pattern of TEST_PATTERNS) {
|
|
33391
33471
|
const nameWithoutExt = basename4.replace(/\.[^.]+$/, "");
|
|
33392
|
-
const ext =
|
|
33472
|
+
const ext = path20.extname(basename4);
|
|
33393
33473
|
const possibleTestFiles = [
|
|
33394
|
-
|
|
33395
|
-
|
|
33396
|
-
|
|
33397
|
-
|
|
33398
|
-
|
|
33474
|
+
path20.join(dirname8, `${nameWithoutExt}.spec${ext}`),
|
|
33475
|
+
path20.join(dirname8, `${nameWithoutExt}.test${ext}`),
|
|
33476
|
+
path20.join(dirname8, "__tests__", `${nameWithoutExt}${ext}`),
|
|
33477
|
+
path20.join(dirname8, "tests", `${nameWithoutExt}${ext}`),
|
|
33478
|
+
path20.join(dirname8, "test", `${nameWithoutExt}${ext}`)
|
|
33399
33479
|
];
|
|
33400
33480
|
for (const testFile of possibleTestFiles) {
|
|
33401
33481
|
if (fs9.existsSync(testFile) && !testFiles.includes(testFile)) {
|
|
@@ -33415,7 +33495,7 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
33415
33495
|
for (const testFile of candidateTestFiles) {
|
|
33416
33496
|
try {
|
|
33417
33497
|
const content = fs9.readFileSync(testFile, "utf-8");
|
|
33418
|
-
const testDir =
|
|
33498
|
+
const testDir = path20.dirname(testFile);
|
|
33419
33499
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
33420
33500
|
let match;
|
|
33421
33501
|
match = importRegex.exec(content);
|
|
@@ -33423,8 +33503,8 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
33423
33503
|
const importPath = match[1];
|
|
33424
33504
|
let resolvedImport;
|
|
33425
33505
|
if (importPath.startsWith(".")) {
|
|
33426
|
-
resolvedImport =
|
|
33427
|
-
const existingExt =
|
|
33506
|
+
resolvedImport = path20.resolve(testDir, importPath);
|
|
33507
|
+
const existingExt = path20.extname(resolvedImport);
|
|
33428
33508
|
if (!existingExt) {
|
|
33429
33509
|
for (const extToTry of [
|
|
33430
33510
|
".ts",
|
|
@@ -33444,12 +33524,12 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
33444
33524
|
} else {
|
|
33445
33525
|
continue;
|
|
33446
33526
|
}
|
|
33447
|
-
const importBasename =
|
|
33448
|
-
const importDir =
|
|
33527
|
+
const importBasename = path20.basename(resolvedImport, path20.extname(resolvedImport));
|
|
33528
|
+
const importDir = path20.dirname(resolvedImport);
|
|
33449
33529
|
for (const sourceFile of sourceFiles) {
|
|
33450
|
-
const sourceDir =
|
|
33451
|
-
const sourceBasename =
|
|
33452
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
33530
|
+
const sourceDir = path20.dirname(sourceFile);
|
|
33531
|
+
const sourceBasename = path20.basename(sourceFile, path20.extname(sourceFile));
|
|
33532
|
+
const isRelatedDir = importDir === sourceDir || importDir === path20.join(sourceDir, "__tests__") || importDir === path20.join(sourceDir, "tests") || importDir === path20.join(sourceDir, "test");
|
|
33453
33533
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
33454
33534
|
if (!testFiles.includes(testFile)) {
|
|
33455
33535
|
testFiles.push(testFile);
|
|
@@ -33464,8 +33544,8 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
33464
33544
|
while (match !== null) {
|
|
33465
33545
|
const importPath = match[1];
|
|
33466
33546
|
if (importPath.startsWith(".")) {
|
|
33467
|
-
let resolvedImport =
|
|
33468
|
-
const existingExt =
|
|
33547
|
+
let resolvedImport = path20.resolve(testDir, importPath);
|
|
33548
|
+
const existingExt = path20.extname(resolvedImport);
|
|
33469
33549
|
if (!existingExt) {
|
|
33470
33550
|
for (const extToTry of [
|
|
33471
33551
|
".ts",
|
|
@@ -33482,12 +33562,12 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
33482
33562
|
}
|
|
33483
33563
|
}
|
|
33484
33564
|
}
|
|
33485
|
-
const importDir =
|
|
33486
|
-
const importBasename =
|
|
33565
|
+
const importDir = path20.dirname(resolvedImport);
|
|
33566
|
+
const importBasename = path20.basename(resolvedImport, path20.extname(resolvedImport));
|
|
33487
33567
|
for (const sourceFile of sourceFiles) {
|
|
33488
|
-
const sourceDir =
|
|
33489
|
-
const sourceBasename =
|
|
33490
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
33568
|
+
const sourceDir = path20.dirname(sourceFile);
|
|
33569
|
+
const sourceBasename = path20.basename(sourceFile, path20.extname(sourceFile));
|
|
33570
|
+
const isRelatedDir = importDir === sourceDir || importDir === path20.join(sourceDir, "__tests__") || importDir === path20.join(sourceDir, "tests") || importDir === path20.join(sourceDir, "test");
|
|
33491
33571
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
33492
33572
|
if (!testFiles.includes(testFile)) {
|
|
33493
33573
|
testFiles.push(testFile);
|
|
@@ -33572,8 +33652,8 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
|
|
|
33572
33652
|
return ["mvn", "test"];
|
|
33573
33653
|
case "gradle": {
|
|
33574
33654
|
const isWindows = process.platform === "win32";
|
|
33575
|
-
const hasGradlewBat = fs9.existsSync(
|
|
33576
|
-
const hasGradlew = fs9.existsSync(
|
|
33655
|
+
const hasGradlewBat = fs9.existsSync(path20.join(baseDir, "gradlew.bat"));
|
|
33656
|
+
const hasGradlew = fs9.existsSync(path20.join(baseDir, "gradlew"));
|
|
33577
33657
|
if (hasGradlewBat && isWindows)
|
|
33578
33658
|
return ["gradlew.bat", "test"];
|
|
33579
33659
|
if (hasGradlew)
|
|
@@ -33590,7 +33670,7 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
|
|
|
33590
33670
|
"cmake-build-release",
|
|
33591
33671
|
"out"
|
|
33592
33672
|
];
|
|
33593
|
-
const actualBuildDir = buildDirCandidates.find((d) => fs9.existsSync(
|
|
33673
|
+
const actualBuildDir = buildDirCandidates.find((d) => fs9.existsSync(path20.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
|
|
33594
33674
|
return ["ctest", "--test-dir", actualBuildDir];
|
|
33595
33675
|
}
|
|
33596
33676
|
case "swift-test":
|
|
@@ -33943,7 +34023,7 @@ function findSourceFiles(dir, files = []) {
|
|
|
33943
34023
|
for (const entry of entries) {
|
|
33944
34024
|
if (SKIP_DIRECTORIES.has(entry))
|
|
33945
34025
|
continue;
|
|
33946
|
-
const fullPath =
|
|
34026
|
+
const fullPath = path20.join(dir, entry);
|
|
33947
34027
|
let stat2;
|
|
33948
34028
|
try {
|
|
33949
34029
|
stat2 = fs9.statSync(fullPath);
|
|
@@ -33953,7 +34033,7 @@ function findSourceFiles(dir, files = []) {
|
|
|
33953
34033
|
if (stat2.isDirectory()) {
|
|
33954
34034
|
findSourceFiles(fullPath, files);
|
|
33955
34035
|
} else if (stat2.isFile()) {
|
|
33956
|
-
const ext =
|
|
34036
|
+
const ext = path20.extname(fullPath).toLowerCase();
|
|
33957
34037
|
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
33958
34038
|
files.push(fullPath);
|
|
33959
34039
|
}
|
|
@@ -34122,13 +34202,13 @@ var init_test_runner = __esm(() => {
|
|
|
34122
34202
|
testFiles = [];
|
|
34123
34203
|
} else if (scope === "convention") {
|
|
34124
34204
|
const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
|
|
34125
|
-
const ext =
|
|
34205
|
+
const ext = path20.extname(f).toLowerCase();
|
|
34126
34206
|
return SOURCE_EXTENSIONS.has(ext);
|
|
34127
34207
|
}) : findSourceFiles(workingDir);
|
|
34128
34208
|
testFiles = getTestFilesFromConvention(sourceFiles);
|
|
34129
34209
|
} else if (scope === "graph") {
|
|
34130
34210
|
const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
|
|
34131
|
-
const ext =
|
|
34211
|
+
const ext = path20.extname(f).toLowerCase();
|
|
34132
34212
|
return SOURCE_EXTENSIONS.has(ext);
|
|
34133
34213
|
}) : findSourceFiles(workingDir);
|
|
34134
34214
|
const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
|
|
@@ -34151,7 +34231,7 @@ var init_test_runner = __esm(() => {
|
|
|
34151
34231
|
|
|
34152
34232
|
// src/services/preflight-service.ts
|
|
34153
34233
|
import * as fs10 from "fs";
|
|
34154
|
-
import * as
|
|
34234
|
+
import * as path21 from "path";
|
|
34155
34235
|
function validateDirectoryPath(dir) {
|
|
34156
34236
|
if (!dir || typeof dir !== "string") {
|
|
34157
34237
|
throw new Error("Directory path is required");
|
|
@@ -34159,8 +34239,8 @@ function validateDirectoryPath(dir) {
|
|
|
34159
34239
|
if (dir.includes("..")) {
|
|
34160
34240
|
throw new Error("Directory path must not contain path traversal sequences");
|
|
34161
34241
|
}
|
|
34162
|
-
const normalized =
|
|
34163
|
-
const absolutePath =
|
|
34242
|
+
const normalized = path21.normalize(dir);
|
|
34243
|
+
const absolutePath = path21.isAbsolute(normalized) ? normalized : path21.resolve(normalized);
|
|
34164
34244
|
return absolutePath;
|
|
34165
34245
|
}
|
|
34166
34246
|
function validateTimeout(timeoutMs, defaultValue) {
|
|
@@ -34183,7 +34263,7 @@ function validateTimeout(timeoutMs, defaultValue) {
|
|
|
34183
34263
|
}
|
|
34184
34264
|
function getPackageVersion(dir) {
|
|
34185
34265
|
try {
|
|
34186
|
-
const packagePath =
|
|
34266
|
+
const packagePath = path21.join(dir, "package.json");
|
|
34187
34267
|
if (fs10.existsSync(packagePath)) {
|
|
34188
34268
|
const content = fs10.readFileSync(packagePath, "utf-8");
|
|
34189
34269
|
const pkg = JSON.parse(content);
|
|
@@ -34194,7 +34274,7 @@ function getPackageVersion(dir) {
|
|
|
34194
34274
|
}
|
|
34195
34275
|
function getChangelogVersion(dir) {
|
|
34196
34276
|
try {
|
|
34197
|
-
const changelogPath =
|
|
34277
|
+
const changelogPath = path21.join(dir, "CHANGELOG.md");
|
|
34198
34278
|
if (fs10.existsSync(changelogPath)) {
|
|
34199
34279
|
const content = fs10.readFileSync(changelogPath, "utf-8");
|
|
34200
34280
|
const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
|
|
@@ -34208,7 +34288,7 @@ function getChangelogVersion(dir) {
|
|
|
34208
34288
|
function getVersionFileVersion(dir) {
|
|
34209
34289
|
const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
|
|
34210
34290
|
for (const file3 of possibleFiles) {
|
|
34211
|
-
const filePath =
|
|
34291
|
+
const filePath = path21.join(dir, file3);
|
|
34212
34292
|
if (fs10.existsSync(filePath)) {
|
|
34213
34293
|
try {
|
|
34214
34294
|
const content = fs10.readFileSync(filePath, "utf-8").trim();
|
|
@@ -36194,8 +36274,8 @@ var init_tree_sitter = __esm(() => {
|
|
|
36194
36274
|
bytes = Promise.resolve(input);
|
|
36195
36275
|
} else {
|
|
36196
36276
|
if (globalThis.process?.versions.node) {
|
|
36197
|
-
const
|
|
36198
|
-
bytes =
|
|
36277
|
+
const fs24 = await import("fs/promises");
|
|
36278
|
+
bytes = fs24.readFile(input);
|
|
36199
36279
|
} else {
|
|
36200
36280
|
bytes = fetch(input).then((response) => response.arrayBuffer().then((buffer) => {
|
|
36201
36281
|
if (response.ok) {
|
|
@@ -38002,7 +38082,7 @@ var init_runtime = __esm(() => {
|
|
|
38002
38082
|
});
|
|
38003
38083
|
|
|
38004
38084
|
// src/index.ts
|
|
38005
|
-
import * as
|
|
38085
|
+
import * as path44 from "path";
|
|
38006
38086
|
|
|
38007
38087
|
// src/tools/tool-names.ts
|
|
38008
38088
|
var TOOL_NAMES = [
|
|
@@ -38030,7 +38110,9 @@ var TOOL_NAMES = [
|
|
|
38030
38110
|
"retrieve_summary",
|
|
38031
38111
|
"extract_code_blocks",
|
|
38032
38112
|
"phase_complete",
|
|
38033
|
-
"save_plan"
|
|
38113
|
+
"save_plan",
|
|
38114
|
+
"update_task_status",
|
|
38115
|
+
"write_retro"
|
|
38034
38116
|
];
|
|
38035
38117
|
var TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
38036
38118
|
|
|
@@ -38068,7 +38150,9 @@ var AGENT_TOOL_MAP = {
|
|
|
38068
38150
|
"secretscan",
|
|
38069
38151
|
"symbols",
|
|
38070
38152
|
"test_runner",
|
|
38071
|
-
"todo_extract"
|
|
38153
|
+
"todo_extract",
|
|
38154
|
+
"update_task_status",
|
|
38155
|
+
"write_retro"
|
|
38072
38156
|
],
|
|
38073
38157
|
explorer: [
|
|
38074
38158
|
"complexity_hotspots",
|
|
@@ -39009,9 +39093,10 @@ writeCount > 0 on source files from the Architect is equivalent to GATE_DELEGATI
|
|
|
39009
39093
|
|
|
39010
39094
|
PLAN STATE PROTECTION
|
|
39011
39095
|
.swarm/plan.md and .swarm/plan.json are READABLE but NOT DIRECTLY WRITABLE for state transitions.
|
|
39012
|
-
Task status changes (
|
|
39096
|
+
Task-level status changes (marking individual tasks as "completed") must use update_task_status().
|
|
39097
|
+
Phase-level completion (marking an entire phase as done) must use phase_complete().
|
|
39013
39098
|
You may write to plan.md/plan.json for STRUCTURAL changes (adding tasks, updating descriptions).
|
|
39014
|
-
You may NOT write to plan.md/plan.json to change task completion status or phase status.
|
|
39099
|
+
You may NOT write to plan.md/plan.json to change task completion status or phase status directly.
|
|
39015
39100
|
"I'll just mark it done directly" is a bypass \u2014 equivalent to GATE_DELEGATION_BYPASS.
|
|
39016
39101
|
|
|
39017
39102
|
6i. **DELEGATION DISCIPLINE**
|
|
@@ -39047,7 +39132,7 @@ It is the same severity as skipping all gates. The QA gate is ALL steps or NONE.
|
|
|
39047
39132
|
- Target file is in: pages/, components/, views/, screens/, ui/, layouts/
|
|
39048
39133
|
If triggered: delegate to {{AGENT_PREFIX}}designer FIRST to produce a code scaffold. Then pass the scaffold to {{AGENT_PREFIX}}coder as INPUT alongside the task. The coder implements the TODOs in the scaffold without changing component structure or accessibility attributes.
|
|
39049
39134
|
If not triggered: delegate directly to {{AGENT_PREFIX}}coder as normal.
|
|
39050
|
-
10. **RETROSPECTIVE TRACKING**: At the end of every phase, record phase metrics in .swarm/context.md under "## Phase Metrics" and write a retrospective evidence entry via
|
|
39135
|
+
10. **RETROSPECTIVE TRACKING**: At the end of every phase, record phase metrics in .swarm/context.md under "## Phase Metrics" and write a retrospective evidence entry via write_retro. Track: phase, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned (max 5). Reset Phase Metrics to 0 after writing.
|
|
39051
39136
|
11. **CHECKPOINTS**: Before delegating multi-file refactor tasks (3+ files), create a checkpoint save. On critical failures when redo is faster than iterative fixes, restore from checkpoint. Use checkpoint tool: \`checkpoint save\` before risky operations, \`checkpoint restore\` on failure.
|
|
39052
39137
|
|
|
39053
39138
|
SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption, hash, key, certificate, ssl, tls, jwt, oauth, session, csrf, xss, injection, sanitization, permission, access, vulnerable, exploit, privilege, authorization, roles, authentication, mfa, 2fa, totp, otp, salt, iv, nonce, hmac, aes, rsa, sha256, bcrypt, scrypt, argon2, api_key, apikey, private_key, public_key, rbac, admin, superuser, sqli, rce, ssrf, xxe, nosql, command_injection
|
|
@@ -39072,7 +39157,7 @@ Outside OpenCode, invoke any plugin command via: \`bunx opencode-swarm run <comm
|
|
|
39072
39157
|
|
|
39073
39158
|
SMEs advise only. Reviewer and critic review only. None of them write code.
|
|
39074
39159
|
|
|
39075
|
-
Available Tools: symbols (code symbol search), checkpoint (state snapshots), diff (structured git diff with contract change detection), imports (dependency audit), lint (code quality), placeholder_scan (placeholder/todo detection), secretscan (secret detection), sast_scan (static analysis security scan), syntax_check (syntax validation), test_runner (auto-detect and run tests), pkg_audit (dependency vulnerability scan \u2014 npm/pip/cargo), complexity_hotspots (git churn \xD7 complexity risk map), schema_drift (OpenAPI spec vs route drift), todo_extract (structured TODO/FIXME extraction), evidence_check (verify task evidence completeness), sbom_generate (SBOM generation for dependency inventory), build_check (build verification), quality_budget (code quality budget check), pre_check_batch (parallel verification: lint:check + secretscan + sast_scan + quality_budget)
|
|
39160
|
+
Available Tools: symbols (code symbol search), checkpoint (state snapshots), diff (structured git diff with contract change detection), imports (dependency audit), lint (code quality), placeholder_scan (placeholder/todo detection), secretscan (secret detection), sast_scan (static analysis security scan), syntax_check (syntax validation), test_runner (auto-detect and run tests), pkg_audit (dependency vulnerability scan \u2014 npm/pip/cargo), complexity_hotspots (git churn \xD7 complexity risk map), schema_drift (OpenAPI spec vs route drift), todo_extract (structured TODO/FIXME extraction), evidence_check (verify task evidence completeness), sbom_generate (SBOM generation for dependency inventory), build_check (build verification), quality_budget (code quality budget check), pre_check_batch (parallel verification: lint:check + secretscan + sast_scan + quality_budget), update_task_status (mark tasks complete, track phase progress), write_retro (document phase retrospectives via phase_complete workflow, capture lessons learned)
|
|
39076
39161
|
|
|
39077
39162
|
## DELEGATION FORMAT
|
|
39078
39163
|
|
|
@@ -39455,6 +39540,9 @@ The ONLY exception: lint tool in fix mode (step 5g) auto-corrects by design.
|
|
|
39455
39540
|
All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
|
|
39456
39541
|
|
|
39457
39542
|
5a. **UI DESIGN GATE** (conditional \u2014 Rule 9): If task matches UI trigger \u2192 {{AGENT_PREFIX}}designer produces scaffold \u2192 pass scaffold to coder as INPUT. If no match \u2192 skip.
|
|
39543
|
+
|
|
39544
|
+
\u2192 After step 5a (or immediately if no UI task applies): Call update_task_status with status in_progress for the current task. Then proceed to step 5b.
|
|
39545
|
+
|
|
39458
39546
|
5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
|
|
39459
39547
|
5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
|
|
39460
39548
|
\u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
|
|
@@ -39533,7 +39621,7 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
|
|
|
39533
39621
|
Any blank "value: ___" field = gate was not run = task is NOT complete.
|
|
39534
39622
|
Filling this checklist from memory ("I think I ran it") is INVALID. Each value must come from actual tool/agent output in this session.
|
|
39535
39623
|
|
|
39536
|
-
5o.
|
|
39624
|
+
5o. Call update_task_status with status "completed", proceed to next task.
|
|
39537
39625
|
|
|
39538
39626
|
## \u26D4 RETROSPECTIVE GATE
|
|
39539
39627
|
|
|
@@ -39541,31 +39629,26 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
|
|
|
39541
39629
|
|
|
39542
39630
|
**How to write the retrospective:**
|
|
39543
39631
|
|
|
39544
|
-
|
|
39545
|
-
|
|
39546
|
-
|
|
39547
|
-
|
|
39548
|
-
|
|
39549
|
-
|
|
39550
|
-
|
|
39551
|
-
|
|
39552
|
-
|
|
39553
|
-
|
|
39554
|
-
|
|
39555
|
-
|
|
39556
|
-
|
|
39557
|
-
|
|
39558
|
-
|
|
39559
|
-
|
|
39560
|
-
"timestamp": "<ISO 8601>",
|
|
39561
|
-
"agent": "architect",
|
|
39562
|
-
"metadata": { "plan_id": "<current plan title from .swarm/plan.json>" }
|
|
39563
|
-
}
|
|
39564
|
-
\`\`\`
|
|
39632
|
+
Call the \`write_retro\` tool with the required fields:
|
|
39633
|
+
- \`phase\`: The phase number being completed (e.g., 1, 2, 3)
|
|
39634
|
+
- \`summary\`: Human-readable summary of the phase
|
|
39635
|
+
- \`task_count\`: Count of tasks completed in this phase
|
|
39636
|
+
- \`task_complexity\`: One of \`trivial\` | \`simple\` | \`moderate\` | \`complex\`
|
|
39637
|
+
- \`total_tool_calls\`: Total number of tool calls in this phase
|
|
39638
|
+
- \`coder_revisions\`: Number of coder revisions made
|
|
39639
|
+
- \`reviewer_rejections\`: Number of reviewer rejections received
|
|
39640
|
+
- \`test_failures\`: Number of test failures encountered
|
|
39641
|
+
- \`security_findings\`: Number of security findings
|
|
39642
|
+
- \`integration_issues\`: Number of integration issues
|
|
39643
|
+
- \`lessons_learned\`: (optional) Key lessons learned from this phase (max 5)
|
|
39644
|
+
- \`top_rejection_reasons\`: (optional) Top reasons for reviewer rejections
|
|
39645
|
+
- \`metadata\`: (optional) Additional metadata, e.g., \`{ "plan_id": "<current plan title from .swarm/plan.json>" }\`
|
|
39646
|
+
|
|
39647
|
+
The tool will automatically write the retrospective to \`.swarm/evidence/retro-{phase}/evidence.json\` with the correct schema wrapper.
|
|
39565
39648
|
|
|
39566
39649
|
**Required field rules:**
|
|
39567
|
-
- \`verdict\`
|
|
39568
|
-
- \`
|
|
39650
|
+
- \`verdict\` is auto-generated by write_retro with value \`"pass"\`. The resulting retrospective entry will have verdict \`"pass"\`; this is required for phase_complete to succeed.
|
|
39651
|
+
- \`phase\` MUST match the phase number you are completing
|
|
39569
39652
|
- \`lessons_learned\` should be 3-5 concrete, actionable items from this phase
|
|
39570
39653
|
- Write the bundle as task_id \`retro-{N}\` (e.g., \`retro-1\` for Phase 1, \`retro-2\` for Phase 2)
|
|
39571
39654
|
- \`metadata.plan_id\` should be set to the current project's plan title (from \`.swarm/plan.json\` header). This enables cross-project filtering in the retrospective injection system.
|
|
@@ -39590,7 +39673,7 @@ Use the evidence manager tool to write a bundle at \`retro-{N}\` (where N is the
|
|
|
39590
39673
|
- Summary of what was added/modified/removed
|
|
39591
39674
|
- List of doc files that may need updating (README.md, CONTRIBUTING.md, docs/)
|
|
39592
39675
|
3. Update context.md
|
|
39593
|
-
4. Write retrospective evidence: record
|
|
39676
|
+
4. Write retrospective evidence: record phase, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned to .swarm/evidence/ via write_retro. Reset Phase Metrics in context.md to 0.
|
|
39594
39677
|
4.5. Run \`evidence_check\` to verify all completed tasks have required evidence (review + test). If gaps found, note in retrospective lessons_learned. Optionally run \`pkg_audit\` if dependencies were modified during this phase. Optionally run \`schema_drift\` if API routes were modified during this phase.
|
|
39595
39678
|
5. Run \`sbom_generate\` with scope='changed' to capture post-implementation dependency snapshot (saved to \`.swarm/evidence/sbom/\`). This is a non-blocking step - always proceeds to summary.
|
|
39596
39679
|
5.5. If \`.swarm/spec.md\` exists: delegate {{AGENT_PREFIX}}critic with DRIFT-CHECK context \u2014 include phase number, list of completed task IDs and descriptions, and evidence path (\`.swarm/evidence/\`). If SIGNIFICANT DRIFT is returned: surface as a warning to the user before proceeding. If spec.md does not exist: skip silently.
|
|
@@ -40757,6 +40840,9 @@ function getAgentConfigs(config2) {
|
|
|
40757
40840
|
} else {
|
|
40758
40841
|
sdkConfig.mode = "subagent";
|
|
40759
40842
|
}
|
|
40843
|
+
if (sdkConfig.mode === "primary") {
|
|
40844
|
+
delete sdkConfig.model;
|
|
40845
|
+
}
|
|
40760
40846
|
const baseAgentName = stripKnownSwarmPrefix(agent.name);
|
|
40761
40847
|
if (!toolFilterEnabled) {
|
|
40762
40848
|
sdkConfig.tools = agent.config.tools ?? {};
|
|
@@ -43721,6 +43807,416 @@ async function handleExportCommand(directory, _args) {
|
|
|
43721
43807
|
const exportData = await getExportData(directory);
|
|
43722
43808
|
return formatExportMarkdown(exportData);
|
|
43723
43809
|
}
|
|
43810
|
+
// src/commands/handoff.ts
|
|
43811
|
+
init_utils2();
|
|
43812
|
+
import { renameSync as renameSync4 } from "fs";
|
|
43813
|
+
|
|
43814
|
+
// src/services/handoff-service.ts
|
|
43815
|
+
init_utils2();
|
|
43816
|
+
init_manager2();
|
|
43817
|
+
var RTL_OVERRIDE_PATTERN = /[\u202e\u202d\u202c\u200f]/g;
|
|
43818
|
+
var MAX_TASK_ID_LENGTH = 100;
|
|
43819
|
+
var MAX_DECISION_LENGTH = 500;
|
|
43820
|
+
var MAX_INCOMPLETE_TASKS = 20;
|
|
43821
|
+
function escapeHtml(str) {
|
|
43822
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
43823
|
+
}
|
|
43824
|
+
function sanitizeString(str, maxLength) {
|
|
43825
|
+
if (!str)
|
|
43826
|
+
return "";
|
|
43827
|
+
const sanitized = String(str).replace(RTL_OVERRIDE_PATTERN, "");
|
|
43828
|
+
if (sanitized.length > maxLength) {
|
|
43829
|
+
return sanitized.substring(0, maxLength - 3) + "...";
|
|
43830
|
+
}
|
|
43831
|
+
return sanitized;
|
|
43832
|
+
}
|
|
43833
|
+
function validatePlanPhases(plan) {
|
|
43834
|
+
if (!plan || typeof plan !== "object")
|
|
43835
|
+
return false;
|
|
43836
|
+
const p = plan;
|
|
43837
|
+
if (!Array.isArray(p.phases))
|
|
43838
|
+
return false;
|
|
43839
|
+
for (const phase of p.phases) {
|
|
43840
|
+
if (!phase || typeof phase !== "object")
|
|
43841
|
+
return false;
|
|
43842
|
+
const phaseObj = phase;
|
|
43843
|
+
if (!Array.isArray(phaseObj.tasks))
|
|
43844
|
+
return false;
|
|
43845
|
+
}
|
|
43846
|
+
return true;
|
|
43847
|
+
}
|
|
43848
|
+
function extractCurrentPhaseFromPlan(plan) {
|
|
43849
|
+
if (!plan) {
|
|
43850
|
+
return { currentPhase: null, currentTask: null, incompleteTasks: [] };
|
|
43851
|
+
}
|
|
43852
|
+
if (!validatePlanPhases(plan)) {
|
|
43853
|
+
return { currentPhase: null, currentTask: null, incompleteTasks: [] };
|
|
43854
|
+
}
|
|
43855
|
+
let currentPhase = null;
|
|
43856
|
+
const currentPhaseNum = plan.current_phase;
|
|
43857
|
+
if (currentPhaseNum) {
|
|
43858
|
+
const phase = plan.phases.find((p) => p.id === currentPhaseNum);
|
|
43859
|
+
currentPhase = phase ? `Phase ${phase.id}: ${phase.name}` : null;
|
|
43860
|
+
} else {
|
|
43861
|
+
const inProgressPhase = plan.phases.find((p) => p.status === "in_progress");
|
|
43862
|
+
if (inProgressPhase) {
|
|
43863
|
+
currentPhase = `Phase ${inProgressPhase.id}: ${inProgressPhase.name}`;
|
|
43864
|
+
} else if (plan.phases.length > 0) {
|
|
43865
|
+
currentPhase = `Phase ${plan.phases[0].id}: ${plan.phases[0].name}`;
|
|
43866
|
+
}
|
|
43867
|
+
}
|
|
43868
|
+
let currentTask = null;
|
|
43869
|
+
const incompleteTasks = [];
|
|
43870
|
+
for (const phase of plan.phases) {
|
|
43871
|
+
for (const task of phase.tasks) {
|
|
43872
|
+
if (task.status === "in_progress") {
|
|
43873
|
+
currentTask = sanitizeString(task.id, MAX_TASK_ID_LENGTH);
|
|
43874
|
+
}
|
|
43875
|
+
if (task.status !== "completed") {
|
|
43876
|
+
if (incompleteTasks.length < MAX_INCOMPLETE_TASKS) {
|
|
43877
|
+
incompleteTasks.push(sanitizeString(task.id, MAX_TASK_ID_LENGTH));
|
|
43878
|
+
}
|
|
43879
|
+
}
|
|
43880
|
+
}
|
|
43881
|
+
}
|
|
43882
|
+
if (!currentTask && incompleteTasks.length > 0) {
|
|
43883
|
+
currentTask = incompleteTasks[0];
|
|
43884
|
+
}
|
|
43885
|
+
return { currentPhase, currentTask, incompleteTasks };
|
|
43886
|
+
}
|
|
43887
|
+
function parseSessionState(content) {
|
|
43888
|
+
if (!content)
|
|
43889
|
+
return null;
|
|
43890
|
+
try {
|
|
43891
|
+
const state = JSON.parse(content);
|
|
43892
|
+
let activeAgent = null;
|
|
43893
|
+
if (state.activeAgent && typeof state.activeAgent === "object") {
|
|
43894
|
+
const entries = Object.entries(state.activeAgent);
|
|
43895
|
+
if (entries.length > 0) {
|
|
43896
|
+
activeAgent = sanitizeString(entries[entries.length - 1][1], MAX_TASK_ID_LENGTH);
|
|
43897
|
+
}
|
|
43898
|
+
}
|
|
43899
|
+
let delegationState = null;
|
|
43900
|
+
if (state.delegationChains && typeof state.delegationChains === "object") {
|
|
43901
|
+
const chains = Object.entries(state.delegationChains);
|
|
43902
|
+
const activeChains = [];
|
|
43903
|
+
let maxDepth = 0;
|
|
43904
|
+
for (const [, chain] of chains) {
|
|
43905
|
+
if (Array.isArray(chain) && chain.length > 0) {
|
|
43906
|
+
const sanitizedChain = chain.map((e) => `${sanitizeString(e.from, MAX_TASK_ID_LENGTH)}->${sanitizeString(e.to, MAX_TASK_ID_LENGTH)}`).join(" | ");
|
|
43907
|
+
activeChains.push(sanitizedChain);
|
|
43908
|
+
maxDepth = Math.max(maxDepth, chain.length);
|
|
43909
|
+
}
|
|
43910
|
+
}
|
|
43911
|
+
if (activeChains.length > 0) {
|
|
43912
|
+
delegationState = {
|
|
43913
|
+
activeChains,
|
|
43914
|
+
delegationDepth: maxDepth,
|
|
43915
|
+
pendingHandoffs: []
|
|
43916
|
+
};
|
|
43917
|
+
}
|
|
43918
|
+
}
|
|
43919
|
+
let pendingQA = null;
|
|
43920
|
+
if (state.agentSessions && typeof state.agentSessions === "object") {
|
|
43921
|
+
for (const [, session] of Object.entries(state.agentSessions)) {
|
|
43922
|
+
const sess = session;
|
|
43923
|
+
if (sess.lastGateFailure && sess.currentTaskId) {
|
|
43924
|
+
pendingQA = {
|
|
43925
|
+
taskId: sanitizeString(sess.lastGateFailure.taskId, MAX_TASK_ID_LENGTH),
|
|
43926
|
+
lastFailure: sanitizeString(sess.lastGateFailure.tool, MAX_TASK_ID_LENGTH)
|
|
43927
|
+
};
|
|
43928
|
+
break;
|
|
43929
|
+
}
|
|
43930
|
+
}
|
|
43931
|
+
}
|
|
43932
|
+
return { activeAgent, delegationState, pendingQA };
|
|
43933
|
+
} catch {
|
|
43934
|
+
return null;
|
|
43935
|
+
}
|
|
43936
|
+
}
|
|
43937
|
+
function extractDecisions(content) {
|
|
43938
|
+
if (!content)
|
|
43939
|
+
return [];
|
|
43940
|
+
const decisions = [];
|
|
43941
|
+
const lines = content.split(`
|
|
43942
|
+
`);
|
|
43943
|
+
let inDecisionsSection = false;
|
|
43944
|
+
for (const line of lines) {
|
|
43945
|
+
if (line.trim() === "## Decisions") {
|
|
43946
|
+
inDecisionsSection = true;
|
|
43947
|
+
continue;
|
|
43948
|
+
}
|
|
43949
|
+
if (inDecisionsSection && line.startsWith("## ") && line.trim() !== "## Decisions") {
|
|
43950
|
+
break;
|
|
43951
|
+
}
|
|
43952
|
+
if (inDecisionsSection && line.trim().startsWith("- ")) {
|
|
43953
|
+
const text = line.trim().substring(2);
|
|
43954
|
+
const cleaned = text.replace(/\s*\[.*?\]\s*/g, "").replace(/\u2705/g, "").replace(/\[confirmed\]/g, "").trim();
|
|
43955
|
+
if (cleaned) {
|
|
43956
|
+
const sanitized = sanitizeString(cleaned, MAX_DECISION_LENGTH);
|
|
43957
|
+
if (sanitized) {
|
|
43958
|
+
decisions.push(sanitized);
|
|
43959
|
+
}
|
|
43960
|
+
}
|
|
43961
|
+
}
|
|
43962
|
+
}
|
|
43963
|
+
return decisions.slice(-5);
|
|
43964
|
+
}
|
|
43965
|
+
function extractPhaseMetrics(content) {
|
|
43966
|
+
if (!content)
|
|
43967
|
+
return "";
|
|
43968
|
+
const lines = content.split(`
|
|
43969
|
+
`);
|
|
43970
|
+
let inPhaseMetrics = false;
|
|
43971
|
+
const metricsLines = [];
|
|
43972
|
+
for (const line of lines) {
|
|
43973
|
+
if (line.trim() === "## Phase Metrics") {
|
|
43974
|
+
inPhaseMetrics = true;
|
|
43975
|
+
continue;
|
|
43976
|
+
}
|
|
43977
|
+
if (inPhaseMetrics && line.startsWith("## ")) {
|
|
43978
|
+
break;
|
|
43979
|
+
}
|
|
43980
|
+
if (inPhaseMetrics) {
|
|
43981
|
+
metricsLines.push(line);
|
|
43982
|
+
}
|
|
43983
|
+
}
|
|
43984
|
+
const lastFive = metricsLines.slice(-5);
|
|
43985
|
+
return lastFive.join(`
|
|
43986
|
+
`).trim();
|
|
43987
|
+
}
|
|
43988
|
+
async function getHandoffData(directory) {
|
|
43989
|
+
const now = new Date().toISOString();
|
|
43990
|
+
const sessionContent = await readSwarmFileAsync(directory, "session/state.json");
|
|
43991
|
+
const sessionState = parseSessionState(sessionContent);
|
|
43992
|
+
const plan = await loadPlanJsonOnly(directory);
|
|
43993
|
+
const planInfo = extractCurrentPhaseFromPlan(plan);
|
|
43994
|
+
if (!plan) {
|
|
43995
|
+
const planMdContent = await readSwarmFileAsync(directory, "plan.md");
|
|
43996
|
+
if (planMdContent) {
|
|
43997
|
+
const phaseMatch = planMdContent.match(/^## Phase (\d+):?\s*(.+)?$/m);
|
|
43998
|
+
const taskMatch = planMdContent.match(/^- \[ \] (\d+\.\d+)/g);
|
|
43999
|
+
if (phaseMatch) {
|
|
44000
|
+
planInfo.currentPhase = sanitizeString(`Phase ${phaseMatch[1]}${phaseMatch[2] ? ": " + phaseMatch[2] : ""}`, MAX_TASK_ID_LENGTH);
|
|
44001
|
+
}
|
|
44002
|
+
if (taskMatch) {
|
|
44003
|
+
const rawTasks = taskMatch.map((t) => t.replace("- [ ] ", ""));
|
|
44004
|
+
planInfo.incompleteTasks = rawTasks.map((t) => sanitizeString(t, MAX_TASK_ID_LENGTH)).slice(0, MAX_INCOMPLETE_TASKS);
|
|
44005
|
+
if (!planInfo.currentTask && planInfo.incompleteTasks.length > 0) {
|
|
44006
|
+
planInfo.currentTask = planInfo.incompleteTasks[0];
|
|
44007
|
+
}
|
|
44008
|
+
}
|
|
44009
|
+
}
|
|
44010
|
+
}
|
|
44011
|
+
const contextContent = await readSwarmFileAsync(directory, "context.md");
|
|
44012
|
+
const recentDecisions = extractDecisions(contextContent);
|
|
44013
|
+
const rawPhaseMetrics = extractPhaseMetrics(contextContent);
|
|
44014
|
+
const phaseMetrics = sanitizeString(rawPhaseMetrics, 1000);
|
|
44015
|
+
let delegationState = null;
|
|
44016
|
+
if (sessionState?.delegationState) {
|
|
44017
|
+
delegationState = {
|
|
44018
|
+
...sessionState.delegationState,
|
|
44019
|
+
pendingHandoffs: phaseMetrics ? [phaseMetrics] : []
|
|
44020
|
+
};
|
|
44021
|
+
}
|
|
44022
|
+
let pendingQA = null;
|
|
44023
|
+
if (sessionState?.pendingQA) {
|
|
44024
|
+
pendingQA = {
|
|
44025
|
+
taskId: escapeHtml(sessionState.pendingQA.taskId),
|
|
44026
|
+
lastFailure: sessionState.pendingQA.lastFailure ? escapeHtml(sessionState.pendingQA.lastFailure) : null
|
|
44027
|
+
};
|
|
44028
|
+
}
|
|
44029
|
+
const escapedDecisions = recentDecisions.map((d) => escapeHtml(d));
|
|
44030
|
+
let escapedDelegationState = null;
|
|
44031
|
+
if (delegationState) {
|
|
44032
|
+
escapedDelegationState = {
|
|
44033
|
+
...delegationState,
|
|
44034
|
+
activeChains: delegationState.activeChains.map((c) => escapeHtml(c)),
|
|
44035
|
+
pendingHandoffs: delegationState.pendingHandoffs.map((p) => escapeHtml(p))
|
|
44036
|
+
};
|
|
44037
|
+
}
|
|
44038
|
+
const escapedIncompleteTasks = planInfo.incompleteTasks.map((t) => escapeHtml(t));
|
|
44039
|
+
return {
|
|
44040
|
+
generated: now,
|
|
44041
|
+
currentPhase: planInfo.currentPhase ? escapeHtml(planInfo.currentPhase) : null,
|
|
44042
|
+
currentTask: planInfo.currentTask ? escapeHtml(planInfo.currentTask) : null,
|
|
44043
|
+
incompleteTasks: escapedIncompleteTasks,
|
|
44044
|
+
pendingQA,
|
|
44045
|
+
activeAgent: sessionState?.activeAgent ? escapeHtml(sessionState.activeAgent) : null,
|
|
44046
|
+
recentDecisions: escapedDecisions,
|
|
44047
|
+
delegationState: escapedDelegationState
|
|
44048
|
+
};
|
|
44049
|
+
}
|
|
44050
|
+
function formatHandoffMarkdown(data) {
|
|
44051
|
+
const lines = [];
|
|
44052
|
+
lines.push("## Swarm Handoff");
|
|
44053
|
+
lines.push("");
|
|
44054
|
+
lines.push(`**Generated**: ${data.generated}`);
|
|
44055
|
+
lines.push("");
|
|
44056
|
+
lines.push("### Current State");
|
|
44057
|
+
if (data.currentPhase) {
|
|
44058
|
+
lines.push(`- **Phase**: ${data.currentPhase}`);
|
|
44059
|
+
}
|
|
44060
|
+
if (data.currentTask) {
|
|
44061
|
+
lines.push(`- **Task**: ${data.currentTask}`);
|
|
44062
|
+
}
|
|
44063
|
+
if (data.activeAgent) {
|
|
44064
|
+
lines.push(`- **Active Agent**: ${data.activeAgent}`);
|
|
44065
|
+
}
|
|
44066
|
+
lines.push("");
|
|
44067
|
+
if (data.incompleteTasks.length > 0) {
|
|
44068
|
+
lines.push("### Incomplete Tasks");
|
|
44069
|
+
const displayTasks = data.incompleteTasks.slice(0, 10);
|
|
44070
|
+
for (const taskId of displayTasks) {
|
|
44071
|
+
lines.push(`- ${taskId}`);
|
|
44072
|
+
}
|
|
44073
|
+
if (data.incompleteTasks.length > 10) {
|
|
44074
|
+
lines.push(`- ... and ${data.incompleteTasks.length - 10} more`);
|
|
44075
|
+
}
|
|
44076
|
+
lines.push("");
|
|
44077
|
+
}
|
|
44078
|
+
if (data.pendingQA) {
|
|
44079
|
+
lines.push("### Pending QA");
|
|
44080
|
+
lines.push(`- **Task**: ${data.pendingQA.taskId}`);
|
|
44081
|
+
if (data.pendingQA.lastFailure) {
|
|
44082
|
+
lines.push(`- **Last Failure**: ${data.pendingQA.lastFailure}`);
|
|
44083
|
+
}
|
|
44084
|
+
lines.push("");
|
|
44085
|
+
}
|
|
44086
|
+
if (data.delegationState && data.delegationState.activeChains.length > 0) {
|
|
44087
|
+
lines.push("### Delegation");
|
|
44088
|
+
lines.push(`- **Depth**: ${data.delegationState.delegationDepth}`);
|
|
44089
|
+
for (const chain of data.delegationState.activeChains.slice(0, 3)) {
|
|
44090
|
+
lines.push(`- ${chain}`);
|
|
44091
|
+
}
|
|
44092
|
+
lines.push("");
|
|
44093
|
+
}
|
|
44094
|
+
if (data.recentDecisions.length > 0) {
|
|
44095
|
+
lines.push("### Recent Decisions");
|
|
44096
|
+
for (const decision of data.recentDecisions.slice(0, 5)) {
|
|
44097
|
+
lines.push(`- ${decision}`);
|
|
44098
|
+
}
|
|
44099
|
+
lines.push("");
|
|
44100
|
+
}
|
|
44101
|
+
if (data.delegationState?.pendingHandoffs && data.delegationState.pendingHandoffs.length > 0) {
|
|
44102
|
+
lines.push("### Phase Metrics");
|
|
44103
|
+
lines.push("```");
|
|
44104
|
+
lines.push(data.delegationState.pendingHandoffs[0]);
|
|
44105
|
+
lines.push("```");
|
|
44106
|
+
}
|
|
44107
|
+
return lines.join(`
|
|
44108
|
+
`);
|
|
44109
|
+
}
|
|
44110
|
+
|
|
44111
|
+
// src/session/snapshot-writer.ts
|
|
44112
|
+
init_utils2();
|
|
44113
|
+
import { mkdirSync as mkdirSync5, renameSync as renameSync3 } from "fs";
|
|
44114
|
+
import * as path14 from "path";
|
|
44115
|
+
function serializeAgentSession(s) {
|
|
44116
|
+
const gateLog = {};
|
|
44117
|
+
const rawGateLog = s.gateLog ?? new Map;
|
|
44118
|
+
for (const [taskId, gates] of rawGateLog) {
|
|
44119
|
+
gateLog[taskId] = Array.from(gates ?? []);
|
|
44120
|
+
}
|
|
44121
|
+
const reviewerCallCount = {};
|
|
44122
|
+
const rawReviewerCallCount = s.reviewerCallCount ?? new Map;
|
|
44123
|
+
for (const [phase, count] of rawReviewerCallCount) {
|
|
44124
|
+
reviewerCallCount[String(phase)] = count;
|
|
44125
|
+
}
|
|
44126
|
+
const partialGateWarningsIssuedForTask = Array.from(s.partialGateWarningsIssuedForTask ?? new Set);
|
|
44127
|
+
const catastrophicPhaseWarnings = Array.from(s.catastrophicPhaseWarnings ?? new Set);
|
|
44128
|
+
const phaseAgentsDispatched = Array.from(s.phaseAgentsDispatched ?? new Set);
|
|
44129
|
+
const windows = {};
|
|
44130
|
+
const rawWindows = s.windows ?? {};
|
|
44131
|
+
for (const [key, win] of Object.entries(rawWindows)) {
|
|
44132
|
+
windows[key] = {
|
|
44133
|
+
id: win.id,
|
|
44134
|
+
agentName: win.agentName,
|
|
44135
|
+
startedAtMs: win.startedAtMs,
|
|
44136
|
+
toolCalls: win.toolCalls,
|
|
44137
|
+
consecutiveErrors: win.consecutiveErrors,
|
|
44138
|
+
hardLimitHit: win.hardLimitHit,
|
|
44139
|
+
lastSuccessTimeMs: win.lastSuccessTimeMs,
|
|
44140
|
+
recentToolCalls: win.recentToolCalls,
|
|
44141
|
+
warningIssued: win.warningIssued,
|
|
44142
|
+
warningReason: win.warningReason
|
|
44143
|
+
};
|
|
44144
|
+
}
|
|
44145
|
+
return {
|
|
44146
|
+
agentName: s.agentName,
|
|
44147
|
+
lastToolCallTime: s.lastToolCallTime,
|
|
44148
|
+
lastAgentEventTime: s.lastAgentEventTime,
|
|
44149
|
+
delegationActive: s.delegationActive,
|
|
44150
|
+
activeInvocationId: s.activeInvocationId,
|
|
44151
|
+
lastInvocationIdByAgent: s.lastInvocationIdByAgent ?? {},
|
|
44152
|
+
windows,
|
|
44153
|
+
lastCompactionHint: s.lastCompactionHint ?? 0,
|
|
44154
|
+
architectWriteCount: s.architectWriteCount ?? 0,
|
|
44155
|
+
lastCoderDelegationTaskId: s.lastCoderDelegationTaskId ?? null,
|
|
44156
|
+
currentTaskId: s.currentTaskId ?? null,
|
|
44157
|
+
gateLog,
|
|
44158
|
+
reviewerCallCount,
|
|
44159
|
+
lastGateFailure: s.lastGateFailure ?? null,
|
|
44160
|
+
partialGateWarningsIssuedForTask,
|
|
44161
|
+
selfFixAttempted: s.selfFixAttempted ?? false,
|
|
44162
|
+
catastrophicPhaseWarnings,
|
|
44163
|
+
lastPhaseCompleteTimestamp: s.lastPhaseCompleteTimestamp ?? 0,
|
|
44164
|
+
lastPhaseCompletePhase: s.lastPhaseCompletePhase ?? 0,
|
|
44165
|
+
phaseAgentsDispatched,
|
|
44166
|
+
qaSkipCount: s.qaSkipCount ?? 0,
|
|
44167
|
+
qaSkipTaskIds: s.qaSkipTaskIds ?? []
|
|
44168
|
+
};
|
|
44169
|
+
}
|
|
44170
|
+
async function writeSnapshot(directory, state) {
|
|
44171
|
+
try {
|
|
44172
|
+
const snapshot = {
|
|
44173
|
+
version: 1,
|
|
44174
|
+
writtenAt: Date.now(),
|
|
44175
|
+
toolAggregates: Object.fromEntries(state.toolAggregates),
|
|
44176
|
+
activeAgent: Object.fromEntries(state.activeAgent),
|
|
44177
|
+
delegationChains: Object.fromEntries(state.delegationChains),
|
|
44178
|
+
agentSessions: {}
|
|
44179
|
+
};
|
|
44180
|
+
for (const [sessionId, sessionState] of state.agentSessions) {
|
|
44181
|
+
snapshot.agentSessions[sessionId] = serializeAgentSession(sessionState);
|
|
44182
|
+
}
|
|
44183
|
+
const content = JSON.stringify(snapshot, null, 2);
|
|
44184
|
+
const resolvedPath = validateSwarmPath(directory, "session/state.json");
|
|
44185
|
+
const dir = path14.dirname(resolvedPath);
|
|
44186
|
+
mkdirSync5(dir, { recursive: true });
|
|
44187
|
+
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
44188
|
+
await Bun.write(tempPath, content);
|
|
44189
|
+
renameSync3(tempPath, resolvedPath);
|
|
44190
|
+
} catch {}
|
|
44191
|
+
}
|
|
44192
|
+
function createSnapshotWriterHook(directory) {
|
|
44193
|
+
return async (_input, _output) => {
|
|
44194
|
+
try {
|
|
44195
|
+
await writeSnapshot(directory, swarmState);
|
|
44196
|
+
} catch {}
|
|
44197
|
+
};
|
|
44198
|
+
}
|
|
44199
|
+
|
|
44200
|
+
// src/commands/handoff.ts
|
|
44201
|
+
async function handleHandoffCommand(directory, _args) {
|
|
44202
|
+
const handoffData = await getHandoffData(directory);
|
|
44203
|
+
const markdown = formatHandoffMarkdown(handoffData);
|
|
44204
|
+
const resolvedPath = validateSwarmPath(directory, "handoff.md");
|
|
44205
|
+
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
44206
|
+
await Bun.write(tempPath, markdown);
|
|
44207
|
+
renameSync4(tempPath, resolvedPath);
|
|
44208
|
+
await writeSnapshot(directory, swarmState);
|
|
44209
|
+
return `## Handoff Brief Written
|
|
44210
|
+
|
|
44211
|
+
Brief written to \`.swarm/handoff.md\`.
|
|
44212
|
+
|
|
44213
|
+
${markdown}
|
|
44214
|
+
|
|
44215
|
+
---
|
|
44216
|
+
|
|
44217
|
+
**Next Step:** Start a new OpenCode session, switch to your target model, and send: \`continue the previous work\``;
|
|
44218
|
+
}
|
|
44219
|
+
|
|
43724
44220
|
// src/services/history-service.ts
|
|
43725
44221
|
init_utils2();
|
|
43726
44222
|
init_manager2();
|
|
@@ -43850,12 +44346,12 @@ async function handleHistoryCommand(directory, _args) {
|
|
|
43850
44346
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
43851
44347
|
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
43852
44348
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
43853
|
-
import * as
|
|
44349
|
+
import * as path16 from "path";
|
|
43854
44350
|
|
|
43855
44351
|
// src/hooks/knowledge-validator.ts
|
|
43856
44352
|
var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
|
|
43857
44353
|
import { appendFile as appendFile2, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
43858
|
-
import * as
|
|
44354
|
+
import * as path15 from "path";
|
|
43859
44355
|
var DANGEROUS_COMMAND_PATTERNS = [
|
|
43860
44356
|
/\brm\s+-rf\b/,
|
|
43861
44357
|
/\bsudo\s+rm\b/,
|
|
@@ -44107,10 +44603,10 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
|
|
|
44107
44603
|
return;
|
|
44108
44604
|
}
|
|
44109
44605
|
const sanitizedReason = reason.slice(0, 500).replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\x0d]/g, "");
|
|
44110
|
-
const knowledgePath =
|
|
44111
|
-
const quarantinePath =
|
|
44112
|
-
const rejectedPath =
|
|
44113
|
-
const swarmDir =
|
|
44606
|
+
const knowledgePath = path15.join(directory, ".swarm", "knowledge.jsonl");
|
|
44607
|
+
const quarantinePath = path15.join(directory, ".swarm", "knowledge-quarantined.jsonl");
|
|
44608
|
+
const rejectedPath = path15.join(directory, ".swarm", "knowledge-rejected.jsonl");
|
|
44609
|
+
const swarmDir = path15.join(directory, ".swarm");
|
|
44114
44610
|
await mkdir2(swarmDir, { recursive: true });
|
|
44115
44611
|
let release;
|
|
44116
44612
|
try {
|
|
@@ -44167,10 +44663,10 @@ async function restoreEntry(directory, entryId) {
|
|
|
44167
44663
|
console.warn("[knowledge-validator] restoreEntry: invalid entryId rejected");
|
|
44168
44664
|
return;
|
|
44169
44665
|
}
|
|
44170
|
-
const knowledgePath =
|
|
44171
|
-
const quarantinePath =
|
|
44172
|
-
const rejectedPath =
|
|
44173
|
-
const swarmDir =
|
|
44666
|
+
const knowledgePath = path15.join(directory, ".swarm", "knowledge.jsonl");
|
|
44667
|
+
const quarantinePath = path15.join(directory, ".swarm", "knowledge-quarantined.jsonl");
|
|
44668
|
+
const rejectedPath = path15.join(directory, ".swarm", "knowledge-rejected.jsonl");
|
|
44669
|
+
const swarmDir = path15.join(directory, ".swarm");
|
|
44174
44670
|
await mkdir2(swarmDir, { recursive: true });
|
|
44175
44671
|
let release;
|
|
44176
44672
|
try {
|
|
@@ -44205,8 +44701,8 @@ async function restoreEntry(directory, entryId) {
|
|
|
44205
44701
|
|
|
44206
44702
|
// src/hooks/knowledge-migrator.ts
|
|
44207
44703
|
async function migrateContextToKnowledge(directory, config3) {
|
|
44208
|
-
const sentinelPath =
|
|
44209
|
-
const contextPath =
|
|
44704
|
+
const sentinelPath = path16.join(directory, ".swarm", ".knowledge-migrated");
|
|
44705
|
+
const contextPath = path16.join(directory, ".swarm", "context.md");
|
|
44210
44706
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
44211
44707
|
if (existsSync8(sentinelPath)) {
|
|
44212
44708
|
return {
|
|
@@ -44402,7 +44898,7 @@ function truncateLesson(text) {
|
|
|
44402
44898
|
return `${text.slice(0, 277)}...`;
|
|
44403
44899
|
}
|
|
44404
44900
|
function inferProjectName(directory) {
|
|
44405
|
-
const packageJsonPath =
|
|
44901
|
+
const packageJsonPath = path16.join(directory, "package.json");
|
|
44406
44902
|
if (existsSync8(packageJsonPath)) {
|
|
44407
44903
|
try {
|
|
44408
44904
|
const pkg = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
@@ -44411,7 +44907,7 @@ function inferProjectName(directory) {
|
|
|
44411
44907
|
}
|
|
44412
44908
|
} catch {}
|
|
44413
44909
|
}
|
|
44414
|
-
return
|
|
44910
|
+
return path16.basename(directory);
|
|
44415
44911
|
}
|
|
44416
44912
|
async function writeSentinel(sentinelPath, migrated, dropped) {
|
|
44417
44913
|
const sentinel = {
|
|
@@ -44423,7 +44919,7 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
|
|
|
44423
44919
|
schema_version: 1,
|
|
44424
44920
|
migration_tool: "knowledge-migrator.ts"
|
|
44425
44921
|
};
|
|
44426
|
-
await mkdir3(
|
|
44922
|
+
await mkdir3(path16.dirname(sentinelPath), { recursive: true });
|
|
44427
44923
|
await writeFile3(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
|
|
44428
44924
|
}
|
|
44429
44925
|
|
|
@@ -44660,7 +45156,7 @@ async function handlePlanCommand(directory, args2) {
|
|
|
44660
45156
|
init_preflight_service();
|
|
44661
45157
|
|
|
44662
45158
|
// src/hooks/hive-promoter.ts
|
|
44663
|
-
import
|
|
45159
|
+
import path22 from "path";
|
|
44664
45160
|
init_utils2();
|
|
44665
45161
|
function isAlreadyInHive(entry, hiveEntries, threshold) {
|
|
44666
45162
|
return findNearDuplicate(entry.lesson, hiveEntries, threshold) !== undefined;
|
|
@@ -44818,7 +45314,7 @@ async function promoteToHive(directory, lesson, category) {
|
|
|
44818
45314
|
schema_version: 1,
|
|
44819
45315
|
created_at: new Date().toISOString(),
|
|
44820
45316
|
updated_at: new Date().toISOString(),
|
|
44821
|
-
source_project:
|
|
45317
|
+
source_project: path22.basename(directory) || "unknown"
|
|
44822
45318
|
};
|
|
44823
45319
|
await appendKnowledge(resolveHiveKnowledgePath(), newHiveEntry);
|
|
44824
45320
|
return `Promoted to hive: "${trimmedLesson.slice(0, 50)}${trimmedLesson.length > 50 ? "..." : ""}" (confidence: 1.0, source: manual)`;
|
|
@@ -44978,8 +45474,8 @@ async function handleResetCommand(directory, args2) {
|
|
|
44978
45474
|
// src/summaries/manager.ts
|
|
44979
45475
|
init_utils2();
|
|
44980
45476
|
init_utils();
|
|
44981
|
-
import { mkdirSync as
|
|
44982
|
-
import * as
|
|
45477
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, renameSync as renameSync5, rmSync as rmSync3, statSync as statSync7 } from "fs";
|
|
45478
|
+
import * as path23 from "path";
|
|
44983
45479
|
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
44984
45480
|
function sanitizeSummaryId(id) {
|
|
44985
45481
|
if (!id || id.length === 0) {
|
|
@@ -45007,9 +45503,9 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
45007
45503
|
if (outputBytes > maxStoredBytes) {
|
|
45008
45504
|
throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
|
|
45009
45505
|
}
|
|
45010
|
-
const relativePath =
|
|
45506
|
+
const relativePath = path23.join("summaries", `${sanitizedId}.json`);
|
|
45011
45507
|
const summaryPath = validateSwarmPath(directory, relativePath);
|
|
45012
|
-
const summaryDir =
|
|
45508
|
+
const summaryDir = path23.dirname(summaryPath);
|
|
45013
45509
|
const entry = {
|
|
45014
45510
|
id: sanitizedId,
|
|
45015
45511
|
summaryText,
|
|
@@ -45018,11 +45514,11 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
45018
45514
|
originalBytes: outputBytes
|
|
45019
45515
|
};
|
|
45020
45516
|
const entryJson = JSON.stringify(entry);
|
|
45021
|
-
|
|
45022
|
-
const tempPath =
|
|
45517
|
+
mkdirSync6(summaryDir, { recursive: true });
|
|
45518
|
+
const tempPath = path23.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
|
|
45023
45519
|
try {
|
|
45024
45520
|
await Bun.write(tempPath, entryJson);
|
|
45025
|
-
|
|
45521
|
+
renameSync5(tempPath, summaryPath);
|
|
45026
45522
|
} catch (error93) {
|
|
45027
45523
|
try {
|
|
45028
45524
|
rmSync3(tempPath, { force: true });
|
|
@@ -45032,7 +45528,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
45032
45528
|
}
|
|
45033
45529
|
async function loadFullOutput(directory, id) {
|
|
45034
45530
|
const sanitizedId = sanitizeSummaryId(id);
|
|
45035
|
-
const relativePath =
|
|
45531
|
+
const relativePath = path23.join("summaries", `${sanitizedId}.json`);
|
|
45036
45532
|
validateSwarmPath(directory, relativePath);
|
|
45037
45533
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
45038
45534
|
if (content === null) {
|
|
@@ -45086,7 +45582,7 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
|
|
|
45086
45582
|
// src/commands/rollback.ts
|
|
45087
45583
|
init_utils2();
|
|
45088
45584
|
import * as fs12 from "fs";
|
|
45089
|
-
import * as
|
|
45585
|
+
import * as path24 from "path";
|
|
45090
45586
|
async function handleRollbackCommand(directory, args2) {
|
|
45091
45587
|
const phaseArg = args2[0];
|
|
45092
45588
|
if (!phaseArg) {
|
|
@@ -45134,8 +45630,8 @@ async function handleRollbackCommand(directory, args2) {
|
|
|
45134
45630
|
const successes = [];
|
|
45135
45631
|
const failures = [];
|
|
45136
45632
|
for (const file3 of checkpointFiles) {
|
|
45137
|
-
const src =
|
|
45138
|
-
const dest =
|
|
45633
|
+
const src = path24.join(checkpointDir, file3);
|
|
45634
|
+
const dest = path24.join(swarmDir, file3);
|
|
45139
45635
|
try {
|
|
45140
45636
|
fs12.cpSync(src, dest, { recursive: true, force: true });
|
|
45141
45637
|
successes.push(file3);
|
|
@@ -45198,9 +45694,9 @@ async function handleSimulateCommand(directory, args2) {
|
|
|
45198
45694
|
const report = reportLines.filter(Boolean).join(`
|
|
45199
45695
|
`);
|
|
45200
45696
|
const fs13 = await import("fs/promises");
|
|
45201
|
-
const
|
|
45202
|
-
const reportPath =
|
|
45203
|
-
await fs13.mkdir(
|
|
45697
|
+
const path25 = await import("path");
|
|
45698
|
+
const reportPath = path25.join(directory, ".swarm", "simulate-report.md");
|
|
45699
|
+
await fs13.mkdir(path25.dirname(reportPath), { recursive: true });
|
|
45204
45700
|
await fs13.writeFile(reportPath, report, "utf-8");
|
|
45205
45701
|
return `${darkMatterPairs.length} hidden coupling pairs detected`;
|
|
45206
45702
|
}
|
|
@@ -45263,7 +45759,7 @@ function extractCurrentTask(planContent) {
|
|
|
45263
45759
|
}
|
|
45264
45760
|
return null;
|
|
45265
45761
|
}
|
|
45266
|
-
function
|
|
45762
|
+
function extractDecisions2(contextContent, maxChars = 500) {
|
|
45267
45763
|
if (!contextContent) {
|
|
45268
45764
|
return null;
|
|
45269
45765
|
}
|
|
@@ -45359,7 +45855,7 @@ function extractPatterns(contextContent, maxChars = 500) {
|
|
|
45359
45855
|
}
|
|
45360
45856
|
return `${trimmed.slice(0, maxChars)}...`;
|
|
45361
45857
|
}
|
|
45362
|
-
function
|
|
45858
|
+
function extractCurrentPhaseFromPlan2(plan) {
|
|
45363
45859
|
const phase = plan.phases.find((p) => p.id === plan.current_phase);
|
|
45364
45860
|
if (!phase)
|
|
45365
45861
|
return null;
|
|
@@ -45559,7 +46055,7 @@ init_manager2();
|
|
|
45559
46055
|
async function getStatusData(directory, agents) {
|
|
45560
46056
|
const plan = await loadPlan(directory);
|
|
45561
46057
|
if (plan && plan.migration_status !== "migration_failed") {
|
|
45562
|
-
const currentPhase2 =
|
|
46058
|
+
const currentPhase2 = extractCurrentPhaseFromPlan2(plan) || "Unknown";
|
|
45563
46059
|
let completedTasks2 = 0;
|
|
45564
46060
|
let totalTasks2 = 0;
|
|
45565
46061
|
for (const phase of plan.phases) {
|
|
@@ -45859,6 +46355,7 @@ var HELP_TEXT = [
|
|
|
45859
46355
|
"- `/swarm knowledge restore <id>` \u2014 Restore a quarantined knowledge entry",
|
|
45860
46356
|
"- `/swarm knowledge migrate` \u2014 Migrate knowledge entries to the current format",
|
|
45861
46357
|
'- `/swarm promote "<lesson>" | --category <cat> | --from-swarm <id> \u2014 Manually promote lesson to hive knowledge',
|
|
46358
|
+
"- `/swarm handoff` \u2014 Prepare state for clean model switch (new session)",
|
|
45862
46359
|
"- `/swarm write-retro <json>` \u2014 Write a retrospective evidence bundle for a completed phase"
|
|
45863
46360
|
].join(`
|
|
45864
46361
|
`);
|
|
@@ -45960,6 +46457,9 @@ function createSwarmCommandHandler(directory, agents) {
|
|
|
45960
46457
|
case "diagnose":
|
|
45961
46458
|
text = await handleDiagnoseCommand(directory, args2);
|
|
45962
46459
|
break;
|
|
46460
|
+
case "handoff":
|
|
46461
|
+
text = await handleHandoffCommand(directory, args2);
|
|
46462
|
+
break;
|
|
45963
46463
|
default:
|
|
45964
46464
|
text = HELP_TEXT;
|
|
45965
46465
|
break;
|
|
@@ -45971,7 +46471,7 @@ function createSwarmCommandHandler(directory, agents) {
|
|
|
45971
46471
|
}
|
|
45972
46472
|
|
|
45973
46473
|
// src/hooks/agent-activity.ts
|
|
45974
|
-
import { renameSync as
|
|
46474
|
+
import { renameSync as renameSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
45975
46475
|
init_utils();
|
|
45976
46476
|
init_utils2();
|
|
45977
46477
|
function createAgentActivityHooks(config3, directory) {
|
|
@@ -46041,11 +46541,11 @@ async function doFlush(directory) {
|
|
|
46041
46541
|
const activitySection = renderActivitySection();
|
|
46042
46542
|
const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
|
|
46043
46543
|
const flushedCount = swarmState.pendingEvents;
|
|
46044
|
-
const
|
|
46045
|
-
const tempPath = `${
|
|
46544
|
+
const path25 = `${directory}/.swarm/context.md`;
|
|
46545
|
+
const tempPath = `${path25}.tmp`;
|
|
46046
46546
|
try {
|
|
46047
46547
|
await Bun.write(tempPath, updated);
|
|
46048
|
-
|
|
46548
|
+
renameSync6(tempPath, path25);
|
|
46049
46549
|
} catch (writeError) {
|
|
46050
46550
|
try {
|
|
46051
46551
|
unlinkSync3(tempPath);
|
|
@@ -46105,7 +46605,7 @@ function createCompactionCustomizerHook(config3, directory) {
|
|
|
46105
46605
|
const contextContent = await readSwarmFileAsync(directory, "context.md");
|
|
46106
46606
|
const plan = await loadPlan(directory);
|
|
46107
46607
|
if (plan && plan.migration_status !== "migration_failed") {
|
|
46108
|
-
const currentPhase =
|
|
46608
|
+
const currentPhase = extractCurrentPhaseFromPlan2(plan);
|
|
46109
46609
|
if (currentPhase) {
|
|
46110
46610
|
output.context.push(`[SWARM PLAN] ${currentPhase}`);
|
|
46111
46611
|
}
|
|
@@ -46127,7 +46627,7 @@ function createCompactionCustomizerHook(config3, directory) {
|
|
|
46127
46627
|
}
|
|
46128
46628
|
}
|
|
46129
46629
|
if (contextContent) {
|
|
46130
|
-
const decisionsSummary =
|
|
46630
|
+
const decisionsSummary = extractDecisions2(contextContent);
|
|
46131
46631
|
if (decisionsSummary) {
|
|
46132
46632
|
output.context.push(`[SWARM DECISIONS] ${decisionsSummary}`);
|
|
46133
46633
|
}
|
|
@@ -46885,7 +47385,7 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
|
46885
47385
|
};
|
|
46886
47386
|
}
|
|
46887
47387
|
// src/hooks/guardrails.ts
|
|
46888
|
-
import * as
|
|
47388
|
+
import * as path25 from "path";
|
|
46889
47389
|
init_manager2();
|
|
46890
47390
|
init_utils();
|
|
46891
47391
|
function extractPhaseNumber(phaseString) {
|
|
@@ -46927,10 +47427,10 @@ function isArchitect(sessionId) {
|
|
|
46927
47427
|
function isOutsideSwarmDir(filePath, directory) {
|
|
46928
47428
|
if (!filePath)
|
|
46929
47429
|
return false;
|
|
46930
|
-
const swarmDir =
|
|
46931
|
-
const resolved =
|
|
46932
|
-
const relative3 =
|
|
46933
|
-
return relative3.startsWith("..") ||
|
|
47430
|
+
const swarmDir = path25.resolve(directory, ".swarm");
|
|
47431
|
+
const resolved = path25.resolve(directory, filePath);
|
|
47432
|
+
const relative3 = path25.relative(swarmDir, resolved);
|
|
47433
|
+
return relative3.startsWith("..") || path25.isAbsolute(relative3);
|
|
46934
47434
|
}
|
|
46935
47435
|
function isSourceCodePath(filePath) {
|
|
46936
47436
|
if (!filePath)
|
|
@@ -47221,7 +47721,7 @@ function createGuardrailsHooks(directory, config3) {
|
|
|
47221
47721
|
try {
|
|
47222
47722
|
const plan = await loadPlan(directory);
|
|
47223
47723
|
if (plan) {
|
|
47224
|
-
const phaseString =
|
|
47724
|
+
const phaseString = extractCurrentPhaseFromPlan2(plan);
|
|
47225
47725
|
currentPhase = extractPhaseNumber(phaseString);
|
|
47226
47726
|
}
|
|
47227
47727
|
} catch {}
|
|
@@ -47305,7 +47805,7 @@ function createGuardrailsHooks(directory, config3) {
|
|
|
47305
47805
|
try {
|
|
47306
47806
|
const plan = await loadPlan(directory);
|
|
47307
47807
|
if (plan) {
|
|
47308
|
-
const phaseString =
|
|
47808
|
+
const phaseString = extractCurrentPhaseFromPlan2(plan);
|
|
47309
47809
|
currentPhaseForCheck = extractPhaseNumber(phaseString);
|
|
47310
47810
|
}
|
|
47311
47811
|
} catch {}
|
|
@@ -47543,7 +48043,7 @@ function createPipelineTrackerHook(config3) {
|
|
|
47543
48043
|
try {
|
|
47544
48044
|
const plan = await loadPlan(process.cwd());
|
|
47545
48045
|
if (plan) {
|
|
47546
|
-
const phaseString =
|
|
48046
|
+
const phaseString = extractCurrentPhaseFromPlan2(plan);
|
|
47547
48047
|
phaseNumber = parsePhaseNumber(phaseString);
|
|
47548
48048
|
}
|
|
47549
48049
|
} catch {
|
|
@@ -47560,6 +48060,7 @@ ${originalText}`;
|
|
|
47560
48060
|
};
|
|
47561
48061
|
}
|
|
47562
48062
|
// src/hooks/system-enhancer.ts
|
|
48063
|
+
import * as fs15 from "fs";
|
|
47563
48064
|
init_manager();
|
|
47564
48065
|
init_detector();
|
|
47565
48066
|
init_manager2();
|
|
@@ -47568,7 +48069,7 @@ init_manager2();
|
|
|
47568
48069
|
init_utils2();
|
|
47569
48070
|
init_manager2();
|
|
47570
48071
|
import * as fs14 from "fs";
|
|
47571
|
-
import * as
|
|
48072
|
+
import * as path26 from "path";
|
|
47572
48073
|
var DEFAULT_DRIFT_CONFIG = {
|
|
47573
48074
|
staleThresholdPhases: 1,
|
|
47574
48075
|
detectContradictions: true,
|
|
@@ -47722,7 +48223,7 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
|
|
|
47722
48223
|
currentPhase = legacyPhase;
|
|
47723
48224
|
}
|
|
47724
48225
|
}
|
|
47725
|
-
const contextPath =
|
|
48226
|
+
const contextPath = path26.join(directory, ".swarm", "context.md");
|
|
47726
48227
|
let contextContent = "";
|
|
47727
48228
|
try {
|
|
47728
48229
|
if (fs14.existsSync(contextPath)) {
|
|
@@ -47834,6 +48335,165 @@ function formatDriftForContext(result) {
|
|
|
47834
48335
|
|
|
47835
48336
|
// src/services/index.ts
|
|
47836
48337
|
init_config_doctor();
|
|
48338
|
+
|
|
48339
|
+
// src/services/context-budget-service.ts
|
|
48340
|
+
init_utils2();
|
|
48341
|
+
function validateDirectory(directory) {
|
|
48342
|
+
if (!directory || directory.trim() === "") {
|
|
48343
|
+
throw new Error("Invalid directory: empty");
|
|
48344
|
+
}
|
|
48345
|
+
if (/\.\.[/\\]/.test(directory)) {
|
|
48346
|
+
throw new Error("Invalid directory: path traversal detected");
|
|
48347
|
+
}
|
|
48348
|
+
if (directory.startsWith("/") || directory.startsWith("\\")) {
|
|
48349
|
+
throw new Error("Invalid directory: absolute path");
|
|
48350
|
+
}
|
|
48351
|
+
if (/^[A-Za-z]:[\\/]/.test(directory)) {
|
|
48352
|
+
throw new Error("Invalid directory: Windows absolute path");
|
|
48353
|
+
}
|
|
48354
|
+
}
|
|
48355
|
+
var COST_PER_1K_TOKENS = 0.003;
|
|
48356
|
+
function estimateTokens2(text) {
|
|
48357
|
+
if (!text || typeof text !== "string") {
|
|
48358
|
+
return 0;
|
|
48359
|
+
}
|
|
48360
|
+
return Math.ceil(text.length / 3.5);
|
|
48361
|
+
}
|
|
48362
|
+
async function readBudgetState(directory) {
|
|
48363
|
+
const content = await readSwarmFileAsync(directory, "session/budget-state.json");
|
|
48364
|
+
if (!content) {
|
|
48365
|
+
return null;
|
|
48366
|
+
}
|
|
48367
|
+
try {
|
|
48368
|
+
return JSON.parse(content);
|
|
48369
|
+
} catch {
|
|
48370
|
+
return null;
|
|
48371
|
+
}
|
|
48372
|
+
}
|
|
48373
|
+
async function writeBudgetState(directory, state) {
|
|
48374
|
+
const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
|
|
48375
|
+
const content = JSON.stringify(state, null, 2);
|
|
48376
|
+
await Bun.write(resolvedPath, content);
|
|
48377
|
+
}
|
|
48378
|
+
async function countEvents(directory) {
|
|
48379
|
+
const content = await readSwarmFileAsync(directory, "events.jsonl");
|
|
48380
|
+
if (!content) {
|
|
48381
|
+
return 0;
|
|
48382
|
+
}
|
|
48383
|
+
const lines = content.split(`
|
|
48384
|
+
`).filter((line) => line.trim().length > 0);
|
|
48385
|
+
return lines.length;
|
|
48386
|
+
}
|
|
48387
|
+
async function getPlanCursorContent(directory) {
|
|
48388
|
+
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
48389
|
+
if (!planContent) {
|
|
48390
|
+
return "";
|
|
48391
|
+
}
|
|
48392
|
+
const lines = planContent.split(`
|
|
48393
|
+
`);
|
|
48394
|
+
const cursorLines = [];
|
|
48395
|
+
let inCurrentSection = false;
|
|
48396
|
+
for (const line of lines) {
|
|
48397
|
+
if (line.includes("in_progress") || line.includes("**Current**")) {
|
|
48398
|
+
inCurrentSection = true;
|
|
48399
|
+
}
|
|
48400
|
+
if (inCurrentSection) {
|
|
48401
|
+
cursorLines.push(line);
|
|
48402
|
+
if (cursorLines.length > 30) {
|
|
48403
|
+
break;
|
|
48404
|
+
}
|
|
48405
|
+
}
|
|
48406
|
+
}
|
|
48407
|
+
return cursorLines.join(`
|
|
48408
|
+
`) || planContent.substring(0, 1000);
|
|
48409
|
+
}
|
|
48410
|
+
async function getContextBudgetReport(directory, assembledSystemPrompt, config3) {
|
|
48411
|
+
validateDirectory(directory);
|
|
48412
|
+
const timestamp = new Date().toISOString();
|
|
48413
|
+
const systemPromptTokens = estimateTokens2(assembledSystemPrompt);
|
|
48414
|
+
const planCursorContent = await getPlanCursorContent(directory);
|
|
48415
|
+
const planCursorTokens = estimateTokens2(planCursorContent);
|
|
48416
|
+
const knowledgeContent = await readSwarmFileAsync(directory, "knowledge.jsonl");
|
|
48417
|
+
const knowledgeTokens = estimateTokens2(knowledgeContent || "");
|
|
48418
|
+
const runMemoryContent = await readSwarmFileAsync(directory, "run-memory.jsonl");
|
|
48419
|
+
const runMemoryTokens = estimateTokens2(runMemoryContent || "");
|
|
48420
|
+
const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
|
|
48421
|
+
const handoffTokens = estimateTokens2(handoffContent || "");
|
|
48422
|
+
const contextMdContent = await readSwarmFileAsync(directory, "context.md");
|
|
48423
|
+
const contextMdTokens = estimateTokens2(contextMdContent || "");
|
|
48424
|
+
const swarmTotalTokens = systemPromptTokens + planCursorTokens + knowledgeTokens + runMemoryTokens + handoffTokens + contextMdTokens;
|
|
48425
|
+
const estimatedTurnCount = await countEvents(directory);
|
|
48426
|
+
const budgetPct = swarmTotalTokens / config3.budgetTokens * 100;
|
|
48427
|
+
let status;
|
|
48428
|
+
let recommendation = null;
|
|
48429
|
+
if (budgetPct < config3.warningPct) {
|
|
48430
|
+
status = "ok";
|
|
48431
|
+
} else if (budgetPct < config3.criticalPct) {
|
|
48432
|
+
status = "warning";
|
|
48433
|
+
recommendation = "Consider wrapping up current phase and running /swarm handoff before starting new work.";
|
|
48434
|
+
} else {
|
|
48435
|
+
status = "critical";
|
|
48436
|
+
recommendation = "Run /swarm handoff and start a new session to avoid cost escalation.";
|
|
48437
|
+
}
|
|
48438
|
+
const estimatedSessionTokens = swarmTotalTokens * Math.max(1, estimatedTurnCount);
|
|
48439
|
+
return {
|
|
48440
|
+
timestamp,
|
|
48441
|
+
systemPromptTokens,
|
|
48442
|
+
planCursorTokens,
|
|
48443
|
+
knowledgeTokens,
|
|
48444
|
+
runMemoryTokens,
|
|
48445
|
+
handoffTokens,
|
|
48446
|
+
contextMdTokens,
|
|
48447
|
+
swarmTotalTokens,
|
|
48448
|
+
estimatedTurnCount,
|
|
48449
|
+
estimatedSessionTokens,
|
|
48450
|
+
budgetPct,
|
|
48451
|
+
status,
|
|
48452
|
+
recommendation
|
|
48453
|
+
};
|
|
48454
|
+
}
|
|
48455
|
+
async function formatBudgetWarning(report, directory, config3) {
|
|
48456
|
+
validateDirectory(directory);
|
|
48457
|
+
if (report.status === "ok") {
|
|
48458
|
+
return null;
|
|
48459
|
+
}
|
|
48460
|
+
if (!directory || directory.trim() === "") {
|
|
48461
|
+
return formatWarningMessage(report);
|
|
48462
|
+
}
|
|
48463
|
+
const budgetState = await readBudgetState(directory);
|
|
48464
|
+
const state = budgetState || {
|
|
48465
|
+
warningFiredAtTurn: null,
|
|
48466
|
+
criticalFiredAtTurn: null,
|
|
48467
|
+
lastInjectedAtTurn: null
|
|
48468
|
+
};
|
|
48469
|
+
const currentTurn = report.estimatedTurnCount;
|
|
48470
|
+
if (report.status === "warning") {
|
|
48471
|
+
if (config3.warningMode === "once" && state.warningFiredAtTurn !== null) {
|
|
48472
|
+
return null;
|
|
48473
|
+
}
|
|
48474
|
+
if (config3.warningMode === "interval" && state.warningFiredAtTurn !== null && currentTurn - state.warningFiredAtTurn < config3.warningIntervalTurns) {
|
|
48475
|
+
return null;
|
|
48476
|
+
}
|
|
48477
|
+
state.warningFiredAtTurn = currentTurn;
|
|
48478
|
+
state.lastInjectedAtTurn = currentTurn;
|
|
48479
|
+
await writeBudgetState(directory, state);
|
|
48480
|
+
} else if (report.status === "critical") {
|
|
48481
|
+
state.criticalFiredAtTurn = currentTurn;
|
|
48482
|
+
state.lastInjectedAtTurn = currentTurn;
|
|
48483
|
+
}
|
|
48484
|
+
return formatWarningMessage(report);
|
|
48485
|
+
}
|
|
48486
|
+
function formatWarningMessage(report) {
|
|
48487
|
+
const budgetPctStr = report.budgetPct.toFixed(1);
|
|
48488
|
+
const tokensPerTurn = report.swarmTotalTokens.toLocaleString();
|
|
48489
|
+
if (report.status === "warning") {
|
|
48490
|
+
return `[CONTEXT BUDGET: ${budgetPctStr}% \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Consider wrapping current phase and running /swarm handoff before starting new work.]`;
|
|
48491
|
+
}
|
|
48492
|
+
const costPerTurn = (report.swarmTotalTokens / 1000 * COST_PER_1K_TOKENS).toFixed(3);
|
|
48493
|
+
return `[CONTEXT BUDGET: ${budgetPctStr}% CRITICAL \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Run /swarm handoff and start a new session to avoid cost escalation. Estimated session cost scaling: ~$${costPerTurn}/turn at current context size.]`;
|
|
48494
|
+
}
|
|
48495
|
+
|
|
48496
|
+
// src/services/index.ts
|
|
47837
48497
|
init_evidence_summary_service();
|
|
47838
48498
|
init_preflight_integration();
|
|
47839
48499
|
init_preflight_service();
|
|
@@ -48191,7 +48851,7 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
48191
48851
|
let planContent = null;
|
|
48192
48852
|
let phaseHeader = "";
|
|
48193
48853
|
if (plan2 && plan2.migration_status !== "migration_failed") {
|
|
48194
|
-
phaseHeader =
|
|
48854
|
+
phaseHeader = extractCurrentPhaseFromPlan2(plan2) || "";
|
|
48195
48855
|
planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
48196
48856
|
} else {
|
|
48197
48857
|
planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
@@ -48204,8 +48864,32 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
48204
48864
|
const planCursor = extractPlanCursor(planContent);
|
|
48205
48865
|
tryInject(planCursor);
|
|
48206
48866
|
}
|
|
48867
|
+
if (mode !== "DISCOVER") {
|
|
48868
|
+
try {
|
|
48869
|
+
const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
|
|
48870
|
+
if (handoffContent) {
|
|
48871
|
+
const handoffPath = validateSwarmPath(directory, "handoff.md");
|
|
48872
|
+
const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
|
|
48873
|
+
if (fs15.existsSync(consumedPath)) {
|
|
48874
|
+
warn("Duplicate handoff detected: handoff-consumed.md already exists");
|
|
48875
|
+
fs15.unlinkSync(consumedPath);
|
|
48876
|
+
}
|
|
48877
|
+
fs15.renameSync(handoffPath, consumedPath);
|
|
48878
|
+
const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
|
|
48879
|
+
The previous model's session ended. Here is your starting context:
|
|
48880
|
+
|
|
48881
|
+
${handoffContent}`;
|
|
48882
|
+
tryInject(`[HANDOFF BRIEF]
|
|
48883
|
+
${handoffBlock}`);
|
|
48884
|
+
}
|
|
48885
|
+
} catch (error93) {
|
|
48886
|
+
if (error93?.code !== "ENOENT") {
|
|
48887
|
+
warn("Handoff injection failed:", error93);
|
|
48888
|
+
}
|
|
48889
|
+
}
|
|
48890
|
+
}
|
|
48207
48891
|
if (mode !== "DISCOVER" && contextContent) {
|
|
48208
|
-
const decisions =
|
|
48892
|
+
const decisions = extractDecisions2(contextContent, 200);
|
|
48209
48893
|
if (decisions) {
|
|
48210
48894
|
tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
|
|
48211
48895
|
}
|
|
@@ -48363,6 +49047,37 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
48363
49047
|
}
|
|
48364
49048
|
}
|
|
48365
49049
|
}
|
|
49050
|
+
const userConfig = config3.context_budget;
|
|
49051
|
+
const defaultConfig = {
|
|
49052
|
+
enabled: true,
|
|
49053
|
+
budgetTokens: 40000,
|
|
49054
|
+
warningPct: 70,
|
|
49055
|
+
criticalPct: 90,
|
|
49056
|
+
warningMode: "once",
|
|
49057
|
+
warningIntervalTurns: 20
|
|
49058
|
+
};
|
|
49059
|
+
const contextBudgetConfig = userConfig ? {
|
|
49060
|
+
...defaultConfig,
|
|
49061
|
+
...userConfig,
|
|
49062
|
+
warningPct: userConfig.warn_threshold ? userConfig.warn_threshold * 100 : defaultConfig.warningPct,
|
|
49063
|
+
criticalPct: userConfig.critical_threshold ? userConfig.critical_threshold * 100 : defaultConfig.criticalPct,
|
|
49064
|
+
budgetTokens: userConfig.model_limits?.default ?? defaultConfig.budgetTokens
|
|
49065
|
+
} : defaultConfig;
|
|
49066
|
+
if (contextBudgetConfig.enabled !== false) {
|
|
49067
|
+
const assembledSystemPrompt = output.system.join(`
|
|
49068
|
+
`);
|
|
49069
|
+
const budgetReport = await getContextBudgetReport(directory, assembledSystemPrompt, contextBudgetConfig);
|
|
49070
|
+
const budgetWarning = await formatBudgetWarning(budgetReport, directory, contextBudgetConfig);
|
|
49071
|
+
if (budgetWarning) {
|
|
49072
|
+
const sessionId_cb = _input.sessionID;
|
|
49073
|
+
const activeAgent_cb = sessionId_cb ? swarmState.activeAgent.get(sessionId_cb) : null;
|
|
49074
|
+
const isArchitect_cb = !activeAgent_cb || stripKnownSwarmPrefix(activeAgent_cb) === "architect";
|
|
49075
|
+
if (isArchitect_cb) {
|
|
49076
|
+
output.system.push(`[FOR: architect]
|
|
49077
|
+
${budgetWarning}`);
|
|
49078
|
+
}
|
|
49079
|
+
}
|
|
49080
|
+
}
|
|
48366
49081
|
return;
|
|
48367
49082
|
}
|
|
48368
49083
|
const mode_b = await detectArchitectMode(directory);
|
|
@@ -48384,7 +49099,7 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
48384
49099
|
let currentPhase = null;
|
|
48385
49100
|
let currentTask = null;
|
|
48386
49101
|
if (plan && plan.migration_status !== "migration_failed") {
|
|
48387
|
-
currentPhase =
|
|
49102
|
+
currentPhase = extractCurrentPhaseFromPlan2(plan);
|
|
48388
49103
|
currentTask = extractCurrentTaskFromPlan(plan);
|
|
48389
49104
|
} else {
|
|
48390
49105
|
planContentForCursor = await readSwarmFileAsync(directory, "plan.md");
|
|
@@ -48429,8 +49144,40 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
48429
49144
|
metadata: { contentType: "markdown" }
|
|
48430
49145
|
});
|
|
48431
49146
|
}
|
|
49147
|
+
if (mode_b !== "DISCOVER") {
|
|
49148
|
+
try {
|
|
49149
|
+
const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
|
|
49150
|
+
if (handoffContent) {
|
|
49151
|
+
const handoffPath = validateSwarmPath(directory, "handoff.md");
|
|
49152
|
+
const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
|
|
49153
|
+
if (fs15.existsSync(consumedPath)) {
|
|
49154
|
+
warn("Duplicate handoff detected: handoff-consumed.md already exists");
|
|
49155
|
+
fs15.unlinkSync(consumedPath);
|
|
49156
|
+
}
|
|
49157
|
+
fs15.renameSync(handoffPath, consumedPath);
|
|
49158
|
+
const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
|
|
49159
|
+
The previous model's session ended. Here is your starting context:
|
|
49160
|
+
|
|
49161
|
+
${handoffContent}`;
|
|
49162
|
+
const handoffText = `[HANDOFF BRIEF]
|
|
49163
|
+
${handoffBlock}`;
|
|
49164
|
+
candidates.push({
|
|
49165
|
+
id: `candidate-${idCounter++}`,
|
|
49166
|
+
kind: "phase",
|
|
49167
|
+
text: handoffText,
|
|
49168
|
+
tokens: estimateTokens(handoffText),
|
|
49169
|
+
priority: 1,
|
|
49170
|
+
metadata: { contentType: "markdown" }
|
|
49171
|
+
});
|
|
49172
|
+
}
|
|
49173
|
+
} catch (error93) {
|
|
49174
|
+
if (error93?.code !== "ENOENT") {
|
|
49175
|
+
warn("Handoff injection failed:", error93);
|
|
49176
|
+
}
|
|
49177
|
+
}
|
|
49178
|
+
}
|
|
48432
49179
|
if (contextContent) {
|
|
48433
|
-
const decisions =
|
|
49180
|
+
const decisions = extractDecisions2(contextContent, 200);
|
|
48434
49181
|
if (decisions) {
|
|
48435
49182
|
const text = `[SWARM CONTEXT] Key decisions: ${decisions}`;
|
|
48436
49183
|
candidates.push({
|
|
@@ -48700,6 +49447,37 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
48700
49447
|
output.system.push(candidate.text);
|
|
48701
49448
|
injectedTokens += candidate.tokens;
|
|
48702
49449
|
}
|
|
49450
|
+
const userConfig_b = config3.context_budget;
|
|
49451
|
+
const defaultConfig_b = {
|
|
49452
|
+
enabled: true,
|
|
49453
|
+
budgetTokens: 40000,
|
|
49454
|
+
warningPct: 70,
|
|
49455
|
+
criticalPct: 90,
|
|
49456
|
+
warningMode: "once",
|
|
49457
|
+
warningIntervalTurns: 20
|
|
49458
|
+
};
|
|
49459
|
+
const contextBudgetConfig_b = userConfig_b ? {
|
|
49460
|
+
...defaultConfig_b,
|
|
49461
|
+
...userConfig_b,
|
|
49462
|
+
warningPct: userConfig_b.warn_threshold ? userConfig_b.warn_threshold * 100 : defaultConfig_b.warningPct,
|
|
49463
|
+
criticalPct: userConfig_b.critical_threshold ? userConfig_b.critical_threshold * 100 : defaultConfig_b.criticalPct,
|
|
49464
|
+
budgetTokens: userConfig_b.model_limits?.default ?? defaultConfig_b.budgetTokens
|
|
49465
|
+
} : defaultConfig_b;
|
|
49466
|
+
if (contextBudgetConfig_b.enabled !== false) {
|
|
49467
|
+
const assembledSystemPrompt_b = output.system.join(`
|
|
49468
|
+
`);
|
|
49469
|
+
const budgetReport_b = await getContextBudgetReport(directory, assembledSystemPrompt_b, contextBudgetConfig_b);
|
|
49470
|
+
const budgetWarning_b = await formatBudgetWarning(budgetReport_b, directory, contextBudgetConfig_b);
|
|
49471
|
+
if (budgetWarning_b) {
|
|
49472
|
+
const sessionId_cb_b = _input.sessionID;
|
|
49473
|
+
const activeAgent_cb_b = sessionId_cb_b ? swarmState.activeAgent.get(sessionId_cb_b) : null;
|
|
49474
|
+
const isArchitect_cb_b = !activeAgent_cb_b || stripKnownSwarmPrefix(activeAgent_cb_b) === "architect";
|
|
49475
|
+
if (isArchitect_cb_b) {
|
|
49476
|
+
output.system.push(`[FOR: architect]
|
|
49477
|
+
${budgetWarning_b}`);
|
|
49478
|
+
}
|
|
49479
|
+
}
|
|
49480
|
+
}
|
|
48703
49481
|
} catch (error93) {
|
|
48704
49482
|
warn("System enhancer failed:", error93);
|
|
48705
49483
|
}
|
|
@@ -49048,9 +49826,9 @@ function createDarkMatterDetectorHook(directory) {
|
|
|
49048
49826
|
}
|
|
49049
49827
|
|
|
49050
49828
|
// src/hooks/knowledge-reader.ts
|
|
49051
|
-
import { existsSync as
|
|
49829
|
+
import { existsSync as existsSync18 } from "fs";
|
|
49052
49830
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
49053
|
-
import * as
|
|
49831
|
+
import * as path27 from "path";
|
|
49054
49832
|
var JACCARD_THRESHOLD = 0.6;
|
|
49055
49833
|
var HIVE_TIER_BOOST = 0.05;
|
|
49056
49834
|
var SAME_PROJECT_PENALTY = -0.05;
|
|
@@ -49098,15 +49876,15 @@ function inferCategoriesFromPhase(phaseDescription) {
|
|
|
49098
49876
|
return ["process", "tooling"];
|
|
49099
49877
|
}
|
|
49100
49878
|
async function recordLessonsShown(directory, lessonIds, currentPhase) {
|
|
49101
|
-
const shownFile =
|
|
49879
|
+
const shownFile = path27.join(directory, ".swarm", ".knowledge-shown.json");
|
|
49102
49880
|
try {
|
|
49103
49881
|
let shownData = {};
|
|
49104
|
-
if (
|
|
49882
|
+
if (existsSync18(shownFile)) {
|
|
49105
49883
|
const content = await readFile4(shownFile, "utf-8");
|
|
49106
49884
|
shownData = JSON.parse(content);
|
|
49107
49885
|
}
|
|
49108
49886
|
shownData[currentPhase] = lessonIds;
|
|
49109
|
-
await mkdir4(
|
|
49887
|
+
await mkdir4(path27.dirname(shownFile), { recursive: true });
|
|
49110
49888
|
await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
|
|
49111
49889
|
} catch {
|
|
49112
49890
|
console.warn("[swarm] Knowledge: failed to record shown lessons");
|
|
@@ -49201,9 +49979,9 @@ async function readMergedKnowledge(directory, config3, context) {
|
|
|
49201
49979
|
return topN;
|
|
49202
49980
|
}
|
|
49203
49981
|
async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
|
|
49204
|
-
const shownFile =
|
|
49982
|
+
const shownFile = path27.join(directory, ".swarm", ".knowledge-shown.json");
|
|
49205
49983
|
try {
|
|
49206
|
-
if (!
|
|
49984
|
+
if (!existsSync18(shownFile)) {
|
|
49207
49985
|
return;
|
|
49208
49986
|
}
|
|
49209
49987
|
const content = await readFile4(shownFile, "utf-8");
|
|
@@ -49565,6 +50343,111 @@ function createKnowledgeCuratorHook(directory, config3) {
|
|
|
49565
50343
|
|
|
49566
50344
|
// src/hooks/knowledge-injector.ts
|
|
49567
50345
|
init_manager2();
|
|
50346
|
+
|
|
50347
|
+
// src/services/run-memory.ts
|
|
50348
|
+
init_utils2();
|
|
50349
|
+
function validateDirectory2(directory) {
|
|
50350
|
+
if (!directory || directory.trim() === "") {
|
|
50351
|
+
throw new Error("Invalid directory: empty");
|
|
50352
|
+
}
|
|
50353
|
+
if (/\.\.[/\\]/.test(directory)) {
|
|
50354
|
+
throw new Error("Invalid directory: path traversal detected");
|
|
50355
|
+
}
|
|
50356
|
+
if (directory.startsWith("/") || directory.startsWith("\\")) {
|
|
50357
|
+
throw new Error("Invalid directory: absolute path");
|
|
50358
|
+
}
|
|
50359
|
+
if (/^[A-Za-z]:[\\/]/.test(directory)) {
|
|
50360
|
+
throw new Error("Invalid directory: Windows absolute path");
|
|
50361
|
+
}
|
|
50362
|
+
}
|
|
50363
|
+
var RUN_MEMORY_FILENAME = "run-memory.jsonl";
|
|
50364
|
+
var MAX_SUMMARY_TOKENS = 500;
|
|
50365
|
+
function groupByTaskId(entries) {
|
|
50366
|
+
const groups = new Map;
|
|
50367
|
+
for (const entry of entries) {
|
|
50368
|
+
const existing = groups.get(entry.taskId) || [];
|
|
50369
|
+
existing.push(entry);
|
|
50370
|
+
groups.set(entry.taskId, existing);
|
|
50371
|
+
}
|
|
50372
|
+
return groups;
|
|
50373
|
+
}
|
|
50374
|
+
function summarizeTask(taskId, entries) {
|
|
50375
|
+
const failures = entries.filter((e) => e.outcome === "fail" || e.outcome === "retry");
|
|
50376
|
+
const passes = entries.filter((e) => e.outcome === "pass");
|
|
50377
|
+
if (failures.length === 0) {
|
|
50378
|
+
return null;
|
|
50379
|
+
}
|
|
50380
|
+
failures.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
50381
|
+
const lastFailure = failures[0];
|
|
50382
|
+
passes.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
50383
|
+
const lastPass = passes[0];
|
|
50384
|
+
const failCount = failures.length;
|
|
50385
|
+
if (lastPass) {
|
|
50386
|
+
const passAttempt = lastPass.attemptNumber;
|
|
50387
|
+
const failAttempt = lastFailure.attemptNumber;
|
|
50388
|
+
return `Task ${taskId}: FAILED attempt ${failAttempt} \u2014 ${lastFailure.failureReason || "unknown"}. Passed on attempt ${passAttempt}.`;
|
|
50389
|
+
} else {
|
|
50390
|
+
return `Task ${taskId}: FAILED ${failCount} times \u2014 last: ${lastFailure.failureReason || "unknown"}. Still failing.`;
|
|
50391
|
+
}
|
|
50392
|
+
}
|
|
50393
|
+
async function getRunMemorySummary(directory) {
|
|
50394
|
+
validateDirectory2(directory);
|
|
50395
|
+
const content = await readSwarmFileAsync(directory, RUN_MEMORY_FILENAME);
|
|
50396
|
+
if (!content) {
|
|
50397
|
+
return null;
|
|
50398
|
+
}
|
|
50399
|
+
const entries = [];
|
|
50400
|
+
const lines = content.split(`
|
|
50401
|
+
`);
|
|
50402
|
+
for (const line of lines) {
|
|
50403
|
+
if (!line.trim())
|
|
50404
|
+
continue;
|
|
50405
|
+
try {
|
|
50406
|
+
const entry = JSON.parse(line);
|
|
50407
|
+
entries.push(entry);
|
|
50408
|
+
} catch {}
|
|
50409
|
+
}
|
|
50410
|
+
if (entries.length === 0) {
|
|
50411
|
+
return null;
|
|
50412
|
+
}
|
|
50413
|
+
const groups = groupByTaskId(entries);
|
|
50414
|
+
const summaries = [];
|
|
50415
|
+
for (const [taskId, taskEntries] of groups) {
|
|
50416
|
+
const summary = summarizeTask(taskId, taskEntries);
|
|
50417
|
+
if (summary) {
|
|
50418
|
+
summaries.push(summary);
|
|
50419
|
+
}
|
|
50420
|
+
}
|
|
50421
|
+
if (summaries.length === 0) {
|
|
50422
|
+
return null;
|
|
50423
|
+
}
|
|
50424
|
+
const prefix = `[FOR: architect, coder]
|
|
50425
|
+
## RUN MEMORY \u2014 Previous Task Outcomes
|
|
50426
|
+
`;
|
|
50427
|
+
const suffix = `
|
|
50428
|
+
Use this data to avoid repeating known failure patterns.`;
|
|
50429
|
+
let summaryText = summaries.join(`
|
|
50430
|
+
`);
|
|
50431
|
+
const estimateTokens3 = (text) => {
|
|
50432
|
+
return Math.ceil(text.length * 0.33);
|
|
50433
|
+
};
|
|
50434
|
+
const totalText = prefix + summaryText + suffix;
|
|
50435
|
+
const estimatedTokens = estimateTokens3(totalText);
|
|
50436
|
+
if (estimatedTokens > MAX_SUMMARY_TOKENS) {
|
|
50437
|
+
const prefixTokens = estimateTokens3(prefix);
|
|
50438
|
+
const suffixTokens = estimateTokens3(suffix);
|
|
50439
|
+
const availableContentTokens = MAX_SUMMARY_TOKENS - prefixTokens - suffixTokens;
|
|
50440
|
+
if (availableContentTokens > 0) {
|
|
50441
|
+
const maxContentChars = Math.floor(availableContentTokens / 0.33);
|
|
50442
|
+
summaryText = summaryText.slice(-maxContentChars);
|
|
50443
|
+
} else {
|
|
50444
|
+
summaryText = "";
|
|
50445
|
+
}
|
|
50446
|
+
}
|
|
50447
|
+
return prefix + summaryText + suffix;
|
|
50448
|
+
}
|
|
50449
|
+
|
|
50450
|
+
// src/hooks/knowledge-injector.ts
|
|
49568
50451
|
init_utils2();
|
|
49569
50452
|
function formatStars(confidence) {
|
|
49570
50453
|
if (confidence >= 0.9)
|
|
@@ -49638,7 +50521,7 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
49638
50521
|
lastSeenPhase = currentPhase;
|
|
49639
50522
|
cachedInjectionText = null;
|
|
49640
50523
|
}
|
|
49641
|
-
const phaseDescription =
|
|
50524
|
+
const phaseDescription = extractCurrentPhaseFromPlan2(plan) ?? `Phase ${currentPhase}`;
|
|
49642
50525
|
const context = {
|
|
49643
50526
|
projectName: plan.title,
|
|
49644
50527
|
currentPhase: phaseDescription
|
|
@@ -49646,6 +50529,7 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
49646
50529
|
const entries = await readMergedKnowledge(directory, config3, context);
|
|
49647
50530
|
if (entries.length === 0)
|
|
49648
50531
|
return;
|
|
50532
|
+
const runMemory = await getRunMemorySummary(directory);
|
|
49649
50533
|
const lines = entries.map((entry) => {
|
|
49650
50534
|
const stars = formatStars(entry.confidence);
|
|
49651
50535
|
const tierLabel = entry.tier === "hive" ? "[HIVE]" : "[SWARM]";
|
|
@@ -49655,7 +50539,7 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
49655
50539
|
const source = rawSource !== null ? ` \u2014 Source: ${sanitizeLessonForContext(rawSource)}` : "";
|
|
49656
50540
|
return `${stars} ${tierLabel} ${sanitizeLessonForContext(entry.lesson)}${source}${confirmText}`;
|
|
49657
50541
|
});
|
|
49658
|
-
|
|
50542
|
+
const knowledgeSection = [
|
|
49659
50543
|
`\uD83D\uDCDA Knowledge (${entries.length} relevant lesson${entries.length > 1 ? "s" : ""}):`,
|
|
49660
50544
|
"",
|
|
49661
50545
|
...lines,
|
|
@@ -49663,6 +50547,13 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
49663
50547
|
"These are lessons learned from this project and past projects. Consider them as context but use your judgment \u2014 they may not all apply."
|
|
49664
50548
|
].join(`
|
|
49665
50549
|
`);
|
|
50550
|
+
if (runMemory) {
|
|
50551
|
+
cachedInjectionText = runMemory + `
|
|
50552
|
+
|
|
50553
|
+
` + knowledgeSection;
|
|
50554
|
+
} else {
|
|
50555
|
+
cachedInjectionText = knowledgeSection;
|
|
50556
|
+
}
|
|
49666
50557
|
const rejected = await readRejectedLessons(directory);
|
|
49667
50558
|
if (rejected.length > 0) {
|
|
49668
50559
|
const recentRejected = rejected.slice(-3);
|
|
@@ -49679,7 +50570,7 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
49679
50570
|
|
|
49680
50571
|
// src/hooks/steering-consumed.ts
|
|
49681
50572
|
init_utils2();
|
|
49682
|
-
import * as
|
|
50573
|
+
import * as fs16 from "fs";
|
|
49683
50574
|
function recordSteeringConsumed(directory, directiveId) {
|
|
49684
50575
|
try {
|
|
49685
50576
|
const eventsPath = validateSwarmPath(directory, "events.jsonl");
|
|
@@ -49688,7 +50579,7 @@ function recordSteeringConsumed(directory, directiveId) {
|
|
|
49688
50579
|
directiveId,
|
|
49689
50580
|
timestamp: new Date().toISOString()
|
|
49690
50581
|
};
|
|
49691
|
-
|
|
50582
|
+
fs16.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
49692
50583
|
`, "utf-8");
|
|
49693
50584
|
} catch {}
|
|
49694
50585
|
}
|
|
@@ -49833,95 +50724,6 @@ async function loadSnapshot(directory) {
|
|
|
49833
50724
|
} catch {}
|
|
49834
50725
|
}
|
|
49835
50726
|
|
|
49836
|
-
// src/session/snapshot-writer.ts
|
|
49837
|
-
init_utils2();
|
|
49838
|
-
import { mkdirSync as mkdirSync6, renameSync as renameSync5 } from "fs";
|
|
49839
|
-
import * as path27 from "path";
|
|
49840
|
-
function serializeAgentSession(s) {
|
|
49841
|
-
const gateLog = {};
|
|
49842
|
-
const rawGateLog = s.gateLog ?? new Map;
|
|
49843
|
-
for (const [taskId, gates] of rawGateLog) {
|
|
49844
|
-
gateLog[taskId] = Array.from(gates ?? []);
|
|
49845
|
-
}
|
|
49846
|
-
const reviewerCallCount = {};
|
|
49847
|
-
const rawReviewerCallCount = s.reviewerCallCount ?? new Map;
|
|
49848
|
-
for (const [phase, count] of rawReviewerCallCount) {
|
|
49849
|
-
reviewerCallCount[String(phase)] = count;
|
|
49850
|
-
}
|
|
49851
|
-
const partialGateWarningsIssuedForTask = Array.from(s.partialGateWarningsIssuedForTask ?? new Set);
|
|
49852
|
-
const catastrophicPhaseWarnings = Array.from(s.catastrophicPhaseWarnings ?? new Set);
|
|
49853
|
-
const phaseAgentsDispatched = Array.from(s.phaseAgentsDispatched ?? new Set);
|
|
49854
|
-
const windows = {};
|
|
49855
|
-
const rawWindows = s.windows ?? {};
|
|
49856
|
-
for (const [key, win] of Object.entries(rawWindows)) {
|
|
49857
|
-
windows[key] = {
|
|
49858
|
-
id: win.id,
|
|
49859
|
-
agentName: win.agentName,
|
|
49860
|
-
startedAtMs: win.startedAtMs,
|
|
49861
|
-
toolCalls: win.toolCalls,
|
|
49862
|
-
consecutiveErrors: win.consecutiveErrors,
|
|
49863
|
-
hardLimitHit: win.hardLimitHit,
|
|
49864
|
-
lastSuccessTimeMs: win.lastSuccessTimeMs,
|
|
49865
|
-
recentToolCalls: win.recentToolCalls,
|
|
49866
|
-
warningIssued: win.warningIssued,
|
|
49867
|
-
warningReason: win.warningReason
|
|
49868
|
-
};
|
|
49869
|
-
}
|
|
49870
|
-
return {
|
|
49871
|
-
agentName: s.agentName,
|
|
49872
|
-
lastToolCallTime: s.lastToolCallTime,
|
|
49873
|
-
lastAgentEventTime: s.lastAgentEventTime,
|
|
49874
|
-
delegationActive: s.delegationActive,
|
|
49875
|
-
activeInvocationId: s.activeInvocationId,
|
|
49876
|
-
lastInvocationIdByAgent: s.lastInvocationIdByAgent ?? {},
|
|
49877
|
-
windows,
|
|
49878
|
-
lastCompactionHint: s.lastCompactionHint ?? 0,
|
|
49879
|
-
architectWriteCount: s.architectWriteCount ?? 0,
|
|
49880
|
-
lastCoderDelegationTaskId: s.lastCoderDelegationTaskId ?? null,
|
|
49881
|
-
currentTaskId: s.currentTaskId ?? null,
|
|
49882
|
-
gateLog,
|
|
49883
|
-
reviewerCallCount,
|
|
49884
|
-
lastGateFailure: s.lastGateFailure ?? null,
|
|
49885
|
-
partialGateWarningsIssuedForTask,
|
|
49886
|
-
selfFixAttempted: s.selfFixAttempted ?? false,
|
|
49887
|
-
catastrophicPhaseWarnings,
|
|
49888
|
-
lastPhaseCompleteTimestamp: s.lastPhaseCompleteTimestamp ?? 0,
|
|
49889
|
-
lastPhaseCompletePhase: s.lastPhaseCompletePhase ?? 0,
|
|
49890
|
-
phaseAgentsDispatched,
|
|
49891
|
-
qaSkipCount: s.qaSkipCount ?? 0,
|
|
49892
|
-
qaSkipTaskIds: s.qaSkipTaskIds ?? []
|
|
49893
|
-
};
|
|
49894
|
-
}
|
|
49895
|
-
async function writeSnapshot(directory, state) {
|
|
49896
|
-
try {
|
|
49897
|
-
const snapshot = {
|
|
49898
|
-
version: 1,
|
|
49899
|
-
writtenAt: Date.now(),
|
|
49900
|
-
toolAggregates: Object.fromEntries(state.toolAggregates),
|
|
49901
|
-
activeAgent: Object.fromEntries(state.activeAgent),
|
|
49902
|
-
delegationChains: Object.fromEntries(state.delegationChains),
|
|
49903
|
-
agentSessions: {}
|
|
49904
|
-
};
|
|
49905
|
-
for (const [sessionId, sessionState] of state.agentSessions) {
|
|
49906
|
-
snapshot.agentSessions[sessionId] = serializeAgentSession(sessionState);
|
|
49907
|
-
}
|
|
49908
|
-
const content = JSON.stringify(snapshot, null, 2);
|
|
49909
|
-
const resolvedPath = validateSwarmPath(directory, "session/state.json");
|
|
49910
|
-
const dir = path27.dirname(resolvedPath);
|
|
49911
|
-
mkdirSync6(dir, { recursive: true });
|
|
49912
|
-
const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
49913
|
-
await Bun.write(tempPath, content);
|
|
49914
|
-
renameSync5(tempPath, resolvedPath);
|
|
49915
|
-
} catch {}
|
|
49916
|
-
}
|
|
49917
|
-
function createSnapshotWriterHook(directory) {
|
|
49918
|
-
return async (_input, _output) => {
|
|
49919
|
-
try {
|
|
49920
|
-
await writeSnapshot(directory, swarmState);
|
|
49921
|
-
} catch {}
|
|
49922
|
-
};
|
|
49923
|
-
}
|
|
49924
|
-
|
|
49925
50727
|
// src/tools/build-check.ts
|
|
49926
50728
|
init_dist();
|
|
49927
50729
|
init_discovery();
|
|
@@ -50097,7 +50899,7 @@ var build_check = createSwarmTool({
|
|
|
50097
50899
|
init_tool();
|
|
50098
50900
|
init_create_tool();
|
|
50099
50901
|
import { spawnSync } from "child_process";
|
|
50100
|
-
import * as
|
|
50902
|
+
import * as fs17 from "fs";
|
|
50101
50903
|
import * as path28 from "path";
|
|
50102
50904
|
var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
|
|
50103
50905
|
var MAX_LABEL_LENGTH = 100;
|
|
@@ -50154,8 +50956,8 @@ function getCheckpointLogPath(directory) {
|
|
|
50154
50956
|
function readCheckpointLog(directory) {
|
|
50155
50957
|
const logPath = getCheckpointLogPath(directory);
|
|
50156
50958
|
try {
|
|
50157
|
-
if (
|
|
50158
|
-
const content =
|
|
50959
|
+
if (fs17.existsSync(logPath)) {
|
|
50960
|
+
const content = fs17.readFileSync(logPath, "utf-8");
|
|
50159
50961
|
const parsed = JSON.parse(content);
|
|
50160
50962
|
if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
|
|
50161
50963
|
return { version: 1, checkpoints: [] };
|
|
@@ -50168,12 +50970,12 @@ function readCheckpointLog(directory) {
|
|
|
50168
50970
|
function writeCheckpointLog(log2, directory) {
|
|
50169
50971
|
const logPath = getCheckpointLogPath(directory);
|
|
50170
50972
|
const dir = path28.dirname(logPath);
|
|
50171
|
-
if (!
|
|
50172
|
-
|
|
50973
|
+
if (!fs17.existsSync(dir)) {
|
|
50974
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
50173
50975
|
}
|
|
50174
50976
|
const tempPath = `${logPath}.tmp`;
|
|
50175
|
-
|
|
50176
|
-
|
|
50977
|
+
fs17.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
|
|
50978
|
+
fs17.renameSync(tempPath, logPath);
|
|
50177
50979
|
}
|
|
50178
50980
|
function gitExec(args2) {
|
|
50179
50981
|
const result = spawnSync("git", args2, {
|
|
@@ -50374,7 +51176,7 @@ var checkpoint = createSwarmTool({
|
|
|
50374
51176
|
// src/tools/complexity-hotspots.ts
|
|
50375
51177
|
init_dist();
|
|
50376
51178
|
init_create_tool();
|
|
50377
|
-
import * as
|
|
51179
|
+
import * as fs18 from "fs";
|
|
50378
51180
|
import * as path29 from "path";
|
|
50379
51181
|
var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
|
|
50380
51182
|
var DEFAULT_DAYS = 90;
|
|
@@ -50503,11 +51305,11 @@ function estimateComplexity(content) {
|
|
|
50503
51305
|
}
|
|
50504
51306
|
function getComplexityForFile(filePath) {
|
|
50505
51307
|
try {
|
|
50506
|
-
const stat2 =
|
|
51308
|
+
const stat2 = fs18.statSync(filePath);
|
|
50507
51309
|
if (stat2.size > MAX_FILE_SIZE_BYTES2) {
|
|
50508
51310
|
return null;
|
|
50509
51311
|
}
|
|
50510
|
-
const content =
|
|
51312
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
50511
51313
|
return estimateComplexity(content);
|
|
50512
51314
|
} catch {
|
|
50513
51315
|
return null;
|
|
@@ -50528,7 +51330,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
50528
51330
|
let analyzedFiles = 0;
|
|
50529
51331
|
for (const [file3, churnCount] of filteredChurn) {
|
|
50530
51332
|
let fullPath = file3;
|
|
50531
|
-
if (!
|
|
51333
|
+
if (!fs18.existsSync(fullPath)) {
|
|
50532
51334
|
fullPath = path29.join(cwd, file3);
|
|
50533
51335
|
}
|
|
50534
51336
|
const complexity = getComplexityForFile(fullPath);
|
|
@@ -51000,7 +51802,7 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
51000
51802
|
// src/tools/evidence-check.ts
|
|
51001
51803
|
init_dist();
|
|
51002
51804
|
init_create_tool();
|
|
51003
|
-
import * as
|
|
51805
|
+
import * as fs19 from "fs";
|
|
51004
51806
|
import * as path30 from "path";
|
|
51005
51807
|
var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
|
|
51006
51808
|
var MAX_EVIDENCE_FILES = 1000;
|
|
@@ -51042,12 +51844,12 @@ function parseCompletedTasks(planContent) {
|
|
|
51042
51844
|
}
|
|
51043
51845
|
function readEvidenceFiles(evidenceDir, _cwd) {
|
|
51044
51846
|
const evidence = [];
|
|
51045
|
-
if (!
|
|
51847
|
+
if (!fs19.existsSync(evidenceDir) || !fs19.statSync(evidenceDir).isDirectory()) {
|
|
51046
51848
|
return evidence;
|
|
51047
51849
|
}
|
|
51048
51850
|
let files;
|
|
51049
51851
|
try {
|
|
51050
|
-
files =
|
|
51852
|
+
files = fs19.readdirSync(evidenceDir);
|
|
51051
51853
|
} catch {
|
|
51052
51854
|
return evidence;
|
|
51053
51855
|
}
|
|
@@ -51063,7 +51865,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
51063
51865
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
51064
51866
|
continue;
|
|
51065
51867
|
}
|
|
51066
|
-
const stat2 =
|
|
51868
|
+
const stat2 = fs19.lstatSync(filePath);
|
|
51067
51869
|
if (!stat2.isFile()) {
|
|
51068
51870
|
continue;
|
|
51069
51871
|
}
|
|
@@ -51072,7 +51874,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
51072
51874
|
}
|
|
51073
51875
|
let fileStat;
|
|
51074
51876
|
try {
|
|
51075
|
-
fileStat =
|
|
51877
|
+
fileStat = fs19.statSync(filePath);
|
|
51076
51878
|
if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
|
|
51077
51879
|
continue;
|
|
51078
51880
|
}
|
|
@@ -51081,7 +51883,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
51081
51883
|
}
|
|
51082
51884
|
let content;
|
|
51083
51885
|
try {
|
|
51084
|
-
content =
|
|
51886
|
+
content = fs19.readFileSync(filePath, "utf-8");
|
|
51085
51887
|
} catch {
|
|
51086
51888
|
continue;
|
|
51087
51889
|
}
|
|
@@ -51180,7 +51982,7 @@ var evidence_check = createSwarmTool({
|
|
|
51180
51982
|
}
|
|
51181
51983
|
let planContent;
|
|
51182
51984
|
try {
|
|
51183
|
-
planContent =
|
|
51985
|
+
planContent = fs19.readFileSync(planPath, "utf-8");
|
|
51184
51986
|
} catch {
|
|
51185
51987
|
const result2 = {
|
|
51186
51988
|
message: "No completed tasks found in plan.",
|
|
@@ -51215,7 +52017,7 @@ var evidence_check = createSwarmTool({
|
|
|
51215
52017
|
// src/tools/file-extractor.ts
|
|
51216
52018
|
init_tool();
|
|
51217
52019
|
init_create_tool();
|
|
51218
|
-
import * as
|
|
52020
|
+
import * as fs20 from "fs";
|
|
51219
52021
|
import * as path31 from "path";
|
|
51220
52022
|
var EXT_MAP = {
|
|
51221
52023
|
python: ".py",
|
|
@@ -51278,8 +52080,8 @@ var extract_code_blocks = createSwarmTool({
|
|
|
51278
52080
|
execute: async (args2, directory) => {
|
|
51279
52081
|
const { content, output_dir, prefix } = args2;
|
|
51280
52082
|
const targetDir = output_dir || directory;
|
|
51281
|
-
if (!
|
|
51282
|
-
|
|
52083
|
+
if (!fs20.existsSync(targetDir)) {
|
|
52084
|
+
fs20.mkdirSync(targetDir, { recursive: true });
|
|
51283
52085
|
}
|
|
51284
52086
|
if (!content) {
|
|
51285
52087
|
return "Error: content is required";
|
|
@@ -51301,12 +52103,12 @@ var extract_code_blocks = createSwarmTool({
|
|
|
51301
52103
|
const base = path31.basename(filepath, path31.extname(filepath));
|
|
51302
52104
|
const ext = path31.extname(filepath);
|
|
51303
52105
|
let counter = 1;
|
|
51304
|
-
while (
|
|
52106
|
+
while (fs20.existsSync(filepath)) {
|
|
51305
52107
|
filepath = path31.join(targetDir, `${base}_${counter}${ext}`);
|
|
51306
52108
|
counter++;
|
|
51307
52109
|
}
|
|
51308
52110
|
try {
|
|
51309
|
-
|
|
52111
|
+
fs20.writeFileSync(filepath, code.trim(), "utf-8");
|
|
51310
52112
|
savedFiles.push(filepath);
|
|
51311
52113
|
} catch (error93) {
|
|
51312
52114
|
errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
|
|
@@ -51414,7 +52216,7 @@ var gitingest = tool({
|
|
|
51414
52216
|
});
|
|
51415
52217
|
// src/tools/imports.ts
|
|
51416
52218
|
init_dist();
|
|
51417
|
-
import * as
|
|
52219
|
+
import * as fs21 from "fs";
|
|
51418
52220
|
import * as path32 from "path";
|
|
51419
52221
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
51420
52222
|
var MAX_SYMBOL_LENGTH = 256;
|
|
@@ -51583,7 +52385,7 @@ var SKIP_DIRECTORIES2 = new Set([
|
|
|
51583
52385
|
function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
|
|
51584
52386
|
let entries;
|
|
51585
52387
|
try {
|
|
51586
|
-
entries =
|
|
52388
|
+
entries = fs21.readdirSync(dir);
|
|
51587
52389
|
} catch (e) {
|
|
51588
52390
|
stats.fileErrors.push({
|
|
51589
52391
|
path: dir,
|
|
@@ -51600,7 +52402,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
51600
52402
|
const fullPath = path32.join(dir, entry);
|
|
51601
52403
|
let stat2;
|
|
51602
52404
|
try {
|
|
51603
|
-
stat2 =
|
|
52405
|
+
stat2 = fs21.statSync(fullPath);
|
|
51604
52406
|
} catch (e) {
|
|
51605
52407
|
stats.fileErrors.push({
|
|
51606
52408
|
path: fullPath,
|
|
@@ -51668,7 +52470,7 @@ var imports = tool({
|
|
|
51668
52470
|
}
|
|
51669
52471
|
try {
|
|
51670
52472
|
const targetFile = path32.resolve(file3);
|
|
51671
|
-
if (!
|
|
52473
|
+
if (!fs21.existsSync(targetFile)) {
|
|
51672
52474
|
const errorResult = {
|
|
51673
52475
|
error: `target file not found: ${file3}`,
|
|
51674
52476
|
target: file3,
|
|
@@ -51678,7 +52480,7 @@ var imports = tool({
|
|
|
51678
52480
|
};
|
|
51679
52481
|
return JSON.stringify(errorResult, null, 2);
|
|
51680
52482
|
}
|
|
51681
|
-
const targetStat =
|
|
52483
|
+
const targetStat = fs21.statSync(targetFile);
|
|
51682
52484
|
if (!targetStat.isFile()) {
|
|
51683
52485
|
const errorResult = {
|
|
51684
52486
|
error: "target must be a file, not a directory",
|
|
@@ -51704,12 +52506,12 @@ var imports = tool({
|
|
|
51704
52506
|
if (consumers.length >= MAX_CONSUMERS)
|
|
51705
52507
|
break;
|
|
51706
52508
|
try {
|
|
51707
|
-
const stat2 =
|
|
52509
|
+
const stat2 = fs21.statSync(filePath);
|
|
51708
52510
|
if (stat2.size > MAX_FILE_SIZE_BYTES4) {
|
|
51709
52511
|
skippedFileCount++;
|
|
51710
52512
|
continue;
|
|
51711
52513
|
}
|
|
51712
|
-
const buffer =
|
|
52514
|
+
const buffer = fs21.readFileSync(filePath);
|
|
51713
52515
|
if (isBinaryFile2(filePath, buffer)) {
|
|
51714
52516
|
skippedFileCount++;
|
|
51715
52517
|
continue;
|
|
@@ -51778,7 +52580,7 @@ init_lint();
|
|
|
51778
52580
|
|
|
51779
52581
|
// src/tools/phase-complete.ts
|
|
51780
52582
|
init_dist();
|
|
51781
|
-
import * as
|
|
52583
|
+
import * as fs22 from "fs";
|
|
51782
52584
|
import * as path33 from "path";
|
|
51783
52585
|
init_manager();
|
|
51784
52586
|
init_utils2();
|
|
@@ -51890,10 +52692,17 @@ async function executePhaseComplete(args2, workingDirectory) {
|
|
|
51890
52692
|
let retroFound = false;
|
|
51891
52693
|
let retroEntry = null;
|
|
51892
52694
|
let invalidSchemaErrors = [];
|
|
52695
|
+
let loadedRetroTaskId = null;
|
|
52696
|
+
let loadedRetroBundle = null;
|
|
52697
|
+
const primaryRetroTaskId = `retro-${phase}`;
|
|
51893
52698
|
if (retroResult.status === "found") {
|
|
51894
52699
|
const validEntry = retroResult.bundle.entries?.find((entry) => isValidRetroEntry(entry, phase));
|
|
51895
|
-
|
|
51896
|
-
|
|
52700
|
+
if (validEntry) {
|
|
52701
|
+
retroFound = true;
|
|
52702
|
+
retroEntry = validEntry;
|
|
52703
|
+
loadedRetroTaskId = primaryRetroTaskId;
|
|
52704
|
+
loadedRetroBundle = retroResult.bundle;
|
|
52705
|
+
}
|
|
51897
52706
|
} else if (retroResult.status === "invalid_schema") {
|
|
51898
52707
|
invalidSchemaErrors = retroResult.errors;
|
|
51899
52708
|
}
|
|
@@ -51909,9 +52718,11 @@ async function executePhaseComplete(args2, workingDirectory) {
|
|
|
51909
52718
|
continue;
|
|
51910
52719
|
}
|
|
51911
52720
|
const validEntry = bundleResult.bundle.entries?.find((entry) => isValidRetroEntry(entry, phase));
|
|
51912
|
-
|
|
51913
|
-
|
|
52721
|
+
if (validEntry) {
|
|
52722
|
+
retroFound = true;
|
|
51914
52723
|
retroEntry = validEntry;
|
|
52724
|
+
loadedRetroTaskId = taskId;
|
|
52725
|
+
loadedRetroBundle = bundleResult.bundle;
|
|
51915
52726
|
break;
|
|
51916
52727
|
}
|
|
51917
52728
|
}
|
|
@@ -51989,6 +52800,11 @@ async function executePhaseComplete(args2, workingDirectory) {
|
|
|
51989
52800
|
}
|
|
51990
52801
|
const agentsMissing = effectiveRequired.filter((req) => !crossSessionResult.agents.has(req));
|
|
51991
52802
|
const warnings = [];
|
|
52803
|
+
const VALID_TASK_COMPLEXITY = ["trivial", "simple", "moderate", "complex"];
|
|
52804
|
+
const firstEntry = loadedRetroBundle?.entries?.[0];
|
|
52805
|
+
if (loadedRetroTaskId?.startsWith("retro-") && loadedRetroBundle?.schema_version === "1.0.0" && firstEntry?.task_complexity && VALID_TASK_COMPLEXITY.includes(firstEntry.task_complexity)) {
|
|
52806
|
+
warnings.push(`Retrospective data for phase ${phase} may have been automatically migrated to current schema format.`);
|
|
52807
|
+
}
|
|
51992
52808
|
let success3 = true;
|
|
51993
52809
|
let status = "success";
|
|
51994
52810
|
const safeSummary = summary?.trim().slice(0, 500);
|
|
@@ -52016,7 +52832,7 @@ async function executePhaseComplete(args2, workingDirectory) {
|
|
|
52016
52832
|
};
|
|
52017
52833
|
try {
|
|
52018
52834
|
const eventsPath = validateSwarmPath(dir, "events.jsonl");
|
|
52019
|
-
|
|
52835
|
+
fs22.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
52020
52836
|
`, "utf-8");
|
|
52021
52837
|
} catch (writeError) {
|
|
52022
52838
|
warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
@@ -52074,7 +52890,7 @@ init_dist();
|
|
|
52074
52890
|
init_discovery();
|
|
52075
52891
|
init_utils();
|
|
52076
52892
|
init_create_tool();
|
|
52077
|
-
import * as
|
|
52893
|
+
import * as fs23 from "fs";
|
|
52078
52894
|
import * as path34 from "path";
|
|
52079
52895
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
52080
52896
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
@@ -52093,28 +52909,28 @@ function validateArgs3(args2) {
|
|
|
52093
52909
|
function detectEcosystems() {
|
|
52094
52910
|
const ecosystems = [];
|
|
52095
52911
|
const cwd = process.cwd();
|
|
52096
|
-
if (
|
|
52912
|
+
if (fs23.existsSync(path34.join(cwd, "package.json"))) {
|
|
52097
52913
|
ecosystems.push("npm");
|
|
52098
52914
|
}
|
|
52099
|
-
if (
|
|
52915
|
+
if (fs23.existsSync(path34.join(cwd, "pyproject.toml")) || fs23.existsSync(path34.join(cwd, "requirements.txt"))) {
|
|
52100
52916
|
ecosystems.push("pip");
|
|
52101
52917
|
}
|
|
52102
|
-
if (
|
|
52918
|
+
if (fs23.existsSync(path34.join(cwd, "Cargo.toml"))) {
|
|
52103
52919
|
ecosystems.push("cargo");
|
|
52104
52920
|
}
|
|
52105
|
-
if (
|
|
52921
|
+
if (fs23.existsSync(path34.join(cwd, "go.mod"))) {
|
|
52106
52922
|
ecosystems.push("go");
|
|
52107
52923
|
}
|
|
52108
52924
|
try {
|
|
52109
|
-
const files =
|
|
52925
|
+
const files = fs23.readdirSync(cwd);
|
|
52110
52926
|
if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
|
|
52111
52927
|
ecosystems.push("dotnet");
|
|
52112
52928
|
}
|
|
52113
52929
|
} catch {}
|
|
52114
|
-
if (
|
|
52930
|
+
if (fs23.existsSync(path34.join(cwd, "Gemfile")) || fs23.existsSync(path34.join(cwd, "Gemfile.lock"))) {
|
|
52115
52931
|
ecosystems.push("ruby");
|
|
52116
52932
|
}
|
|
52117
|
-
if (
|
|
52933
|
+
if (fs23.existsSync(path34.join(cwd, "pubspec.yaml"))) {
|
|
52118
52934
|
ecosystems.push("dart");
|
|
52119
52935
|
}
|
|
52120
52936
|
return ecosystems;
|
|
@@ -53169,7 +53985,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
|
|
|
53169
53985
|
]);
|
|
53170
53986
|
// src/tools/pre-check-batch.ts
|
|
53171
53987
|
init_dist();
|
|
53172
|
-
import * as
|
|
53988
|
+
import * as fs26 from "fs";
|
|
53173
53989
|
import * as path37 from "path";
|
|
53174
53990
|
|
|
53175
53991
|
// node_modules/yocto-queue/index.js
|
|
@@ -53337,7 +54153,7 @@ init_lint();
|
|
|
53337
54153
|
init_manager();
|
|
53338
54154
|
|
|
53339
54155
|
// src/quality/metrics.ts
|
|
53340
|
-
import * as
|
|
54156
|
+
import * as fs24 from "fs";
|
|
53341
54157
|
import * as path35 from "path";
|
|
53342
54158
|
var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
|
|
53343
54159
|
var MIN_DUPLICATION_LINES = 10;
|
|
@@ -53376,11 +54192,11 @@ function estimateCyclomaticComplexity(content) {
|
|
|
53376
54192
|
}
|
|
53377
54193
|
function getComplexityForFile2(filePath) {
|
|
53378
54194
|
try {
|
|
53379
|
-
const stat2 =
|
|
54195
|
+
const stat2 = fs24.statSync(filePath);
|
|
53380
54196
|
if (stat2.size > MAX_FILE_SIZE_BYTES5) {
|
|
53381
54197
|
return null;
|
|
53382
54198
|
}
|
|
53383
|
-
const content =
|
|
54199
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
53384
54200
|
return estimateCyclomaticComplexity(content);
|
|
53385
54201
|
} catch {
|
|
53386
54202
|
return null;
|
|
@@ -53391,7 +54207,7 @@ async function computeComplexityDelta(files, workingDir) {
|
|
|
53391
54207
|
const analyzedFiles = [];
|
|
53392
54208
|
for (const file3 of files) {
|
|
53393
54209
|
const fullPath = path35.isAbsolute(file3) ? file3 : path35.join(workingDir, file3);
|
|
53394
|
-
if (!
|
|
54210
|
+
if (!fs24.existsSync(fullPath)) {
|
|
53395
54211
|
continue;
|
|
53396
54212
|
}
|
|
53397
54213
|
const complexity = getComplexityForFile2(fullPath);
|
|
@@ -53512,7 +54328,7 @@ function countGoExports(content) {
|
|
|
53512
54328
|
}
|
|
53513
54329
|
function getExportCountForFile(filePath) {
|
|
53514
54330
|
try {
|
|
53515
|
-
const content =
|
|
54331
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
53516
54332
|
const ext = path35.extname(filePath).toLowerCase();
|
|
53517
54333
|
switch (ext) {
|
|
53518
54334
|
case ".ts":
|
|
@@ -53540,7 +54356,7 @@ async function computePublicApiDelta(files, workingDir) {
|
|
|
53540
54356
|
const analyzedFiles = [];
|
|
53541
54357
|
for (const file3 of files) {
|
|
53542
54358
|
const fullPath = path35.isAbsolute(file3) ? file3 : path35.join(workingDir, file3);
|
|
53543
|
-
if (!
|
|
54359
|
+
if (!fs24.existsSync(fullPath)) {
|
|
53544
54360
|
continue;
|
|
53545
54361
|
}
|
|
53546
54362
|
const exports = getExportCountForFile(fullPath);
|
|
@@ -53574,15 +54390,15 @@ async function computeDuplicationRatio(files, workingDir) {
|
|
|
53574
54390
|
const analyzedFiles = [];
|
|
53575
54391
|
for (const file3 of files) {
|
|
53576
54392
|
const fullPath = path35.isAbsolute(file3) ? file3 : path35.join(workingDir, file3);
|
|
53577
|
-
if (!
|
|
54393
|
+
if (!fs24.existsSync(fullPath)) {
|
|
53578
54394
|
continue;
|
|
53579
54395
|
}
|
|
53580
54396
|
try {
|
|
53581
|
-
const stat2 =
|
|
54397
|
+
const stat2 = fs24.statSync(fullPath);
|
|
53582
54398
|
if (stat2.size > MAX_FILE_SIZE_BYTES5) {
|
|
53583
54399
|
continue;
|
|
53584
54400
|
}
|
|
53585
|
-
const content =
|
|
54401
|
+
const content = fs24.readFileSync(fullPath, "utf-8");
|
|
53586
54402
|
const lines = content.split(`
|
|
53587
54403
|
`).filter((line) => line.trim().length > 0);
|
|
53588
54404
|
if (lines.length < MIN_DUPLICATION_LINES) {
|
|
@@ -53758,7 +54574,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
53758
54574
|
let testLines = 0;
|
|
53759
54575
|
let codeLines = 0;
|
|
53760
54576
|
const srcDir = path35.join(workingDir, "src");
|
|
53761
|
-
if (
|
|
54577
|
+
if (fs24.existsSync(srcDir)) {
|
|
53762
54578
|
await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
53763
54579
|
codeLines += lines;
|
|
53764
54580
|
});
|
|
@@ -53766,14 +54582,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
53766
54582
|
const possibleSrcDirs = ["lib", "app", "source", "core"];
|
|
53767
54583
|
for (const dir of possibleSrcDirs) {
|
|
53768
54584
|
const dirPath = path35.join(workingDir, dir);
|
|
53769
|
-
if (
|
|
54585
|
+
if (fs24.existsSync(dirPath)) {
|
|
53770
54586
|
await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
53771
54587
|
codeLines += lines;
|
|
53772
54588
|
});
|
|
53773
54589
|
}
|
|
53774
54590
|
}
|
|
53775
54591
|
const testsDir = path35.join(workingDir, "tests");
|
|
53776
|
-
if (
|
|
54592
|
+
if (fs24.existsSync(testsDir)) {
|
|
53777
54593
|
await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
53778
54594
|
testLines += lines;
|
|
53779
54595
|
});
|
|
@@ -53781,7 +54597,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
53781
54597
|
const possibleTestDirs = ["test", "__tests__", "specs"];
|
|
53782
54598
|
for (const dir of possibleTestDirs) {
|
|
53783
54599
|
const dirPath = path35.join(workingDir, dir);
|
|
53784
|
-
if (
|
|
54600
|
+
if (fs24.existsSync(dirPath) && dirPath !== testsDir) {
|
|
53785
54601
|
await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
53786
54602
|
testLines += lines;
|
|
53787
54603
|
});
|
|
@@ -53793,7 +54609,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
53793
54609
|
}
|
|
53794
54610
|
async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
|
|
53795
54611
|
try {
|
|
53796
|
-
const entries =
|
|
54612
|
+
const entries = fs24.readdirSync(dirPath, { withFileTypes: true });
|
|
53797
54613
|
for (const entry of entries) {
|
|
53798
54614
|
const fullPath = path35.join(dirPath, entry.name);
|
|
53799
54615
|
if (entry.isDirectory()) {
|
|
@@ -53839,7 +54655,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
|
|
|
53839
54655
|
continue;
|
|
53840
54656
|
}
|
|
53841
54657
|
try {
|
|
53842
|
-
const content =
|
|
54658
|
+
const content = fs24.readFileSync(fullPath, "utf-8");
|
|
53843
54659
|
const lines = countCodeLines(content);
|
|
53844
54660
|
callback(lines);
|
|
53845
54661
|
} catch {}
|
|
@@ -54053,7 +54869,7 @@ async function qualityBudget(input, directory) {
|
|
|
54053
54869
|
init_dist();
|
|
54054
54870
|
init_manager();
|
|
54055
54871
|
init_detector();
|
|
54056
|
-
import * as
|
|
54872
|
+
import * as fs25 from "fs";
|
|
54057
54873
|
import * as path36 from "path";
|
|
54058
54874
|
import { extname as extname9 } from "path";
|
|
54059
54875
|
|
|
@@ -54921,17 +55737,17 @@ var SEVERITY_ORDER = {
|
|
|
54921
55737
|
};
|
|
54922
55738
|
function shouldSkipFile(filePath) {
|
|
54923
55739
|
try {
|
|
54924
|
-
const stats =
|
|
55740
|
+
const stats = fs25.statSync(filePath);
|
|
54925
55741
|
if (stats.size > MAX_FILE_SIZE_BYTES6) {
|
|
54926
55742
|
return { skip: true, reason: "file too large" };
|
|
54927
55743
|
}
|
|
54928
55744
|
if (stats.size === 0) {
|
|
54929
55745
|
return { skip: true, reason: "empty file" };
|
|
54930
55746
|
}
|
|
54931
|
-
const fd =
|
|
55747
|
+
const fd = fs25.openSync(filePath, "r");
|
|
54932
55748
|
const buffer = Buffer.alloc(8192);
|
|
54933
|
-
const bytesRead =
|
|
54934
|
-
|
|
55749
|
+
const bytesRead = fs25.readSync(fd, buffer, 0, 8192, 0);
|
|
55750
|
+
fs25.closeSync(fd);
|
|
54935
55751
|
if (bytesRead > 0) {
|
|
54936
55752
|
let nullCount = 0;
|
|
54937
55753
|
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
@@ -54970,7 +55786,7 @@ function countBySeverity(findings) {
|
|
|
54970
55786
|
}
|
|
54971
55787
|
function scanFileWithTierA(filePath, language) {
|
|
54972
55788
|
try {
|
|
54973
|
-
const content =
|
|
55789
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
54974
55790
|
const findings = executeRulesSync(filePath, content, language);
|
|
54975
55791
|
return findings.map((f) => ({
|
|
54976
55792
|
rule_id: f.rule_id,
|
|
@@ -55018,7 +55834,7 @@ async function sastScan(input, directory, config3) {
|
|
|
55018
55834
|
continue;
|
|
55019
55835
|
}
|
|
55020
55836
|
const resolvedPath = path36.isAbsolute(filePath) ? filePath : path36.resolve(directory, filePath);
|
|
55021
|
-
if (!
|
|
55837
|
+
if (!fs25.existsSync(resolvedPath)) {
|
|
55022
55838
|
_filesSkipped++;
|
|
55023
55839
|
continue;
|
|
55024
55840
|
}
|
|
@@ -55234,7 +56050,7 @@ function validatePath(inputPath, baseDir, workspaceDir) {
|
|
|
55234
56050
|
}
|
|
55235
56051
|
return null;
|
|
55236
56052
|
}
|
|
55237
|
-
function
|
|
56053
|
+
function validateDirectory3(dir, workspaceDir) {
|
|
55238
56054
|
if (!dir || dir.length === 0) {
|
|
55239
56055
|
return "directory is required";
|
|
55240
56056
|
}
|
|
@@ -55476,7 +56292,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
55476
56292
|
}
|
|
55477
56293
|
let stat2;
|
|
55478
56294
|
try {
|
|
55479
|
-
stat2 =
|
|
56295
|
+
stat2 = fs26.statSync(file3);
|
|
55480
56296
|
} catch {
|
|
55481
56297
|
skippedFiles++;
|
|
55482
56298
|
continue;
|
|
@@ -55487,7 +56303,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
55487
56303
|
}
|
|
55488
56304
|
let content;
|
|
55489
56305
|
try {
|
|
55490
|
-
const buffer =
|
|
56306
|
+
const buffer = fs26.readFileSync(file3);
|
|
55491
56307
|
if (buffer.includes(0)) {
|
|
55492
56308
|
skippedFiles++;
|
|
55493
56309
|
continue;
|
|
@@ -55590,7 +56406,7 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
|
|
|
55590
56406
|
async function runPreCheckBatch(input, workspaceDir) {
|
|
55591
56407
|
const effectiveWorkspaceDir = workspaceDir || input.directory || process.cwd();
|
|
55592
56408
|
const { files, directory, sast_threshold = "medium", config: config3 } = input;
|
|
55593
|
-
const dirError =
|
|
56409
|
+
const dirError = validateDirectory3(directory, effectiveWorkspaceDir);
|
|
55594
56410
|
if (dirError) {
|
|
55595
56411
|
warn(`pre_check_batch: Invalid directory: ${dirError}`);
|
|
55596
56412
|
return {
|
|
@@ -55754,7 +56570,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
55754
56570
|
}
|
|
55755
56571
|
const resolvedDirectory = path37.resolve(typedArgs.directory);
|
|
55756
56572
|
const workspaceAnchor = resolvedDirectory;
|
|
55757
|
-
const dirError =
|
|
56573
|
+
const dirError = validateDirectory3(resolvedDirectory, workspaceAnchor);
|
|
55758
56574
|
if (dirError) {
|
|
55759
56575
|
const errorResult = {
|
|
55760
56576
|
gates_passed: false,
|
|
@@ -55944,7 +56760,7 @@ var save_plan = createSwarmTool({
|
|
|
55944
56760
|
// src/tools/sbom-generate.ts
|
|
55945
56761
|
init_dist();
|
|
55946
56762
|
init_manager();
|
|
55947
|
-
import * as
|
|
56763
|
+
import * as fs27 from "fs";
|
|
55948
56764
|
import * as path39 from "path";
|
|
55949
56765
|
|
|
55950
56766
|
// src/sbom/detectors/dart.ts
|
|
@@ -56791,7 +57607,7 @@ function findManifestFiles(rootDir) {
|
|
|
56791
57607
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
56792
57608
|
function searchDir(dir) {
|
|
56793
57609
|
try {
|
|
56794
|
-
const entries =
|
|
57610
|
+
const entries = fs27.readdirSync(dir, { withFileTypes: true });
|
|
56795
57611
|
for (const entry of entries) {
|
|
56796
57612
|
const fullPath = path39.join(dir, entry.name);
|
|
56797
57613
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
@@ -56820,7 +57636,7 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
56820
57636
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
56821
57637
|
for (const dir of directories) {
|
|
56822
57638
|
try {
|
|
56823
|
-
const entries =
|
|
57639
|
+
const entries = fs27.readdirSync(dir, { withFileTypes: true });
|
|
56824
57640
|
for (const entry of entries) {
|
|
56825
57641
|
const fullPath = path39.join(dir, entry.name);
|
|
56826
57642
|
if (entry.isFile()) {
|
|
@@ -56858,7 +57674,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
56858
57674
|
}
|
|
56859
57675
|
function ensureOutputDir(outputDir) {
|
|
56860
57676
|
try {
|
|
56861
|
-
|
|
57677
|
+
fs27.mkdirSync(outputDir, { recursive: true });
|
|
56862
57678
|
} catch (error93) {
|
|
56863
57679
|
if (!error93 || error93.code !== "EEXIST") {
|
|
56864
57680
|
throw error93;
|
|
@@ -56951,10 +57767,10 @@ var sbom_generate = createSwarmTool({
|
|
|
56951
57767
|
for (const manifestFile of manifestFiles) {
|
|
56952
57768
|
try {
|
|
56953
57769
|
const fullPath = path39.isAbsolute(manifestFile) ? manifestFile : path39.join(workingDir, manifestFile);
|
|
56954
|
-
if (!
|
|
57770
|
+
if (!fs27.existsSync(fullPath)) {
|
|
56955
57771
|
continue;
|
|
56956
57772
|
}
|
|
56957
|
-
const content =
|
|
57773
|
+
const content = fs27.readFileSync(fullPath, "utf-8");
|
|
56958
57774
|
const components = detectComponents(manifestFile, content);
|
|
56959
57775
|
processedFiles.push(manifestFile);
|
|
56960
57776
|
if (components.length > 0) {
|
|
@@ -56968,7 +57784,7 @@ var sbom_generate = createSwarmTool({
|
|
|
56968
57784
|
const bomJson = serializeCycloneDX(bom);
|
|
56969
57785
|
const filename = generateSbomFilename();
|
|
56970
57786
|
const outputPath = path39.join(outputDir, filename);
|
|
56971
|
-
|
|
57787
|
+
fs27.writeFileSync(outputPath, bomJson, "utf-8");
|
|
56972
57788
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
56973
57789
|
try {
|
|
56974
57790
|
const timestamp = new Date().toISOString();
|
|
@@ -57010,7 +57826,7 @@ var sbom_generate = createSwarmTool({
|
|
|
57010
57826
|
// src/tools/schema-drift.ts
|
|
57011
57827
|
init_dist();
|
|
57012
57828
|
init_create_tool();
|
|
57013
|
-
import * as
|
|
57829
|
+
import * as fs28 from "fs";
|
|
57014
57830
|
import * as path40 from "path";
|
|
57015
57831
|
var SPEC_CANDIDATES = [
|
|
57016
57832
|
"openapi.json",
|
|
@@ -57052,19 +57868,19 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
57052
57868
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
57053
57869
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
57054
57870
|
}
|
|
57055
|
-
const stats =
|
|
57871
|
+
const stats = fs28.statSync(resolvedPath);
|
|
57056
57872
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
57057
57873
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
57058
57874
|
}
|
|
57059
|
-
if (!
|
|
57875
|
+
if (!fs28.existsSync(resolvedPath)) {
|
|
57060
57876
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
57061
57877
|
}
|
|
57062
57878
|
return resolvedPath;
|
|
57063
57879
|
}
|
|
57064
57880
|
for (const candidate of SPEC_CANDIDATES) {
|
|
57065
57881
|
const candidatePath = path40.resolve(cwd, candidate);
|
|
57066
|
-
if (
|
|
57067
|
-
const stats =
|
|
57882
|
+
if (fs28.existsSync(candidatePath)) {
|
|
57883
|
+
const stats = fs28.statSync(candidatePath);
|
|
57068
57884
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
57069
57885
|
return candidatePath;
|
|
57070
57886
|
}
|
|
@@ -57073,7 +57889,7 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
57073
57889
|
return null;
|
|
57074
57890
|
}
|
|
57075
57891
|
function parseSpec(specFile) {
|
|
57076
|
-
const content =
|
|
57892
|
+
const content = fs28.readFileSync(specFile, "utf-8");
|
|
57077
57893
|
const ext = path40.extname(specFile).toLowerCase();
|
|
57078
57894
|
if (ext === ".json") {
|
|
57079
57895
|
return parseJsonSpec(content);
|
|
@@ -57140,7 +57956,7 @@ function extractRoutes(cwd) {
|
|
|
57140
57956
|
function walkDir(dir) {
|
|
57141
57957
|
let entries;
|
|
57142
57958
|
try {
|
|
57143
|
-
entries =
|
|
57959
|
+
entries = fs28.readdirSync(dir, { withFileTypes: true });
|
|
57144
57960
|
} catch {
|
|
57145
57961
|
return;
|
|
57146
57962
|
}
|
|
@@ -57173,7 +57989,7 @@ function extractRoutes(cwd) {
|
|
|
57173
57989
|
}
|
|
57174
57990
|
function extractRoutesFromFile(filePath) {
|
|
57175
57991
|
const routes = [];
|
|
57176
|
-
const content =
|
|
57992
|
+
const content = fs28.readFileSync(filePath, "utf-8");
|
|
57177
57993
|
const lines = content.split(/\r?\n/);
|
|
57178
57994
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
57179
57995
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -57324,7 +58140,7 @@ init_secretscan();
|
|
|
57324
58140
|
// src/tools/symbols.ts
|
|
57325
58141
|
init_tool();
|
|
57326
58142
|
init_create_tool();
|
|
57327
|
-
import * as
|
|
58143
|
+
import * as fs29 from "fs";
|
|
57328
58144
|
import * as path41 from "path";
|
|
57329
58145
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
57330
58146
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
@@ -57355,8 +58171,8 @@ function containsWindowsAttacks(str) {
|
|
|
57355
58171
|
function isPathInWorkspace(filePath, workspace) {
|
|
57356
58172
|
try {
|
|
57357
58173
|
const resolvedPath = path41.resolve(workspace, filePath);
|
|
57358
|
-
const realWorkspace =
|
|
57359
|
-
const realResolvedPath =
|
|
58174
|
+
const realWorkspace = fs29.realpathSync(workspace);
|
|
58175
|
+
const realResolvedPath = fs29.realpathSync(resolvedPath);
|
|
57360
58176
|
const relativePath = path41.relative(realWorkspace, realResolvedPath);
|
|
57361
58177
|
if (relativePath.startsWith("..") || path41.isAbsolute(relativePath)) {
|
|
57362
58178
|
return false;
|
|
@@ -57376,11 +58192,11 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
57376
58192
|
}
|
|
57377
58193
|
let content;
|
|
57378
58194
|
try {
|
|
57379
|
-
const stats =
|
|
58195
|
+
const stats = fs29.statSync(fullPath);
|
|
57380
58196
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
57381
58197
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
57382
58198
|
}
|
|
57383
|
-
content =
|
|
58199
|
+
content = fs29.readFileSync(fullPath, "utf-8");
|
|
57384
58200
|
} catch {
|
|
57385
58201
|
return [];
|
|
57386
58202
|
}
|
|
@@ -57528,11 +58344,11 @@ function extractPythonSymbols(filePath, cwd) {
|
|
|
57528
58344
|
}
|
|
57529
58345
|
let content;
|
|
57530
58346
|
try {
|
|
57531
|
-
const stats =
|
|
58347
|
+
const stats = fs29.statSync(fullPath);
|
|
57532
58348
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
57533
58349
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
57534
58350
|
}
|
|
57535
|
-
content =
|
|
58351
|
+
content = fs29.readFileSync(fullPath, "utf-8");
|
|
57536
58352
|
} catch {
|
|
57537
58353
|
return [];
|
|
57538
58354
|
}
|
|
@@ -57672,11 +58488,133 @@ var MAX_FILE_SIZE2 = 5 * 1024 * 1024;
|
|
|
57672
58488
|
// src/tools/index.ts
|
|
57673
58489
|
init_test_runner();
|
|
57674
58490
|
|
|
58491
|
+
// src/tools/update-task-status.ts
|
|
58492
|
+
init_tool();
|
|
58493
|
+
init_manager2();
|
|
58494
|
+
init_create_tool();
|
|
58495
|
+
import * as fs30 from "fs";
|
|
58496
|
+
import * as path42 from "path";
|
|
58497
|
+
var VALID_STATUSES = [
|
|
58498
|
+
"pending",
|
|
58499
|
+
"in_progress",
|
|
58500
|
+
"completed",
|
|
58501
|
+
"blocked"
|
|
58502
|
+
];
|
|
58503
|
+
function validateStatus(status) {
|
|
58504
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
58505
|
+
return `Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(", ")}`;
|
|
58506
|
+
}
|
|
58507
|
+
return;
|
|
58508
|
+
}
|
|
58509
|
+
function validateTaskId(taskId) {
|
|
58510
|
+
const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
|
|
58511
|
+
if (!taskIdPattern.test(taskId)) {
|
|
58512
|
+
return `Invalid task_id "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
|
|
58513
|
+
}
|
|
58514
|
+
return;
|
|
58515
|
+
}
|
|
58516
|
+
async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
58517
|
+
const statusError = validateStatus(args2.status);
|
|
58518
|
+
if (statusError) {
|
|
58519
|
+
return {
|
|
58520
|
+
success: false,
|
|
58521
|
+
message: "Validation failed",
|
|
58522
|
+
errors: [statusError]
|
|
58523
|
+
};
|
|
58524
|
+
}
|
|
58525
|
+
const taskIdError = validateTaskId(args2.task_id);
|
|
58526
|
+
if (taskIdError) {
|
|
58527
|
+
return {
|
|
58528
|
+
success: false,
|
|
58529
|
+
message: "Validation failed",
|
|
58530
|
+
errors: [taskIdError]
|
|
58531
|
+
};
|
|
58532
|
+
}
|
|
58533
|
+
let normalizedDir;
|
|
58534
|
+
if (args2.working_directory != null) {
|
|
58535
|
+
if (args2.working_directory.includes("\x00")) {
|
|
58536
|
+
return {
|
|
58537
|
+
success: false,
|
|
58538
|
+
message: "Invalid working_directory: null bytes are not allowed"
|
|
58539
|
+
};
|
|
58540
|
+
}
|
|
58541
|
+
if (process.platform === "win32") {
|
|
58542
|
+
const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
|
|
58543
|
+
if (devicePathPattern.test(args2.working_directory)) {
|
|
58544
|
+
return {
|
|
58545
|
+
success: false,
|
|
58546
|
+
message: "Invalid working_directory: Windows device paths are not allowed"
|
|
58547
|
+
};
|
|
58548
|
+
}
|
|
58549
|
+
}
|
|
58550
|
+
normalizedDir = path42.normalize(args2.working_directory);
|
|
58551
|
+
const pathParts = normalizedDir.split(path42.sep);
|
|
58552
|
+
if (pathParts.includes("..")) {
|
|
58553
|
+
return {
|
|
58554
|
+
success: false,
|
|
58555
|
+
message: "Invalid working_directory: path traversal sequences (..) are not allowed",
|
|
58556
|
+
errors: [
|
|
58557
|
+
"Invalid working_directory: path traversal sequences (..) are not allowed"
|
|
58558
|
+
]
|
|
58559
|
+
};
|
|
58560
|
+
}
|
|
58561
|
+
const resolvedDir = path42.resolve(normalizedDir);
|
|
58562
|
+
try {
|
|
58563
|
+
const realPath = fs30.realpathSync(resolvedDir);
|
|
58564
|
+
const planPath = path42.join(realPath, ".swarm", "plan.json");
|
|
58565
|
+
if (!fs30.existsSync(planPath)) {
|
|
58566
|
+
return {
|
|
58567
|
+
success: false,
|
|
58568
|
+
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
58569
|
+
errors: [
|
|
58570
|
+
`Invalid working_directory: plan not found in "${realPath}"`
|
|
58571
|
+
]
|
|
58572
|
+
};
|
|
58573
|
+
}
|
|
58574
|
+
} catch {
|
|
58575
|
+
return {
|
|
58576
|
+
success: false,
|
|
58577
|
+
message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`,
|
|
58578
|
+
errors: [
|
|
58579
|
+
`Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
|
|
58580
|
+
]
|
|
58581
|
+
};
|
|
58582
|
+
}
|
|
58583
|
+
}
|
|
58584
|
+
const directory = normalizedDir ?? fallbackDir ?? process.cwd();
|
|
58585
|
+
try {
|
|
58586
|
+
const updatedPlan = await updateTaskStatus(directory, args2.task_id, args2.status);
|
|
58587
|
+
return {
|
|
58588
|
+
success: true,
|
|
58589
|
+
message: "Task status updated successfully",
|
|
58590
|
+
task_id: args2.task_id,
|
|
58591
|
+
new_status: args2.status,
|
|
58592
|
+
current_phase: updatedPlan.current_phase
|
|
58593
|
+
};
|
|
58594
|
+
} catch (error93) {
|
|
58595
|
+
return {
|
|
58596
|
+
success: false,
|
|
58597
|
+
message: "Failed to update task status",
|
|
58598
|
+
errors: [String(error93)]
|
|
58599
|
+
};
|
|
58600
|
+
}
|
|
58601
|
+
}
|
|
58602
|
+
var update_task_status = createSwarmTool({
|
|
58603
|
+
description: "Update the status of a specific task in the implementation plan. " + "Task status can be one of: pending, in_progress, completed, blocked.",
|
|
58604
|
+
args: {
|
|
58605
|
+
task_id: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID in N.M format, e.g. "1.1", "1.2.3"'),
|
|
58606
|
+
status: tool.schema.enum(["pending", "in_progress", "completed", "blocked"]).describe("New status for the task: pending, in_progress, completed, or blocked"),
|
|
58607
|
+
working_directory: tool.schema.string().optional().describe("Working directory where the plan is located")
|
|
58608
|
+
},
|
|
58609
|
+
execute: async (args2, _directory) => {
|
|
58610
|
+
return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
|
|
58611
|
+
}
|
|
58612
|
+
});
|
|
57675
58613
|
// src/tools/todo-extract.ts
|
|
57676
58614
|
init_dist();
|
|
57677
58615
|
init_create_tool();
|
|
57678
|
-
import * as
|
|
57679
|
-
import * as
|
|
58616
|
+
import * as fs31 from "fs";
|
|
58617
|
+
import * as path43 from "path";
|
|
57680
58618
|
var MAX_TEXT_LENGTH = 200;
|
|
57681
58619
|
var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
|
|
57682
58620
|
var SUPPORTED_EXTENSIONS2 = new Set([
|
|
@@ -57747,9 +58685,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
57747
58685
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
57748
58686
|
}
|
|
57749
58687
|
try {
|
|
57750
|
-
const resolvedPath =
|
|
57751
|
-
const normalizedCwd =
|
|
57752
|
-
const normalizedResolved =
|
|
58688
|
+
const resolvedPath = path43.resolve(paths);
|
|
58689
|
+
const normalizedCwd = path43.resolve(cwd);
|
|
58690
|
+
const normalizedResolved = path43.resolve(resolvedPath);
|
|
57753
58691
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
57754
58692
|
return {
|
|
57755
58693
|
error: "paths must be within the current working directory",
|
|
@@ -57765,13 +58703,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
57765
58703
|
}
|
|
57766
58704
|
}
|
|
57767
58705
|
function isSupportedExtension(filePath) {
|
|
57768
|
-
const ext =
|
|
58706
|
+
const ext = path43.extname(filePath).toLowerCase();
|
|
57769
58707
|
return SUPPORTED_EXTENSIONS2.has(ext);
|
|
57770
58708
|
}
|
|
57771
58709
|
function findSourceFiles3(dir, files = []) {
|
|
57772
58710
|
let entries;
|
|
57773
58711
|
try {
|
|
57774
|
-
entries =
|
|
58712
|
+
entries = fs31.readdirSync(dir);
|
|
57775
58713
|
} catch {
|
|
57776
58714
|
return files;
|
|
57777
58715
|
}
|
|
@@ -57780,10 +58718,10 @@ function findSourceFiles3(dir, files = []) {
|
|
|
57780
58718
|
if (SKIP_DIRECTORIES3.has(entry)) {
|
|
57781
58719
|
continue;
|
|
57782
58720
|
}
|
|
57783
|
-
const fullPath =
|
|
58721
|
+
const fullPath = path43.join(dir, entry);
|
|
57784
58722
|
let stat2;
|
|
57785
58723
|
try {
|
|
57786
|
-
stat2 =
|
|
58724
|
+
stat2 = fs31.statSync(fullPath);
|
|
57787
58725
|
} catch {
|
|
57788
58726
|
continue;
|
|
57789
58727
|
}
|
|
@@ -57876,7 +58814,7 @@ var todo_extract = createSwarmTool({
|
|
|
57876
58814
|
return JSON.stringify(errorResult, null, 2);
|
|
57877
58815
|
}
|
|
57878
58816
|
const scanPath = resolvedPath;
|
|
57879
|
-
if (!
|
|
58817
|
+
if (!fs31.existsSync(scanPath)) {
|
|
57880
58818
|
const errorResult = {
|
|
57881
58819
|
error: `path not found: ${pathsInput}`,
|
|
57882
58820
|
total: 0,
|
|
@@ -57886,13 +58824,13 @@ var todo_extract = createSwarmTool({
|
|
|
57886
58824
|
return JSON.stringify(errorResult, null, 2);
|
|
57887
58825
|
}
|
|
57888
58826
|
const filesToScan = [];
|
|
57889
|
-
const stat2 =
|
|
58827
|
+
const stat2 = fs31.statSync(scanPath);
|
|
57890
58828
|
if (stat2.isFile()) {
|
|
57891
58829
|
if (isSupportedExtension(scanPath)) {
|
|
57892
58830
|
filesToScan.push(scanPath);
|
|
57893
58831
|
} else {
|
|
57894
58832
|
const errorResult = {
|
|
57895
|
-
error: `unsupported file extension: ${
|
|
58833
|
+
error: `unsupported file extension: ${path43.extname(scanPath)}`,
|
|
57896
58834
|
total: 0,
|
|
57897
58835
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
57898
58836
|
entries: []
|
|
@@ -57905,11 +58843,11 @@ var todo_extract = createSwarmTool({
|
|
|
57905
58843
|
const allEntries = [];
|
|
57906
58844
|
for (const filePath of filesToScan) {
|
|
57907
58845
|
try {
|
|
57908
|
-
const fileStat =
|
|
58846
|
+
const fileStat = fs31.statSync(filePath);
|
|
57909
58847
|
if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
|
|
57910
58848
|
continue;
|
|
57911
58849
|
}
|
|
57912
|
-
const content =
|
|
58850
|
+
const content = fs31.readFileSync(filePath, "utf-8");
|
|
57913
58851
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
57914
58852
|
allEntries.push(...entries);
|
|
57915
58853
|
} catch {}
|
|
@@ -58017,7 +58955,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
58017
58955
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
58018
58956
|
preflightTriggerManager = new PTM(automationConfig);
|
|
58019
58957
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
58020
|
-
const swarmDir =
|
|
58958
|
+
const swarmDir = path44.resolve(ctx.directory, ".swarm");
|
|
58021
58959
|
statusArtifact = new ASA(swarmDir);
|
|
58022
58960
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
58023
58961
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -58129,7 +59067,9 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
58129
59067
|
secretscan,
|
|
58130
59068
|
symbols,
|
|
58131
59069
|
test_runner,
|
|
58132
|
-
todo_extract
|
|
59070
|
+
todo_extract,
|
|
59071
|
+
update_task_status,
|
|
59072
|
+
write_retro
|
|
58133
59073
|
},
|
|
58134
59074
|
config: async (opencodeConfig) => {
|
|
58135
59075
|
if (!opencodeConfig.agent) {
|
|
@@ -58167,6 +59107,10 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
58167
59107
|
template: "/swarm evidence $ARGUMENTS",
|
|
58168
59108
|
description: "View evidence bundles and summaries"
|
|
58169
59109
|
},
|
|
59110
|
+
"swarm-handoff": {
|
|
59111
|
+
template: "/swarm handoff",
|
|
59112
|
+
description: "Prepare handoff brief for switching models mid-task"
|
|
59113
|
+
},
|
|
58170
59114
|
"swarm-archive": {
|
|
58171
59115
|
template: "/swarm archive",
|
|
58172
59116
|
description: "Archive old evidence bundles"
|