opencode-swarm 6.85.2 → 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 +363 -410
- package/dist/index.js +1034 -1085
- package/dist/services/diagnose-service.d.ts +10 -0
- 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",
|
|
@@ -36305,6 +36393,12 @@ async function checkConfigParseability(directory) {
|
|
|
36305
36393
|
};
|
|
36306
36394
|
}
|
|
36307
36395
|
}
|
|
36396
|
+
function resolveGrammarDir(thisDir) {
|
|
36397
|
+
const normalized = thisDir.replace(/\\/g, "/");
|
|
36398
|
+
const isSource = normalized.endsWith("/src/services");
|
|
36399
|
+
const isCliBundle = normalized.endsWith("/cli");
|
|
36400
|
+
return isSource || isCliBundle ? path18.join(thisDir, "..", "lang", "grammars") : path18.join(thisDir, "lang", "grammars");
|
|
36401
|
+
}
|
|
36308
36402
|
async function checkGrammarWasmFiles() {
|
|
36309
36403
|
const grammarFiles = [
|
|
36310
36404
|
"tree-sitter-javascript.wasm",
|
|
@@ -36327,15 +36421,14 @@ async function checkGrammarWasmFiles() {
|
|
|
36327
36421
|
"tree-sitter-ini.wasm",
|
|
36328
36422
|
"tree-sitter-regex.wasm"
|
|
36329
36423
|
];
|
|
36330
|
-
const thisDir =
|
|
36331
|
-
const
|
|
36332
|
-
const grammarDir = isSource ? path17.join(thisDir, "..", "lang", "grammars") : path17.join(thisDir, "lang", "grammars");
|
|
36424
|
+
const thisDir = path18.dirname(fileURLToPath(import.meta.url));
|
|
36425
|
+
const grammarDir = resolveGrammarDir(thisDir);
|
|
36333
36426
|
const missing = [];
|
|
36334
|
-
if (!existsSync8(
|
|
36427
|
+
if (!existsSync8(path18.join(grammarDir, "tree-sitter.wasm"))) {
|
|
36335
36428
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
36336
36429
|
}
|
|
36337
36430
|
for (const file3 of grammarFiles) {
|
|
36338
|
-
if (!existsSync8(
|
|
36431
|
+
if (!existsSync8(path18.join(grammarDir, file3))) {
|
|
36339
36432
|
missing.push(file3);
|
|
36340
36433
|
}
|
|
36341
36434
|
}
|
|
@@ -36353,7 +36446,7 @@ async function checkGrammarWasmFiles() {
|
|
|
36353
36446
|
};
|
|
36354
36447
|
}
|
|
36355
36448
|
async function checkCheckpointManifest(directory) {
|
|
36356
|
-
const manifestPath =
|
|
36449
|
+
const manifestPath = path18.join(directory, ".swarm/checkpoints.json");
|
|
36357
36450
|
if (!existsSync8(manifestPath)) {
|
|
36358
36451
|
return {
|
|
36359
36452
|
name: "Checkpoint Manifest",
|
|
@@ -36405,7 +36498,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
36405
36498
|
}
|
|
36406
36499
|
}
|
|
36407
36500
|
async function checkEventStreamIntegrity(directory) {
|
|
36408
|
-
const eventsPath =
|
|
36501
|
+
const eventsPath = path18.join(directory, ".swarm/events.jsonl");
|
|
36409
36502
|
if (!existsSync8(eventsPath)) {
|
|
36410
36503
|
return {
|
|
36411
36504
|
name: "Event Stream",
|
|
@@ -36446,7 +36539,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
36446
36539
|
}
|
|
36447
36540
|
}
|
|
36448
36541
|
async function checkSteeringDirectives(directory) {
|
|
36449
|
-
const eventsPath =
|
|
36542
|
+
const eventsPath = path18.join(directory, ".swarm/events.jsonl");
|
|
36450
36543
|
if (!existsSync8(eventsPath)) {
|
|
36451
36544
|
return {
|
|
36452
36545
|
name: "Steering Directives",
|
|
@@ -36502,7 +36595,7 @@ async function checkCurator(directory) {
|
|
|
36502
36595
|
detail: "Disabled (enable via curator.enabled)"
|
|
36503
36596
|
};
|
|
36504
36597
|
}
|
|
36505
|
-
const summaryPath =
|
|
36598
|
+
const summaryPath = path18.join(directory, ".swarm/curator-summary.json");
|
|
36506
36599
|
if (!existsSync8(summaryPath)) {
|
|
36507
36600
|
return {
|
|
36508
36601
|
name: "Curator",
|
|
@@ -36650,7 +36743,7 @@ async function getDiagnoseData(directory) {
|
|
|
36650
36743
|
checks5.push(await checkSteeringDirectives(directory));
|
|
36651
36744
|
checks5.push(await checkCurator(directory));
|
|
36652
36745
|
try {
|
|
36653
|
-
const evidenceDir =
|
|
36746
|
+
const evidenceDir = path18.join(directory, ".swarm", "evidence");
|
|
36654
36747
|
const snapshotFiles = existsSync8(evidenceDir) ? readdirSync4(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
|
|
36655
36748
|
if (snapshotFiles.length > 0) {
|
|
36656
36749
|
const latest = snapshotFiles.sort().pop();
|
|
@@ -36703,11 +36796,11 @@ init_config_doctor();
|
|
|
36703
36796
|
|
|
36704
36797
|
// src/services/tool-doctor.ts
|
|
36705
36798
|
import * as fs11 from "fs";
|
|
36706
|
-
import * as
|
|
36799
|
+
import * as path21 from "path";
|
|
36707
36800
|
|
|
36708
36801
|
// src/build/discovery.ts
|
|
36709
36802
|
import * as fs10 from "fs";
|
|
36710
|
-
import * as
|
|
36803
|
+
import * as path20 from "path";
|
|
36711
36804
|
|
|
36712
36805
|
// src/lang/detector.ts
|
|
36713
36806
|
import { access as access2, readdir as readdir2 } from "fs/promises";
|
|
@@ -37211,7 +37304,7 @@ LANGUAGE_REGISTRY.register({
|
|
|
37211
37304
|
displayName: "C# / .NET",
|
|
37212
37305
|
tier: 2,
|
|
37213
37306
|
extensions: [".cs", ".csx"],
|
|
37214
|
-
treeSitter: { grammarId: "
|
|
37307
|
+
treeSitter: { grammarId: "csharp", wasmFile: "tree-sitter-c-sharp.wasm" },
|
|
37215
37308
|
build: {
|
|
37216
37309
|
detectFiles: ["*.csproj", "*.sln", "Directory.Build.props"],
|
|
37217
37310
|
commands: [
|
|
@@ -37871,11 +37964,11 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37871
37964
|
const regex = simpleGlobToRegex(pattern);
|
|
37872
37965
|
const matches = files.filter((f) => regex.test(f));
|
|
37873
37966
|
if (matches.length > 0) {
|
|
37874
|
-
return
|
|
37967
|
+
return path20.join(dir, matches[0]);
|
|
37875
37968
|
}
|
|
37876
37969
|
} catch {}
|
|
37877
37970
|
} else {
|
|
37878
|
-
const filePath =
|
|
37971
|
+
const filePath = path20.join(workingDir, pattern);
|
|
37879
37972
|
if (fs10.existsSync(filePath)) {
|
|
37880
37973
|
return filePath;
|
|
37881
37974
|
}
|
|
@@ -37884,7 +37977,7 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37884
37977
|
return null;
|
|
37885
37978
|
}
|
|
37886
37979
|
function getRepoDefinedScripts(workingDir, scripts) {
|
|
37887
|
-
const packageJsonPath =
|
|
37980
|
+
const packageJsonPath = path20.join(workingDir, "package.json");
|
|
37888
37981
|
if (!fs10.existsSync(packageJsonPath)) {
|
|
37889
37982
|
return [];
|
|
37890
37983
|
}
|
|
@@ -37925,7 +38018,7 @@ function findAllBuildFiles(workingDir) {
|
|
|
37925
38018
|
const regex = simpleGlobToRegex(pattern);
|
|
37926
38019
|
findFilesRecursive(workingDir, regex, allBuildFiles);
|
|
37927
38020
|
} else {
|
|
37928
|
-
const filePath =
|
|
38021
|
+
const filePath = path20.join(workingDir, pattern);
|
|
37929
38022
|
if (fs10.existsSync(filePath)) {
|
|
37930
38023
|
allBuildFiles.add(filePath);
|
|
37931
38024
|
}
|
|
@@ -37938,7 +38031,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
37938
38031
|
try {
|
|
37939
38032
|
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
37940
38033
|
for (const entry of entries) {
|
|
37941
|
-
const fullPath =
|
|
38034
|
+
const fullPath = path20.join(dir, entry.name);
|
|
37942
38035
|
if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
|
|
37943
38036
|
findFilesRecursive(fullPath, regex, results);
|
|
37944
38037
|
} else if (entry.isFile() && regex.test(entry.name)) {
|
|
@@ -37961,7 +38054,7 @@ async function discoverBuildCommandsFromProfiles(workingDir) {
|
|
|
37961
38054
|
let foundCommand = false;
|
|
37962
38055
|
for (const cmd of sortedCommands) {
|
|
37963
38056
|
if (cmd.detectFile) {
|
|
37964
|
-
const detectFilePath =
|
|
38057
|
+
const detectFilePath = path20.join(workingDir, cmd.detectFile);
|
|
37965
38058
|
if (!fs10.existsSync(detectFilePath)) {
|
|
37966
38059
|
continue;
|
|
37967
38060
|
}
|
|
@@ -38136,8 +38229,8 @@ function checkBinaryReadiness() {
|
|
|
38136
38229
|
}
|
|
38137
38230
|
function runToolDoctor(_directory, pluginRoot) {
|
|
38138
38231
|
const findings = [];
|
|
38139
|
-
const resolvedPluginRoot = pluginRoot ??
|
|
38140
|
-
const indexPath =
|
|
38232
|
+
const resolvedPluginRoot = pluginRoot ?? path21.resolve(import.meta.dir, "..", "..");
|
|
38233
|
+
const indexPath = path21.join(resolvedPluginRoot, "src", "index.ts");
|
|
38141
38234
|
if (!fs11.existsSync(indexPath)) {
|
|
38142
38235
|
return {
|
|
38143
38236
|
findings: [
|
|
@@ -39060,10 +39153,10 @@ async function handleHistoryCommand(directory, _args) {
|
|
|
39060
39153
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
39061
39154
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
39062
39155
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
39063
|
-
import * as
|
|
39156
|
+
import * as path22 from "path";
|
|
39064
39157
|
async function migrateContextToKnowledge(directory, config3) {
|
|
39065
|
-
const sentinelPath =
|
|
39066
|
-
const contextPath =
|
|
39158
|
+
const sentinelPath = path22.join(directory, ".swarm", ".knowledge-migrated");
|
|
39159
|
+
const contextPath = path22.join(directory, ".swarm", "context.md");
|
|
39067
39160
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
39068
39161
|
if (existsSync12(sentinelPath)) {
|
|
39069
39162
|
return {
|
|
@@ -39259,7 +39352,7 @@ function truncateLesson(text) {
|
|
|
39259
39352
|
return `${text.slice(0, 277)}...`;
|
|
39260
39353
|
}
|
|
39261
39354
|
function inferProjectName(directory) {
|
|
39262
|
-
const packageJsonPath =
|
|
39355
|
+
const packageJsonPath = path22.join(directory, "package.json");
|
|
39263
39356
|
if (existsSync12(packageJsonPath)) {
|
|
39264
39357
|
try {
|
|
39265
39358
|
const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
@@ -39268,7 +39361,7 @@ function inferProjectName(directory) {
|
|
|
39268
39361
|
}
|
|
39269
39362
|
} catch {}
|
|
39270
39363
|
}
|
|
39271
|
-
return
|
|
39364
|
+
return path22.basename(directory);
|
|
39272
39365
|
}
|
|
39273
39366
|
async function writeSentinel(sentinelPath, migrated, dropped) {
|
|
39274
39367
|
const sentinel = {
|
|
@@ -39280,7 +39373,7 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
|
|
|
39280
39373
|
schema_version: 1,
|
|
39281
39374
|
migration_tool: "knowledge-migrator.ts"
|
|
39282
39375
|
};
|
|
39283
|
-
await mkdir3(
|
|
39376
|
+
await mkdir3(path22.dirname(sentinelPath), { recursive: true });
|
|
39284
39377
|
await writeFile4(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
|
|
39285
39378
|
}
|
|
39286
39379
|
|
|
@@ -39517,11 +39610,12 @@ async function handlePlanCommand(directory, args) {
|
|
|
39517
39610
|
init_manager2();
|
|
39518
39611
|
init_manager();
|
|
39519
39612
|
import * as fs18 from "fs";
|
|
39520
|
-
import * as
|
|
39613
|
+
import * as path29 from "path";
|
|
39521
39614
|
|
|
39522
39615
|
// src/tools/lint.ts
|
|
39616
|
+
init_zod();
|
|
39523
39617
|
import * as fs12 from "fs";
|
|
39524
|
-
import * as
|
|
39618
|
+
import * as path23 from "path";
|
|
39525
39619
|
init_utils();
|
|
39526
39620
|
|
|
39527
39621
|
// src/utils/path-security.ts
|
|
@@ -39567,9 +39661,9 @@ function validateArgs(args) {
|
|
|
39567
39661
|
}
|
|
39568
39662
|
function getLinterCommand(linter, mode, projectDir) {
|
|
39569
39663
|
const isWindows = process.platform === "win32";
|
|
39570
|
-
const binDir =
|
|
39571
|
-
const biomeBin = isWindows ?
|
|
39572
|
-
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");
|
|
39573
39667
|
switch (linter) {
|
|
39574
39668
|
case "biome":
|
|
39575
39669
|
if (mode === "fix") {
|
|
@@ -39585,7 +39679,7 @@ function getLinterCommand(linter, mode, projectDir) {
|
|
|
39585
39679
|
}
|
|
39586
39680
|
function getAdditionalLinterCommand(linter, mode, cwd) {
|
|
39587
39681
|
const gradlewName = process.platform === "win32" ? "gradlew.bat" : "gradlew";
|
|
39588
|
-
const gradlew = fs12.existsSync(
|
|
39682
|
+
const gradlew = fs12.existsSync(path23.join(cwd, gradlewName)) ? path23.join(cwd, gradlewName) : null;
|
|
39589
39683
|
switch (linter) {
|
|
39590
39684
|
case "ruff":
|
|
39591
39685
|
return mode === "fix" ? ["ruff", "check", "--fix", "."] : ["ruff", "check", "."];
|
|
@@ -39619,10 +39713,10 @@ function getAdditionalLinterCommand(linter, mode, cwd) {
|
|
|
39619
39713
|
}
|
|
39620
39714
|
}
|
|
39621
39715
|
function detectRuff(cwd) {
|
|
39622
|
-
if (fs12.existsSync(
|
|
39716
|
+
if (fs12.existsSync(path23.join(cwd, "ruff.toml")))
|
|
39623
39717
|
return isCommandAvailable("ruff");
|
|
39624
39718
|
try {
|
|
39625
|
-
const pyproject =
|
|
39719
|
+
const pyproject = path23.join(cwd, "pyproject.toml");
|
|
39626
39720
|
if (fs12.existsSync(pyproject)) {
|
|
39627
39721
|
const content = fs12.readFileSync(pyproject, "utf-8");
|
|
39628
39722
|
if (content.includes("[tool.ruff]"))
|
|
@@ -39632,19 +39726,19 @@ function detectRuff(cwd) {
|
|
|
39632
39726
|
return false;
|
|
39633
39727
|
}
|
|
39634
39728
|
function detectClippy(cwd) {
|
|
39635
|
-
return fs12.existsSync(
|
|
39729
|
+
return fs12.existsSync(path23.join(cwd, "Cargo.toml")) && isCommandAvailable("cargo");
|
|
39636
39730
|
}
|
|
39637
39731
|
function detectGolangciLint(cwd) {
|
|
39638
|
-
return fs12.existsSync(
|
|
39732
|
+
return fs12.existsSync(path23.join(cwd, "go.mod")) && isCommandAvailable("golangci-lint");
|
|
39639
39733
|
}
|
|
39640
39734
|
function detectCheckstyle(cwd) {
|
|
39641
|
-
const hasMaven = fs12.existsSync(
|
|
39642
|
-
const hasGradle = fs12.existsSync(
|
|
39643
|
-
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"));
|
|
39644
39738
|
return (hasMaven || hasGradle) && hasBinary;
|
|
39645
39739
|
}
|
|
39646
39740
|
function detectKtlint(cwd) {
|
|
39647
|
-
const hasKotlin = fs12.existsSync(
|
|
39741
|
+
const hasKotlin = fs12.existsSync(path23.join(cwd, "build.gradle.kts")) || fs12.existsSync(path23.join(cwd, "build.gradle")) || (() => {
|
|
39648
39742
|
try {
|
|
39649
39743
|
return fs12.readdirSync(cwd).some((f) => f.endsWith(".kt") || f.endsWith(".kts"));
|
|
39650
39744
|
} catch {
|
|
@@ -39663,11 +39757,11 @@ function detectDotnetFormat(cwd) {
|
|
|
39663
39757
|
}
|
|
39664
39758
|
}
|
|
39665
39759
|
function detectCppcheck(cwd) {
|
|
39666
|
-
if (fs12.existsSync(
|
|
39760
|
+
if (fs12.existsSync(path23.join(cwd, "CMakeLists.txt"))) {
|
|
39667
39761
|
return isCommandAvailable("cppcheck");
|
|
39668
39762
|
}
|
|
39669
39763
|
try {
|
|
39670
|
-
const dirsToCheck = [cwd,
|
|
39764
|
+
const dirsToCheck = [cwd, path23.join(cwd, "src")];
|
|
39671
39765
|
const hasCpp = dirsToCheck.some((dir) => {
|
|
39672
39766
|
try {
|
|
39673
39767
|
return fs12.readdirSync(dir).some((f) => /\.(c|cpp|cc|cxx|h|hpp)$/.test(f));
|
|
@@ -39681,13 +39775,13 @@ function detectCppcheck(cwd) {
|
|
|
39681
39775
|
}
|
|
39682
39776
|
}
|
|
39683
39777
|
function detectSwiftlint(cwd) {
|
|
39684
|
-
return fs12.existsSync(
|
|
39778
|
+
return fs12.existsSync(path23.join(cwd, "Package.swift")) && isCommandAvailable("swiftlint");
|
|
39685
39779
|
}
|
|
39686
39780
|
function detectDartAnalyze(cwd) {
|
|
39687
|
-
return fs12.existsSync(
|
|
39781
|
+
return fs12.existsSync(path23.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
39688
39782
|
}
|
|
39689
39783
|
function detectRubocop(cwd) {
|
|
39690
|
-
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"));
|
|
39691
39785
|
}
|
|
39692
39786
|
function detectAdditionalLinter(cwd) {
|
|
39693
39787
|
if (detectRuff(cwd))
|
|
@@ -39715,10 +39809,10 @@ function detectAdditionalLinter(cwd) {
|
|
|
39715
39809
|
function findBinInAncestors(startDir, binName) {
|
|
39716
39810
|
let dir = startDir;
|
|
39717
39811
|
while (true) {
|
|
39718
|
-
const candidate =
|
|
39812
|
+
const candidate = path23.join(dir, "node_modules", ".bin", binName);
|
|
39719
39813
|
if (fs12.existsSync(candidate))
|
|
39720
39814
|
return candidate;
|
|
39721
|
-
const parent =
|
|
39815
|
+
const parent = path23.dirname(dir);
|
|
39722
39816
|
if (parent === dir)
|
|
39723
39817
|
break;
|
|
39724
39818
|
dir = parent;
|
|
@@ -39727,10 +39821,10 @@ function findBinInAncestors(startDir, binName) {
|
|
|
39727
39821
|
}
|
|
39728
39822
|
function findBinInEnvPath(binName) {
|
|
39729
39823
|
const searchPath = process.env.PATH ?? "";
|
|
39730
|
-
for (const dir of searchPath.split(
|
|
39824
|
+
for (const dir of searchPath.split(path23.delimiter)) {
|
|
39731
39825
|
if (!dir)
|
|
39732
39826
|
continue;
|
|
39733
|
-
const candidate =
|
|
39827
|
+
const candidate = path23.join(dir, binName);
|
|
39734
39828
|
if (fs12.existsSync(candidate))
|
|
39735
39829
|
return candidate;
|
|
39736
39830
|
}
|
|
@@ -39743,13 +39837,13 @@ async function detectAvailableLinter(directory) {
|
|
|
39743
39837
|
return null;
|
|
39744
39838
|
const projectDir = directory;
|
|
39745
39839
|
const isWindows = process.platform === "win32";
|
|
39746
|
-
const biomeBin = isWindows ?
|
|
39747
|
-
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");
|
|
39748
39842
|
const localResult = await _detectAvailableLinter(projectDir, biomeBin, eslintBin);
|
|
39749
39843
|
if (localResult)
|
|
39750
39844
|
return localResult;
|
|
39751
|
-
const biomeAncestor = findBinInAncestors(
|
|
39752
|
-
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");
|
|
39753
39847
|
if (biomeAncestor || eslintAncestor) {
|
|
39754
39848
|
return _detectAvailableLinter(projectDir, biomeAncestor ?? biomeBin, eslintAncestor ?? eslintBin);
|
|
39755
39849
|
}
|
|
@@ -39911,7 +40005,7 @@ async function runAdditionalLint(linter, mode, cwd) {
|
|
|
39911
40005
|
var lint = createSwarmTool({
|
|
39912
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.",
|
|
39913
40007
|
args: {
|
|
39914
|
-
mode:
|
|
40008
|
+
mode: exports_external.enum(["fix", "check"]).describe('Linting mode: "check" for read-only lint check, "fix" to automatically apply fixes')
|
|
39915
40009
|
},
|
|
39916
40010
|
async execute(args, directory) {
|
|
39917
40011
|
if (!validateArgs(args)) {
|
|
@@ -39956,8 +40050,9 @@ For Rust: rustup component add clippy`
|
|
|
39956
40050
|
});
|
|
39957
40051
|
|
|
39958
40052
|
// src/tools/secretscan.ts
|
|
40053
|
+
init_zod();
|
|
39959
40054
|
import * as fs13 from "fs";
|
|
39960
|
-
import * as
|
|
40055
|
+
import * as path24 from "path";
|
|
39961
40056
|
var MAX_FILE_PATH_LENGTH = 500;
|
|
39962
40057
|
var MAX_FILE_SIZE_BYTES = 512 * 1024;
|
|
39963
40058
|
var MAX_FILES_SCANNED = 1000;
|
|
@@ -40184,7 +40279,7 @@ function isGlobOrPathPattern(pattern) {
|
|
|
40184
40279
|
return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
|
|
40185
40280
|
}
|
|
40186
40281
|
function loadSecretScanIgnore(scanDir) {
|
|
40187
|
-
const ignorePath =
|
|
40282
|
+
const ignorePath = path24.join(scanDir, ".secretscanignore");
|
|
40188
40283
|
try {
|
|
40189
40284
|
if (!fs13.existsSync(ignorePath))
|
|
40190
40285
|
return [];
|
|
@@ -40207,7 +40302,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
|
|
|
40207
40302
|
if (exactNames.has(entry))
|
|
40208
40303
|
return true;
|
|
40209
40304
|
for (const pattern of globPatterns) {
|
|
40210
|
-
if (
|
|
40305
|
+
if (path24.matchesGlob(relPath, pattern))
|
|
40211
40306
|
return true;
|
|
40212
40307
|
}
|
|
40213
40308
|
return false;
|
|
@@ -40228,7 +40323,7 @@ function validateDirectoryInput(dir) {
|
|
|
40228
40323
|
return null;
|
|
40229
40324
|
}
|
|
40230
40325
|
function isBinaryFile(filePath, buffer) {
|
|
40231
|
-
const ext =
|
|
40326
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
40232
40327
|
if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
40233
40328
|
return true;
|
|
40234
40329
|
}
|
|
@@ -40365,9 +40460,9 @@ function isSymlinkLoop(realPath, visited) {
|
|
|
40365
40460
|
return false;
|
|
40366
40461
|
}
|
|
40367
40462
|
function isPathWithinScope(realPath, scanDir) {
|
|
40368
|
-
const resolvedScanDir =
|
|
40369
|
-
const resolvedRealPath =
|
|
40370
|
-
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}\\`);
|
|
40371
40466
|
}
|
|
40372
40467
|
function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
|
|
40373
40468
|
skippedDirs: 0,
|
|
@@ -40393,8 +40488,8 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
|
|
|
40393
40488
|
return a.localeCompare(b);
|
|
40394
40489
|
});
|
|
40395
40490
|
for (const entry of entries) {
|
|
40396
|
-
const fullPath =
|
|
40397
|
-
const relPath =
|
|
40491
|
+
const fullPath = path24.join(dir, entry);
|
|
40492
|
+
const relPath = path24.relative(scanDir, fullPath).replace(/\\/g, "/");
|
|
40398
40493
|
if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
|
|
40399
40494
|
stats.skippedDirs++;
|
|
40400
40495
|
continue;
|
|
@@ -40429,7 +40524,7 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
|
|
|
40429
40524
|
const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
|
|
40430
40525
|
files.push(...subFiles);
|
|
40431
40526
|
} else if (lstat.isFile()) {
|
|
40432
|
-
const ext =
|
|
40527
|
+
const ext = path24.extname(fullPath).toLowerCase();
|
|
40433
40528
|
if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
40434
40529
|
files.push(fullPath);
|
|
40435
40530
|
} else {
|
|
@@ -40442,8 +40537,8 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
|
|
|
40442
40537
|
var secretscan = createSwarmTool({
|
|
40443
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.",
|
|
40444
40539
|
args: {
|
|
40445
|
-
directory:
|
|
40446
|
-
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.")
|
|
40447
40542
|
},
|
|
40448
40543
|
async execute(args, _directory, _ctx) {
|
|
40449
40544
|
const typedArgs = args;
|
|
@@ -40495,7 +40590,7 @@ var secretscan = createSwarmTool({
|
|
|
40495
40590
|
}
|
|
40496
40591
|
}
|
|
40497
40592
|
try {
|
|
40498
|
-
const _scanDirRaw =
|
|
40593
|
+
const _scanDirRaw = path24.resolve(directory);
|
|
40499
40594
|
const scanDir = (() => {
|
|
40500
40595
|
try {
|
|
40501
40596
|
return fs13.realpathSync(_scanDirRaw);
|
|
@@ -40637,7 +40732,8 @@ var secretscan = createSwarmTool({
|
|
|
40637
40732
|
async function runSecretscan(directory) {
|
|
40638
40733
|
try {
|
|
40639
40734
|
const result = await secretscan.execute({ directory }, {});
|
|
40640
|
-
|
|
40735
|
+
const jsonStr = typeof result === "string" ? result : result.output;
|
|
40736
|
+
return JSON.parse(jsonStr);
|
|
40641
40737
|
} catch (e) {
|
|
40642
40738
|
const errorResult = {
|
|
40643
40739
|
error: e instanceof Error ? `scan failed: ${e.message}` : "scan failed: unknown error",
|
|
@@ -40652,12 +40748,13 @@ async function runSecretscan(directory) {
|
|
|
40652
40748
|
}
|
|
40653
40749
|
|
|
40654
40750
|
// src/tools/test-runner.ts
|
|
40751
|
+
init_zod();
|
|
40655
40752
|
import * as fs17 from "fs";
|
|
40656
|
-
import * as
|
|
40753
|
+
import * as path28 from "path";
|
|
40657
40754
|
|
|
40658
40755
|
// src/test-impact/analyzer.ts
|
|
40659
40756
|
import fs14 from "fs";
|
|
40660
|
-
import
|
|
40757
|
+
import path25 from "path";
|
|
40661
40758
|
var IMPORT_REGEX_ES = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
40662
40759
|
var IMPORT_REGEX_REQUIRE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
40663
40760
|
var IMPORT_REGEX_REEXPORT = /export\s+(?:\{[^}]*\}|\*)\s+from\s+['"]([^'"]+)['"]/g;
|
|
@@ -40682,8 +40779,8 @@ function resolveRelativeImport(fromDir, importPath) {
|
|
|
40682
40779
|
if (!importPath.startsWith(".")) {
|
|
40683
40780
|
return null;
|
|
40684
40781
|
}
|
|
40685
|
-
const resolved =
|
|
40686
|
-
if (
|
|
40782
|
+
const resolved = path25.resolve(fromDir, importPath);
|
|
40783
|
+
if (path25.extname(resolved)) {
|
|
40687
40784
|
if (fs14.existsSync(resolved) && fs14.statSync(resolved).isFile()) {
|
|
40688
40785
|
return normalizePath(resolved);
|
|
40689
40786
|
}
|
|
@@ -40728,12 +40825,12 @@ function findTestFilesSync(cwd) {
|
|
|
40728
40825
|
for (const entry of entries) {
|
|
40729
40826
|
if (entry.isDirectory()) {
|
|
40730
40827
|
if (!skipDirs.has(entry.name)) {
|
|
40731
|
-
walk(
|
|
40828
|
+
walk(path25.join(dir, entry.name), visitedInodes);
|
|
40732
40829
|
}
|
|
40733
40830
|
} else if (entry.isFile()) {
|
|
40734
40831
|
const name = entry.name;
|
|
40735
40832
|
if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(name) || dir.includes("__tests__") && /\.(ts|tsx|js|jsx)$/.test(name)) {
|
|
40736
|
-
testFiles.push(normalizePath(
|
|
40833
|
+
testFiles.push(normalizePath(path25.join(dir, entry.name)));
|
|
40737
40834
|
}
|
|
40738
40835
|
}
|
|
40739
40836
|
}
|
|
@@ -40771,7 +40868,7 @@ async function buildImpactMapInternal(cwd) {
|
|
|
40771
40868
|
continue;
|
|
40772
40869
|
}
|
|
40773
40870
|
const imports = extractImports(content);
|
|
40774
|
-
const testDir =
|
|
40871
|
+
const testDir = path25.dirname(testFile);
|
|
40775
40872
|
for (const importPath of imports) {
|
|
40776
40873
|
const resolvedSource = resolveRelativeImport(testDir, importPath);
|
|
40777
40874
|
if (resolvedSource === null) {
|
|
@@ -40793,7 +40890,7 @@ async function buildImpactMap(cwd) {
|
|
|
40793
40890
|
return impactMap;
|
|
40794
40891
|
}
|
|
40795
40892
|
async function loadImpactMap(cwd) {
|
|
40796
|
-
const cachePath =
|
|
40893
|
+
const cachePath = path25.join(cwd, ".swarm", "cache", "impact-map.json");
|
|
40797
40894
|
if (fs14.existsSync(cachePath)) {
|
|
40798
40895
|
try {
|
|
40799
40896
|
const content = fs14.readFileSync(cachePath, "utf-8");
|
|
@@ -40808,8 +40905,8 @@ async function loadImpactMap(cwd) {
|
|
|
40808
40905
|
return buildImpactMap(cwd);
|
|
40809
40906
|
}
|
|
40810
40907
|
async function saveImpactMap(cwd, impactMap) {
|
|
40811
|
-
const cacheDir =
|
|
40812
|
-
const cachePath =
|
|
40908
|
+
const cacheDir = path25.join(cwd, ".swarm", "cache");
|
|
40909
|
+
const cachePath = path25.join(cacheDir, "impact-map.json");
|
|
40813
40910
|
if (!fs14.existsSync(cacheDir)) {
|
|
40814
40911
|
fs14.mkdirSync(cacheDir, { recursive: true });
|
|
40815
40912
|
}
|
|
@@ -40835,7 +40932,7 @@ async function analyzeImpact(changedFiles, cwd) {
|
|
|
40835
40932
|
const impactedTestsSet = new Set;
|
|
40836
40933
|
const untestedFiles = [];
|
|
40837
40934
|
for (const changedFile of validFiles) {
|
|
40838
|
-
const normalizedChanged = normalizePath(
|
|
40935
|
+
const normalizedChanged = normalizePath(path25.resolve(changedFile));
|
|
40839
40936
|
const tests = impactMap[normalizedChanged];
|
|
40840
40937
|
if (tests && tests.length > 0) {
|
|
40841
40938
|
for (const test of tests) {
|
|
@@ -41082,13 +41179,13 @@ function detectFlakyTests(allHistory) {
|
|
|
41082
41179
|
|
|
41083
41180
|
// src/test-impact/history-store.ts
|
|
41084
41181
|
import fs15 from "fs";
|
|
41085
|
-
import
|
|
41182
|
+
import path26 from "path";
|
|
41086
41183
|
var MAX_HISTORY_PER_TEST = 20;
|
|
41087
41184
|
var MAX_ERROR_LENGTH = 500;
|
|
41088
41185
|
var MAX_STACK_LENGTH = 200;
|
|
41089
41186
|
var MAX_CHANGED_FILES = 50;
|
|
41090
41187
|
function getHistoryPath(workingDir) {
|
|
41091
|
-
return
|
|
41188
|
+
return path26.join(workingDir || process.cwd(), ".swarm", "cache", "test-history.jsonl");
|
|
41092
41189
|
}
|
|
41093
41190
|
function sanitizeErrorMessage(errorMessage) {
|
|
41094
41191
|
if (errorMessage === undefined) {
|
|
@@ -41148,7 +41245,7 @@ function appendTestRun(record3, workingDir) {
|
|
|
41148
41245
|
changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
|
|
41149
41246
|
};
|
|
41150
41247
|
const historyPath = getHistoryPath(workingDir);
|
|
41151
|
-
const historyDir =
|
|
41248
|
+
const historyDir = path26.dirname(historyPath);
|
|
41152
41249
|
if (!fs15.existsSync(historyDir)) {
|
|
41153
41250
|
fs15.mkdirSync(historyDir, { recursive: true });
|
|
41154
41251
|
}
|
|
@@ -41222,7 +41319,7 @@ function getAllHistory(workingDir) {
|
|
|
41222
41319
|
|
|
41223
41320
|
// src/tools/resolve-working-directory.ts
|
|
41224
41321
|
import * as fs16 from "fs";
|
|
41225
|
-
import * as
|
|
41322
|
+
import * as path27 from "path";
|
|
41226
41323
|
function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
41227
41324
|
if (workingDirectory == null || workingDirectory === "") {
|
|
41228
41325
|
return { success: true, directory: fallbackDirectory };
|
|
@@ -41242,15 +41339,15 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
41242
41339
|
};
|
|
41243
41340
|
}
|
|
41244
41341
|
}
|
|
41245
|
-
const normalizedDir =
|
|
41246
|
-
const pathParts = normalizedDir.split(
|
|
41342
|
+
const normalizedDir = path27.normalize(workingDirectory);
|
|
41343
|
+
const pathParts = normalizedDir.split(path27.sep);
|
|
41247
41344
|
if (pathParts.includes("..")) {
|
|
41248
41345
|
return {
|
|
41249
41346
|
success: false,
|
|
41250
41347
|
message: "Invalid working_directory: path traversal sequences (..) are not allowed"
|
|
41251
41348
|
};
|
|
41252
41349
|
}
|
|
41253
|
-
const resolvedDir =
|
|
41350
|
+
const resolvedDir = path27.resolve(normalizedDir);
|
|
41254
41351
|
let statResult;
|
|
41255
41352
|
try {
|
|
41256
41353
|
statResult = fs16.statSync(resolvedDir);
|
|
@@ -41266,7 +41363,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
41266
41363
|
message: `Invalid working_directory: path "${resolvedDir}" is not a directory`
|
|
41267
41364
|
};
|
|
41268
41365
|
}
|
|
41269
|
-
const resolvedFallback =
|
|
41366
|
+
const resolvedFallback = path27.resolve(fallbackDirectory);
|
|
41270
41367
|
let fallbackExists = false;
|
|
41271
41368
|
try {
|
|
41272
41369
|
fs16.statSync(resolvedFallback);
|
|
@@ -41276,7 +41373,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
|
|
|
41276
41373
|
}
|
|
41277
41374
|
if (workingDirectory != null && workingDirectory !== "") {
|
|
41278
41375
|
if (fallbackExists) {
|
|
41279
|
-
const isSubdirectory = resolvedDir.startsWith(resolvedFallback +
|
|
41376
|
+
const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path27.sep);
|
|
41280
41377
|
if (isSubdirectory) {
|
|
41281
41378
|
return {
|
|
41282
41379
|
success: false,
|
|
@@ -41366,14 +41463,14 @@ function hasDevDependency(devDeps, ...patterns) {
|
|
|
41366
41463
|
return hasPackageJsonDependency(devDeps, ...patterns);
|
|
41367
41464
|
}
|
|
41368
41465
|
function detectGoTest(cwd) {
|
|
41369
|
-
return fs17.existsSync(
|
|
41466
|
+
return fs17.existsSync(path28.join(cwd, "go.mod")) && isCommandAvailable("go");
|
|
41370
41467
|
}
|
|
41371
41468
|
function detectJavaMaven(cwd) {
|
|
41372
|
-
return fs17.existsSync(
|
|
41469
|
+
return fs17.existsSync(path28.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
|
|
41373
41470
|
}
|
|
41374
41471
|
function detectGradle(cwd) {
|
|
41375
|
-
const hasBuildFile = fs17.existsSync(
|
|
41376
|
-
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"));
|
|
41377
41474
|
return hasBuildFile && (hasGradlew || isCommandAvailable("gradle"));
|
|
41378
41475
|
}
|
|
41379
41476
|
function detectDotnetTest(cwd) {
|
|
@@ -41386,30 +41483,30 @@ function detectDotnetTest(cwd) {
|
|
|
41386
41483
|
}
|
|
41387
41484
|
}
|
|
41388
41485
|
function detectCTest(cwd) {
|
|
41389
|
-
const hasSource = fs17.existsSync(
|
|
41390
|
-
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"));
|
|
41391
41488
|
return (hasSource || hasBuildCache) && isCommandAvailable("ctest");
|
|
41392
41489
|
}
|
|
41393
41490
|
function detectSwiftTest(cwd) {
|
|
41394
|
-
return fs17.existsSync(
|
|
41491
|
+
return fs17.existsSync(path28.join(cwd, "Package.swift")) && isCommandAvailable("swift");
|
|
41395
41492
|
}
|
|
41396
41493
|
function detectDartTest(cwd) {
|
|
41397
|
-
return fs17.existsSync(
|
|
41494
|
+
return fs17.existsSync(path28.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
41398
41495
|
}
|
|
41399
41496
|
function detectRSpec(cwd) {
|
|
41400
|
-
const hasRSpecFile = fs17.existsSync(
|
|
41401
|
-
const hasGemfile = fs17.existsSync(
|
|
41402
|
-
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"));
|
|
41403
41500
|
const hasRSpec = hasRSpecFile || hasGemfile && hasSpecDir;
|
|
41404
41501
|
return hasRSpec && (isCommandAvailable("bundle") || isCommandAvailable("rspec"));
|
|
41405
41502
|
}
|
|
41406
41503
|
function detectMinitest(cwd) {
|
|
41407
|
-
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");
|
|
41408
41505
|
}
|
|
41409
41506
|
async function detectTestFramework(cwd) {
|
|
41410
41507
|
const baseDir = cwd;
|
|
41411
41508
|
try {
|
|
41412
|
-
const packageJsonPath =
|
|
41509
|
+
const packageJsonPath = path28.join(baseDir, "package.json");
|
|
41413
41510
|
if (fs17.existsSync(packageJsonPath)) {
|
|
41414
41511
|
const content = fs17.readFileSync(packageJsonPath, "utf-8");
|
|
41415
41512
|
const pkg = JSON.parse(content);
|
|
@@ -41430,16 +41527,16 @@ async function detectTestFramework(cwd) {
|
|
|
41430
41527
|
return "jest";
|
|
41431
41528
|
if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
|
|
41432
41529
|
return "mocha";
|
|
41433
|
-
if (fs17.existsSync(
|
|
41530
|
+
if (fs17.existsSync(path28.join(baseDir, "bun.lockb")) || fs17.existsSync(path28.join(baseDir, "bun.lock"))) {
|
|
41434
41531
|
if (scripts.test?.includes("bun"))
|
|
41435
41532
|
return "bun";
|
|
41436
41533
|
}
|
|
41437
41534
|
}
|
|
41438
41535
|
} catch {}
|
|
41439
41536
|
try {
|
|
41440
|
-
const pyprojectTomlPath =
|
|
41441
|
-
const setupCfgPath =
|
|
41442
|
-
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");
|
|
41443
41540
|
if (fs17.existsSync(pyprojectTomlPath)) {
|
|
41444
41541
|
const content = fs17.readFileSync(pyprojectTomlPath, "utf-8");
|
|
41445
41542
|
if (content.includes("[tool.pytest"))
|
|
@@ -41459,7 +41556,7 @@ async function detectTestFramework(cwd) {
|
|
|
41459
41556
|
}
|
|
41460
41557
|
} catch {}
|
|
41461
41558
|
try {
|
|
41462
|
-
const cargoTomlPath =
|
|
41559
|
+
const cargoTomlPath = path28.join(baseDir, "Cargo.toml");
|
|
41463
41560
|
if (fs17.existsSync(cargoTomlPath)) {
|
|
41464
41561
|
const content = fs17.readFileSync(cargoTomlPath, "utf-8");
|
|
41465
41562
|
if (content.includes("[dev-dependencies]")) {
|
|
@@ -41470,9 +41567,9 @@ async function detectTestFramework(cwd) {
|
|
|
41470
41567
|
}
|
|
41471
41568
|
} catch {}
|
|
41472
41569
|
try {
|
|
41473
|
-
const pesterConfigPath =
|
|
41474
|
-
const pesterConfigJsonPath =
|
|
41475
|
-
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");
|
|
41476
41573
|
if (fs17.existsSync(pesterConfigPath) || fs17.existsSync(pesterConfigJsonPath) || fs17.existsSync(pesterPs1Path)) {
|
|
41477
41574
|
return "pester";
|
|
41478
41575
|
}
|
|
@@ -41515,12 +41612,12 @@ function isTestDirectoryPath(normalizedPath) {
|
|
|
41515
41612
|
return normalizedPath.split("/").some((segment) => TEST_DIRECTORY_NAMES.includes(segment));
|
|
41516
41613
|
}
|
|
41517
41614
|
function resolveWorkspacePath(file3, workingDir) {
|
|
41518
|
-
return
|
|
41615
|
+
return path28.isAbsolute(file3) ? path28.resolve(file3) : path28.resolve(workingDir, file3);
|
|
41519
41616
|
}
|
|
41520
41617
|
function toWorkspaceOutputPath(absolutePath, workingDir, preferRelative) {
|
|
41521
41618
|
if (!preferRelative)
|
|
41522
41619
|
return absolutePath;
|
|
41523
|
-
return
|
|
41620
|
+
return path28.relative(workingDir, absolutePath);
|
|
41524
41621
|
}
|
|
41525
41622
|
function dedupePush(target, value) {
|
|
41526
41623
|
if (!target.includes(value)) {
|
|
@@ -41557,18 +41654,18 @@ function buildLanguageSpecificTestNames(nameWithoutExt, ext) {
|
|
|
41557
41654
|
}
|
|
41558
41655
|
}
|
|
41559
41656
|
function getRepoLevelCandidateDirectories(workingDir, relativePath, ext) {
|
|
41560
|
-
const relativeDir =
|
|
41657
|
+
const relativeDir = path28.dirname(relativePath);
|
|
41561
41658
|
const nestedRelativeDir = relativeDir === "." ? "" : relativeDir;
|
|
41562
41659
|
const directories = TEST_DIRECTORY_NAMES.flatMap((dirName) => {
|
|
41563
|
-
const rootDir =
|
|
41564
|
-
return nestedRelativeDir ? [rootDir,
|
|
41660
|
+
const rootDir = path28.join(workingDir, dirName);
|
|
41661
|
+
return nestedRelativeDir ? [rootDir, path28.join(rootDir, nestedRelativeDir)] : [rootDir];
|
|
41565
41662
|
});
|
|
41566
41663
|
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
41567
41664
|
if (ext === ".java" && normalizedRelativePath.startsWith("src/main/java/")) {
|
|
41568
|
-
directories.push(
|
|
41665
|
+
directories.push(path28.join(workingDir, "src/test/java", path28.dirname(normalizedRelativePath.slice("src/main/java/".length))));
|
|
41569
41666
|
}
|
|
41570
41667
|
if ((ext === ".kt" || ext === ".java") && normalizedRelativePath.startsWith("src/main/kotlin/")) {
|
|
41571
|
-
directories.push(
|
|
41668
|
+
directories.push(path28.join(workingDir, "src/test/kotlin", path28.dirname(normalizedRelativePath.slice("src/main/kotlin/".length))));
|
|
41572
41669
|
}
|
|
41573
41670
|
return [...new Set(directories)];
|
|
41574
41671
|
}
|
|
@@ -41596,23 +41693,23 @@ function isLanguageSpecificTestFile(basename4) {
|
|
|
41596
41693
|
}
|
|
41597
41694
|
function isConventionTestFilePath(filePath) {
|
|
41598
41695
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
41599
|
-
const basename4 =
|
|
41696
|
+
const basename4 = path28.basename(filePath);
|
|
41600
41697
|
return hasCompoundTestExtension(basename4) || basename4.includes(".spec.") || basename4.includes(".test.") || isLanguageSpecificTestFile(basename4) || isTestDirectoryPath(normalizedPath);
|
|
41601
41698
|
}
|
|
41602
41699
|
function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
|
|
41603
41700
|
const testFiles = [];
|
|
41604
41701
|
for (const file3 of sourceFiles) {
|
|
41605
41702
|
const absoluteFile = resolveWorkspacePath(file3, workingDir);
|
|
41606
|
-
const relativeFile =
|
|
41607
|
-
const basename4 =
|
|
41608
|
-
const dirname11 =
|
|
41609
|
-
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);
|
|
41610
41707
|
if (isConventionTestFilePath(relativeFile) || isConventionTestFilePath(file3)) {
|
|
41611
41708
|
dedupePush(testFiles, toWorkspaceOutputPath(absoluteFile, workingDir, preferRelativeOutput));
|
|
41612
41709
|
continue;
|
|
41613
41710
|
}
|
|
41614
41711
|
const nameWithoutExt = basename4.replace(/\.[^.]+$/, "");
|
|
41615
|
-
const ext =
|
|
41712
|
+
const ext = path28.extname(basename4);
|
|
41616
41713
|
const genericTestNames = [
|
|
41617
41714
|
`${nameWithoutExt}.spec${ext}`,
|
|
41618
41715
|
`${nameWithoutExt}.test${ext}`
|
|
@@ -41621,7 +41718,7 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
|
|
|
41621
41718
|
const colocatedCandidates = [
|
|
41622
41719
|
...genericTestNames,
|
|
41623
41720
|
...languageSpecificTestNames
|
|
41624
|
-
].map((candidateName) =>
|
|
41721
|
+
].map((candidateName) => path28.join(dirname11, candidateName));
|
|
41625
41722
|
const testDirectoryNames = [
|
|
41626
41723
|
basename4,
|
|
41627
41724
|
...genericTestNames,
|
|
@@ -41630,8 +41727,8 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
|
|
|
41630
41727
|
const repoLevelDirectories = getRepoLevelCandidateDirectories(workingDir, relativeFile, ext);
|
|
41631
41728
|
const possibleTestFiles = [
|
|
41632
41729
|
...colocatedCandidates,
|
|
41633
|
-
...TEST_DIRECTORY_NAMES.flatMap((dirName) => testDirectoryNames.map((candidateName) =>
|
|
41634
|
-
...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)))
|
|
41635
41732
|
];
|
|
41636
41733
|
for (const testFile of possibleTestFiles) {
|
|
41637
41734
|
if (fs17.existsSync(testFile)) {
|
|
@@ -41652,7 +41749,7 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41652
41749
|
try {
|
|
41653
41750
|
const absoluteTestFile = resolveWorkspacePath(testFile, workingDir);
|
|
41654
41751
|
const content = fs17.readFileSync(absoluteTestFile, "utf-8");
|
|
41655
|
-
const testDir =
|
|
41752
|
+
const testDir = path28.dirname(absoluteTestFile);
|
|
41656
41753
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
41657
41754
|
let match;
|
|
41658
41755
|
match = importRegex.exec(content);
|
|
@@ -41660,8 +41757,8 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41660
41757
|
const importPath = match[1];
|
|
41661
41758
|
let resolvedImport;
|
|
41662
41759
|
if (importPath.startsWith(".")) {
|
|
41663
|
-
resolvedImport =
|
|
41664
|
-
const existingExt =
|
|
41760
|
+
resolvedImport = path28.resolve(testDir, importPath);
|
|
41761
|
+
const existingExt = path28.extname(resolvedImport);
|
|
41665
41762
|
if (!existingExt) {
|
|
41666
41763
|
for (const extToTry of [
|
|
41667
41764
|
".ts",
|
|
@@ -41681,12 +41778,12 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41681
41778
|
} else {
|
|
41682
41779
|
continue;
|
|
41683
41780
|
}
|
|
41684
|
-
const importBasename =
|
|
41685
|
-
const importDir =
|
|
41781
|
+
const importBasename = path28.basename(resolvedImport, path28.extname(resolvedImport));
|
|
41782
|
+
const importDir = path28.dirname(resolvedImport);
|
|
41686
41783
|
for (const sourceFile of absoluteSourceFiles) {
|
|
41687
|
-
const sourceDir =
|
|
41688
|
-
const sourceBasename =
|
|
41689
|
-
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");
|
|
41690
41787
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
41691
41788
|
dedupePush(testFiles, testFile);
|
|
41692
41789
|
break;
|
|
@@ -41699,8 +41796,8 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41699
41796
|
while (match !== null) {
|
|
41700
41797
|
const importPath = match[1];
|
|
41701
41798
|
if (importPath.startsWith(".")) {
|
|
41702
|
-
let resolvedImport =
|
|
41703
|
-
const existingExt =
|
|
41799
|
+
let resolvedImport = path28.resolve(testDir, importPath);
|
|
41800
|
+
const existingExt = path28.extname(resolvedImport);
|
|
41704
41801
|
if (!existingExt) {
|
|
41705
41802
|
for (const extToTry of [
|
|
41706
41803
|
".ts",
|
|
@@ -41717,12 +41814,12 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
|
|
|
41717
41814
|
}
|
|
41718
41815
|
}
|
|
41719
41816
|
}
|
|
41720
|
-
const importDir =
|
|
41721
|
-
const importBasename =
|
|
41817
|
+
const importDir = path28.dirname(resolvedImport);
|
|
41818
|
+
const importBasename = path28.basename(resolvedImport, path28.extname(resolvedImport));
|
|
41722
41819
|
for (const sourceFile of absoluteSourceFiles) {
|
|
41723
|
-
const sourceDir =
|
|
41724
|
-
const sourceBasename =
|
|
41725
|
-
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");
|
|
41726
41823
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
41727
41824
|
dedupePush(testFiles, testFile);
|
|
41728
41825
|
break;
|
|
@@ -41825,8 +41922,8 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
|
|
|
41825
41922
|
return ["mvn", "test"];
|
|
41826
41923
|
case "gradle": {
|
|
41827
41924
|
const isWindows = process.platform === "win32";
|
|
41828
|
-
const hasGradlewBat = fs17.existsSync(
|
|
41829
|
-
const hasGradlew = fs17.existsSync(
|
|
41925
|
+
const hasGradlewBat = fs17.existsSync(path28.join(baseDir, "gradlew.bat"));
|
|
41926
|
+
const hasGradlew = fs17.existsSync(path28.join(baseDir, "gradlew"));
|
|
41830
41927
|
if (hasGradlewBat && isWindows)
|
|
41831
41928
|
return ["gradlew.bat", "test"];
|
|
41832
41929
|
if (hasGradlew)
|
|
@@ -41843,7 +41940,7 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
|
|
|
41843
41940
|
"cmake-build-release",
|
|
41844
41941
|
"out"
|
|
41845
41942
|
];
|
|
41846
|
-
const actualBuildDir = buildDirCandidates.find((d) => fs17.existsSync(
|
|
41943
|
+
const actualBuildDir = buildDirCandidates.find((d) => fs17.existsSync(path28.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
|
|
41847
41944
|
return ["ctest", "--test-dir", actualBuildDir];
|
|
41848
41945
|
}
|
|
41849
41946
|
case "swift-test":
|
|
@@ -42341,12 +42438,12 @@ function analyzeFailures(workingDir) {
|
|
|
42341
42438
|
var test_runner = createSwarmTool({
|
|
42342
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.',
|
|
42343
42440
|
args: {
|
|
42344
|
-
scope:
|
|
42345
|
-
files:
|
|
42346
|
-
coverage:
|
|
42347
|
-
timeout_ms:
|
|
42348
|
-
allow_full_suite:
|
|
42349
|
-
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.")
|
|
42350
42447
|
},
|
|
42351
42448
|
async execute(args, directory) {
|
|
42352
42449
|
let workingDirInput;
|
|
@@ -42471,7 +42568,7 @@ var test_runner = createSwarmTool({
|
|
|
42471
42568
|
const sourceFiles = args.files.filter((file3) => {
|
|
42472
42569
|
if (directTestFiles.includes(file3))
|
|
42473
42570
|
return false;
|
|
42474
|
-
const ext =
|
|
42571
|
+
const ext = path28.extname(file3).toLowerCase();
|
|
42475
42572
|
return SOURCE_EXTENSIONS.has(ext);
|
|
42476
42573
|
});
|
|
42477
42574
|
const invalidFiles = args.files.filter((file3) => !directTestFiles.includes(file3) && !sourceFiles.includes(file3));
|
|
@@ -42506,7 +42603,7 @@ var test_runner = createSwarmTool({
|
|
|
42506
42603
|
if (isConventionTestFilePath(f)) {
|
|
42507
42604
|
return false;
|
|
42508
42605
|
}
|
|
42509
|
-
const ext =
|
|
42606
|
+
const ext = path28.extname(f).toLowerCase();
|
|
42510
42607
|
return SOURCE_EXTENSIONS.has(ext);
|
|
42511
42608
|
});
|
|
42512
42609
|
if (sourceFiles.length === 0) {
|
|
@@ -42533,7 +42630,7 @@ var test_runner = createSwarmTool({
|
|
|
42533
42630
|
if (isConventionTestFilePath(f)) {
|
|
42534
42631
|
return false;
|
|
42535
42632
|
}
|
|
42536
|
-
const ext =
|
|
42633
|
+
const ext = path28.extname(f).toLowerCase();
|
|
42537
42634
|
return SOURCE_EXTENSIONS.has(ext);
|
|
42538
42635
|
});
|
|
42539
42636
|
if (sourceFiles.length === 0) {
|
|
@@ -42551,8 +42648,8 @@ var test_runner = createSwarmTool({
|
|
|
42551
42648
|
const impactResult = await analyzeImpact(sourceFiles, workingDir);
|
|
42552
42649
|
if (impactResult.impactedTests.length > 0) {
|
|
42553
42650
|
testFiles = impactResult.impactedTests.map((absPath) => {
|
|
42554
|
-
const relativePath =
|
|
42555
|
-
return
|
|
42651
|
+
const relativePath = path28.relative(workingDir, absPath);
|
|
42652
|
+
return path28.isAbsolute(relativePath) ? absPath : relativePath;
|
|
42556
42653
|
});
|
|
42557
42654
|
} else {
|
|
42558
42655
|
graphFallbackReason = "no impacted tests found via impact analysis, falling back to graph";
|
|
@@ -42645,8 +42742,8 @@ function validateDirectoryPath(dir) {
|
|
|
42645
42742
|
if (dir.includes("..")) {
|
|
42646
42743
|
throw new Error("Directory path must not contain path traversal sequences");
|
|
42647
42744
|
}
|
|
42648
|
-
const normalized =
|
|
42649
|
-
const absolutePath =
|
|
42745
|
+
const normalized = path29.normalize(dir);
|
|
42746
|
+
const absolutePath = path29.isAbsolute(normalized) ? normalized : path29.resolve(normalized);
|
|
42650
42747
|
return absolutePath;
|
|
42651
42748
|
}
|
|
42652
42749
|
function validateTimeout(timeoutMs, defaultValue) {
|
|
@@ -42669,7 +42766,7 @@ function validateTimeout(timeoutMs, defaultValue) {
|
|
|
42669
42766
|
}
|
|
42670
42767
|
function getPackageVersion(dir) {
|
|
42671
42768
|
try {
|
|
42672
|
-
const packagePath =
|
|
42769
|
+
const packagePath = path29.join(dir, "package.json");
|
|
42673
42770
|
if (fs18.existsSync(packagePath)) {
|
|
42674
42771
|
const content = fs18.readFileSync(packagePath, "utf-8");
|
|
42675
42772
|
const pkg = JSON.parse(content);
|
|
@@ -42680,7 +42777,7 @@ function getPackageVersion(dir) {
|
|
|
42680
42777
|
}
|
|
42681
42778
|
function getChangelogVersion(dir) {
|
|
42682
42779
|
try {
|
|
42683
|
-
const changelogPath =
|
|
42780
|
+
const changelogPath = path29.join(dir, "CHANGELOG.md");
|
|
42684
42781
|
if (fs18.existsSync(changelogPath)) {
|
|
42685
42782
|
const content = fs18.readFileSync(changelogPath, "utf-8");
|
|
42686
42783
|
const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
|
|
@@ -42694,7 +42791,7 @@ function getChangelogVersion(dir) {
|
|
|
42694
42791
|
function getVersionFileVersion(dir) {
|
|
42695
42792
|
const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
|
|
42696
42793
|
for (const file3 of possibleFiles) {
|
|
42697
|
-
const filePath =
|
|
42794
|
+
const filePath = path29.join(dir, file3);
|
|
42698
42795
|
if (fs18.existsSync(filePath)) {
|
|
42699
42796
|
try {
|
|
42700
42797
|
const content = fs18.readFileSync(filePath, "utf-8").trim();
|
|
@@ -43021,7 +43118,7 @@ async function runEvidenceCheck(dir) {
|
|
|
43021
43118
|
async function runRequirementCoverageCheck(dir, currentPhase) {
|
|
43022
43119
|
const startTime = Date.now();
|
|
43023
43120
|
try {
|
|
43024
|
-
const specPath =
|
|
43121
|
+
const specPath = path29.join(dir, ".swarm", "spec.md");
|
|
43025
43122
|
if (!fs18.existsSync(specPath)) {
|
|
43026
43123
|
return {
|
|
43027
43124
|
type: "req_coverage",
|
|
@@ -43234,144 +43331,6 @@ async function handlePreflightCommand(directory, _args) {
|
|
|
43234
43331
|
const report = await runPreflight(directory, phase);
|
|
43235
43332
|
return formatPreflightMarkdown(report);
|
|
43236
43333
|
}
|
|
43237
|
-
// src/knowledge/hive-promoter.ts
|
|
43238
|
-
import * as fs19 from "fs";
|
|
43239
|
-
import * as os6 from "os";
|
|
43240
|
-
import * as path29 from "path";
|
|
43241
|
-
var DANGEROUS_PATTERNS = [
|
|
43242
|
-
[/rm\s+-rf/, "rm\\s+-rf"],
|
|
43243
|
-
[/:\s*!\s*\|/, ":\\s*!\\s*\\|"],
|
|
43244
|
-
[/\|\s*sh\b/, "\\|\\s*sh\\b"],
|
|
43245
|
-
[/`[^`]*`/, "`[^`]*`"],
|
|
43246
|
-
[/\$\(/, "\\$\\("],
|
|
43247
|
-
[/;\s*rm\s+\//, ";\\s*rm\\s+\\/"],
|
|
43248
|
-
[/>\s*\/dev\//, ">\\s*\\/dev\\/"],
|
|
43249
|
-
[/\bmkfs\b/, "\\bmkfs\\b"],
|
|
43250
|
-
[/\bdd\s+if=/, "\\bdd\\s+if="],
|
|
43251
|
-
[/chmod\s+[0-7]*7[0-7]{2}/, "chmod\\s+[0-7]*7[0-7]\\{2\\}"],
|
|
43252
|
-
[/\bchown\s+-R\b/, "\\bchown\\s+-R\\b"],
|
|
43253
|
-
[/(?<!\.)\beval\s*\(/, "(?<!\\.)\\beval\\s*\\("],
|
|
43254
|
-
[/(?<!\.)\bexec\s*\(/, "(?<!\\.)\\bexec\\s*\\("]
|
|
43255
|
-
];
|
|
43256
|
-
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/;
|
|
43257
|
-
function validateLesson2(text) {
|
|
43258
|
-
if (!text || !text.trim()) {
|
|
43259
|
-
return { valid: false, reason: "Lesson text cannot be empty" };
|
|
43260
|
-
}
|
|
43261
|
-
for (const [pattern, patternSource] of DANGEROUS_PATTERNS) {
|
|
43262
|
-
if (pattern.test(text)) {
|
|
43263
|
-
return {
|
|
43264
|
-
valid: false,
|
|
43265
|
-
reason: `Dangerous pattern detected: ${patternSource}`
|
|
43266
|
-
};
|
|
43267
|
-
}
|
|
43268
|
-
}
|
|
43269
|
-
const trimmed = text.trim();
|
|
43270
|
-
if (SHELL_COMMAND_START.test(trimmed)) {
|
|
43271
|
-
const lastChar = trimmed[trimmed.length - 1];
|
|
43272
|
-
if (![".", "!", "?", ";"].includes(lastChar)) {
|
|
43273
|
-
return {
|
|
43274
|
-
valid: false,
|
|
43275
|
-
reason: "Lesson appears to contain raw shell commands"
|
|
43276
|
-
};
|
|
43277
|
-
}
|
|
43278
|
-
}
|
|
43279
|
-
return { valid: true };
|
|
43280
|
-
}
|
|
43281
|
-
function getHiveFilePath() {
|
|
43282
|
-
const platform = process.platform;
|
|
43283
|
-
const home = os6.homedir();
|
|
43284
|
-
let dataDir;
|
|
43285
|
-
if (platform === "win32") {
|
|
43286
|
-
dataDir = path29.join(process.env.LOCALAPPDATA || path29.join(home, "AppData", "Local"), "opencode-swarm", "Data");
|
|
43287
|
-
} else if (platform === "darwin") {
|
|
43288
|
-
dataDir = path29.join(home, "Library", "Application Support", "opencode-swarm");
|
|
43289
|
-
} else {
|
|
43290
|
-
dataDir = path29.join(process.env.XDG_DATA_HOME || path29.join(home, ".local", "share"), "opencode-swarm");
|
|
43291
|
-
}
|
|
43292
|
-
return path29.join(dataDir, "hive-knowledge.jsonl");
|
|
43293
|
-
}
|
|
43294
|
-
async function promoteToHive(_directory, lesson, category) {
|
|
43295
|
-
const trimmed = (lesson ?? "").trim();
|
|
43296
|
-
if (!trimmed) {
|
|
43297
|
-
throw new Error("Lesson text required");
|
|
43298
|
-
}
|
|
43299
|
-
const validation = validateLesson2(trimmed);
|
|
43300
|
-
if (!validation.valid) {
|
|
43301
|
-
throw new Error(`Lesson rejected by validator: ${validation.reason}`);
|
|
43302
|
-
}
|
|
43303
|
-
const hivePath = getHiveFilePath();
|
|
43304
|
-
const hiveDir = path29.dirname(hivePath);
|
|
43305
|
-
if (!fs19.existsSync(hiveDir)) {
|
|
43306
|
-
fs19.mkdirSync(hiveDir, { recursive: true });
|
|
43307
|
-
}
|
|
43308
|
-
const now = new Date;
|
|
43309
|
-
const entry = {
|
|
43310
|
-
id: `hive-manual-${now.getTime()}`,
|
|
43311
|
-
lesson: trimmed,
|
|
43312
|
-
category: category || "process",
|
|
43313
|
-
scope_tag: "global",
|
|
43314
|
-
confidence: 1,
|
|
43315
|
-
status: "promoted",
|
|
43316
|
-
promotion_source: "manual",
|
|
43317
|
-
promotedAt: now.toISOString(),
|
|
43318
|
-
retrievalOutcomes: { applied: 0, succeededAfter: 0, failedAfter: 0 }
|
|
43319
|
-
};
|
|
43320
|
-
fs19.appendFileSync(hivePath, `${JSON.stringify(entry)}
|
|
43321
|
-
`, "utf-8");
|
|
43322
|
-
const preview = `${trimmed.slice(0, 50)}${trimmed.length > 50 ? "..." : ""}`;
|
|
43323
|
-
return `Promoted to hive: "${preview}" (confidence: 1.0, source: manual)`;
|
|
43324
|
-
}
|
|
43325
|
-
async function promoteFromSwarm(directory, lessonId) {
|
|
43326
|
-
const knowledgePath = path29.join(directory, ".swarm", "knowledge.jsonl");
|
|
43327
|
-
const entries = [];
|
|
43328
|
-
if (fs19.existsSync(knowledgePath)) {
|
|
43329
|
-
const content = fs19.readFileSync(knowledgePath, "utf-8");
|
|
43330
|
-
for (const line of content.split(`
|
|
43331
|
-
`)) {
|
|
43332
|
-
const t = line.trim();
|
|
43333
|
-
if (!t)
|
|
43334
|
-
continue;
|
|
43335
|
-
try {
|
|
43336
|
-
entries.push(JSON.parse(t));
|
|
43337
|
-
} catch {}
|
|
43338
|
-
}
|
|
43339
|
-
}
|
|
43340
|
-
const swarmEntry = entries.find((e) => e.id === lessonId);
|
|
43341
|
-
if (!swarmEntry) {
|
|
43342
|
-
throw new Error(`Lesson ${lessonId} not found in .swarm/knowledge.jsonl`);
|
|
43343
|
-
}
|
|
43344
|
-
const lessonText = typeof swarmEntry.lesson === "string" ? swarmEntry.lesson.trim() : "";
|
|
43345
|
-
if (!lessonText) {
|
|
43346
|
-
throw new Error("Lesson text required");
|
|
43347
|
-
}
|
|
43348
|
-
const validation = validateLesson2(lessonText);
|
|
43349
|
-
if (!validation.valid) {
|
|
43350
|
-
throw new Error(`Lesson rejected by validator: ${validation.reason}`);
|
|
43351
|
-
}
|
|
43352
|
-
const hivePath = getHiveFilePath();
|
|
43353
|
-
const hiveDir = path29.dirname(hivePath);
|
|
43354
|
-
if (!fs19.existsSync(hiveDir)) {
|
|
43355
|
-
fs19.mkdirSync(hiveDir, { recursive: true });
|
|
43356
|
-
}
|
|
43357
|
-
const now = new Date;
|
|
43358
|
-
const hiveEntry = {
|
|
43359
|
-
id: `hive-manual-${now.getTime()}`,
|
|
43360
|
-
lesson: lessonText,
|
|
43361
|
-
category: typeof swarmEntry.category === "string" ? swarmEntry.category : "process",
|
|
43362
|
-
scope_tag: typeof swarmEntry.scope === "string" ? swarmEntry.scope : "global",
|
|
43363
|
-
confidence: 1,
|
|
43364
|
-
status: "promoted",
|
|
43365
|
-
promotion_source: "manual",
|
|
43366
|
-
promotedAt: now.toISOString(),
|
|
43367
|
-
retrievalOutcomes: { applied: 0, succeededAfter: 0, failedAfter: 0 }
|
|
43368
|
-
};
|
|
43369
|
-
fs19.appendFileSync(hivePath, `${JSON.stringify(hiveEntry)}
|
|
43370
|
-
`, "utf-8");
|
|
43371
|
-
const preview = `${lessonText.slice(0, 50)}${lessonText.length > 50 ? "..." : ""}`;
|
|
43372
|
-
return `Promoted to hive: "${preview}" (confidence: 1.0, source: manual)`;
|
|
43373
|
-
}
|
|
43374
|
-
|
|
43375
43334
|
// src/commands/promote.ts
|
|
43376
43335
|
async function handlePromoteCommand(directory, args) {
|
|
43377
43336
|
let category;
|
|
@@ -43393,12 +43352,6 @@ async function handlePromoteCommand(directory, args) {
|
|
|
43393
43352
|
if (!lessonText && !lessonId) {
|
|
43394
43353
|
return `Usage: /swarm promote "<lesson text>" or /swarm promote --from-swarm <id>`;
|
|
43395
43354
|
}
|
|
43396
|
-
if (lessonText) {
|
|
43397
|
-
const validation = validateLesson2(lessonText);
|
|
43398
|
-
if (!validation.valid) {
|
|
43399
|
-
return `Lesson rejected by validator: ${validation.reason}`;
|
|
43400
|
-
}
|
|
43401
|
-
}
|
|
43402
43355
|
if (lessonId) {
|
|
43403
43356
|
try {
|
|
43404
43357
|
return await promoteFromSwarm(directory, lessonId);
|
|
@@ -43547,7 +43500,7 @@ async function handleQaGatesCommand(directory, args, sessionID) {
|
|
|
43547
43500
|
}
|
|
43548
43501
|
|
|
43549
43502
|
// src/commands/reset.ts
|
|
43550
|
-
import * as
|
|
43503
|
+
import * as fs19 from "fs";
|
|
43551
43504
|
|
|
43552
43505
|
// src/background/manager.ts
|
|
43553
43506
|
init_utils();
|
|
@@ -44248,8 +44201,8 @@ async function handleResetCommand(directory, args) {
|
|
|
44248
44201
|
for (const filename of filesToReset) {
|
|
44249
44202
|
try {
|
|
44250
44203
|
const resolvedPath = validateSwarmPath(directory, filename);
|
|
44251
|
-
if (
|
|
44252
|
-
|
|
44204
|
+
if (fs19.existsSync(resolvedPath)) {
|
|
44205
|
+
fs19.unlinkSync(resolvedPath);
|
|
44253
44206
|
results.push(`- \u2705 Deleted ${filename}`);
|
|
44254
44207
|
} else {
|
|
44255
44208
|
results.push(`- \u23ED\uFE0F ${filename} not found (skipped)`);
|
|
@@ -44266,8 +44219,8 @@ async function handleResetCommand(directory, args) {
|
|
|
44266
44219
|
}
|
|
44267
44220
|
try {
|
|
44268
44221
|
const summariesPath = validateSwarmPath(directory, "summaries");
|
|
44269
|
-
if (
|
|
44270
|
-
|
|
44222
|
+
if (fs19.existsSync(summariesPath)) {
|
|
44223
|
+
fs19.rmSync(summariesPath, { recursive: true, force: true });
|
|
44271
44224
|
results.push("- \u2705 Deleted summaries/ directory");
|
|
44272
44225
|
} else {
|
|
44273
44226
|
results.push("- \u23ED\uFE0F summaries/ not found (skipped)");
|
|
@@ -44287,14 +44240,14 @@ async function handleResetCommand(directory, args) {
|
|
|
44287
44240
|
|
|
44288
44241
|
// src/commands/reset-session.ts
|
|
44289
44242
|
init_utils2();
|
|
44290
|
-
import * as
|
|
44243
|
+
import * as fs20 from "fs";
|
|
44291
44244
|
import * as path30 from "path";
|
|
44292
44245
|
async function handleResetSessionCommand(directory, _args) {
|
|
44293
44246
|
const results = [];
|
|
44294
44247
|
try {
|
|
44295
44248
|
const statePath = validateSwarmPath(directory, "session/state.json");
|
|
44296
|
-
if (
|
|
44297
|
-
|
|
44249
|
+
if (fs20.existsSync(statePath)) {
|
|
44250
|
+
fs20.unlinkSync(statePath);
|
|
44298
44251
|
results.push("\u2705 Deleted .swarm/session/state.json");
|
|
44299
44252
|
} else {
|
|
44300
44253
|
results.push("\u23ED\uFE0F state.json not found (already clean)");
|
|
@@ -44304,14 +44257,14 @@ async function handleResetSessionCommand(directory, _args) {
|
|
|
44304
44257
|
}
|
|
44305
44258
|
try {
|
|
44306
44259
|
const sessionDir = path30.dirname(validateSwarmPath(directory, "session/state.json"));
|
|
44307
|
-
if (
|
|
44308
|
-
const files =
|
|
44260
|
+
if (fs20.existsSync(sessionDir)) {
|
|
44261
|
+
const files = fs20.readdirSync(sessionDir);
|
|
44309
44262
|
const otherFiles = files.filter((f) => f !== "state.json");
|
|
44310
44263
|
let deletedCount = 0;
|
|
44311
44264
|
for (const file3 of otherFiles) {
|
|
44312
44265
|
const filePath = path30.join(sessionDir, file3);
|
|
44313
|
-
if (
|
|
44314
|
-
|
|
44266
|
+
if (fs20.lstatSync(filePath).isFile()) {
|
|
44267
|
+
fs20.unlinkSync(filePath);
|
|
44315
44268
|
deletedCount++;
|
|
44316
44269
|
}
|
|
44317
44270
|
}
|
|
@@ -44416,18 +44369,18 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
|
|
|
44416
44369
|
|
|
44417
44370
|
// src/commands/rollback.ts
|
|
44418
44371
|
init_utils2();
|
|
44419
|
-
import * as
|
|
44372
|
+
import * as fs21 from "fs";
|
|
44420
44373
|
import * as path32 from "path";
|
|
44421
44374
|
async function handleRollbackCommand(directory, args) {
|
|
44422
44375
|
const phaseArg = args[0];
|
|
44423
44376
|
if (!phaseArg) {
|
|
44424
44377
|
const manifestPath2 = validateSwarmPath(directory, "checkpoints/manifest.json");
|
|
44425
|
-
if (!
|
|
44378
|
+
if (!fs21.existsSync(manifestPath2)) {
|
|
44426
44379
|
return "No checkpoints found. Use `/swarm checkpoint` to create checkpoints.";
|
|
44427
44380
|
}
|
|
44428
44381
|
let manifest2;
|
|
44429
44382
|
try {
|
|
44430
|
-
manifest2 = JSON.parse(
|
|
44383
|
+
manifest2 = JSON.parse(fs21.readFileSync(manifestPath2, "utf-8"));
|
|
44431
44384
|
} catch {
|
|
44432
44385
|
return "Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.";
|
|
44433
44386
|
}
|
|
@@ -44449,12 +44402,12 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44449
44402
|
return "Error: Phase number must be a positive integer.";
|
|
44450
44403
|
}
|
|
44451
44404
|
const manifestPath = validateSwarmPath(directory, "checkpoints/manifest.json");
|
|
44452
|
-
if (!
|
|
44405
|
+
if (!fs21.existsSync(manifestPath)) {
|
|
44453
44406
|
return `Error: No checkpoints found. Cannot rollback to phase ${targetPhase}.`;
|
|
44454
44407
|
}
|
|
44455
44408
|
let manifest;
|
|
44456
44409
|
try {
|
|
44457
|
-
manifest = JSON.parse(
|
|
44410
|
+
manifest = JSON.parse(fs21.readFileSync(manifestPath, "utf-8"));
|
|
44458
44411
|
} catch {
|
|
44459
44412
|
return `Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.`;
|
|
44460
44413
|
}
|
|
@@ -44464,10 +44417,10 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44464
44417
|
return `Error: Checkpoint for phase ${targetPhase} not found. Available phases: ${available}`;
|
|
44465
44418
|
}
|
|
44466
44419
|
const checkpointDir = validateSwarmPath(directory, `checkpoints/phase-${targetPhase}`);
|
|
44467
|
-
if (!
|
|
44420
|
+
if (!fs21.existsSync(checkpointDir)) {
|
|
44468
44421
|
return `Error: Checkpoint directory for phase ${targetPhase} does not exist.`;
|
|
44469
44422
|
}
|
|
44470
|
-
const checkpointFiles =
|
|
44423
|
+
const checkpointFiles = fs21.readdirSync(checkpointDir);
|
|
44471
44424
|
if (checkpointFiles.length === 0) {
|
|
44472
44425
|
return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
|
|
44473
44426
|
}
|
|
@@ -44478,7 +44431,7 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44478
44431
|
const src = path32.join(checkpointDir, file3);
|
|
44479
44432
|
const dest = path32.join(swarmDir, file3);
|
|
44480
44433
|
try {
|
|
44481
|
-
|
|
44434
|
+
fs21.cpSync(src, dest, { recursive: true, force: true });
|
|
44482
44435
|
successes.push(file3);
|
|
44483
44436
|
} catch (error93) {
|
|
44484
44437
|
failures.push({ file: file3, error: error93.message });
|
|
@@ -44495,7 +44448,7 @@ async function handleRollbackCommand(directory, args) {
|
|
|
44495
44448
|
timestamp: new Date().toISOString()
|
|
44496
44449
|
};
|
|
44497
44450
|
try {
|
|
44498
|
-
|
|
44451
|
+
fs21.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
|
|
44499
44452
|
`);
|
|
44500
44453
|
} catch (error93) {
|
|
44501
44454
|
console.error("Failed to write rollback event:", error93 instanceof Error ? error93.message : String(error93));
|
|
@@ -44538,11 +44491,11 @@ async function handleSimulateCommand(directory, args) {
|
|
|
44538
44491
|
];
|
|
44539
44492
|
const report = reportLines.filter(Boolean).join(`
|
|
44540
44493
|
`);
|
|
44541
|
-
const
|
|
44494
|
+
const fs22 = await import("fs/promises");
|
|
44542
44495
|
const path33 = await import("path");
|
|
44543
44496
|
const reportPath = path33.join(directory, ".swarm", "simulate-report.md");
|
|
44544
|
-
await
|
|
44545
|
-
await
|
|
44497
|
+
await fs22.mkdir(path33.dirname(reportPath), { recursive: true });
|
|
44498
|
+
await fs22.writeFile(reportPath, report, "utf-8");
|
|
44546
44499
|
return `${darkMatterPairs.length} hidden coupling pairs detected`;
|
|
44547
44500
|
}
|
|
44548
44501
|
|
|
@@ -45075,19 +45028,19 @@ function resolveCommand(tokens) {
|
|
|
45075
45028
|
}
|
|
45076
45029
|
|
|
45077
45030
|
// src/cli/index.ts
|
|
45078
|
-
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");
|
|
45079
45032
|
var OPENCODE_CONFIG_PATH = path33.join(CONFIG_DIR, "opencode.json");
|
|
45080
45033
|
var PLUGIN_CONFIG_PATH = path33.join(CONFIG_DIR, "opencode-swarm.json");
|
|
45081
45034
|
var PROMPTS_DIR = path33.join(CONFIG_DIR, "opencode-swarm");
|
|
45082
|
-
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");
|
|
45083
45036
|
function ensureDir(dir) {
|
|
45084
|
-
if (!
|
|
45085
|
-
|
|
45037
|
+
if (!fs22.existsSync(dir)) {
|
|
45038
|
+
fs22.mkdirSync(dir, { recursive: true });
|
|
45086
45039
|
}
|
|
45087
45040
|
}
|
|
45088
45041
|
function loadJson(filepath) {
|
|
45089
45042
|
try {
|
|
45090
|
-
const content =
|
|
45043
|
+
const content = fs22.readFileSync(filepath, "utf-8");
|
|
45091
45044
|
const stripped = content.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (match, comment) => comment ? "" : match).replace(/,(\s*[}\]])/g, "$1");
|
|
45092
45045
|
return JSON.parse(stripped);
|
|
45093
45046
|
} catch {
|
|
@@ -45095,7 +45048,7 @@ function loadJson(filepath) {
|
|
|
45095
45048
|
}
|
|
45096
45049
|
}
|
|
45097
45050
|
function saveJson(filepath, data) {
|
|
45098
|
-
|
|
45051
|
+
fs22.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
|
|
45099
45052
|
`, "utf-8");
|
|
45100
45053
|
}
|
|
45101
45054
|
async function install() {
|
|
@@ -45135,15 +45088,15 @@ async function install() {
|
|
|
45135
45088
|
console.log("\u2713 Added opencode-swarm to OpenCode plugins");
|
|
45136
45089
|
console.log("\u2713 Disabled default OpenCode agents (explore, general)");
|
|
45137
45090
|
try {
|
|
45138
|
-
if (
|
|
45139
|
-
|
|
45091
|
+
if (fs22.existsSync(OPENCODE_PLUGIN_CACHE_PATH)) {
|
|
45092
|
+
fs22.rmSync(OPENCODE_PLUGIN_CACHE_PATH, { recursive: true, force: true });
|
|
45140
45093
|
console.log("\u2713 Cleared opencode plugin cache (next start will fetch latest)");
|
|
45141
45094
|
}
|
|
45142
45095
|
} catch {
|
|
45143
45096
|
console.warn("\u26A0 Could not clear opencode plugin cache \u2014 you may need to delete it manually:");
|
|
45144
45097
|
console.warn(` ${OPENCODE_PLUGIN_CACHE_PATH}`);
|
|
45145
45098
|
}
|
|
45146
|
-
if (!
|
|
45099
|
+
if (!fs22.existsSync(PLUGIN_CONFIG_PATH)) {
|
|
45147
45100
|
const defaultConfig = {
|
|
45148
45101
|
agents: {
|
|
45149
45102
|
coder: {
|
|
@@ -45246,7 +45199,7 @@ async function uninstall() {
|
|
|
45246
45199
|
`);
|
|
45247
45200
|
const opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
|
|
45248
45201
|
if (!opencodeConfig) {
|
|
45249
|
-
if (
|
|
45202
|
+
if (fs22.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
45250
45203
|
console.log(`\u2717 Could not parse opencode config at: ${OPENCODE_CONFIG_PATH}`);
|
|
45251
45204
|
return 1;
|
|
45252
45205
|
} else {
|
|
@@ -45278,13 +45231,13 @@ async function uninstall() {
|
|
|
45278
45231
|
console.log("\u2713 Re-enabled default OpenCode agents (explore, general)");
|
|
45279
45232
|
if (process.argv.includes("--clean")) {
|
|
45280
45233
|
let cleaned = false;
|
|
45281
|
-
if (
|
|
45282
|
-
|
|
45234
|
+
if (fs22.existsSync(PLUGIN_CONFIG_PATH)) {
|
|
45235
|
+
fs22.unlinkSync(PLUGIN_CONFIG_PATH);
|
|
45283
45236
|
console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
|
|
45284
45237
|
cleaned = true;
|
|
45285
45238
|
}
|
|
45286
|
-
if (
|
|
45287
|
-
|
|
45239
|
+
if (fs22.existsSync(PROMPTS_DIR)) {
|
|
45240
|
+
fs22.rmSync(PROMPTS_DIR, { recursive: true });
|
|
45288
45241
|
console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
|
|
45289
45242
|
cleaned = true;
|
|
45290
45243
|
}
|