opencode-swarm 6.85.3 → 6.85.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +356 -408
- package/dist/index.js +1023 -1081
- package/dist/tools/batch-symbols.d.ts +1 -1
- package/dist/tools/build-check.d.ts +1 -1
- package/dist/tools/check-gate-status.d.ts +1 -1
- package/dist/tools/checkpoint.d.ts +1 -1
- package/dist/tools/co-change-analyzer.d.ts +1 -1
- package/dist/tools/completion-verify.d.ts +1 -1
- package/dist/tools/complexity-hotspots.d.ts +1 -1
- package/dist/tools/convene-council.d.ts +1 -1
- package/dist/tools/convene-general-council.d.ts +1 -1
- package/dist/tools/create-tool.d.ts +9 -1
- package/dist/tools/declare-council-criteria.d.ts +1 -1
- package/dist/tools/declare-scope.d.ts +1 -1
- package/dist/tools/domain-detector.d.ts +2 -2
- package/dist/tools/evidence-check.d.ts +1 -1
- package/dist/tools/file-extractor.d.ts +1 -1
- package/dist/tools/get-approved-plan.d.ts +1 -1
- package/dist/tools/knowledge-query.d.ts +1 -1
- package/dist/tools/lint.d.ts +1 -1
- package/dist/tools/pkg-audit.d.ts +1 -1
- package/dist/tools/placeholder-scan.d.ts +1 -1
- package/dist/tools/pre-check-batch.d.ts +1 -1
- package/dist/tools/quality-budget.d.ts +1 -1
- package/dist/tools/req-coverage.d.ts +1 -1
- package/dist/tools/save-plan.d.ts +1 -1
- package/dist/tools/sbom-generate.d.ts +1 -1
- package/dist/tools/schema-drift.d.ts +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/set-qa-gates.d.ts +1 -1
- package/dist/tools/suggest-patch.d.ts +1 -1
- package/dist/tools/symbols.d.ts +1 -1
- package/dist/tools/syntax-check.d.ts +1 -1
- package/dist/tools/test-runner.d.ts +1 -1
- package/dist/tools/todo-extract.d.ts +1 -1
- package/dist/tools/update-task-status.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools/write-drift-evidence.d.ts +1 -1
- package/dist/tools/write-hallucination-evidence.d.ts +1 -1
- package/dist/tools/write-mutation-evidence.d.ts +1 -1
- package/dist/tools/write-retro.d.ts +1 -1
- package/package.json +1 -1
- package/dist/knowledge/hive-promoter.d.ts +0 -23
package/dist/cli/index.js
CHANGED
|
@@ -17611,13 +17611,13 @@ __export(exports_config_doctor, {
|
|
|
17611
17611
|
import * as crypto3 from "crypto";
|
|
17612
17612
|
import * as fs9 from "fs";
|
|
17613
17613
|
import * as os5 from "os";
|
|
17614
|
-
import * as
|
|
17614
|
+
import * as path19 from "path";
|
|
17615
17615
|
function getUserConfigDir3() {
|
|
17616
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
17616
|
+
return process.env.XDG_CONFIG_HOME || path19.join(os5.homedir(), ".config");
|
|
17617
17617
|
}
|
|
17618
17618
|
function getConfigPaths(directory) {
|
|
17619
|
-
const userConfigPath =
|
|
17620
|
-
const projectConfigPath =
|
|
17619
|
+
const userConfigPath = path19.join(getUserConfigDir3(), "opencode", "opencode-swarm.json");
|
|
17620
|
+
const projectConfigPath = path19.join(directory, ".opencode", "opencode-swarm.json");
|
|
17621
17621
|
return { userConfigPath, projectConfigPath };
|
|
17622
17622
|
}
|
|
17623
17623
|
function computeHash(content) {
|
|
@@ -17642,9 +17642,9 @@ function isValidConfigPath(configPath, directory) {
|
|
|
17642
17642
|
const normalizedUser = userConfigPath.replace(/\\/g, "/");
|
|
17643
17643
|
const normalizedProject = projectConfigPath.replace(/\\/g, "/");
|
|
17644
17644
|
try {
|
|
17645
|
-
const resolvedConfig =
|
|
17646
|
-
const resolvedUser =
|
|
17647
|
-
const resolvedProject =
|
|
17645
|
+
const resolvedConfig = path19.resolve(configPath);
|
|
17646
|
+
const resolvedUser = path19.resolve(normalizedUser);
|
|
17647
|
+
const resolvedProject = path19.resolve(normalizedProject);
|
|
17648
17648
|
return resolvedConfig === resolvedUser || resolvedConfig === resolvedProject;
|
|
17649
17649
|
} catch {
|
|
17650
17650
|
return false;
|
|
@@ -17684,12 +17684,12 @@ function createConfigBackup(directory) {
|
|
|
17684
17684
|
};
|
|
17685
17685
|
}
|
|
17686
17686
|
function writeBackupArtifact(directory, backup) {
|
|
17687
|
-
const swarmDir =
|
|
17687
|
+
const swarmDir = path19.join(directory, ".swarm");
|
|
17688
17688
|
if (!fs9.existsSync(swarmDir)) {
|
|
17689
17689
|
fs9.mkdirSync(swarmDir, { recursive: true });
|
|
17690
17690
|
}
|
|
17691
17691
|
const backupFilename = `config-backup-${backup.createdAt}.json`;
|
|
17692
|
-
const backupPath =
|
|
17692
|
+
const backupPath = path19.join(swarmDir, backupFilename);
|
|
17693
17693
|
const artifact = {
|
|
17694
17694
|
createdAt: backup.createdAt,
|
|
17695
17695
|
configPath: backup.configPath,
|
|
@@ -17719,7 +17719,7 @@ function restoreFromBackup(backupPath, directory) {
|
|
|
17719
17719
|
return null;
|
|
17720
17720
|
}
|
|
17721
17721
|
const targetPath = artifact.configPath;
|
|
17722
|
-
const targetDir =
|
|
17722
|
+
const targetDir = path19.dirname(targetPath);
|
|
17723
17723
|
if (!fs9.existsSync(targetDir)) {
|
|
17724
17724
|
fs9.mkdirSync(targetDir, { recursive: true });
|
|
17725
17725
|
}
|
|
@@ -17750,9 +17750,9 @@ function readConfigFromFile(directory) {
|
|
|
17750
17750
|
return null;
|
|
17751
17751
|
}
|
|
17752
17752
|
}
|
|
17753
|
-
function validateConfigKey(
|
|
17753
|
+
function validateConfigKey(path20, value, _config) {
|
|
17754
17754
|
const findings = [];
|
|
17755
|
-
switch (
|
|
17755
|
+
switch (path20) {
|
|
17756
17756
|
case "agents": {
|
|
17757
17757
|
if (value !== undefined) {
|
|
17758
17758
|
findings.push({
|
|
@@ -17999,27 +17999,27 @@ function validateConfigKey(path19, value, _config) {
|
|
|
17999
17999
|
}
|
|
18000
18000
|
return findings;
|
|
18001
18001
|
}
|
|
18002
|
-
function walkConfigAndValidate(obj,
|
|
18002
|
+
function walkConfigAndValidate(obj, path20, config3, findings) {
|
|
18003
18003
|
if (obj === null || obj === undefined) {
|
|
18004
18004
|
return;
|
|
18005
18005
|
}
|
|
18006
|
-
if (
|
|
18007
|
-
const keyFindings = validateConfigKey(
|
|
18006
|
+
if (path20 && typeof obj === "object" && !Array.isArray(obj)) {
|
|
18007
|
+
const keyFindings = validateConfigKey(path20, obj, config3);
|
|
18008
18008
|
findings.push(...keyFindings);
|
|
18009
18009
|
}
|
|
18010
18010
|
if (typeof obj !== "object") {
|
|
18011
|
-
const keyFindings = validateConfigKey(
|
|
18011
|
+
const keyFindings = validateConfigKey(path20, obj, config3);
|
|
18012
18012
|
findings.push(...keyFindings);
|
|
18013
18013
|
return;
|
|
18014
18014
|
}
|
|
18015
18015
|
if (Array.isArray(obj)) {
|
|
18016
18016
|
obj.forEach((item, index) => {
|
|
18017
|
-
walkConfigAndValidate(item, `${
|
|
18017
|
+
walkConfigAndValidate(item, `${path20}[${index}]`, config3, findings);
|
|
18018
18018
|
});
|
|
18019
18019
|
return;
|
|
18020
18020
|
}
|
|
18021
18021
|
for (const [key, value] of Object.entries(obj)) {
|
|
18022
|
-
const newPath =
|
|
18022
|
+
const newPath = path20 ? `${path20}.${key}` : key;
|
|
18023
18023
|
walkConfigAndValidate(value, newPath, config3, findings);
|
|
18024
18024
|
}
|
|
18025
18025
|
}
|
|
@@ -18139,7 +18139,7 @@ function applySafeAutoFixes(directory, result) {
|
|
|
18139
18139
|
}
|
|
18140
18140
|
}
|
|
18141
18141
|
if (appliedFixes.length > 0) {
|
|
18142
|
-
const configDir =
|
|
18142
|
+
const configDir = path19.dirname(configPath);
|
|
18143
18143
|
if (!fs9.existsSync(configDir)) {
|
|
18144
18144
|
fs9.mkdirSync(configDir, { recursive: true });
|
|
18145
18145
|
}
|
|
@@ -18149,12 +18149,12 @@ function applySafeAutoFixes(directory, result) {
|
|
|
18149
18149
|
return { appliedFixes, updatedConfigPath };
|
|
18150
18150
|
}
|
|
18151
18151
|
function writeDoctorArtifact(directory, result) {
|
|
18152
|
-
const swarmDir =
|
|
18152
|
+
const swarmDir = path19.join(directory, ".swarm");
|
|
18153
18153
|
if (!fs9.existsSync(swarmDir)) {
|
|
18154
18154
|
fs9.mkdirSync(swarmDir, { recursive: true });
|
|
18155
18155
|
}
|
|
18156
18156
|
const artifactFilename = "config-doctor.json";
|
|
18157
|
-
const artifactPath =
|
|
18157
|
+
const artifactPath = path19.join(swarmDir, artifactFilename);
|
|
18158
18158
|
const guiOutput = {
|
|
18159
18159
|
timestamp: result.timestamp,
|
|
18160
18160
|
summary: result.summary,
|
|
@@ -18515,8 +18515,8 @@ var init_evidence_summary_service = __esm(() => {
|
|
|
18515
18515
|
});
|
|
18516
18516
|
|
|
18517
18517
|
// src/cli/index.ts
|
|
18518
|
-
import * as
|
|
18519
|
-
import * as
|
|
18518
|
+
import * as fs22 from "fs";
|
|
18519
|
+
import * as os6 from "os";
|
|
18520
18520
|
import * as path33 from "path";
|
|
18521
18521
|
|
|
18522
18522
|
// src/commands/acknowledge-spec-drift.ts
|
|
@@ -20842,6 +20842,7 @@ async function handleBrainstormCommand(_directory, args) {
|
|
|
20842
20842
|
init_zod();
|
|
20843
20843
|
|
|
20844
20844
|
// src/tools/checkpoint.ts
|
|
20845
|
+
init_zod();
|
|
20845
20846
|
import * as child_process from "child_process";
|
|
20846
20847
|
import * as fs6 from "fs";
|
|
20847
20848
|
import * as path8 from "path";
|
|
@@ -33184,7 +33185,8 @@ function createSwarmTool(opts) {
|
|
|
33184
33185
|
execute: async (args, ctx) => {
|
|
33185
33186
|
const directory = ctx?.directory ?? process.cwd();
|
|
33186
33187
|
try {
|
|
33187
|
-
|
|
33188
|
+
const result = await opts.execute(args, directory, ctx);
|
|
33189
|
+
return result;
|
|
33188
33190
|
} catch (error93) {
|
|
33189
33191
|
const message = error93 instanceof Error ? error93.message : String(error93);
|
|
33190
33192
|
return JSON.stringify({
|
|
@@ -33410,8 +33412,8 @@ function handleDelete(label, directory) {
|
|
|
33410
33412
|
var checkpoint = createSwarmTool({
|
|
33411
33413
|
description: "Save, restore, list, and delete git checkpoints. " + "Use save to create a named snapshot, restore to return to a checkpoint (soft reset), " + "list to see all checkpoints, and delete to remove a checkpoint from the log. " + "Git commits are preserved on delete.",
|
|
33412
33414
|
args: {
|
|
33413
|
-
action:
|
|
33414
|
-
label:
|
|
33415
|
+
action: exports_external.string().describe("Action to perform: save, restore, list, or delete"),
|
|
33416
|
+
label: exports_external.string().optional().describe("Checkpoint label (required for save, restore, delete)")
|
|
33415
33417
|
},
|
|
33416
33418
|
execute: async (args, directory) => {
|
|
33417
33419
|
if (!isGitRepo()) {
|
|
@@ -33485,7 +33487,8 @@ var CheckpointResultSchema = exports_external.object({
|
|
|
33485
33487
|
checkpoints: exports_external.array(exports_external.unknown()).optional()
|
|
33486
33488
|
}).passthrough();
|
|
33487
33489
|
function safeParseResult(result) {
|
|
33488
|
-
const
|
|
33490
|
+
const jsonStr = typeof result === "string" ? result : result.output;
|
|
33491
|
+
const parsed = CheckpointResultSchema.safeParse(JSON.parse(jsonStr));
|
|
33489
33492
|
if (!parsed.success) {
|
|
33490
33493
|
return {
|
|
33491
33494
|
success: false,
|
|
@@ -33671,7 +33674,7 @@ function resolveSwarmRejectedPath(directory) {
|
|
|
33671
33674
|
}
|
|
33672
33675
|
function resolveHiveKnowledgePath() {
|
|
33673
33676
|
const platform = process.platform;
|
|
33674
|
-
const home = os3.homedir();
|
|
33677
|
+
const home = process.env.HOME || os3.homedir();
|
|
33675
33678
|
let dataDir;
|
|
33676
33679
|
if (platform === "win32") {
|
|
33677
33680
|
dataDir = path9.join(process.env.LOCALAPPDATA || path9.join(home, "AppData", "Local"), "opencode-swarm", "Data");
|
|
@@ -34429,6 +34432,7 @@ async function flushPendingSnapshot(directory) {
|
|
|
34429
34432
|
}
|
|
34430
34433
|
|
|
34431
34434
|
// src/tools/write-retro.ts
|
|
34435
|
+
init_zod();
|
|
34432
34436
|
init_evidence_schema();
|
|
34433
34437
|
init_manager2();
|
|
34434
34438
|
async function executeWriteRetro(args, directory) {
|
|
@@ -34734,22 +34738,22 @@ async function executeWriteRetro(args, directory) {
|
|
|
34734
34738
|
var write_retro = createSwarmTool({
|
|
34735
34739
|
description: "Write a retrospective evidence bundle for a completed phase. " + "Accepts flat retro fields and writes a correctly-wrapped EvidenceBundle to " + ".swarm/evidence/retro-{phase}/evidence.json. " + "Use this instead of manually writing retro JSON to avoid schema validation failures in phase_complete.",
|
|
34736
34740
|
args: {
|
|
34737
|
-
phase:
|
|
34738
|
-
summary:
|
|
34739
|
-
task_count:
|
|
34740
|
-
task_complexity:
|
|
34741
|
-
total_tool_calls:
|
|
34742
|
-
coder_revisions:
|
|
34743
|
-
reviewer_rejections:
|
|
34744
|
-
loop_detections:
|
|
34745
|
-
circuit_breaker_trips:
|
|
34746
|
-
test_failures:
|
|
34747
|
-
security_findings:
|
|
34748
|
-
integration_issues:
|
|
34749
|
-
lessons_learned:
|
|
34750
|
-
top_rejection_reasons:
|
|
34751
|
-
task_id:
|
|
34752
|
-
metadata:
|
|
34741
|
+
phase: exports_external.number().int().min(1).max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
|
|
34742
|
+
summary: exports_external.string().describe("Human-readable summary of the phase"),
|
|
34743
|
+
task_count: exports_external.number().int().min(1).max(9999).describe("Count of tasks completed in this phase"),
|
|
34744
|
+
task_complexity: exports_external.enum(["trivial", "simple", "moderate", "complex"]).describe("Complexity level of the completed tasks"),
|
|
34745
|
+
total_tool_calls: exports_external.number().int().min(0).max(9999).describe("Total number of tool calls in this phase"),
|
|
34746
|
+
coder_revisions: exports_external.number().int().min(0).max(999).describe("Number of coder revisions made"),
|
|
34747
|
+
reviewer_rejections: exports_external.number().int().min(0).max(999).describe("Number of reviewer rejections received"),
|
|
34748
|
+
loop_detections: exports_external.number().int().min(0).max(9999).optional().describe("Number of loop detection events in this phase"),
|
|
34749
|
+
circuit_breaker_trips: exports_external.number().int().min(0).max(9999).optional().describe("Number of circuit breaker trips in this phase"),
|
|
34750
|
+
test_failures: exports_external.number().int().min(0).max(9999).describe("Number of test failures encountered"),
|
|
34751
|
+
security_findings: exports_external.number().int().min(0).max(999).describe("Number of security findings"),
|
|
34752
|
+
integration_issues: exports_external.number().int().min(0).max(999).describe("Number of integration issues"),
|
|
34753
|
+
lessons_learned: exports_external.array(exports_external.string()).max(5).optional().describe("Key lessons learned from this phase (max 5)"),
|
|
34754
|
+
top_rejection_reasons: exports_external.array(exports_external.string()).optional().describe("Top reasons for reviewer rejections"),
|
|
34755
|
+
task_id: exports_external.string().optional().describe("Optional custom task ID (defaults to retro-{phase})"),
|
|
34756
|
+
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Optional additional metadata")
|
|
34753
34757
|
},
|
|
34754
34758
|
execute: async (args, directory) => {
|
|
34755
34759
|
const rawPhase = args.phase !== undefined ? Number(args.phase) : 0;
|
|
@@ -35346,6 +35350,9 @@ async function handleCouncilCommand(_directory, args) {
|
|
|
35346
35350
|
return `[${tokens.join(" ")}] ${question}`;
|
|
35347
35351
|
}
|
|
35348
35352
|
|
|
35353
|
+
// src/hooks/hive-promoter.ts
|
|
35354
|
+
import path15 from "path";
|
|
35355
|
+
|
|
35349
35356
|
// src/background/event-bus.ts
|
|
35350
35357
|
init_utils();
|
|
35351
35358
|
|
|
@@ -35567,6 +35574,86 @@ async function checkHivePromotions(swarmEntries, config3) {
|
|
|
35567
35574
|
total_hive_entries: hiveEntries.length
|
|
35568
35575
|
};
|
|
35569
35576
|
}
|
|
35577
|
+
async function promoteToHive(directory, lesson, category) {
|
|
35578
|
+
const trimmedLesson = lesson.trim();
|
|
35579
|
+
const hiveEntries = await readKnowledge(resolveHiveKnowledgePath());
|
|
35580
|
+
const validationResult = validateLesson(trimmedLesson, hiveEntries.map((e) => e.lesson), {
|
|
35581
|
+
category: category || "process",
|
|
35582
|
+
scope: "global",
|
|
35583
|
+
confidence: 1
|
|
35584
|
+
});
|
|
35585
|
+
if (validationResult.severity === "error") {
|
|
35586
|
+
throw new Error(`Lesson rejected by validator: ${validationResult.reason}`);
|
|
35587
|
+
}
|
|
35588
|
+
if (findNearDuplicate(trimmedLesson, hiveEntries, 0.6)) {
|
|
35589
|
+
return `Lesson already exists in hive (near-duplicate).`;
|
|
35590
|
+
}
|
|
35591
|
+
const newHiveEntry = {
|
|
35592
|
+
id: crypto.randomUUID(),
|
|
35593
|
+
tier: "hive",
|
|
35594
|
+
lesson: trimmedLesson,
|
|
35595
|
+
category: category || "process",
|
|
35596
|
+
tags: [],
|
|
35597
|
+
scope: "global",
|
|
35598
|
+
confidence: 1,
|
|
35599
|
+
status: "promoted",
|
|
35600
|
+
confirmed_by: [],
|
|
35601
|
+
retrieval_outcomes: {
|
|
35602
|
+
applied_count: 0,
|
|
35603
|
+
succeeded_after_count: 0,
|
|
35604
|
+
failed_after_count: 0
|
|
35605
|
+
},
|
|
35606
|
+
schema_version: 1,
|
|
35607
|
+
created_at: new Date().toISOString(),
|
|
35608
|
+
updated_at: new Date().toISOString(),
|
|
35609
|
+
source_project: path15.basename(directory) || "unknown",
|
|
35610
|
+
encounter_score: 1
|
|
35611
|
+
};
|
|
35612
|
+
await appendKnowledge(resolveHiveKnowledgePath(), newHiveEntry);
|
|
35613
|
+
return `Promoted to hive: "${trimmedLesson.slice(0, 50)}${trimmedLesson.length > 50 ? "..." : ""}" (confidence: 1.0, source: manual)`;
|
|
35614
|
+
}
|
|
35615
|
+
async function promoteFromSwarm(directory, lessonId) {
|
|
35616
|
+
const swarmEntries = await readKnowledge(resolveSwarmKnowledgePath(directory));
|
|
35617
|
+
const swarmEntry = swarmEntries.find((e) => e.id === lessonId);
|
|
35618
|
+
if (!swarmEntry) {
|
|
35619
|
+
throw new Error(`Lesson ${lessonId} not found in .swarm/knowledge.jsonl`);
|
|
35620
|
+
}
|
|
35621
|
+
const hiveEntries = await readKnowledge(resolveHiveKnowledgePath());
|
|
35622
|
+
const validationResult = validateLesson(swarmEntry.lesson, hiveEntries.map((e) => e.lesson), {
|
|
35623
|
+
category: swarmEntry.category,
|
|
35624
|
+
scope: swarmEntry.scope,
|
|
35625
|
+
confidence: swarmEntry.confidence
|
|
35626
|
+
});
|
|
35627
|
+
if (validationResult.severity === "error") {
|
|
35628
|
+
throw new Error(`Lesson rejected by validator: ${validationResult.reason}`);
|
|
35629
|
+
}
|
|
35630
|
+
if (findNearDuplicate(swarmEntry.lesson, hiveEntries, 0.6)) {
|
|
35631
|
+
return `Lesson already exists in hive (near-duplicate).`;
|
|
35632
|
+
}
|
|
35633
|
+
const newHiveEntry = {
|
|
35634
|
+
id: crypto.randomUUID(),
|
|
35635
|
+
tier: "hive",
|
|
35636
|
+
lesson: swarmEntry.lesson,
|
|
35637
|
+
category: swarmEntry.category,
|
|
35638
|
+
tags: swarmEntry.tags,
|
|
35639
|
+
scope: swarmEntry.scope,
|
|
35640
|
+
confidence: 1,
|
|
35641
|
+
status: "promoted",
|
|
35642
|
+
confirmed_by: [],
|
|
35643
|
+
retrieval_outcomes: {
|
|
35644
|
+
applied_count: 0,
|
|
35645
|
+
succeeded_after_count: 0,
|
|
35646
|
+
failed_after_count: 0
|
|
35647
|
+
},
|
|
35648
|
+
schema_version: 1,
|
|
35649
|
+
created_at: new Date().toISOString(),
|
|
35650
|
+
updated_at: new Date().toISOString(),
|
|
35651
|
+
source_project: swarmEntry.project_name,
|
|
35652
|
+
encounter_score: 1
|
|
35653
|
+
};
|
|
35654
|
+
await appendKnowledge(resolveHiveKnowledgePath(), newHiveEntry);
|
|
35655
|
+
return `Promoted lesson ${lessonId} from swarm to hive: "${swarmEntry.lesson.slice(0, 50)}${swarmEntry.lesson.length > 50 ? "..." : ""}"`;
|
|
35656
|
+
}
|
|
35570
35657
|
|
|
35571
35658
|
// src/commands/curate.ts
|
|
35572
35659
|
async function handleCurateCommand(directory, _args) {
|
|
@@ -35597,13 +35684,14 @@ function formatCurationSummary(summary) {
|
|
|
35597
35684
|
}
|
|
35598
35685
|
|
|
35599
35686
|
// src/commands/dark-matter.ts
|
|
35600
|
-
import
|
|
35687
|
+
import path17 from "path";
|
|
35601
35688
|
|
|
35602
35689
|
// src/tools/co-change-analyzer.ts
|
|
35690
|
+
init_zod();
|
|
35603
35691
|
import * as child_process3 from "child_process";
|
|
35604
35692
|
import { randomUUID } from "crypto";
|
|
35605
35693
|
import { readdir, readFile as readFile3, stat } from "fs/promises";
|
|
35606
|
-
import * as
|
|
35694
|
+
import * as path16 from "path";
|
|
35607
35695
|
import { promisify } from "util";
|
|
35608
35696
|
function getExecFileAsync() {
|
|
35609
35697
|
return promisify(child_process3.execFile);
|
|
@@ -35705,7 +35793,7 @@ async function scanSourceFiles(dir) {
|
|
|
35705
35793
|
try {
|
|
35706
35794
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
35707
35795
|
for (const entry of entries) {
|
|
35708
|
-
const fullPath =
|
|
35796
|
+
const fullPath = path16.join(dir, entry.name);
|
|
35709
35797
|
if (entry.isDirectory()) {
|
|
35710
35798
|
if (skipDirs.has(entry.name)) {
|
|
35711
35799
|
continue;
|
|
@@ -35713,7 +35801,7 @@ async function scanSourceFiles(dir) {
|
|
|
35713
35801
|
const subFiles = await scanSourceFiles(fullPath);
|
|
35714
35802
|
results.push(...subFiles);
|
|
35715
35803
|
} else if (entry.isFile()) {
|
|
35716
|
-
const ext =
|
|
35804
|
+
const ext = path16.extname(entry.name);
|
|
35717
35805
|
if ([".ts", ".tsx", ".js", ".jsx", ".mjs"].includes(ext)) {
|
|
35718
35806
|
results.push(fullPath);
|
|
35719
35807
|
}
|
|
@@ -35735,8 +35823,8 @@ async function getStaticEdges(directory) {
|
|
|
35735
35823
|
continue;
|
|
35736
35824
|
}
|
|
35737
35825
|
try {
|
|
35738
|
-
const sourceDir =
|
|
35739
|
-
const resolvedPath =
|
|
35826
|
+
const sourceDir = path16.dirname(sourceFile);
|
|
35827
|
+
const resolvedPath = path16.resolve(sourceDir, importPath);
|
|
35740
35828
|
const extensions = [
|
|
35741
35829
|
"",
|
|
35742
35830
|
".ts",
|
|
@@ -35761,8 +35849,8 @@ async function getStaticEdges(directory) {
|
|
|
35761
35849
|
if (!targetFile) {
|
|
35762
35850
|
continue;
|
|
35763
35851
|
}
|
|
35764
|
-
const relSource =
|
|
35765
|
-
const relTarget =
|
|
35852
|
+
const relSource = path16.relative(directory, sourceFile).replace(/\\/g, "/");
|
|
35853
|
+
const relTarget = path16.relative(directory, targetFile).replace(/\\/g, "/");
|
|
35766
35854
|
const [key] = relSource < relTarget ? [`${relSource}::${relTarget}`, relSource, relTarget] : [`${relTarget}::${relSource}`, relTarget, relSource];
|
|
35767
35855
|
edges.add(key);
|
|
35768
35856
|
} catch {}
|
|
@@ -35774,7 +35862,7 @@ async function getStaticEdges(directory) {
|
|
|
35774
35862
|
function isTestImplementationPair(fileA, fileB) {
|
|
35775
35863
|
const testPatterns = [".test.ts", ".test.js", ".spec.ts", ".spec.js"];
|
|
35776
35864
|
const getBaseName = (filePath) => {
|
|
35777
|
-
const base =
|
|
35865
|
+
const base = path16.basename(filePath);
|
|
35778
35866
|
for (const pattern of testPatterns) {
|
|
35779
35867
|
if (base.endsWith(pattern)) {
|
|
35780
35868
|
return base.slice(0, -pattern.length);
|
|
@@ -35784,16 +35872,16 @@ function isTestImplementationPair(fileA, fileB) {
|
|
|
35784
35872
|
};
|
|
35785
35873
|
const baseA = getBaseName(fileA);
|
|
35786
35874
|
const baseB = getBaseName(fileB);
|
|
35787
|
-
return baseA === baseB && baseA !==
|
|
35875
|
+
return baseA === baseB && baseA !== path16.basename(fileA) && baseA !== path16.basename(fileB);
|
|
35788
35876
|
}
|
|
35789
35877
|
function hasSharedPrefix(fileA, fileB) {
|
|
35790
|
-
const dirA =
|
|
35791
|
-
const dirB =
|
|
35878
|
+
const dirA = path16.dirname(fileA);
|
|
35879
|
+
const dirB = path16.dirname(fileB);
|
|
35792
35880
|
if (dirA !== dirB) {
|
|
35793
35881
|
return false;
|
|
35794
35882
|
}
|
|
35795
|
-
const baseA =
|
|
35796
|
-
const baseB =
|
|
35883
|
+
const baseA = path16.basename(fileA).replace(/\.(ts|js|tsx|jsx|mjs)$/, "");
|
|
35884
|
+
const baseB = path16.basename(fileB).replace(/\.(ts|js|tsx|jsx|mjs)$/, "");
|
|
35797
35885
|
if (baseA.startsWith(baseB) || baseB.startsWith(baseA)) {
|
|
35798
35886
|
return true;
|
|
35799
35887
|
}
|
|
@@ -35847,8 +35935,8 @@ function darkMatterToKnowledgeEntries(pairs, projectName) {
|
|
|
35847
35935
|
const entries = [];
|
|
35848
35936
|
const now = new Date().toISOString();
|
|
35849
35937
|
for (const pair of pairs.slice(0, 10)) {
|
|
35850
|
-
const baseA =
|
|
35851
|
-
const baseB =
|
|
35938
|
+
const baseA = path16.basename(pair.fileA);
|
|
35939
|
+
const baseB = path16.basename(pair.fileB);
|
|
35852
35940
|
let lesson = `Files ${pair.fileA} and ${pair.fileB} co-change with NPMI=${pair.npmi.toFixed(3)} but have no import relationship. This hidden coupling suggests a shared architectural concern \u2014 changes to one likely require changes to the other.`;
|
|
35853
35941
|
if (lesson.length > 280) {
|
|
35854
35942
|
lesson = `Files ${baseA} and ${baseB} co-change with NPMI=${pair.npmi.toFixed(3)} but have no import relationship. This hidden coupling suggests a shared architectural concern \u2014 changes to one likely require changes to the other.`;
|
|
@@ -35906,10 +35994,10 @@ Consider adding explicit documentation or extracting the shared concern.`;
|
|
|
35906
35994
|
var co_change_analyzer = createSwarmTool({
|
|
35907
35995
|
description: "Detects hidden couplings (dark matter) by analyzing git history to find file pairs that frequently co-change but have no import relationship. Useful for identifying architectural concerns that are not explicitly documented.",
|
|
35908
35996
|
args: {
|
|
35909
|
-
min_commits:
|
|
35910
|
-
min_co_changes:
|
|
35911
|
-
threshold:
|
|
35912
|
-
max_commits:
|
|
35997
|
+
min_commits: exports_external.number().optional().describe("Minimum commit count to analyze (default: 20)"),
|
|
35998
|
+
min_co_changes: exports_external.number().optional().describe("Minimum co-change count to consider (default: 3)"),
|
|
35999
|
+
threshold: exports_external.number().optional().describe("NPMI threshold for filtering (default: 0.5)"),
|
|
36000
|
+
max_commits: exports_external.number().optional().describe("Maximum commits to analyze (default: 500)")
|
|
35913
36001
|
},
|
|
35914
36002
|
async execute(args, directory) {
|
|
35915
36003
|
let minCommits;
|
|
@@ -35958,7 +36046,7 @@ async function handleDarkMatterCommand(directory, args) {
|
|
|
35958
36046
|
const output = formatDarkMatterOutput(pairs);
|
|
35959
36047
|
if (pairs.length > 0) {
|
|
35960
36048
|
try {
|
|
35961
|
-
const projectName =
|
|
36049
|
+
const projectName = path17.basename(path17.resolve(directory));
|
|
35962
36050
|
const entries = darkMatterToKnowledgeEntries(pairs, projectName);
|
|
35963
36051
|
if (entries.length > 0) {
|
|
35964
36052
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
@@ -35980,7 +36068,7 @@ async function handleDarkMatterCommand(directory, args) {
|
|
|
35980
36068
|
// src/services/diagnose-service.ts
|
|
35981
36069
|
import * as child_process4 from "child_process";
|
|
35982
36070
|
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
|
|
35983
|
-
import
|
|
36071
|
+
import path18 from "path";
|
|
35984
36072
|
import { fileURLToPath } from "url";
|
|
35985
36073
|
init_manager2();
|
|
35986
36074
|
init_utils2();
|
|
@@ -36280,7 +36368,7 @@ async function checkSpecStaleness(directory, plan) {
|
|
|
36280
36368
|
};
|
|
36281
36369
|
}
|
|
36282
36370
|
async function checkConfigParseability(directory) {
|
|
36283
|
-
const configPath =
|
|
36371
|
+
const configPath = path18.join(directory, ".opencode/opencode-swarm.json");
|
|
36284
36372
|
if (!existsSync8(configPath)) {
|
|
36285
36373
|
return {
|
|
36286
36374
|
name: "Config Parseability",
|
|
@@ -36309,7 +36397,7 @@ function resolveGrammarDir(thisDir) {
|
|
|
36309
36397
|
const normalized = thisDir.replace(/\\/g, "/");
|
|
36310
36398
|
const isSource = normalized.endsWith("/src/services");
|
|
36311
36399
|
const isCliBundle = normalized.endsWith("/cli");
|
|
36312
|
-
return isSource || isCliBundle ?
|
|
36400
|
+
return isSource || isCliBundle ? path18.join(thisDir, "..", "lang", "grammars") : path18.join(thisDir, "lang", "grammars");
|
|
36313
36401
|
}
|
|
36314
36402
|
async function checkGrammarWasmFiles() {
|
|
36315
36403
|
const grammarFiles = [
|
|
@@ -36333,14 +36421,14 @@ async function checkGrammarWasmFiles() {
|
|
|
36333
36421
|
"tree-sitter-ini.wasm",
|
|
36334
36422
|
"tree-sitter-regex.wasm"
|
|
36335
36423
|
];
|
|
36336
|
-
const thisDir =
|
|
36424
|
+
const thisDir = path18.dirname(fileURLToPath(import.meta.url));
|
|
36337
36425
|
const grammarDir = resolveGrammarDir(thisDir);
|
|
36338
36426
|
const missing = [];
|
|
36339
|
-
if (!existsSync8(
|
|
36427
|
+
if (!existsSync8(path18.join(grammarDir, "tree-sitter.wasm"))) {
|
|
36340
36428
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
36341
36429
|
}
|
|
36342
36430
|
for (const file3 of grammarFiles) {
|
|
36343
|
-
if (!existsSync8(
|
|
36431
|
+
if (!existsSync8(path18.join(grammarDir, file3))) {
|
|
36344
36432
|
missing.push(file3);
|
|
36345
36433
|
}
|
|
36346
36434
|
}
|
|
@@ -36358,7 +36446,7 @@ async function checkGrammarWasmFiles() {
|
|
|
36358
36446
|
};
|
|
36359
36447
|
}
|
|
36360
36448
|
async function checkCheckpointManifest(directory) {
|
|
36361
|
-
const manifestPath =
|
|
36449
|
+
const manifestPath = path18.join(directory, ".swarm/checkpoints.json");
|
|
36362
36450
|
if (!existsSync8(manifestPath)) {
|
|
36363
36451
|
return {
|
|
36364
36452
|
name: "Checkpoint Manifest",
|
|
@@ -36410,7 +36498,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
36410
36498
|
}
|
|
36411
36499
|
}
|
|
36412
36500
|
async function checkEventStreamIntegrity(directory) {
|
|
36413
|
-
const eventsPath =
|
|
36501
|
+
const eventsPath = path18.join(directory, ".swarm/events.jsonl");
|
|
36414
36502
|
if (!existsSync8(eventsPath)) {
|
|
36415
36503
|
return {
|
|
36416
36504
|
name: "Event Stream",
|
|
@@ -36451,7 +36539,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
36451
36539
|
}
|
|
36452
36540
|
}
|
|
36453
36541
|
async function checkSteeringDirectives(directory) {
|
|
36454
|
-
const eventsPath =
|
|
36542
|
+
const eventsPath = path18.join(directory, ".swarm/events.jsonl");
|
|
36455
36543
|
if (!existsSync8(eventsPath)) {
|
|
36456
36544
|
return {
|
|
36457
36545
|
name: "Steering Directives",
|
|
@@ -36507,7 +36595,7 @@ async function checkCurator(directory) {
|
|
|
36507
36595
|
detail: "Disabled (enable via curator.enabled)"
|
|
36508
36596
|
};
|
|
36509
36597
|
}
|
|
36510
|
-
const summaryPath =
|
|
36598
|
+
const summaryPath = path18.join(directory, ".swarm/curator-summary.json");
|
|
36511
36599
|
if (!existsSync8(summaryPath)) {
|
|
36512
36600
|
return {
|
|
36513
36601
|
name: "Curator",
|
|
@@ -36655,7 +36743,7 @@ async function getDiagnoseData(directory) {
|
|
|
36655
36743
|
checks5.push(await checkSteeringDirectives(directory));
|
|
36656
36744
|
checks5.push(await checkCurator(directory));
|
|
36657
36745
|
try {
|
|
36658
|
-
const evidenceDir =
|
|
36746
|
+
const evidenceDir = path18.join(directory, ".swarm", "evidence");
|
|
36659
36747
|
const snapshotFiles = existsSync8(evidenceDir) ? readdirSync4(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
|
|
36660
36748
|
if (snapshotFiles.length > 0) {
|
|
36661
36749
|
const latest = snapshotFiles.sort().pop();
|
|
@@ -36708,11 +36796,11 @@ init_config_doctor();
|
|
|
36708
36796
|
|
|
36709
36797
|
// src/services/tool-doctor.ts
|
|
36710
36798
|
import * as fs11 from "fs";
|
|
36711
|
-
import * as
|
|
36799
|
+
import * as path21 from "path";
|
|
36712
36800
|
|
|
36713
36801
|
// src/build/discovery.ts
|
|
36714
36802
|
import * as fs10 from "fs";
|
|
36715
|
-
import * as
|
|
36803
|
+
import * as path20 from "path";
|
|
36716
36804
|
|
|
36717
36805
|
// src/lang/detector.ts
|
|
36718
36806
|
import { access as access2, readdir as readdir2 } from "fs/promises";
|
|
@@ -37876,11 +37964,11 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37876
37964
|
const regex = simpleGlobToRegex(pattern);
|
|
37877
37965
|
const matches = files.filter((f) => regex.test(f));
|
|
37878
37966
|
if (matches.length > 0) {
|
|
37879
|
-
return
|
|
37967
|
+
return path20.join(dir, matches[0]);
|
|
37880
37968
|
}
|
|
37881
37969
|
} catch {}
|
|
37882
37970
|
} else {
|
|
37883
|
-
const filePath =
|
|
37971
|
+
const filePath = path20.join(workingDir, pattern);
|
|
37884
37972
|
if (fs10.existsSync(filePath)) {
|
|
37885
37973
|
return filePath;
|
|
37886
37974
|
}
|
|
@@ -37889,7 +37977,7 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37889
37977
|
return null;
|
|
37890
37978
|
}
|
|
37891
37979
|
function getRepoDefinedScripts(workingDir, scripts) {
|
|
37892
|
-
const packageJsonPath =
|
|
37980
|
+
const packageJsonPath = path20.join(workingDir, "package.json");
|
|
37893
37981
|
if (!fs10.existsSync(packageJsonPath)) {
|
|
37894
37982
|
return [];
|
|
37895
37983
|
}
|
|
@@ -37930,7 +38018,7 @@ function findAllBuildFiles(workingDir) {
|
|
|
37930
38018
|
const regex = simpleGlobToRegex(pattern);
|
|
37931
38019
|
findFilesRecursive(workingDir, regex, allBuildFiles);
|
|
37932
38020
|
} else {
|
|
37933
|
-
const filePath =
|
|
38021
|
+
const filePath = path20.join(workingDir, pattern);
|
|
37934
38022
|
if (fs10.existsSync(filePath)) {
|
|
37935
38023
|
allBuildFiles.add(filePath);
|
|
37936
38024
|
}
|
|
@@ -37943,7 +38031,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
37943
38031
|
try {
|
|
37944
38032
|
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
37945
38033
|
for (const entry of entries) {
|
|
37946
|
-
const fullPath =
|
|
38034
|
+
const fullPath = path20.join(dir, entry.name);
|
|
37947
38035
|
if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
|
|
37948
38036
|
findFilesRecursive(fullPath, regex, results);
|
|
37949
38037
|
} else if (entry.isFile() && regex.test(entry.name)) {
|
|
@@ -37966,7 +38054,7 @@ async function discoverBuildCommandsFromProfiles(workingDir) {
|
|
|
37966
38054
|
let foundCommand = false;
|
|
37967
38055
|
for (const cmd of sortedCommands) {
|
|
37968
38056
|
if (cmd.detectFile) {
|
|
37969
|
-
const detectFilePath =
|
|
38057
|
+
const detectFilePath = path20.join(workingDir, cmd.detectFile);
|
|
37970
38058
|
if (!fs10.existsSync(detectFilePath)) {
|
|
37971
38059
|
continue;
|
|
37972
38060
|
}
|
|
@@ -38141,8 +38229,8 @@ function checkBinaryReadiness() {
|
|
|
38141
38229
|
}
|
|
38142
38230
|
function runToolDoctor(_directory, pluginRoot) {
|
|
38143
38231
|
const findings = [];
|
|
38144
|
-
const resolvedPluginRoot = pluginRoot ??
|
|
38145
|
-
const indexPath =
|
|
38232
|
+
const resolvedPluginRoot = pluginRoot ?? path21.resolve(import.meta.dir, "..", "..");
|
|
38233
|
+
const indexPath = path21.join(resolvedPluginRoot, "src", "index.ts");
|
|
38146
38234
|
if (!fs11.existsSync(indexPath)) {
|
|
38147
38235
|
return {
|
|
38148
38236
|
findings: [
|
|
@@ -39065,10 +39153,10 @@ async function handleHistoryCommand(directory, _args) {
|
|
|
39065
39153
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
39066
39154
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
39067
39155
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
39068
|
-
import * as
|
|
39156
|
+
import * as path22 from "path";
|
|
39069
39157
|
async function migrateContextToKnowledge(directory, config3) {
|
|
39070
|
-
const sentinelPath =
|
|
39071
|
-
const contextPath =
|
|
39158
|
+
const sentinelPath = path22.join(directory, ".swarm", ".knowledge-migrated");
|
|
39159
|
+
const contextPath = path22.join(directory, ".swarm", "context.md");
|
|
39072
39160
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
39073
39161
|
if (existsSync12(sentinelPath)) {
|
|
39074
39162
|
return {
|
|
@@ -39264,7 +39352,7 @@ function truncateLesson(text) {
|
|
|
39264
39352
|
return `${text.slice(0, 277)}...`;
|
|
39265
39353
|
}
|
|
39266
39354
|
function inferProjectName(directory) {
|
|
39267
|
-
const packageJsonPath =
|
|
39355
|
+
const packageJsonPath = path22.join(directory, "package.json");
|
|
39268
39356
|
if (existsSync12(packageJsonPath)) {
|
|
39269
39357
|
try {
|
|
39270
39358
|
const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
@@ -39273,7 +39361,7 @@ function inferProjectName(directory) {
|
|
|
39273
39361
|
}
|
|
39274
39362
|
} catch {}
|
|
39275
39363
|
}
|
|
39276
|
-
return
|
|
39364
|
+
return path22.basename(directory);
|
|
39277
39365
|
}
|
|
39278
39366
|
async function writeSentinel(sentinelPath, migrated, dropped) {
|
|
39279
39367
|
const sentinel = {
|
|
@@ -39285,7 +39373,7 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
|
|
|
39285
39373
|
schema_version: 1,
|
|
39286
39374
|
migration_tool: "knowledge-migrator.ts"
|
|
39287
39375
|
};
|
|
39288
|
-
await mkdir3(
|
|
39376
|
+
await mkdir3(path22.dirname(sentinelPath), { recursive: true });
|
|
39289
39377
|
await writeFile4(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
|
|
39290
39378
|
}
|
|
39291
39379
|
|
|
@@ -39522,11 +39610,12 @@ async function handlePlanCommand(directory, args) {
|
|
|
39522
39610
|
init_manager2();
|
|
39523
39611
|
init_manager();
|
|
39524
39612
|
import * as fs18 from "fs";
|
|
39525
|
-
import * as
|
|
39613
|
+
import * as path29 from "path";
|
|
39526
39614
|
|
|
39527
39615
|
// src/tools/lint.ts
|
|
39616
|
+
init_zod();
|
|
39528
39617
|
import * as fs12 from "fs";
|
|
39529
|
-
import * as
|
|
39618
|
+
import * as path23 from "path";
|
|
39530
39619
|
init_utils();
|
|
39531
39620
|
|
|
39532
39621
|
// src/utils/path-security.ts
|
|
@@ -39572,9 +39661,9 @@ function validateArgs(args) {
|
|
|
39572
39661
|
}
|
|
39573
39662
|
function getLinterCommand(linter, mode, projectDir) {
|
|
39574
39663
|
const isWindows = process.platform === "win32";
|
|
39575
|
-
const binDir =
|
|
39576
|
-
const biomeBin = isWindows ?
|
|
39577
|
-
const eslintBin = isWindows ?
|
|
39664
|
+
const binDir = path23.join(projectDir, "node_modules", ".bin");
|
|
39665
|
+
const biomeBin = isWindows ? path23.join(binDir, "biome.EXE") : path23.join(binDir, "biome");
|
|
39666
|
+
const eslintBin = isWindows ? path23.join(binDir, "eslint.cmd") : path23.join(binDir, "eslint");
|
|
39578
39667
|
switch (linter) {
|
|
39579
39668
|
case "biome":
|
|
39580
39669
|
if (mode === "fix") {
|
|
@@ -39590,7 +39679,7 @@ function getLinterCommand(linter, mode, projectDir) {
|
|
|
39590
39679
|
}
|
|
39591
39680
|
function getAdditionalLinterCommand(linter, mode, cwd) {
|
|
39592
39681
|
const gradlewName = process.platform === "win32" ? "gradlew.bat" : "gradlew";
|
|
39593
|
-
const gradlew = fs12.existsSync(
|
|
39682
|
+
const gradlew = fs12.existsSync(path23.join(cwd, gradlewName)) ? path23.join(cwd, gradlewName) : null;
|
|
39594
39683
|
switch (linter) {
|
|
39595
39684
|
case "ruff":
|
|
39596
39685
|
return mode === "fix" ? ["ruff", "check", "--fix", "."] : ["ruff", "check", "."];
|
|
@@ -39624,10 +39713,10 @@ function getAdditionalLinterCommand(linter, mode, cwd) {
|
|
|
39624
39713
|
}
|
|
39625
39714
|
}
|
|
39626
39715
|
function detectRuff(cwd) {
|
|
39627
|
-
if (fs12.existsSync(
|
|
39716
|
+
if (fs12.existsSync(path23.join(cwd, "ruff.toml")))
|
|
39628
39717
|
return isCommandAvailable("ruff");
|
|
39629
39718
|
try {
|
|
39630
|
-
const pyproject =
|
|
39719
|
+
const pyproject = path23.join(cwd, "pyproject.toml");
|
|
39631
39720
|
if (fs12.existsSync(pyproject)) {
|
|
39632
39721
|
const content = fs12.readFileSync(pyproject, "utf-8");
|
|
39633
39722
|
if (content.includes("[tool.ruff]"))
|
|
@@ -39637,19 +39726,19 @@ function detectRuff(cwd) {
|
|
|
39637
39726
|
return false;
|
|
39638
39727
|
}
|
|
39639
39728
|
function detectClippy(cwd) {
|
|
39640
|
-
return fs12.existsSync(
|
|
39729
|
+
return fs12.existsSync(path23.join(cwd, "Cargo.toml")) && isCommandAvailable("cargo");
|
|
39641
39730
|
}
|
|
39642
39731
|
function detectGolangciLint(cwd) {
|
|
39643
|
-
return fs12.existsSync(
|
|
39732
|
+
return fs12.existsSync(path23.join(cwd, "go.mod")) && isCommandAvailable("golangci-lint");
|
|
39644
39733
|
}
|
|
39645
39734
|
function detectCheckstyle(cwd) {
|
|
39646
|
-
const hasMaven = fs12.existsSync(
|
|
39647
|
-
const hasGradle = fs12.existsSync(
|
|
39648
|
-
const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs12.existsSync(
|
|
39735
|
+
const hasMaven = fs12.existsSync(path23.join(cwd, "pom.xml"));
|
|
39736
|
+
const hasGradle = fs12.existsSync(path23.join(cwd, "build.gradle")) || fs12.existsSync(path23.join(cwd, "build.gradle.kts"));
|
|
39737
|
+
const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs12.existsSync(path23.join(cwd, "gradlew")) || isCommandAvailable("gradle"));
|
|
39649
39738
|
return (hasMaven || hasGradle) && hasBinary;
|
|
39650
39739
|
}
|
|
39651
39740
|
function detectKtlint(cwd) {
|
|
39652
|
-
const hasKotlin = fs12.existsSync(
|
|
39741
|
+
const hasKotlin = fs12.existsSync(path23.join(cwd, "build.gradle.kts")) || fs12.existsSync(path23.join(cwd, "build.gradle")) || (() => {
|
|
39653
39742
|
try {
|
|
39654
39743
|
return fs12.readdirSync(cwd).some((f) => f.endsWith(".kt") || f.endsWith(".kts"));
|
|
39655
39744
|
} catch {
|
|
@@ -39668,11 +39757,11 @@ function detectDotnetFormat(cwd) {
|
|
|
39668
39757
|
}
|
|
39669
39758
|
}
|
|
39670
39759
|
function detectCppcheck(cwd) {
|
|
39671
|
-
if (fs12.existsSync(
|
|
39760
|
+
if (fs12.existsSync(path23.join(cwd, "CMakeLists.txt"))) {
|
|
39672
39761
|
return isCommandAvailable("cppcheck");
|
|
39673
39762
|
}
|
|
39674
39763
|
try {
|
|
39675
|
-
const dirsToCheck = [cwd,
|
|
39764
|
+
const dirsToCheck = [cwd, path23.join(cwd, "src")];
|
|
39676
39765
|
const hasCpp = dirsToCheck.some((dir) => {
|
|
39677
39766
|
try {
|
|
39678
39767
|
return fs12.readdirSync(dir).some((f) => /\.(c|cpp|cc|cxx|h|hpp)$/.test(f));
|
|
@@ -39686,13 +39775,13 @@ function detectCppcheck(cwd) {
|
|
|
39686
39775
|
}
|
|
39687
39776
|
}
|
|
39688
39777
|
function detectSwiftlint(cwd) {
|
|
39689
|
-
return fs12.existsSync(
|
|
39778
|
+
return fs12.existsSync(path23.join(cwd, "Package.swift")) && isCommandAvailable("swiftlint");
|
|
39690
39779
|
}
|
|
39691
39780
|
function detectDartAnalyze(cwd) {
|
|
39692
|
-
return fs12.existsSync(
|
|
39781
|
+
return fs12.existsSync(path23.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
39693
39782
|
}
|
|
39694
39783
|
function detectRubocop(cwd) {
|
|
39695
|
-
return (fs12.existsSync(
|
|
39784
|
+
return (fs12.existsSync(path23.join(cwd, "Gemfile")) || fs12.existsSync(path23.join(cwd, "gems.rb")) || fs12.existsSync(path23.join(cwd, ".rubocop.yml"))) && (isCommandAvailable("rubocop") || isCommandAvailable("bundle"));
|
|
39696
39785
|
}
|
|
39697
39786
|
function detectAdditionalLinter(cwd) {
|
|
39698
39787
|
if (detectRuff(cwd))
|
|
@@ -39720,10 +39809,10 @@ function detectAdditionalLinter(cwd) {
|
|
|
39720
39809
|
function findBinInAncestors(startDir, binName) {
|
|
39721
39810
|
let dir = startDir;
|
|
39722
39811
|
while (true) {
|
|
39723
|
-
const candidate =
|
|
39812
|
+
const candidate = path23.join(dir, "node_modules", ".bin", binName);
|
|
39724
39813
|
if (fs12.existsSync(candidate))
|
|
39725
39814
|
return candidate;
|
|
39726
|
-
const parent =
|
|
39815
|
+
const parent = path23.dirname(dir);
|
|
39727
39816
|
if (parent === dir)
|
|
39728
39817
|
break;
|
|
39729
39818
|
dir = parent;
|
|
@@ -39732,10 +39821,10 @@ function findBinInAncestors(startDir, binName) {
|
|
|
39732
39821
|
}
|
|
39733
39822
|
function findBinInEnvPath(binName) {
|
|
39734
39823
|
const searchPath = process.env.PATH ?? "";
|
|
39735
|
-
for (const dir of searchPath.split(
|
|
39824
|
+
for (const dir of searchPath.split(path23.delimiter)) {
|
|
39736
39825
|
if (!dir)
|
|
39737
39826
|
continue;
|
|
39738
|
-
const candidate =
|
|
39827
|
+
const candidate = path23.join(dir, binName);
|
|
39739
39828
|
if (fs12.existsSync(candidate))
|
|
39740
39829
|
return candidate;
|
|
39741
39830
|
}
|
|
@@ -39748,13 +39837,13 @@ async function detectAvailableLinter(directory) {
|
|
|
39748
39837
|
return null;
|
|
39749
39838
|
const projectDir = directory;
|
|
39750
39839
|
const isWindows = process.platform === "win32";
|
|
39751
|
-
const biomeBin = isWindows ?
|
|
39752
|
-
const eslintBin = isWindows ?
|
|
39840
|
+
const biomeBin = isWindows ? path23.join(projectDir, "node_modules", ".bin", "biome.EXE") : path23.join(projectDir, "node_modules", ".bin", "biome");
|
|
39841
|
+
const eslintBin = isWindows ? path23.join(projectDir, "node_modules", ".bin", "eslint.cmd") : path23.join(projectDir, "node_modules", ".bin", "eslint");
|
|
39753
39842
|
const localResult = await _detectAvailableLinter(projectDir, biomeBin, eslintBin);
|
|
39754
39843
|
if (localResult)
|
|
39755
39844
|
return localResult;
|
|
39756
|
-
const biomeAncestor = findBinInAncestors(
|
|
39757
|
-
const eslintAncestor = findBinInAncestors(
|
|
39845
|
+
const biomeAncestor = findBinInAncestors(path23.dirname(projectDir), isWindows ? "biome.EXE" : "biome");
|
|
39846
|
+
const eslintAncestor = findBinInAncestors(path23.dirname(projectDir), isWindows ? "eslint.cmd" : "eslint");
|
|
39758
39847
|
if (biomeAncestor || eslintAncestor) {
|
|
39759
39848
|
return _detectAvailableLinter(projectDir, biomeAncestor ?? biomeBin, eslintAncestor ?? eslintBin);
|
|
39760
39849
|
}
|
|
@@ -39916,7 +40005,7 @@ async function runAdditionalLint(linter, mode, cwd) {
|
|
|
39916
40005
|
var lint = createSwarmTool({
|
|
39917
40006
|
description: "Run project linter in check or fix mode. Supports biome, eslint (JS/TS), ruff (Python), clippy (Rust), golangci-lint (Go), checkstyle (Java), ktlint (Kotlin), dotnet-format (C#), cppcheck (C/C++), swiftlint (Swift), dart analyze (Dart), and rubocop (Ruby). Returns JSON with success status, exit code, and output for architect pre-reviewer gate. Use check mode for CI/linting and fix mode to automatically apply fixes.",
|
|
39918
40007
|
args: {
|
|
39919
|
-
mode:
|
|
40008
|
+
mode: exports_external.enum(["fix", "check"]).describe('Linting mode: "check" for read-only lint check, "fix" to automatically apply fixes')
|
|
39920
40009
|
},
|
|
39921
40010
|
async execute(args, directory) {
|
|
39922
40011
|
if (!validateArgs(args)) {
|
|
@@ -39961,8 +40050,9 @@ For Rust: rustup component add clippy`
|
|
|
39961
40050
|
});
|
|
39962
40051
|
|
|
39963
40052
|
// src/tools/secretscan.ts
|
|
40053
|
+
init_zod();
|
|
39964
40054
|
import * as fs13 from "fs";
|
|
39965
|
-
import * as
|
|
40055
|
+
import * as path24 from "path";
|
|
39966
40056
|
var MAX_FILE_PATH_LENGTH = 500;
|
|
39967
40057
|
var MAX_FILE_SIZE_BYTES = 512 * 1024;
|
|
39968
40058
|
var MAX_FILES_SCANNED = 1000;
|
|
@@ -40189,7 +40279,7 @@ function isGlobOrPathPattern(pattern) {
|
|
|
40189
40279
|
return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
|
|
40190
40280
|
}
|
|
40191
40281
|
function loadSecretScanIgnore(scanDir) {
|
|
40192
|
-
const ignorePath =
|
|
40282
|
+
const ignorePath = path24.join(scanDir, ".secretscanignore");
|
|
40193
40283
|
try {
|
|
40194
40284
|
if (!fs13.existsSync(ignorePath))
|
|
40195
40285
|
return [];
|
|
@@ -40212,7 +40302,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
|
|
|
40212
40302
|
if (exactNames.has(entry))
|
|
40213
40303
|
return true;
|
|
40214
40304
|
for (const pattern of globPatterns) {
|
|
40215
|
-
if (
|
|
40305
|
+
if (path24.matchesGlob(relPath, pattern))
|
|
40216
40306
|
return true;
|
|
40217
40307
|
}
|
|
40218
40308
|
return false;
|
|
@@ -40233,7 +40323,7 @@ function validateDirectoryInput(dir) {
|
|
|
40233
40323
|
return null;
|
|
40234
40324
|
}
|
|
40235
40325
|
function isBinaryFile(filePath, buffer) {
|
|
40236
|
-
const ext =
|
|
40326
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
40237
40327
|
if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
40238
40328
|
return true;
|
|
40239
40329
|
}
|
|
@@ -40370,9 +40460,9 @@ function isSymlinkLoop(realPath, visited) {
|
|
|
40370
40460
|
return false;
|
|
40371
40461
|
}
|
|
40372
40462
|
function isPathWithinScope(realPath, scanDir) {
|
|
40373
|
-
const resolvedScanDir =
|
|
40374
|
-
const resolvedRealPath =
|
|
40375
|
-
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir +
|
|
40463
|
+
const resolvedScanDir = path24.resolve(scanDir);
|
|
40464
|
+
const resolvedRealPath = path24.resolve(realPath);
|
|
40465
|
+
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path24.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
|
|
40376
40466
|
}
|
|
40377
40467
|
function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
|
|
40378
40468
|
skippedDirs: 0,
|
|
@@ -40398,8 +40488,8 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
|
|
|
40398
40488
|
return a.localeCompare(b);
|
|
40399
40489
|
});
|
|
40400
40490
|
for (const entry of entries) {
|
|
40401
|
-
const fullPath =
|
|
40402
|
-
const relPath =
|
|
40491
|
+
const fullPath = path24.join(dir, entry);
|
|
40492
|
+
const relPath = path24.relative(scanDir, fullPath).replace(/\\/g, "/");
|
|
40403
40493
|
if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
|
|
40404
40494
|
stats.skippedDirs++;
|
|
40405
40495
|
continue;
|
|
@@ -40434,7 +40524,7 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
|
|
|
40434
40524
|
const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
|
|
40435
40525
|
files.push(...subFiles);
|
|
40436
40526
|
} else if (lstat.isFile()) {
|
|
40437
|
-
const ext =
|
|
40527
|
+
const ext = path24.extname(fullPath).toLowerCase();
|
|
40438
40528
|
if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
40439
40529
|
files.push(fullPath);
|
|
40440
40530
|
} else {
|
|
@@ -40447,8 +40537,8 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
|
|
|
40447
40537
|
var secretscan = createSwarmTool({
|
|
40448
40538
|
description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default. Supports glob patterns (e.g. **/.svelte-kit/**, **/*.test.ts) and reads .secretscanignore at the scan root.",
|
|
40449
40539
|
args: {
|
|
40450
|
-
directory:
|
|
40451
|
-
exclude:
|
|
40540
|
+
directory: exports_external.string().describe('Directory to scan for secrets (e.g., "." or "./src")'),
|
|
40541
|
+
exclude: exports_external.array(exports_external.string()).optional().describe("Patterns to exclude: plain directory names (e.g. node_modules), relative paths, or globs (e.g. **/.svelte-kit/**, **/*.test.ts). Added to default exclusions.")
|
|
40452
40542
|
},
|
|
40453
40543
|
async execute(args, _directory, _ctx) {
|
|
40454
40544
|
const typedArgs = args;
|
|
@@ -40500,7 +40590,7 @@ var secretscan = createSwarmTool({
|
|
|
40500
40590
|
}
|
|
40501
40591
|
}
|
|
40502
40592
|
try {
|
|
40503
|
-
const _scanDirRaw =
|
|
40593
|
+
const _scanDirRaw = path24.resolve(directory);
|
|
40504
40594
|
const scanDir = (() => {
|
|
40505
40595
|
try {
|
|
40506
40596
|
return fs13.realpathSync(_scanDirRaw);
|
|
@@ -40642,7 +40732,8 @@ var secretscan = createSwarmTool({
|
|
|
40642
40732
|
async function runSecretscan(directory) {
|
|
40643
40733
|
try {
|
|
40644
40734
|
const result = await secretscan.execute({ directory }, {});
|
|
40645
|
-
|
|
40735
|
+
const jsonStr = typeof result === "string" ? result : result.output;
|
|
40736
|
+
return JSON.parse(jsonStr);
|
|
40646
40737
|
} catch (e) {
|
|
40647
40738
|
const errorResult = {
|
|
40648
40739
|
error: e instanceof Error ? `scan failed: ${e.message}` : "scan failed: unknown error",
|
|
@@ -40657,12 +40748,13 @@ async function runSecretscan(directory) {
|
|
|
40657
40748
|
}
|
|
40658
40749
|
|
|
40659
40750
|
// src/tools/test-runner.ts
|
|
40751
|
+
init_zod();
|
|
40660
40752
|
import * as fs17 from "fs";
|
|
40661
|
-
import * as
|
|
40753
|
+
import * as path28 from "path";
|
|
40662
40754
|
|
|
40663
40755
|
// src/test-impact/analyzer.ts
|
|
40664
40756
|
import fs14 from "fs";
|
|
40665
|
-
import
|
|
40757
|
+
import path25 from "path";
|
|
40666
40758
|
var IMPORT_REGEX_ES = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
40667
40759
|
var IMPORT_REGEX_REQUIRE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
40668
40760
|
var IMPORT_REGEX_REEXPORT = /export\s+(?:\{[^}]*\}|\*)\s+from\s+['"]([^'"]+)['"]/g;
|
|
@@ -40687,8 +40779,8 @@ function resolveRelativeImport(fromDir, importPath) {
|
|
|
40687
40779
|
if (!importPath.startsWith(".")) {
|
|
40688
40780
|
return null;
|
|
40689
40781
|
}
|
|
40690
|
-
const resolved =
|
|
40691
|
-
if (
|
|
40782
|
+
const resolved = path25.resolve(fromDir, importPath);
|
|
40783
|
+
if (path25.extname(resolved)) {
|
|
40692
40784
|
if (fs14.existsSync(resolved) && fs14.statSync(resolved).isFile()) {
|
|
40693
40785
|
return normalizePath(resolved);
|
|
40694
40786
|
}
|
|
@@ -40733,12 +40825,12 @@ function findTestFilesSync(cwd) {
|
|
|
40733
40825
|
for (const entry of entries) {
|
|
40734
40826
|
if (entry.isDirectory()) {
|
|
40735
40827
|
if (!skipDirs.has(entry.name)) {
|
|
40736
|
-
walk(
|
|
40828
|
+
walk(path25.join(dir, entry.name), visitedInodes);
|
|
40737
40829
|
}
|
|
40738
40830
|
} else if (entry.isFile()) {
|
|
40739
40831
|
const name = entry.name;
|
|
40740
40832
|
if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(name) || dir.includes("__tests__") && /\.(ts|tsx|js|jsx)$/.test(name)) {
|
|
40741
|
-
testFiles.push(normalizePath(
|
|
40833
|
+
testFiles.push(normalizePath(path25.join(dir, entry.name)));
|
|
40742
40834
|
}
|
|
40743
40835
|
}
|
|
40744
40836
|
}
|
|
@@ -40776,7 +40868,7 @@ async function buildImpactMapInternal(cwd) {
|
|
|
40776
40868
|
continue;
|
|
40777
40869
|
}
|
|
40778
40870
|
const imports = extractImports(content);
|
|
40779
|
-
const testDir =
|
|
40871
|
+
const testDir = path25.dirname(testFile);
|
|
40780
40872
|
for (const importPath of imports) {
|
|
40781
40873
|
const resolvedSource = resolveRelativeImport(testDir, importPath);
|
|
40782
40874
|
if (resolvedSource === null) {
|
|
@@ -40798,7 +40890,7 @@ async function buildImpactMap(cwd) {
|
|
|
40798
40890
|
return impactMap;
|
|
40799
40891
|
}
|
|
40800
40892
|
async function loadImpactMap(cwd) {
|
|
40801
|
-
const cachePath =
|
|
40893
|
+
const cachePath = path25.join(cwd, ".swarm", "cache", "impact-map.json");
|
|
40802
40894
|
if (fs14.existsSync(cachePath)) {
|
|
40803
40895
|
try {
|
|
40804
40896
|
const content = fs14.readFileSync(cachePath, "utf-8");
|
|
@@ -40813,8 +40905,8 @@ async function loadImpactMap(cwd) {
|
|
|
40813
40905
|
return buildImpactMap(cwd);
|
|
40814
40906
|
}
|
|
40815
40907
|
async function saveImpactMap(cwd, impactMap) {
|
|
40816
|
-
const cacheDir =
|
|
40817
|
-
const cachePath =
|
|
40908
|
+
const cacheDir = path25.join(cwd, ".swarm", "cache");
|
|
40909
|
+
const cachePath = path25.join(cacheDir, "impact-map.json");
|
|
40818
40910
|
if (!fs14.existsSync(cacheDir)) {
|
|
40819
40911
|
fs14.mkdirSync(cacheDir, { recursive: true });
|
|
40820
40912
|
}
|
|
@@ -40840,7 +40932,7 @@ async function analyzeImpact(changedFiles, cwd) {
|
|
|
40840
40932
|
const impactedTestsSet = new Set;
|
|
40841
40933
|
const untestedFiles = [];
|
|
40842
40934
|
for (const changedFile of validFiles) {
|
|
40843
|
-
const normalizedChanged = normalizePath(
|
|
40935
|
+
const normalizedChanged = normalizePath(path25.resolve(changedFile));
|
|
40844
40936
|
const tests = impactMap[normalizedChanged];
|
|
40845
40937
|
if (tests && tests.length > 0) {
|
|
40846
40938
|
for (const test of tests) {
|
|
@@ -41087,13 +41179,13 @@ function detectFlakyTests(allHistory) {
|
|
|
41087
41179
|
|
|
41088
41180
|
// src/test-impact/history-store.ts
|
|
41089
41181
|
import fs15 from "fs";
|
|
41090
|
-
import
|
|
41182
|
+
import path26 from "path";
|
|
41091
41183
|
var MAX_HISTORY_PER_TEST = 20;
|
|
41092
41184
|
var MAX_ERROR_LENGTH = 500;
|
|
41093
41185
|
var MAX_STACK_LENGTH = 200;
|
|
41094
41186
|
var MAX_CHANGED_FILES = 50;
|
|
41095
41187
|
function getHistoryPath(workingDir) {
|
|
41096
|
-
return
|
|
41188
|
+
return path26.join(workingDir || process.cwd(), ".swarm", "cache", "test-history.jsonl");
|
|
41097
41189
|
}
|
|
41098
41190
|
function sanitizeErrorMessage(errorMessage) {
|
|
41099
41191
|
if (errorMessage === undefined) {
|
|
@@ -41153,7 +41245,7 @@ function appendTestRun(record3, workingDir) {
|
|
|
41153
41245
|
changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
|
|
41154
41246
|
};
|
|
41155
41247
|
const historyPath = getHistoryPath(workingDir);
|
|
41156
|
-
const historyDir =
|
|
41248
|
+
const historyDir = path26.dirname(historyPath);
|
|
41157
41249
|
if (!fs15.existsSync(historyDir)) {
|
|
41158
41250
|
fs15.mkdirSync(historyDir, { recursive: true });
|
|
41159
41251
|
}
|
|
@@ -41227,7 +41319,7 @@ function getAllHistory(workingDir) {
|
|
|
41227
41319
|
|
|
41228
41320
|
// src/tools/resolve-working-directory.ts
|
|
41229
41321
|
import * as fs16 from "fs";
|
|
41230
|
-
import * as
|
|
41322
|
+
import * as path27 from "path";
|
|
41231
41323
|
function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
41232
41324
|
if (workingDirectory == null || workingDirectory === "") {
|
|
41233
41325
|
return { success: true, directory: fallbackDirectory };
|
|
@@ -41247,15 +41339,15 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
41247
41339
|
};
|
|
41248
41340
|
}
|
|
41249
41341
|
}
|
|
41250
|
-
const normalizedDir =
|
|
41251
|
-
const pathParts = normalizedDir.split(
|
|
41342
|
+
const normalizedDir = path27.normalize(workingDirectory);
|
|
41343
|
+
const pathParts = normalizedDir.split(path27.sep);
|
|
41252
41344
|
if (pathParts.includes("..")) {
|
|
41253
41345
|
return {
|
|
41254
41346
|
success: false,
|
|
41255
41347
|
message: "Invalid working_directory: path traversal sequences (..) are not allowed"
|
|
41256
41348
|
};
|
|
41257
41349
|
}
|
|
41258
|
-
const resolvedDir =
|
|
41350
|
+
const resolvedDir = path27.resolve(normalizedDir);
|
|
41259
41351
|
let statResult;
|
|
41260
41352
|
try {
|
|
41261
41353
|
statResult = fs16.statSync(resolvedDir);
|
|
@@ -41271,7 +41363,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
41271
41363
|
message: `Invalid working_directory: path "${resolvedDir}" is not a directory`
|
|
41272
41364
|
};
|
|
41273
41365
|
}
|
|
41274
|
-
const resolvedFallback =
|
|
41366
|
+
const resolvedFallback = path27.resolve(fallbackDirectory);
|
|
41275
41367
|
let fallbackExists = false;
|
|
41276
41368
|
try {
|
|
41277
41369
|
fs16.statSync(resolvedFallback);
|
|
@@ -41281,7 +41373,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
41281
41373
|
}
|
|
41282
41374
|
if (workingDirectory != null && workingDirectory !== "") {
|
|
41283
41375
|
if (fallbackExists) {
|
|
41284
|
-
const isSubdirectory = resolvedDir.startsWith(resolvedFallback +
|
|
41376
|
+
const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path27.sep);
|
|
41285
41377
|
if (isSubdirectory) {
|
|
41286
41378
|
return {
|
|
41287
41379
|
success: false,
|
|
@@ -41371,14 +41463,14 @@ function hasDevDependency(devDeps, ...patterns) {
|
|
|
41371
41463
|
return hasPackageJsonDependency(devDeps, ...patterns);
|
|
41372
41464
|
}
|
|
41373
41465
|
function detectGoTest(cwd) {
|
|
41374
|
-
return fs17.existsSync(
|
|
41466
|
+
return fs17.existsSync(path28.join(cwd, "go.mod")) && isCommandAvailable("go");
|
|
41375
41467
|
}
|
|
41376
41468
|
function detectJavaMaven(cwd) {
|
|
41377
|
-
return fs17.existsSync(
|
|
41469
|
+
return fs17.existsSync(path28.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
|
|
41378
41470
|
}
|
|
41379
41471
|
function detectGradle(cwd) {
|
|
41380
|
-
const hasBuildFile = fs17.existsSync(
|
|
41381
|
-
const hasGradlew = fs17.existsSync(
|
|
41472
|
+
const hasBuildFile = fs17.existsSync(path28.join(cwd, "build.gradle")) || fs17.existsSync(path28.join(cwd, "build.gradle.kts"));
|
|
41473
|
+
const hasGradlew = fs17.existsSync(path28.join(cwd, "gradlew")) || fs17.existsSync(path28.join(cwd, "gradlew.bat"));
|
|
41382
41474
|
return hasBuildFile && (hasGradlew || isCommandAvailable("gradle"));
|
|
41383
41475
|
}
|
|
41384
41476
|
function detectDotnetTest(cwd) {
|
|
@@ -41391,30 +41483,30 @@ function detectDotnetTest(cwd) {
|
|
|
41391
41483
|
}
|
|
41392
41484
|
}
|
|
41393
41485
|
function detectCTest(cwd) {
|
|
41394
|
-
const hasSource = fs17.existsSync(
|
|
41395
|
-
const hasBuildCache = fs17.existsSync(
|
|
41486
|
+
const hasSource = fs17.existsSync(path28.join(cwd, "CMakeLists.txt"));
|
|
41487
|
+
const hasBuildCache = fs17.existsSync(path28.join(cwd, "CMakeCache.txt")) || fs17.existsSync(path28.join(cwd, "build", "CMakeCache.txt"));
|
|
41396
41488
|
return (hasSource || hasBuildCache) && isCommandAvailable("ctest");
|
|
41397
41489
|
}
|
|
41398
41490
|
function detectSwiftTest(cwd) {
|
|
41399
|
-
return fs17.existsSync(
|
|
41491
|
+
return fs17.existsSync(path28.join(cwd, "Package.swift")) && isCommandAvailable("swift");
|
|
41400
41492
|
}
|
|
41401
41493
|
function detectDartTest(cwd) {
|
|
41402
|
-
return fs17.existsSync(
|
|
41494
|
+
return fs17.existsSync(path28.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
41403
41495
|
}
|
|
41404
41496
|
function detectRSpec(cwd) {
|
|
41405
|
-
const hasRSpecFile = fs17.existsSync(
|
|
41406
|
-
const hasGemfile = fs17.existsSync(
|
|
41407
|
-
const hasSpecDir = fs17.existsSync(
|
|
41497
|
+
const hasRSpecFile = fs17.existsSync(path28.join(cwd, ".rspec"));
|
|
41498
|
+
const hasGemfile = fs17.existsSync(path28.join(cwd, "Gemfile"));
|
|
41499
|
+
const hasSpecDir = fs17.existsSync(path28.join(cwd, "spec"));
|
|
41408
41500
|
const hasRSpec = hasRSpecFile || hasGemfile && hasSpecDir;
|
|
41409
41501
|
return hasRSpec && (isCommandAvailable("bundle") || isCommandAvailable("rspec"));
|
|
41410
41502
|
}
|
|
41411
41503
|
function detectMinitest(cwd) {
|
|
41412
|
-
return fs17.existsSync(
|
|
41504
|
+
return fs17.existsSync(path28.join(cwd, "test")) && (fs17.existsSync(path28.join(cwd, "Gemfile")) || fs17.existsSync(path28.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
|
|
41413
41505
|
}
|
|
41414
41506
|
async function detectTestFramework(cwd) {
|
|
41415
41507
|
const baseDir = cwd;
|
|
41416
41508
|
try {
|
|
41417
|
-
const packageJsonPath =
|
|
41509
|
+
const packageJsonPath = path28.join(baseDir, "package.json");
|
|
41418
41510
|
if (fs17.existsSync(packageJsonPath)) {
|
|
41419
41511
|
const content = fs17.readFileSync(packageJsonPath, "utf-8");
|
|
41420
41512
|
const pkg = JSON.parse(content);
|
|
@@ -41435,16 +41527,16 @@ async function detectTestFramework(cwd) {
|
|
|
41435
41527
|
return "jest";
|
|
41436
41528
|
if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
|
|
41437
41529
|
return "mocha";
|
|
41438
|
-
if (fs17.existsSync(
|
|
41530
|
+
if (fs17.existsSync(path28.join(baseDir, "bun.lockb")) || fs17.existsSync(path28.join(baseDir, "bun.lock"))) {
|
|
41439
41531
|
if (scripts.test?.includes("bun"))
|
|
41440
41532
|
return "bun";
|
|
41441
41533
|
}
|
|
41442
41534
|
}
|
|
41443
41535
|
} catch {}
|
|
41444
41536
|
try {
|
|
41445
|
-
const pyprojectTomlPath =
|
|
41446
|
-
const setupCfgPath =
|
|
41447
|
-
const requirementsTxtPath =
|
|
41537
|
+
const pyprojectTomlPath = path28.join(baseDir, "pyproject.toml");
|
|
41538
|
+
const setupCfgPath = path28.join(baseDir, "setup.cfg");
|
|
41539
|
+
const requirementsTxtPath = path28.join(baseDir, "requirements.txt");
|
|
41448
41540
|
if (fs17.existsSync(pyprojectTomlPath)) {
|
|
41449
41541
|
const content = fs17.readFileSync(pyprojectTomlPath, "utf-8");
|
|
41450
41542
|
if (content.includes("[tool.pytest"))
|
|
@@ -41464,7 +41556,7 @@ async function detectTestFramework(cwd) {
|
|
|
41464
41556
|
}
|
|
41465
41557
|
} catch {}
|
|
41466
41558
|
try {
|
|
41467
|
-
const cargoTomlPath =
|
|
41559
|
+
const cargoTomlPath = path28.join(baseDir, "Cargo.toml");
|
|
41468
41560
|
if (fs17.existsSync(cargoTomlPath)) {
|
|
41469
41561
|
const content = fs17.readFileSync(cargoTomlPath, "utf-8");
|
|
41470
41562
|
if (content.includes("[dev-dependencies]")) {
|
|
@@ -41475,9 +41567,9 @@ async function detectTestFramework(cwd) {
|
|
|
41475
41567
|
}
|
|
41476
41568
|
} catch {}
|
|
41477
41569
|
try {
|
|
41478
|
-
const pesterConfigPath =
|
|
41479
|
-
const pesterConfigJsonPath =
|
|
41480
|
-
const pesterPs1Path =
|
|
41570
|
+
const pesterConfigPath = path28.join(baseDir, "pester.config.ps1");
|
|
41571
|
+
const pesterConfigJsonPath = path28.join(baseDir, "pester.config.ps1.json");
|
|
41572
|
+
const pesterPs1Path = path28.join(baseDir, "tests.ps1");
|
|
41481
41573
|
if (fs17.existsSync(pesterConfigPath) || fs17.existsSync(pesterConfigJsonPath) || fs17.existsSync(pesterPs1Path)) {
|
|
41482
41574
|
return "pester";
|
|
41483
41575
|
}
|
|
@@ -41520,12 +41612,12 @@ function isTestDirectoryPath(normalizedPath) {
|
|
|
41520
41612
|
return normalizedPath.split("/").some((segment) => TEST_DIRECTORY_NAMES.includes(segment));
|
|
41521
41613
|
}
|
|
41522
41614
|
function resolveWorkspacePath(file3, workingDir) {
|
|
41523
|
-
return
|
|
41615
|
+
return path28.isAbsolute(file3) ? path28.resolve(file3) : path28.resolve(workingDir, file3);
|
|
41524
41616
|
}
|
|
41525
41617
|
function toWorkspaceOutputPath(absolutePath, workingDir, preferRelative) {
|
|
41526
41618
|
if (!preferRelative)
|
|
41527
41619
|
return absolutePath;
|
|
41528
|
-
return
|
|
41620
|
+
return path28.relative(workingDir, absolutePath);
|
|
41529
41621
|
}
|
|
41530
41622
|
function dedupePush(target, value) {
|
|
41531
41623
|
if (!target.includes(value)) {
|
|
@@ -41562,18 +41654,18 @@ function buildLanguageSpecificTestNames(nameWithoutExt, ext) {
|
|
|
41562
41654
|
}
|
|
41563
41655
|
}
|
|
41564
41656
|
function getRepoLevelCandidateDirectories(workingDir, relativePath, ext) {
|
|
41565
|
-
const relativeDir =
|
|
41657
|
+
const relativeDir = path28.dirname(relativePath);
|
|
41566
41658
|
const nestedRelativeDir = relativeDir === "." ? "" : relativeDir;
|
|
41567
41659
|
const directories = TEST_DIRECTORY_NAMES.flatMap((dirName) => {
|
|
41568
|
-
const rootDir =
|
|
41569
|
-
return nestedRelativeDir ? [rootDir,
|
|
41660
|
+
const rootDir = path28.join(workingDir, dirName);
|
|
41661
|
+
return nestedRelativeDir ? [rootDir, path28.join(rootDir, nestedRelativeDir)] : [rootDir];
|
|
41570
41662
|
});
|
|
41571
41663
|
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
41572
41664
|
if (ext === ".java" && normalizedRelativePath.startsWith("src/main/java/")) {
|
|
41573
|
-
directories.push(
|
|
41665
|
+
directories.push(path28.join(workingDir, "src/test/java", path28.dirname(normalizedRelativePath.slice("src/main/java/".length))));
|
|
41574
41666
|
}
|
|
41575
41667
|
if ((ext === ".kt" || ext === ".java") && normalizedRelativePath.startsWith("src/main/kotlin/")) {
|
|
41576
|
-
directories.push(
|
|
41668
|
+
directories.push(path28.join(workingDir, "src/test/kotlin", path28.dirname(normalizedRelativePath.slice("src/main/kotlin/".length))));
|
|
41577
41669
|
}
|
|
41578
41670
|
return [...new Set(directories)];
|
|
41579
41671
|
}
|
|
@@ -41601,23 +41693,23 @@ function isLanguageSpecificTestFile(basename4) {
|
|
|
41601
41693
|
}
|
|
41602
41694
|
function isConventionTestFilePath(filePath) {
|
|
41603
41695
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
41604
|
-
const basename4 =
|
|
41696
|
+
const basename4 = path28.basename(filePath);
|
|
41605
41697
|
return hasCompoundTestExtension(basename4) || basename4.includes(".spec.") || basename4.includes(".test.") || isLanguageSpecificTestFile(basename4) || isTestDirectoryPath(normalizedPath);
|
|
41606
41698
|
}
|
|
41607
41699
|
function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
|
|
41608
41700
|
const testFiles = [];
|
|
41609
41701
|
for (const file3 of sourceFiles) {
|
|
41610
41702
|
const absoluteFile = resolveWorkspacePath(file3, workingDir);
|
|
41611
|
-
const relativeFile =
|
|
41612
|
-
const basename4 =
|
|
41613
|
-
const dirname11 =
|
|
41614
|
-
const preferRelativeOutput = !
|
|
41703
|
+
const relativeFile = path28.relative(workingDir, absoluteFile);
|
|
41704
|
+
const basename4 = path28.basename(absoluteFile);
|
|
41705
|
+
const dirname11 = path28.dirname(absoluteFile);
|
|
41706
|
+
const preferRelativeOutput = !path28.isAbsolute(file3);
|
|
41615
41707
|
if (isConventionTestFilePath(relativeFile) || isConventionTestFilePath(file3)) {
|
|
41616
41708
|
dedupePush(testFiles, toWorkspaceOutputPath(absoluteFile, workingDir, preferRelativeOutput));
|
|
41617
41709
|
continue;
|
|
41618
41710
|
}
|
|
41619
41711
|
const nameWithoutExt = basename4.replace(/\.[^.]+$/, "");
|
|
41620
|
-
const ext =
|
|
41712
|
+
const ext = path28.extname(basename4);
|
|
41621
41713
|
const genericTestNames = [
|
|
41622
41714
|
`${nameWithoutExt}.spec${ext}`,
|
|
41623
41715
|
`${nameWithoutExt}.test${ext}`
|
|
@@ -41626,7 +41718,7 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
|
|
|
41626
41718
|
const colocatedCandidates = [
|
|
41627
41719
|
...genericTestNames,
|
|
41628
41720
|
...languageSpecificTestNames
|
|
41629
|
-
].map((candidateName) =>
|
|
41721
|
+
].map((candidateName) => path28.join(dirname11, candidateName));
|
|
41630
41722
|
const testDirectoryNames = [
|
|
41631
41723
|
basename4,
|
|
41632
41724
|
...genericTestNames,
|
|
@@ -41635,8 +41727,8 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
|
|
|
41635
41727
|
const repoLevelDirectories = getRepoLevelCandidateDirectories(workingDir, relativeFile, ext);
|
|
41636
41728
|
const possibleTestFiles = [
|
|
41637
41729
|
...colocatedCandidates,
|
|
41638
|
-
...TEST_DIRECTORY_NAMES.flatMap((dirName) => testDirectoryNames.map((candidateName) =>
|
|
41639
|
-
...repoLevelDirectories.flatMap((candidateDir) => testDirectoryNames.map((candidateName) =>
|
|
41730
|
+
...TEST_DIRECTORY_NAMES.flatMap((dirName) => testDirectoryNames.map((candidateName) => path28.join(dirname11, dirName, candidateName))),
|
|
41731
|
+
...repoLevelDirectories.flatMap((candidateDir) => testDirectoryNames.map((candidateName) => path28.join(candidateDir, candidateName)))
|
|
41640
41732
|
];
|
|
41641
41733
|
for (const testFile of possibleTestFiles) {
|
|
41642
41734
|
if (fs17.existsSync(testFile)) {
|
|
@@ -41657,7 +41749,7 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41657
41749
|
try {
|
|
41658
41750
|
const absoluteTestFile = resolveWorkspacePath(testFile, workingDir);
|
|
41659
41751
|
const content = fs17.readFileSync(absoluteTestFile, "utf-8");
|
|
41660
|
-
const testDir =
|
|
41752
|
+
const testDir = path28.dirname(absoluteTestFile);
|
|
41661
41753
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
41662
41754
|
let match;
|
|
41663
41755
|
match = importRegex.exec(content);
|
|
@@ -41665,8 +41757,8 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41665
41757
|
const importPath = match[1];
|
|
41666
41758
|
let resolvedImport;
|
|
41667
41759
|
if (importPath.startsWith(".")) {
|
|
41668
|
-
resolvedImport =
|
|
41669
|
-
const existingExt =
|
|
41760
|
+
resolvedImport = path28.resolve(testDir, importPath);
|
|
41761
|
+
const existingExt = path28.extname(resolvedImport);
|
|
41670
41762
|
if (!existingExt) {
|
|
41671
41763
|
for (const extToTry of [
|
|
41672
41764
|
".ts",
|
|
@@ -41686,12 +41778,12 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41686
41778
|
} else {
|
|
41687
41779
|
continue;
|
|
41688
41780
|
}
|
|
41689
|
-
const importBasename =
|
|
41690
|
-
const importDir =
|
|
41781
|
+
const importBasename = path28.basename(resolvedImport, path28.extname(resolvedImport));
|
|
41782
|
+
const importDir = path28.dirname(resolvedImport);
|
|
41691
41783
|
for (const sourceFile of absoluteSourceFiles) {
|
|
41692
|
-
const sourceDir =
|
|
41693
|
-
const sourceBasename =
|
|
41694
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
41784
|
+
const sourceDir = path28.dirname(sourceFile);
|
|
41785
|
+
const sourceBasename = path28.basename(sourceFile, path28.extname(sourceFile));
|
|
41786
|
+
const isRelatedDir = importDir === sourceDir || importDir === path28.join(sourceDir, "__tests__") || importDir === path28.join(sourceDir, "tests") || importDir === path28.join(sourceDir, "test") || importDir === path28.join(sourceDir, "spec");
|
|
41695
41787
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
41696
41788
|
dedupePush(testFiles, testFile);
|
|
41697
41789
|
break;
|
|
@@ -41704,8 +41796,8 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41704
41796
|
while (match !== null) {
|
|
41705
41797
|
const importPath = match[1];
|
|
41706
41798
|
if (importPath.startsWith(".")) {
|
|
41707
|
-
let resolvedImport =
|
|
41708
|
-
const existingExt =
|
|
41799
|
+
let resolvedImport = path28.resolve(testDir, importPath);
|
|
41800
|
+
const existingExt = path28.extname(resolvedImport);
|
|
41709
41801
|
if (!existingExt) {
|
|
41710
41802
|
for (const extToTry of [
|
|
41711
41803
|
".ts",
|
|
@@ -41722,12 +41814,12 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41722
41814
|
}
|
|
41723
41815
|
}
|
|
41724
41816
|
}
|
|
41725
|
-
const importDir =
|
|
41726
|
-
const importBasename =
|
|
41817
|
+
const importDir = path28.dirname(resolvedImport);
|
|
41818
|
+
const importBasename = path28.basename(resolvedImport, path28.extname(resolvedImport));
|
|
41727
41819
|
for (const sourceFile of absoluteSourceFiles) {
|
|
41728
|
-
const sourceDir =
|
|
41729
|
-
const sourceBasename =
|
|
41730
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
41820
|
+
const sourceDir = path28.dirname(sourceFile);
|
|
41821
|
+
const sourceBasename = path28.basename(sourceFile, path28.extname(sourceFile));
|
|
41822
|
+
const isRelatedDir = importDir === sourceDir || importDir === path28.join(sourceDir, "__tests__") || importDir === path28.join(sourceDir, "tests") || importDir === path28.join(sourceDir, "test") || importDir === path28.join(sourceDir, "spec");
|
|
41731
41823
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
41732
41824
|
dedupePush(testFiles, testFile);
|
|
41733
41825
|
break;
|
|
@@ -41830,8 +41922,8 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
|
|
|
41830
41922
|
return ["mvn", "test"];
|
|
41831
41923
|
case "gradle": {
|
|
41832
41924
|
const isWindows = process.platform === "win32";
|
|
41833
|
-
const hasGradlewBat = fs17.existsSync(
|
|
41834
|
-
const hasGradlew = fs17.existsSync(
|
|
41925
|
+
const hasGradlewBat = fs17.existsSync(path28.join(baseDir, "gradlew.bat"));
|
|
41926
|
+
const hasGradlew = fs17.existsSync(path28.join(baseDir, "gradlew"));
|
|
41835
41927
|
if (hasGradlewBat && isWindows)
|
|
41836
41928
|
return ["gradlew.bat", "test"];
|
|
41837
41929
|
if (hasGradlew)
|
|
@@ -41848,7 +41940,7 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
|
|
|
41848
41940
|
"cmake-build-release",
|
|
41849
41941
|
"out"
|
|
41850
41942
|
];
|
|
41851
|
-
const actualBuildDir = buildDirCandidates.find((d) => fs17.existsSync(
|
|
41943
|
+
const actualBuildDir = buildDirCandidates.find((d) => fs17.existsSync(path28.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
|
|
41852
41944
|
return ["ctest", "--test-dir", actualBuildDir];
|
|
41853
41945
|
}
|
|
41854
41946
|
case "swift-test":
|
|
@@ -42346,12 +42438,12 @@ function analyzeFailures(workingDir) {
|
|
|
42346
42438
|
var test_runner = createSwarmTool({
|
|
42347
42439
|
description: 'Run project tests with framework detection. Supports bun, vitest, jest, mocha, pytest, cargo, pester, go-test, maven, gradle, dotnet-test, ctest, swift-test, dart-test, rspec, and minitest. Returns deterministic normalized JSON with framework, scope, command, totals, coverage, duration, success status, and failures. Use scope "all" for full suite, "convention" to accept direct test files or map source files to test files, "graph" to find related tests via imports from source files, or "impact" to find tests covering changed source files using test-impact analysis.',
|
|
42348
42440
|
args: {
|
|
42349
|
-
scope:
|
|
42350
|
-
files:
|
|
42351
|
-
coverage:
|
|
42352
|
-
timeout_ms:
|
|
42353
|
-
allow_full_suite:
|
|
42354
|
-
working_directory:
|
|
42441
|
+
scope: exports_external.enum(["all", "convention", "graph", "impact"]).optional().describe('Test scope: "all" runs full suite, "convention" accepts direct test files or maps source files to tests by naming, "graph" finds related tests via imports from source files, "impact" finds tests covering changed source files via test-impact analysis'),
|
|
42442
|
+
files: exports_external.array(exports_external.string()).optional().describe('Specific files to test. For "convention", pass source files or direct test files. For "graph" and "impact", pass source files only.'),
|
|
42443
|
+
coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
|
|
42444
|
+
timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
|
|
42445
|
+
allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
|
|
42446
|
+
working_directory: exports_external.string().optional().describe("Explicit project root directory. When provided, tests run relative to this path instead of the plugin context directory. Use this when CWD differs from the actual project root.")
|
|
42355
42447
|
},
|
|
42356
42448
|
async execute(args, directory) {
|
|
42357
42449
|
let workingDirInput;
|
|
@@ -42476,7 +42568,7 @@ var test_runner = createSwarmTool({
|
|
|
42476
42568
|
const sourceFiles = args.files.filter((file3) => {
|
|
42477
42569
|
if (directTestFiles.includes(file3))
|
|
42478
42570
|
return false;
|
|
42479
|
-
const ext =
|
|
42571
|
+
const ext = path28.extname(file3).toLowerCase();
|
|
42480
42572
|
return SOURCE_EXTENSIONS.has(ext);
|
|
42481
42573
|
});
|
|
42482
42574
|
const invalidFiles = args.files.filter((file3) => !directTestFiles.includes(file3) && !sourceFiles.includes(file3));
|
|
@@ -42511,7 +42603,7 @@ var test_runner = createSwarmTool({
|
|
|
42511
42603
|
if (isConventionTestFilePath(f)) {
|
|
42512
42604
|
return false;
|
|
42513
42605
|
}
|
|
42514
|
-
const ext =
|
|
42606
|
+
const ext = path28.extname(f).toLowerCase();
|
|
42515
42607
|
return SOURCE_EXTENSIONS.has(ext);
|
|
42516
42608
|
});
|
|
42517
42609
|
if (sourceFiles.length === 0) {
|
|
@@ -42538,7 +42630,7 @@ var test_runner = createSwarmTool({
|
|
|
42538
42630
|
if (isConventionTestFilePath(f)) {
|
|
42539
42631
|
return false;
|
|
42540
42632
|
}
|
|
42541
|
-
const ext =
|
|
42633
|
+
const ext = path28.extname(f).toLowerCase();
|
|
42542
42634
|
return SOURCE_EXTENSIONS.has(ext);
|
|
42543
42635
|
});
|
|
42544
42636
|
if (sourceFiles.length === 0) {
|
|
@@ -42556,8 +42648,8 @@ var test_runner = createSwarmTool({
|
|
|
42556
42648
|
const impactResult = await analyzeImpact(sourceFiles, workingDir);
|
|
42557
42649
|
if (impactResult.impactedTests.length > 0) {
|
|
42558
42650
|
testFiles = impactResult.impactedTests.map((absPath) => {
|
|
42559
|
-
const relativePath =
|
|
42560
|
-
return
|
|
42651
|
+
const relativePath = path28.relative(workingDir, absPath);
|
|
42652
|
+
return path28.isAbsolute(relativePath) ? absPath : relativePath;
|
|
42561
42653
|
});
|
|
42562
42654
|
} else {
|
|
42563
42655
|
graphFallbackReason = "no impacted tests found via impact analysis, falling back to graph";
|
|
@@ -42650,8 +42742,8 @@ function validateDirectoryPath(dir) {
|
|
|
42650
42742
|
if (dir.includes("..")) {
|
|
42651
42743
|
throw new Error("Directory path must not contain path traversal sequences");
|
|
42652
42744
|
}
|
|
42653
|
-
const normalized =
|
|
42654
|
-
const absolutePath =
|
|
42745
|
+
const normalized = path29.normalize(dir);
|
|
42746
|
+
const absolutePath = path29.isAbsolute(normalized) ? normalized : path29.resolve(normalized);
|
|
42655
42747
|
return absolutePath;
|
|
42656
42748
|
}
|
|
42657
42749
|
function validateTimeout(timeoutMs, defaultValue) {
|
|
@@ -42674,7 +42766,7 @@ function validateTimeout(timeoutMs, defaultValue) {
|
|
|
42674
42766
|
}
|
|
42675
42767
|
function getPackageVersion(dir) {
|
|
42676
42768
|
try {
|
|
42677
|
-
const packagePath =
|
|
42769
|
+
const packagePath = path29.join(dir, "package.json");
|
|
42678
42770
|
if (fs18.existsSync(packagePath)) {
|
|
42679
42771
|
const content = fs18.readFileSync(packagePath, "utf-8");
|
|
42680
42772
|
const pkg = JSON.parse(content);
|
|
@@ -42685,7 +42777,7 @@ function getPackageVersion(dir) {
|
|
|
42685
42777
|
}
|
|
42686
42778
|
function getChangelogVersion(dir) {
|
|
42687
42779
|
try {
|
|
42688
|
-
const changelogPath =
|
|
42780
|
+
const changelogPath = path29.join(dir, "CHANGELOG.md");
|
|
42689
42781
|
if (fs18.existsSync(changelogPath)) {
|
|
42690
42782
|
const content = fs18.readFileSync(changelogPath, "utf-8");
|
|
42691
42783
|
const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
|
|
@@ -42699,7 +42791,7 @@ function getChangelogVersion(dir) {
|
|
|
42699
42791
|
function getVersionFileVersion(dir) {
|
|
42700
42792
|
const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
|
|
42701
42793
|
for (const file3 of possibleFiles) {
|
|
42702
|
-
const filePath =
|
|
42794
|
+
const filePath = path29.join(dir, file3);
|
|
42703
42795
|
if (fs18.existsSync(filePath)) {
|
|
42704
42796
|
try {
|
|
42705
42797
|
const content = fs18.readFileSync(filePath, "utf-8").trim();
|
|
@@ -43026,7 +43118,7 @@ async function runEvidenceCheck(dir) {
|
|
|
43026
43118
|
async function runRequirementCoverageCheck(dir, currentPhase) {
|
|
43027
43119
|
const startTime = Date.now();
|
|
43028
43120
|
try {
|
|
43029
|
-
const specPath =
|
|
43121
|
+
const specPath = path29.join(dir, ".swarm", "spec.md");
|
|
43030
43122
|
if (!fs18.existsSync(specPath)) {
|
|
43031
43123
|
return {
|
|
43032
43124
|
type: "req_coverage",
|
|
@@ -43239,144 +43331,6 @@ async function handlePreflightCommand(directory, _args) {
|
|
|
43239
43331
|
const report = await runPreflight(directory, phase);
|
|
43240
43332
|
return formatPreflightMarkdown(report);
|
|
43241
43333
|
}
|
|
43242
|
-
// src/knowledge/hive-promoter.ts
|
|
43243
|
-
import * as fs19 from "fs";
|
|
43244
|
-
import * as os6 from "os";
|
|
43245
|
-
import * as path29 from "path";
|
|
43246
|
-
var DANGEROUS_PATTERNS = [
|
|
43247
|
-
[/rm\s+-rf/, "rm\\s+-rf"],
|
|
43248
|
-
[/:\s*!\s*\|/, ":\\s*!\\s*\\|"],
|
|
43249
|
-
[/\|\s*sh\b/, "\\|\\s*sh\\b"],
|
|
43250
|
-
[/`[^`]*`/, "`[^`]*`"],
|
|
43251
|
-
[/\$\(/, "\\$\\("],
|
|
43252
|
-
[/;\s*rm\s+\//, ";\\s*rm\\s+\\/"],
|
|
43253
|
-
[/>\s*\/dev\//, ">\\s*\\/dev\\/"],
|
|
43254
|
-
[/\bmkfs\b/, "\\bmkfs\\b"],
|
|
43255
|
-
[/\bdd\s+if=/, "\\bdd\\s+if="],
|
|
43256
|
-
[/chmod\s+[0-7]*7[0-7]{2}/, "chmod\\s+[0-7]*7[0-7]\\{2\\}"],
|
|
43257
|
-
[/\bchown\s+-R\b/, "\\bchown\\s+-R\\b"],
|
|
43258
|
-
[/(?<!\.)\beval\s*\(/, "(?<!\\.)\\beval\\s*\\("],
|
|
43259
|
-
[/(?<!\.)\bexec\s*\(/, "(?<!\\.)\\bexec\\s*\\("]
|
|
43260
|
-
];
|
|
43261
|
-
var SHELL_COMMAND_START = /^(grep|find|ls|cat|sed|awk|curl|wget|ssh|scp|git|mv|cp|mkdir|touch|echo|printf|python|python3|node|bash|sh|zsh|apt|yum|brew)\s/;
|
|
43262
|
-
function validateLesson2(text) {
|
|
43263
|
-
if (!text || !text.trim()) {
|
|
43264
|
-
return { valid: false, reason: "Lesson text cannot be empty" };
|
|
43265
|
-
}
|
|
43266
|
-
for (const [pattern, patternSource] of DANGEROUS_PATTERNS) {
|
|
43267
|
-
if (pattern.test(text)) {
|
|
43268
|
-
return {
|
|
43269
|
-
valid: false,
|
|
43270
|
-
reason: `Dangerous pattern detected: ${patternSource}`
|
|
43271
|
-
};
|
|
43272
|
-
}
|
|
43273
|
-
}
|
|
43274
|
-
const trimmed = text.trim();
|
|
43275
|
-
if (SHELL_COMMAND_START.test(trimmed)) {
|
|
43276
|
-
const lastChar = trimmed[trimmed.length - 1];
|
|
43277
|
-
if (![".", "!", "?", ";"].includes(lastChar)) {
|
|
43278
|
-
return {
|
|
43279
|
-
valid: false,
|
|
43280
|
-
reason: "Lesson appears to contain raw shell commands"
|
|
43281
|
-
};
|
|
43282
|
-
}
|
|
43283
|
-
}
|
|
43284
|
-
return { valid: true };
|
|
43285
|
-
}
|
|
43286
|
-
function getHiveFilePath() {
|
|
43287
|
-
const platform = process.platform;
|
|
43288
|
-
const home = os6.homedir();
|
|
43289
|
-
let dataDir;
|
|
43290
|
-
if (platform === "win32") {
|
|
43291
|
-
dataDir = path29.join(process.env.LOCALAPPDATA || path29.join(home, "AppData", "Local"), "opencode-swarm", "Data");
|
|
43292
|
-
} else if (platform === "darwin") {
|
|
43293
|
-
dataDir = path29.join(home, "Library", "Application Support", "opencode-swarm");
|
|
43294
|
-
} else {
|
|
43295
|
-
dataDir = path29.join(process.env.XDG_DATA_HOME || path29.join(home, ".local", "share"), "opencode-swarm");
|
|
43296
|
-
}
|
|
43297
|
-
return path29.join(dataDir, "hive-knowledge.jsonl");
|
|
43298
|
-
}
|
|
43299
|
-
async function promoteToHive(_directory, lesson, category) {
|
|
43300
|
-
const trimmed = (lesson ?? "").trim();
|
|
43301
|
-
if (!trimmed) {
|
|
43302
|
-
throw new Error("Lesson text required");
|
|
43303
|
-
}
|
|
43304
|
-
const validation = validateLesson2(trimmed);
|
|
43305
|
-
if (!validation.valid) {
|
|
43306
|
-
throw new Error(`Lesson rejected by validator: ${validation.reason}`);
|
|
43307
|
-
}
|
|
43308
|
-
const hivePath = getHiveFilePath();
|
|
43309
|
-
const hiveDir = path29.dirname(hivePath);
|
|
43310
|
-
if (!fs19.existsSync(hiveDir)) {
|
|
43311
|
-
fs19.mkdirSync(hiveDir, { recursive: true });
|
|
43312
|
-
}
|
|
43313
|
-
const now = new Date;
|
|
43314
|
-
const entry = {
|
|
43315
|
-
id: `hive-manual-${now.getTime()}`,
|
|
43316
|
-
lesson: trimmed,
|
|
43317
|
-
category: category || "process",
|
|
43318
|
-
scope_tag: "global",
|
|
43319
|
-
confidence: 1,
|
|
43320
|
-
status: "promoted",
|
|
43321
|
-
promotion_source: "manual",
|
|
43322
|
-
promotedAt: now.toISOString(),
|
|
43323
|
-
retrievalOutcomes: { applied: 0, succeededAfter: 0, failedAfter: 0 }
|
|
43324
|
-
};
|
|
43325
|
-
fs19.appendFileSync(hivePath, `${JSON.stringify(entry)}
|
|
43326
|
-
`, "utf-8");
|
|
43327
|
-
const preview = `${trimmed.slice(0, 50)}${trimmed.length > 50 ? "..." : ""}`;
|
|
43328
|
-
return `Promoted to hive: "${preview}" (confidence: 1.0, source: manual)`;
|
|
43329
|
-
}
|
|
43330
|
-
async function promoteFromSwarm(directory, lessonId) {
|
|
43331
|
-
const knowledgePath = path29.join(directory, ".swarm", "knowledge.jsonl");
|
|
43332
|
-
const entries = [];
|
|
43333
|
-
if (fs19.existsSync(knowledgePath)) {
|
|
43334
|
-
const content = fs19.readFileSync(knowledgePath, "utf-8");
|
|
43335
|
-
for (const line of content.split(`
|
|
43336
|
-
`)) {
|
|
43337
|
-
const t = line.trim();
|
|
43338
|
-
if (!t)
|
|
43339
|
-
continue;
|
|
43340
|
-
try {
|
|
43341
|
-
entries.push(JSON.parse(t));
|
|
43342
|
-
} catch {}
|
|
43343
|
-
}
|
|
43344
|
-
}
|
|
43345
|
-
const swarmEntry = entries.find((e) => e.id === lessonId);
|
|
43346
|
-
if (!swarmEntry) {
|
|
43347
|
-
throw new Error(`Lesson ${lessonId} not found in .swarm/knowledge.jsonl`);
|
|
43348
|
-
}
|
|
43349
|
-
const lessonText = typeof swarmEntry.lesson === "string" ? swarmEntry.lesson.trim() : "";
|
|
43350
|
-
if (!lessonText) {
|
|
43351
|
-
throw new Error("Lesson text required");
|
|
43352
|
-
}
|
|
43353
|
-
const validation = validateLesson2(lessonText);
|
|
43354
|
-
if (!validation.valid) {
|
|
43355
|
-
throw new Error(`Lesson rejected by validator: ${validation.reason}`);
|
|
43356
|
-
}
|
|
43357
|
-
const hivePath = getHiveFilePath();
|
|
43358
|
-
const hiveDir = path29.dirname(hivePath);
|
|
43359
|
-
if (!fs19.existsSync(hiveDir)) {
|
|
43360
|
-
fs19.mkdirSync(hiveDir, { recursive: true });
|
|
43361
|
-
}
|
|
43362
|
-
const now = new Date;
|
|
43363
|
-
const hiveEntry = {
|
|
43364
|
-
id: `hive-manual-${now.getTime()}`,
|
|
43365
|
-
lesson: lessonText,
|
|
43366
|
-
category: typeof swarmEntry.category === "string" ? swarmEntry.category : "process",
|
|
43367
|
-
scope_tag: typeof swarmEntry.scope === "string" ? swarmEntry.scope : "global",
|
|
43368
|
-
confidence: 1,
|
|
43369
|
-
status: "promoted",
|
|
43370
|
-
promotion_source: "manual",
|
|
43371
|
-
promotedAt: now.toISOString(),
|
|
43372
|
-
retrievalOutcomes: { applied: 0, succeededAfter: 0, failedAfter: 0 }
|
|
43373
|
-
};
|
|
43374
|
-
fs19.appendFileSync(hivePath, `${JSON.stringify(hiveEntry)}
|
|
43375
|
-
`, "utf-8");
|
|
43376
|
-
const preview = `${lessonText.slice(0, 50)}${lessonText.length > 50 ? "..." : ""}`;
|
|
43377
|
-
return `Promoted to hive: "${preview}" (confidence: 1.0, source: manual)`;
|
|
43378
|
-
}
|
|
43379
|
-
|
|
43380
43334
|
// src/commands/promote.ts
|
|
43381
43335
|
async function handlePromoteCommand(directory, args) {
|
|
43382
43336
|
let category;
|
|
@@ -43398,12 +43352,6 @@ async function handlePromoteCommand(directory, args) {
|
|
|
43398
43352
|
if (!lessonText && !lessonId) {
|
|
43399
43353
|
return `Usage: /swarm promote "<lesson text>" or /swarm promote --from-swarm <id>`;
|
|
43400
43354
|
}
|
|
43401
|
-
if (lessonText) {
|
|
43402
|
-
const validation = validateLesson2(lessonText);
|
|
43403
|
-
if (!validation.valid) {
|
|
43404
|
-
return `Lesson rejected by validator: ${validation.reason}`;
|
|
43405
|
-
}
|
|
43406
|
-
}
|
|
43407
43355
|
if (lessonId) {
|
|
43408
43356
|
try {
|
|
43409
43357
|
return await promoteFromSwarm(directory, lessonId);
|
|
@@ -43552,7 +43500,7 @@ async function handleQaGatesCommand(directory, args, sessionID) {
|
|
|
43552
43500
|
}
|
|
43553
43501
|
|
|
43554
43502
|
// src/commands/reset.ts
|
|
43555
|
-
import * as
|
|
43503
|
+
import * as fs19 from "fs";
|
|
43556
43504
|
|
|
43557
43505
|
// src/background/manager.ts
|
|
43558
43506
|
init_utils();
|
|
@@ -44253,8 +44201,8 @@ async function handleResetCommand(directory, args) {
|
|
|
44253
44201
|
for (const filename of filesToReset) {
|
|
44254
44202
|
try {
|
|
44255
44203
|
const resolvedPath = validateSwarmPath(directory, filename);
|
|
44256
|
-
if (
|
|
44257
|
-
|
|
44204
|
+
if (fs19.existsSync(resolvedPath)) {
|
|
44205
|
+
fs19.unlinkSync(resolvedPath);
|
|
44258
44206
|
results.push(`- \u2705 Deleted ${filename}`);
|
|
44259
44207
|
} else {
|
|
44260
44208
|
results.push(`- \u23ED\uFE0F ${filename} not found (skipped)`);
|
|
@@ -44271,8 +44219,8 @@ async function handleResetCommand(directory, args) {
|
|
|
44271
44219
|
}
|
|
44272
44220
|
try {
|
|
44273
44221
|
const summariesPath = validateSwarmPath(directory, "summaries");
|
|
44274
|
-
if (
|
|
44275
|
-
|
|
44222
|
+
if (fs19.existsSync(summariesPath)) {
|
|
44223
|
+
fs19.rmSync(summariesPath, { recursive: true, force: true });
|
|
44276
44224
|
results.push("- \u2705 Deleted summaries/ directory");
|
|
44277
44225
|
} else {
|
|
44278
44226
|
results.push("- \u23ED\uFE0F summaries/ not found (skipped)");
|
|
@@ -44292,14 +44240,14 @@ async function handleResetCommand(directory, args) {
|
|
|
44292
44240
|
|
|
44293
44241
|
// src/commands/reset-session.ts
|
|
44294
44242
|
init_utils2();
|
|
44295
|
-
import * as
|
|
44243
|
+
import * as fs20 from "fs";
|
|
44296
44244
|
import * as path30 from "path";
|
|
44297
44245
|
async function handleResetSessionCommand(directory, _args) {
|
|
44298
44246
|
const results = [];
|
|
44299
44247
|
try {
|
|
44300
44248
|
const statePath = validateSwarmPath(directory, "session/state.json");
|
|
44301
|
-
if (
|
|
44302
|
-
|
|
44249
|
+
if (fs20.existsSync(statePath)) {
|
|
44250
|
+
fs20.unlinkSync(statePath);
|
|
44303
44251
|
results.push("\u2705 Deleted .swarm/session/state.json");
|
|
44304
44252
|
} else {
|
|
44305
44253
|
results.push("\u23ED\uFE0F state.json not found (already clean)");
|
|
@@ -44309,14 +44257,14 @@ async function handleResetSessionCommand(directory, _args) {
|
|
|
44309
44257
|
}
|
|
44310
44258
|
try {
|
|
44311
44259
|
const sessionDir = path30.dirname(validateSwarmPath(directory, "session/state.json"));
|
|
44312
|
-
if (
|
|
44313
|
-
const files =
|
|
44260
|
+
if (fs20.existsSync(sessionDir)) {
|
|
44261
|
+
const files = fs20.readdirSync(sessionDir);
|
|
44314
44262
|
const otherFiles = files.filter((f) => f !== "state.json");
|
|
44315
44263
|
let deletedCount = 0;
|
|
44316
44264
|
for (const file3 of otherFiles) {
|
|
44317
44265
|
const filePath = path30.join(sessionDir, file3);
|
|
44318
|
-
if (
|
|
44319
|
-
|
|
44266
|
+
if (fs20.lstatSync(filePath).isFile()) {
|
|
44267
|
+
fs20.unlinkSync(filePath);
|
|
44320
44268
|
deletedCount++;
|
|
44321
44269
|
}
|
|
44322
44270
|
}
|
|
@@ -44421,18 +44369,18 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
|
|
|
44421
44369
|
|
|
44422
44370
|
// src/commands/rollback.ts
|
|
44423
44371
|
init_utils2();
|
|
44424
|
-
import * as
|
|
44372
|
+
import * as fs21 from "fs";
|
|
44425
44373
|
import * as path32 from "path";
|
|
44426
44374
|
async function handleRollbackCommand(directory, args) {
|
|
44427
44375
|
const phaseArg = args[0];
|
|
44428
44376
|
if (!phaseArg) {
|
|
44429
44377
|
const manifestPath2 = validateSwarmPath(directory, "checkpoints/manifest.json");
|
|
44430
|
-
if (!
|
|
44378
|
+
if (!fs21.existsSync(manifestPath2)) {
|
|
44431
44379
|
return "No checkpoints found. Use `/swarm checkpoint` to create checkpoints.";
|
|
44432
44380
|
}
|
|
44433
44381
|
let manifest2;
|
|
44434
44382
|
try {
|
|
44435
|
-
manifest2 = JSON.parse(
|
|
44383
|
+
manifest2 = JSON.parse(fs21.readFileSync(manifestPath2, "utf-8"));
|
|
44436
44384
|
} catch {
|
|
44437
44385
|
return "Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.";
|
|
44438
44386
|
}
|
|
@@ -44454,12 +44402,12 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44454
44402
|
return "Error: Phase number must be a positive integer.";
|
|
44455
44403
|
}
|
|
44456
44404
|
const manifestPath = validateSwarmPath(directory, "checkpoints/manifest.json");
|
|
44457
|
-
if (!
|
|
44405
|
+
if (!fs21.existsSync(manifestPath)) {
|
|
44458
44406
|
return `Error: No checkpoints found. Cannot rollback to phase ${targetPhase}.`;
|
|
44459
44407
|
}
|
|
44460
44408
|
let manifest;
|
|
44461
44409
|
try {
|
|
44462
|
-
manifest = JSON.parse(
|
|
44410
|
+
manifest = JSON.parse(fs21.readFileSync(manifestPath, "utf-8"));
|
|
44463
44411
|
} catch {
|
|
44464
44412
|
return `Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.`;
|
|
44465
44413
|
}
|
|
@@ -44469,10 +44417,10 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44469
44417
|
return `Error: Checkpoint for phase ${targetPhase} not found. Available phases: ${available}`;
|
|
44470
44418
|
}
|
|
44471
44419
|
const checkpointDir = validateSwarmPath(directory, `checkpoints/phase-${targetPhase}`);
|
|
44472
|
-
if (!
|
|
44420
|
+
if (!fs21.existsSync(checkpointDir)) {
|
|
44473
44421
|
return `Error: Checkpoint directory for phase ${targetPhase} does not exist.`;
|
|
44474
44422
|
}
|
|
44475
|
-
const checkpointFiles =
|
|
44423
|
+
const checkpointFiles = fs21.readdirSync(checkpointDir);
|
|
44476
44424
|
if (checkpointFiles.length === 0) {
|
|
44477
44425
|
return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
|
|
44478
44426
|
}
|
|
@@ -44483,7 +44431,7 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44483
44431
|
const src = path32.join(checkpointDir, file3);
|
|
44484
44432
|
const dest = path32.join(swarmDir, file3);
|
|
44485
44433
|
try {
|
|
44486
|
-
|
|
44434
|
+
fs21.cpSync(src, dest, { recursive: true, force: true });
|
|
44487
44435
|
successes.push(file3);
|
|
44488
44436
|
} catch (error93) {
|
|
44489
44437
|
failures.push({ file: file3, error: error93.message });
|
|
@@ -44500,7 +44448,7 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44500
44448
|
timestamp: new Date().toISOString()
|
|
44501
44449
|
};
|
|
44502
44450
|
try {
|
|
44503
|
-
|
|
44451
|
+
fs21.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
|
|
44504
44452
|
`);
|
|
44505
44453
|
} catch (error93) {
|
|
44506
44454
|
console.error("Failed to write rollback event:", error93 instanceof Error ? error93.message : String(error93));
|
|
@@ -44543,11 +44491,11 @@ async function handleSimulateCommand(directory, args) {
|
|
|
44543
44491
|
];
|
|
44544
44492
|
const report = reportLines.filter(Boolean).join(`
|
|
44545
44493
|
`);
|
|
44546
|
-
const
|
|
44494
|
+
const fs22 = await import("fs/promises");
|
|
44547
44495
|
const path33 = await import("path");
|
|
44548
44496
|
const reportPath = path33.join(directory, ".swarm", "simulate-report.md");
|
|
44549
|
-
await
|
|
44550
|
-
await
|
|
44497
|
+
await fs22.mkdir(path33.dirname(reportPath), { recursive: true });
|
|
44498
|
+
await fs22.writeFile(reportPath, report, "utf-8");
|
|
44551
44499
|
return `${darkMatterPairs.length} hidden coupling pairs detected`;
|
|
44552
44500
|
}
|
|
44553
44501
|
|
|
@@ -45080,19 +45028,19 @@ function resolveCommand(tokens) {
|
|
|
45080
45028
|
}
|
|
45081
45029
|
|
|
45082
45030
|
// src/cli/index.ts
|
|
45083
|
-
var CONFIG_DIR = path33.join(process.env.XDG_CONFIG_HOME || path33.join(
|
|
45031
|
+
var CONFIG_DIR = path33.join(process.env.XDG_CONFIG_HOME || path33.join(os6.homedir(), ".config"), "opencode");
|
|
45084
45032
|
var OPENCODE_CONFIG_PATH = path33.join(CONFIG_DIR, "opencode.json");
|
|
45085
45033
|
var PLUGIN_CONFIG_PATH = path33.join(CONFIG_DIR, "opencode-swarm.json");
|
|
45086
45034
|
var PROMPTS_DIR = path33.join(CONFIG_DIR, "opencode-swarm");
|
|
45087
|
-
var OPENCODE_PLUGIN_CACHE_PATH = path33.join(process.env.XDG_CACHE_HOME || path33.join(
|
|
45035
|
+
var OPENCODE_PLUGIN_CACHE_PATH = path33.join(process.env.XDG_CACHE_HOME || path33.join(os6.homedir(), ".cache"), "opencode", "packages", "opencode-swarm@latest");
|
|
45088
45036
|
function ensureDir(dir) {
|
|
45089
|
-
if (!
|
|
45090
|
-
|
|
45037
|
+
if (!fs22.existsSync(dir)) {
|
|
45038
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
45091
45039
|
}
|
|
45092
45040
|
}
|
|
45093
45041
|
function loadJson(filepath) {
|
|
45094
45042
|
try {
|
|
45095
|
-
const content =
|
|
45043
|
+
const content = fs22.readFileSync(filepath, "utf-8");
|
|
45096
45044
|
const stripped = content.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (match, comment) => comment ? "" : match).replace(/,(\s*[}\]])/g, "$1");
|
|
45097
45045
|
return JSON.parse(stripped);
|
|
45098
45046
|
} catch {
|
|
@@ -45100,7 +45048,7 @@ function loadJson(filepath) {
|
|
|
45100
45048
|
}
|
|
45101
45049
|
}
|
|
45102
45050
|
function saveJson(filepath, data) {
|
|
45103
|
-
|
|
45051
|
+
fs22.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
|
|
45104
45052
|
`, "utf-8");
|
|
45105
45053
|
}
|
|
45106
45054
|
async function install() {
|
|
@@ -45140,15 +45088,15 @@ async function install() {
|
|
|
45140
45088
|
console.log("\u2713 Added opencode-swarm to OpenCode plugins");
|
|
45141
45089
|
console.log("\u2713 Disabled default OpenCode agents (explore, general)");
|
|
45142
45090
|
try {
|
|
45143
|
-
if (
|
|
45144
|
-
|
|
45091
|
+
if (fs22.existsSync(OPENCODE_PLUGIN_CACHE_PATH)) {
|
|
45092
|
+
fs22.rmSync(OPENCODE_PLUGIN_CACHE_PATH, { recursive: true, force: true });
|
|
45145
45093
|
console.log("\u2713 Cleared opencode plugin cache (next start will fetch latest)");
|
|
45146
45094
|
}
|
|
45147
45095
|
} catch {
|
|
45148
45096
|
console.warn("\u26A0 Could not clear opencode plugin cache \u2014 you may need to delete it manually:");
|
|
45149
45097
|
console.warn(` ${OPENCODE_PLUGIN_CACHE_PATH}`);
|
|
45150
45098
|
}
|
|
45151
|
-
if (!
|
|
45099
|
+
if (!fs22.existsSync(PLUGIN_CONFIG_PATH)) {
|
|
45152
45100
|
const defaultConfig = {
|
|
45153
45101
|
agents: {
|
|
45154
45102
|
coder: {
|
|
@@ -45251,7 +45199,7 @@ async function uninstall() {
|
|
|
45251
45199
|
`);
|
|
45252
45200
|
const opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
|
|
45253
45201
|
if (!opencodeConfig) {
|
|
45254
|
-
if (
|
|
45202
|
+
if (fs22.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
45255
45203
|
console.log(`\u2717 Could not parse opencode config at: ${OPENCODE_CONFIG_PATH}`);
|
|
45256
45204
|
return 1;
|
|
45257
45205
|
} else {
|
|
@@ -45283,13 +45231,13 @@ async function uninstall() {
|
|
|
45283
45231
|
console.log("\u2713 Re-enabled default OpenCode agents (explore, general)");
|
|
45284
45232
|
if (process.argv.includes("--clean")) {
|
|
45285
45233
|
let cleaned = false;
|
|
45286
|
-
if (
|
|
45287
|
-
|
|
45234
|
+
if (fs22.existsSync(PLUGIN_CONFIG_PATH)) {
|
|
45235
|
+
fs22.unlinkSync(PLUGIN_CONFIG_PATH);
|
|
45288
45236
|
console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
|
|
45289
45237
|
cleaned = true;
|
|
45290
45238
|
}
|
|
45291
|
-
if (
|
|
45292
|
-
|
|
45239
|
+
if (fs22.existsSync(PROMPTS_DIR)) {
|
|
45240
|
+
fs22.rmSync(PROMPTS_DIR, { recursive: true });
|
|
45293
45241
|
console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
|
|
45294
45242
|
cleaned = true;
|
|
45295
45243
|
}
|