opencode-swarm 6.8.0 → 6.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -5
- package/dist/index.js +1252 -1316
- package/dist/src/agents/architect.d.ts +8 -0
- package/dist/{agents/test-engineer.d.ts → src/agents/coder.d.ts} +2 -1
- package/dist/src/agents/critic.d.ts +3 -0
- package/dist/src/agents/designer.d.ts +3 -0
- package/dist/src/agents/docs.d.ts +3 -0
- package/dist/src/agents/explorer.d.ts +3 -0
- package/dist/src/agents/model.d.ts +2 -0
- package/dist/{agents → src/agents}/reviewer.d.ts +2 -1
- package/dist/src/agents/sme.d.ts +3 -0
- package/dist/src/agents/test-engineer.d.ts +3 -0
- package/dist/{background → src/background}/trigger.d.ts +0 -16
- package/dist/src/config/loader.d.ts +16 -0
- package/dist/{config → src/config}/schema.d.ts +171 -0
- package/dist/{index.d.ts → src/index.d.ts} +0 -10
- package/dist/{state.d.ts → src/state.d.ts} +5 -0
- package/dist/{tools → src/tools}/gitingest.d.ts +2 -1
- package/dist/{tools/test-runner.d.ts → src/tools/test-runner/constants.d.ts} +0 -4
- package/dist/src/tools/test-runner/detect.d.ts +2 -0
- package/dist/src/tools/test-runner/discover.d.ts +4 -0
- package/dist/src/tools/test-runner/index.d.ts +6 -0
- package/dist/src/tools/test-runner/run.d.ts +2 -0
- package/dist/src/tools/test-runner/validate.d.ts +2 -0
- package/dist/src/utils/index.d.ts +8 -0
- package/package.json +1 -1
- package/dist/agents/architect.d.ts +0 -7
- package/dist/agents/coder.d.ts +0 -2
- package/dist/agents/critic.d.ts +0 -2
- package/dist/agents/designer.d.ts +0 -2
- package/dist/agents/docs.d.ts +0 -2
- package/dist/agents/explorer.d.ts +0 -2
- package/dist/agents/sme.d.ts +0 -2
- package/dist/config/loader.d.ts +0 -32
- package/dist/utils/index.d.ts +0 -3
- /package/dist/{__tests__ → src/__tests__}/security-adversarial.test.d.ts +0 -0
- /package/dist/{agents → src/agents}/index.d.ts +0 -0
- /package/dist/{agents → src/agents}/test-engineer.adversarial.test.d.ts +0 -0
- /package/dist/{agents → src/agents}/test-engineer.security.test.d.ts +0 -0
- /package/dist/{background → src/background}/circuit-breaker.d.ts +0 -0
- /package/dist/{background → src/background}/event-bus.d.ts +0 -0
- /package/dist/{background → src/background}/evidence-summary-integration.d.ts +0 -0
- /package/dist/{background → src/background}/index.d.ts +0 -0
- /package/dist/{background → src/background}/manager.d.ts +0 -0
- /package/dist/{background → src/background}/plan-sync-worker.d.ts +0 -0
- /package/dist/{background → src/background}/queue.d.ts +0 -0
- /package/dist/{background → src/background}/status-artifact.d.ts +0 -0
- /package/dist/{background → src/background}/trigger.vulnerability.test.d.ts +0 -0
- /package/dist/{background → src/background}/worker.d.ts +0 -0
- /package/dist/{cli → src/cli}/index.d.ts +0 -0
- /package/dist/{commands → src/commands}/agents.d.ts +0 -0
- /package/dist/{commands → src/commands}/archive.d.ts +0 -0
- /package/dist/{commands → src/commands}/benchmark.d.ts +0 -0
- /package/dist/{commands → src/commands}/command-adapters.security.test.d.ts +0 -0
- /package/dist/{commands → src/commands}/commands.test.d.ts +0 -0
- /package/dist/{commands → src/commands}/config.d.ts +0 -0
- /package/dist/{commands → src/commands}/diagnose.d.ts +0 -0
- /package/dist/{commands → src/commands}/doctor.d.ts +0 -0
- /package/dist/{commands → src/commands}/evidence.d.ts +0 -0
- /package/dist/{commands → src/commands}/export.d.ts +0 -0
- /package/dist/{commands → src/commands}/history.d.ts +0 -0
- /package/dist/{commands → src/commands}/index.d.ts +0 -0
- /package/dist/{commands → src/commands}/plan.d.ts +0 -0
- /package/dist/{commands → src/commands}/preflight.d.ts +0 -0
- /package/dist/{commands → src/commands}/reset.d.ts +0 -0
- /package/dist/{commands → src/commands}/retrieve.d.ts +0 -0
- /package/dist/{commands → src/commands}/status.d.ts +0 -0
- /package/dist/{commands → src/commands}/sync-plan.d.ts +0 -0
- /package/dist/{config → src/config}/constants.d.ts +0 -0
- /package/dist/{config → src/config}/evidence-schema.d.ts +0 -0
- /package/dist/{config → src/config}/index.d.ts +0 -0
- /package/dist/{config → src/config}/plan-schema.d.ts +0 -0
- /package/dist/{evidence → src/evidence}/index.d.ts +0 -0
- /package/dist/{evidence → src/evidence}/manager.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/agent-activity.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/compaction-customizer.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/context-budget.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/context-scoring.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/delegation-gate.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/delegation-tracker.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/extractors.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/guardrails.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/index.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/phase-monitor.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/pipeline-tracker.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/system-enhancer.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/tool-summarizer.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/utils.d.ts +0 -0
- /package/dist/{plan → src/plan}/index.d.ts +0 -0
- /package/dist/{plan → src/plan}/manager.d.ts +0 -0
- /package/dist/{services → src/services}/config-doctor.d.ts +0 -0
- /package/dist/{services → src/services}/config-doctor.security.test.d.ts +0 -0
- /package/dist/{services → src/services}/config-doctor.test.d.ts +0 -0
- /package/dist/{services → src/services}/decision-drift-analyzer.d.ts +0 -0
- /package/dist/{services → src/services}/diagnose-service.d.ts +0 -0
- /package/dist/{services → src/services}/evidence-service.d.ts +0 -0
- /package/dist/{services → src/services}/evidence-summary-service.d.ts +0 -0
- /package/dist/{services → src/services}/export-service.d.ts +0 -0
- /package/dist/{services → src/services}/history-service.d.ts +0 -0
- /package/dist/{services → src/services}/index.d.ts +0 -0
- /package/dist/{services → src/services}/plan-service.d.ts +0 -0
- /package/dist/{services → src/services}/preflight-integration.d.ts +0 -0
- /package/dist/{services → src/services}/preflight-service.d.ts +0 -0
- /package/dist/{services → src/services}/status-service.d.ts +0 -0
- /package/dist/{summaries → src/summaries}/index.d.ts +0 -0
- /package/dist/{summaries → src/summaries}/manager.d.ts +0 -0
- /package/dist/{summaries → src/summaries}/summarizer.d.ts +0 -0
- /package/dist/{tools → src/tools}/checkpoint.d.ts +0 -0
- /package/dist/{tools → src/tools}/complexity-hotspots.d.ts +0 -0
- /package/dist/{tools → src/tools}/diff.d.ts +0 -0
- /package/dist/{tools → src/tools}/domain-detector.d.ts +0 -0
- /package/dist/{tools → src/tools}/evidence-check.d.ts +0 -0
- /package/dist/{tools → src/tools}/file-extractor.d.ts +0 -0
- /package/dist/{tools → src/tools}/imports.d.ts +0 -0
- /package/dist/{tools → src/tools}/index.d.ts +0 -0
- /package/dist/{tools → src/tools}/lint.d.ts +0 -0
- /package/dist/{tools → src/tools}/pkg-audit.d.ts +0 -0
- /package/dist/{tools → src/tools}/retrieve-summary.d.ts +0 -0
- /package/dist/{tools → src/tools}/schema-drift.d.ts +0 -0
- /package/dist/{tools → src/tools}/secretscan.d.ts +0 -0
- /package/dist/{tools → src/tools}/symbols.d.ts +0 -0
- /package/dist/{tools → src/tools}/test-runner.security-adversarial.test.d.ts +0 -0
- /package/dist/{tools → src/tools}/todo-extract.d.ts +0 -0
- /package/dist/{utils → src/utils}/errors.d.ts +0 -0
- /package/dist/{utils → src/utils}/logger.d.ts +0 -0
- /package/dist/{utils → src/utils}/merge.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
3
18
|
var __export = (target, all) => {
|
|
4
19
|
for (var name in all)
|
|
5
20
|
__defProp(target, name, {
|
|
@@ -13999,6 +14014,52 @@ var init_evidence_schema = __esm(() => {
|
|
|
13999
14014
|
});
|
|
14000
14015
|
});
|
|
14001
14016
|
|
|
14017
|
+
// src/utils/errors.ts
|
|
14018
|
+
var SwarmError;
|
|
14019
|
+
var init_errors3 = __esm(() => {
|
|
14020
|
+
SwarmError = class SwarmError extends Error {
|
|
14021
|
+
code;
|
|
14022
|
+
guidance;
|
|
14023
|
+
constructor(message, code, guidance) {
|
|
14024
|
+
super(message);
|
|
14025
|
+
this.name = "SwarmError";
|
|
14026
|
+
this.code = code;
|
|
14027
|
+
this.guidance = guidance;
|
|
14028
|
+
}
|
|
14029
|
+
};
|
|
14030
|
+
});
|
|
14031
|
+
|
|
14032
|
+
// src/utils/logger.ts
|
|
14033
|
+
function log(message, data) {
|
|
14034
|
+
if (!isDebug())
|
|
14035
|
+
return;
|
|
14036
|
+
const timestamp = new Date().toISOString();
|
|
14037
|
+
if (data !== undefined) {
|
|
14038
|
+
console.log(`[opencode-swarm ${timestamp}] ${message}`, data);
|
|
14039
|
+
} else {
|
|
14040
|
+
console.log(`[opencode-swarm ${timestamp}] ${message}`);
|
|
14041
|
+
}
|
|
14042
|
+
}
|
|
14043
|
+
function warn(message, data) {
|
|
14044
|
+
const timestamp = new Date().toISOString();
|
|
14045
|
+
if (data !== undefined) {
|
|
14046
|
+
console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
|
|
14047
|
+
} else {
|
|
14048
|
+
console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`);
|
|
14049
|
+
}
|
|
14050
|
+
}
|
|
14051
|
+
var isDebug = () => process.env.OPENCODE_SWARM_DEBUG === "1";
|
|
14052
|
+
|
|
14053
|
+
// src/utils/index.ts
|
|
14054
|
+
import * as os from "os";
|
|
14055
|
+
import * as path from "path";
|
|
14056
|
+
function getUserConfigDir() {
|
|
14057
|
+
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
14058
|
+
}
|
|
14059
|
+
var init_utils = __esm(() => {
|
|
14060
|
+
init_errors3();
|
|
14061
|
+
});
|
|
14062
|
+
|
|
14002
14063
|
// src/config/plan-schema.ts
|
|
14003
14064
|
var TaskStatusSchema, TaskSizeSchema, PhaseStatusSchema, MigrationStatusSchema, TaskSchema, PhaseSchema, PlanSchema;
|
|
14004
14065
|
var init_plan_schema = __esm(() => {
|
|
@@ -14049,51 +14110,6 @@ var init_plan_schema = __esm(() => {
|
|
|
14049
14110
|
});
|
|
14050
14111
|
});
|
|
14051
14112
|
|
|
14052
|
-
// src/utils/errors.ts
|
|
14053
|
-
var SwarmError;
|
|
14054
|
-
var init_errors3 = __esm(() => {
|
|
14055
|
-
SwarmError = class SwarmError extends Error {
|
|
14056
|
-
code;
|
|
14057
|
-
guidance;
|
|
14058
|
-
constructor(message, code, guidance) {
|
|
14059
|
-
super(message);
|
|
14060
|
-
this.name = "SwarmError";
|
|
14061
|
-
this.code = code;
|
|
14062
|
-
this.guidance = guidance;
|
|
14063
|
-
}
|
|
14064
|
-
};
|
|
14065
|
-
});
|
|
14066
|
-
|
|
14067
|
-
// src/utils/logger.ts
|
|
14068
|
-
function log(message, data) {
|
|
14069
|
-
if (!DEBUG)
|
|
14070
|
-
return;
|
|
14071
|
-
const timestamp = new Date().toISOString();
|
|
14072
|
-
if (data !== undefined) {
|
|
14073
|
-
console.log(`[opencode-swarm ${timestamp}] ${message}`, data);
|
|
14074
|
-
} else {
|
|
14075
|
-
console.log(`[opencode-swarm ${timestamp}] ${message}`);
|
|
14076
|
-
}
|
|
14077
|
-
}
|
|
14078
|
-
function warn(message, data) {
|
|
14079
|
-
const timestamp = new Date().toISOString();
|
|
14080
|
-
if (data !== undefined) {
|
|
14081
|
-
console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
|
|
14082
|
-
} else {
|
|
14083
|
-
console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`);
|
|
14084
|
-
}
|
|
14085
|
-
}
|
|
14086
|
-
var DEBUG;
|
|
14087
|
-
var init_logger = __esm(() => {
|
|
14088
|
-
DEBUG = process.env.OPENCODE_SWARM_DEBUG === "1";
|
|
14089
|
-
});
|
|
14090
|
-
|
|
14091
|
-
// src/utils/index.ts
|
|
14092
|
-
var init_utils = __esm(() => {
|
|
14093
|
-
init_errors3();
|
|
14094
|
-
init_logger();
|
|
14095
|
-
});
|
|
14096
|
-
|
|
14097
14113
|
// src/background/event-bus.ts
|
|
14098
14114
|
class AutomationEventBus {
|
|
14099
14115
|
listeners = new Map;
|
|
@@ -14165,7 +14181,7 @@ var init_event_bus = __esm(() => {
|
|
|
14165
14181
|
});
|
|
14166
14182
|
|
|
14167
14183
|
// src/hooks/utils.ts
|
|
14168
|
-
import * as
|
|
14184
|
+
import * as path3 from "path";
|
|
14169
14185
|
function safeHook(fn) {
|
|
14170
14186
|
return async (input, output) => {
|
|
14171
14187
|
try {
|
|
@@ -14196,17 +14212,40 @@ function validateSwarmPath(directory, filename) {
|
|
|
14196
14212
|
if (/[\0]/.test(filename)) {
|
|
14197
14213
|
throw new Error("Invalid filename: contains null bytes");
|
|
14198
14214
|
}
|
|
14215
|
+
if (filename.includes("%")) {
|
|
14216
|
+
let decoded = filename;
|
|
14217
|
+
try {
|
|
14218
|
+
let prev = decoded;
|
|
14219
|
+
do {
|
|
14220
|
+
prev = decoded;
|
|
14221
|
+
decoded = decodeURIComponent(decoded);
|
|
14222
|
+
} while (decoded !== prev && decoded.includes("%"));
|
|
14223
|
+
} catch {
|
|
14224
|
+
throw new Error("Invalid filename: malformed percent-encoding");
|
|
14225
|
+
}
|
|
14226
|
+
if (/[\0]/.test(decoded)) {
|
|
14227
|
+
throw new Error("Invalid filename: contains null bytes (encoded)");
|
|
14228
|
+
}
|
|
14229
|
+
if (/\.\.[/\\]/.test(decoded) || decoded === "..") {
|
|
14230
|
+
throw new Error("Invalid filename: path traversal detected (encoded)");
|
|
14231
|
+
}
|
|
14232
|
+
}
|
|
14233
|
+
const basename2 = path3.basename(filename).split(".")[0].toUpperCase();
|
|
14234
|
+
const WINDOWS_RESERVED = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/;
|
|
14235
|
+
if (WINDOWS_RESERVED.test(basename2)) {
|
|
14236
|
+
throw new Error(`Invalid filename: reserved device name '${basename2}'`);
|
|
14237
|
+
}
|
|
14199
14238
|
if (/\.\.[/\\]/.test(filename)) {
|
|
14200
14239
|
throw new Error("Invalid filename: path traversal detected");
|
|
14201
14240
|
}
|
|
14202
|
-
const baseDir =
|
|
14203
|
-
const resolved =
|
|
14241
|
+
const baseDir = path3.normalize(path3.resolve(directory, ".swarm"));
|
|
14242
|
+
const resolved = path3.normalize(path3.resolve(baseDir, filename));
|
|
14204
14243
|
if (process.platform === "win32") {
|
|
14205
|
-
if (!resolved.toLowerCase().startsWith((baseDir +
|
|
14244
|
+
if (!resolved.toLowerCase().startsWith((baseDir + path3.sep).toLowerCase())) {
|
|
14206
14245
|
throw new Error("Invalid filename: path escapes .swarm directory");
|
|
14207
14246
|
}
|
|
14208
14247
|
} else {
|
|
14209
|
-
if (!resolved.startsWith(baseDir +
|
|
14248
|
+
if (!resolved.startsWith(baseDir + path3.sep)) {
|
|
14210
14249
|
throw new Error("Invalid filename: path escapes .swarm directory");
|
|
14211
14250
|
}
|
|
14212
14251
|
}
|
|
@@ -14233,8 +14272,8 @@ var init_utils2 = __esm(() => {
|
|
|
14233
14272
|
});
|
|
14234
14273
|
|
|
14235
14274
|
// src/evidence/manager.ts
|
|
14236
|
-
import { mkdirSync, readdirSync, renameSync, rmSync, statSync
|
|
14237
|
-
import * as
|
|
14275
|
+
import { mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
|
|
14276
|
+
import * as path4 from "path";
|
|
14238
14277
|
function sanitizeTaskId(taskId) {
|
|
14239
14278
|
if (!taskId || taskId.length === 0) {
|
|
14240
14279
|
throw new Error("Invalid task ID: empty string");
|
|
@@ -14257,7 +14296,7 @@ function sanitizeTaskId(taskId) {
|
|
|
14257
14296
|
}
|
|
14258
14297
|
async function loadEvidence(directory, taskId) {
|
|
14259
14298
|
const sanitizedTaskId = sanitizeTaskId(taskId);
|
|
14260
|
-
const relativePath =
|
|
14299
|
+
const relativePath = path4.join("evidence", sanitizedTaskId, "evidence.json");
|
|
14261
14300
|
validateSwarmPath(directory, relativePath);
|
|
14262
14301
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
14263
14302
|
if (content === null) {
|
|
@@ -14275,7 +14314,7 @@ async function loadEvidence(directory, taskId) {
|
|
|
14275
14314
|
async function listEvidenceTaskIds(directory) {
|
|
14276
14315
|
const evidenceBasePath = validateSwarmPath(directory, "evidence");
|
|
14277
14316
|
try {
|
|
14278
|
-
|
|
14317
|
+
statSync(evidenceBasePath);
|
|
14279
14318
|
} catch {
|
|
14280
14319
|
return [];
|
|
14281
14320
|
}
|
|
@@ -14287,9 +14326,9 @@ async function listEvidenceTaskIds(directory) {
|
|
|
14287
14326
|
}
|
|
14288
14327
|
const taskIds = [];
|
|
14289
14328
|
for (const entry of entries) {
|
|
14290
|
-
const entryPath =
|
|
14329
|
+
const entryPath = path4.join(evidenceBasePath, entry);
|
|
14291
14330
|
try {
|
|
14292
|
-
const stats =
|
|
14331
|
+
const stats = statSync(entryPath);
|
|
14293
14332
|
if (!stats.isDirectory()) {
|
|
14294
14333
|
continue;
|
|
14295
14334
|
}
|
|
@@ -14305,10 +14344,10 @@ async function listEvidenceTaskIds(directory) {
|
|
|
14305
14344
|
}
|
|
14306
14345
|
async function deleteEvidence(directory, taskId) {
|
|
14307
14346
|
const sanitizedTaskId = sanitizeTaskId(taskId);
|
|
14308
|
-
const relativePath =
|
|
14347
|
+
const relativePath = path4.join("evidence", sanitizedTaskId);
|
|
14309
14348
|
const evidenceDir = validateSwarmPath(directory, relativePath);
|
|
14310
14349
|
try {
|
|
14311
|
-
|
|
14350
|
+
statSync(evidenceDir);
|
|
14312
14351
|
} catch {
|
|
14313
14352
|
return false;
|
|
14314
14353
|
}
|
|
@@ -14365,7 +14404,8 @@ var init_manager = __esm(() => {
|
|
|
14365
14404
|
});
|
|
14366
14405
|
|
|
14367
14406
|
// src/plan/manager.ts
|
|
14368
|
-
import
|
|
14407
|
+
import { createHash } from "crypto";
|
|
14408
|
+
import * as path5 from "path";
|
|
14369
14409
|
async function loadPlanJsonOnly(directory) {
|
|
14370
14410
|
const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
|
|
14371
14411
|
if (planJsonContent !== null) {
|
|
@@ -14418,7 +14458,7 @@ function computePlanContentHash(plan) {
|
|
|
14418
14458
|
})).sort((a, b) => a.id - b.id)
|
|
14419
14459
|
};
|
|
14420
14460
|
const jsonString = JSON.stringify(content);
|
|
14421
|
-
return
|
|
14461
|
+
return createHash("sha256").update(jsonString).digest("hex").slice(0, 12);
|
|
14422
14462
|
}
|
|
14423
14463
|
function extractPlanHashFromMarkdown(markdown) {
|
|
14424
14464
|
const match = markdown.match(/<!--\s*PLAN_HASH:\s*(\S+)\s*-->/);
|
|
@@ -14443,12 +14483,12 @@ async function isPlanMdInSync(directory, plan) {
|
|
|
14443
14483
|
return normalizedActual.includes(normalizedExpected) || normalizedExpected.includes(normalizedActual.replace(/^#.*$/gm, "").trim());
|
|
14444
14484
|
}
|
|
14445
14485
|
async function regeneratePlanMarkdown(directory, plan) {
|
|
14446
|
-
const swarmDir =
|
|
14486
|
+
const swarmDir = path5.resolve(directory, ".swarm");
|
|
14447
14487
|
const contentHash = computePlanContentHash(plan);
|
|
14448
14488
|
const markdown = derivePlanMarkdown(plan);
|
|
14449
14489
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
14450
14490
|
${markdown}`;
|
|
14451
|
-
await Bun.write(
|
|
14491
|
+
await Bun.write(path5.join(swarmDir, "plan.md"), markdownWithHash);
|
|
14452
14492
|
}
|
|
14453
14493
|
async function loadPlan(directory) {
|
|
14454
14494
|
const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
|
|
@@ -14485,9 +14525,9 @@ async function loadPlan(directory) {
|
|
|
14485
14525
|
}
|
|
14486
14526
|
async function savePlan(directory, plan) {
|
|
14487
14527
|
const validated = PlanSchema.parse(plan);
|
|
14488
|
-
const swarmDir =
|
|
14489
|
-
const planPath =
|
|
14490
|
-
const tempPath =
|
|
14528
|
+
const swarmDir = path5.resolve(directory, ".swarm");
|
|
14529
|
+
const planPath = path5.join(swarmDir, "plan.json");
|
|
14530
|
+
const tempPath = path5.join(swarmDir, `plan.json.tmp.${Date.now()}`);
|
|
14491
14531
|
await Bun.write(tempPath, JSON.stringify(validated, null, 2));
|
|
14492
14532
|
const { renameSync: renameSync2 } = await import("fs");
|
|
14493
14533
|
renameSync2(tempPath, planPath);
|
|
@@ -14495,7 +14535,7 @@ async function savePlan(directory, plan) {
|
|
|
14495
14535
|
const markdown = derivePlanMarkdown(validated);
|
|
14496
14536
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
14497
14537
|
${markdown}`;
|
|
14498
|
-
await Bun.write(
|
|
14538
|
+
await Bun.write(path5.join(swarmDir, "plan.md"), markdownWithHash);
|
|
14499
14539
|
}
|
|
14500
14540
|
function derivePlanMarkdown(plan) {
|
|
14501
14541
|
const statusMap = {
|
|
@@ -14979,13 +15019,13 @@ __export(exports_evidence_summary_integration, {
|
|
|
14979
15019
|
EvidenceSummaryIntegration: () => EvidenceSummaryIntegration
|
|
14980
15020
|
});
|
|
14981
15021
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
14982
|
-
import * as
|
|
15022
|
+
import * as path6 from "path";
|
|
14983
15023
|
function persistSummary(swarmDir, artifact, filename) {
|
|
14984
|
-
const swarmPath =
|
|
15024
|
+
const swarmPath = path6.join(swarmDir, ".swarm");
|
|
14985
15025
|
if (!existsSync2(swarmPath)) {
|
|
14986
15026
|
mkdirSync2(swarmPath, { recursive: true });
|
|
14987
15027
|
}
|
|
14988
|
-
const artifactPath =
|
|
15028
|
+
const artifactPath = path6.join(swarmPath, filename);
|
|
14989
15029
|
const content = JSON.stringify(artifact, null, 2);
|
|
14990
15030
|
writeFileSync(artifactPath, content, "utf-8");
|
|
14991
15031
|
log("[EvidenceSummaryIntegration] Summary persisted", {
|
|
@@ -15250,7 +15290,7 @@ __export(exports_status_artifact, {
|
|
|
15250
15290
|
AutomationStatusArtifact: () => AutomationStatusArtifact
|
|
15251
15291
|
});
|
|
15252
15292
|
import * as fs3 from "fs";
|
|
15253
|
-
import * as
|
|
15293
|
+
import * as path8 from "path";
|
|
15254
15294
|
function createEmptySnapshot(mode, capabilities) {
|
|
15255
15295
|
return {
|
|
15256
15296
|
timestamp: Date.now(),
|
|
@@ -15309,7 +15349,7 @@ class AutomationStatusArtifact {
|
|
|
15309
15349
|
});
|
|
15310
15350
|
}
|
|
15311
15351
|
getFilePath() {
|
|
15312
|
-
return
|
|
15352
|
+
return path8.join(this.swarmDir, this.filename);
|
|
15313
15353
|
}
|
|
15314
15354
|
load() {
|
|
15315
15355
|
const filePath = this.getFilePath();
|
|
@@ -15714,14 +15754,10 @@ __export(exports_config_doctor, {
|
|
|
15714
15754
|
});
|
|
15715
15755
|
import * as crypto from "crypto";
|
|
15716
15756
|
import * as fs4 from "fs";
|
|
15717
|
-
import * as
|
|
15718
|
-
import * as path9 from "path";
|
|
15719
|
-
function getUserConfigDir3() {
|
|
15720
|
-
return process.env.XDG_CONFIG_HOME || path9.join(os3.homedir(), ".config");
|
|
15721
|
-
}
|
|
15757
|
+
import * as path10 from "path";
|
|
15722
15758
|
function getConfigPaths(directory) {
|
|
15723
|
-
const userConfigPath =
|
|
15724
|
-
const projectConfigPath =
|
|
15759
|
+
const userConfigPath = path10.join(getUserConfigDir(), "opencode", "opencode-swarm.json");
|
|
15760
|
+
const projectConfigPath = path10.join(directory, ".opencode", "opencode-swarm.json");
|
|
15725
15761
|
return { userConfigPath, projectConfigPath };
|
|
15726
15762
|
}
|
|
15727
15763
|
function computeHash(content) {
|
|
@@ -15746,9 +15782,9 @@ function isValidConfigPath(configPath, directory) {
|
|
|
15746
15782
|
const normalizedUser = userConfigPath.replace(/\\/g, "/");
|
|
15747
15783
|
const normalizedProject = projectConfigPath.replace(/\\/g, "/");
|
|
15748
15784
|
try {
|
|
15749
|
-
const resolvedConfig =
|
|
15750
|
-
const resolvedUser =
|
|
15751
|
-
const resolvedProject =
|
|
15785
|
+
const resolvedConfig = path10.resolve(configPath);
|
|
15786
|
+
const resolvedUser = path10.resolve(normalizedUser);
|
|
15787
|
+
const resolvedProject = path10.resolve(normalizedProject);
|
|
15752
15788
|
return resolvedConfig === resolvedUser || resolvedConfig === resolvedProject;
|
|
15753
15789
|
} catch {
|
|
15754
15790
|
return false;
|
|
@@ -15780,12 +15816,12 @@ function createConfigBackup(directory) {
|
|
|
15780
15816
|
};
|
|
15781
15817
|
}
|
|
15782
15818
|
function writeBackupArtifact(directory, backup) {
|
|
15783
|
-
const swarmDir =
|
|
15819
|
+
const swarmDir = path10.join(directory, ".swarm");
|
|
15784
15820
|
if (!fs4.existsSync(swarmDir)) {
|
|
15785
15821
|
fs4.mkdirSync(swarmDir, { recursive: true });
|
|
15786
15822
|
}
|
|
15787
15823
|
const backupFilename = `config-backup-${backup.createdAt}.json`;
|
|
15788
|
-
const backupPath =
|
|
15824
|
+
const backupPath = path10.join(swarmDir, backupFilename);
|
|
15789
15825
|
const artifact = {
|
|
15790
15826
|
createdAt: backup.createdAt,
|
|
15791
15827
|
configPath: backup.configPath,
|
|
@@ -15815,7 +15851,7 @@ function restoreFromBackup(backupPath, directory) {
|
|
|
15815
15851
|
return null;
|
|
15816
15852
|
}
|
|
15817
15853
|
const targetPath = artifact.configPath;
|
|
15818
|
-
const targetDir =
|
|
15854
|
+
const targetDir = path10.dirname(targetPath);
|
|
15819
15855
|
if (!fs4.existsSync(targetDir)) {
|
|
15820
15856
|
fs4.mkdirSync(targetDir, { recursive: true });
|
|
15821
15857
|
}
|
|
@@ -15846,9 +15882,9 @@ function readConfigFromFile(directory) {
|
|
|
15846
15882
|
return null;
|
|
15847
15883
|
}
|
|
15848
15884
|
}
|
|
15849
|
-
function validateConfigKey(
|
|
15885
|
+
function validateConfigKey(path11, value, config2) {
|
|
15850
15886
|
const findings = [];
|
|
15851
|
-
switch (
|
|
15887
|
+
switch (path11) {
|
|
15852
15888
|
case "agents": {
|
|
15853
15889
|
if (value !== undefined) {
|
|
15854
15890
|
findings.push({
|
|
@@ -16095,27 +16131,27 @@ function validateConfigKey(path10, value, config2) {
|
|
|
16095
16131
|
}
|
|
16096
16132
|
return findings;
|
|
16097
16133
|
}
|
|
16098
|
-
function walkConfigAndValidate(obj,
|
|
16134
|
+
function walkConfigAndValidate(obj, path11, config2, findings) {
|
|
16099
16135
|
if (obj === null || obj === undefined) {
|
|
16100
16136
|
return;
|
|
16101
16137
|
}
|
|
16102
|
-
if (
|
|
16103
|
-
const keyFindings = validateConfigKey(
|
|
16138
|
+
if (path11 && typeof obj === "object" && !Array.isArray(obj)) {
|
|
16139
|
+
const keyFindings = validateConfigKey(path11, obj, config2);
|
|
16104
16140
|
findings.push(...keyFindings);
|
|
16105
16141
|
}
|
|
16106
16142
|
if (typeof obj !== "object") {
|
|
16107
|
-
const keyFindings = validateConfigKey(
|
|
16143
|
+
const keyFindings = validateConfigKey(path11, obj, config2);
|
|
16108
16144
|
findings.push(...keyFindings);
|
|
16109
16145
|
return;
|
|
16110
16146
|
}
|
|
16111
16147
|
if (Array.isArray(obj)) {
|
|
16112
16148
|
obj.forEach((item, index) => {
|
|
16113
|
-
walkConfigAndValidate(item, `${
|
|
16149
|
+
walkConfigAndValidate(item, `${path11}[${index}]`, config2, findings);
|
|
16114
16150
|
});
|
|
16115
16151
|
return;
|
|
16116
16152
|
}
|
|
16117
16153
|
for (const [key, value] of Object.entries(obj)) {
|
|
16118
|
-
const newPath =
|
|
16154
|
+
const newPath = path11 ? `${path11}.${key}` : key;
|
|
16119
16155
|
walkConfigAndValidate(value, newPath, config2, findings);
|
|
16120
16156
|
}
|
|
16121
16157
|
}
|
|
@@ -16235,7 +16271,7 @@ function applySafeAutoFixes(directory, result) {
|
|
|
16235
16271
|
}
|
|
16236
16272
|
}
|
|
16237
16273
|
if (appliedFixes.length > 0) {
|
|
16238
|
-
const configDir =
|
|
16274
|
+
const configDir = path10.dirname(configPath);
|
|
16239
16275
|
if (!fs4.existsSync(configDir)) {
|
|
16240
16276
|
fs4.mkdirSync(configDir, { recursive: true });
|
|
16241
16277
|
}
|
|
@@ -16245,12 +16281,12 @@ function applySafeAutoFixes(directory, result) {
|
|
|
16245
16281
|
return { appliedFixes, updatedConfigPath };
|
|
16246
16282
|
}
|
|
16247
16283
|
function writeDoctorArtifact(directory, result) {
|
|
16248
|
-
const swarmDir =
|
|
16284
|
+
const swarmDir = path10.join(directory, ".swarm");
|
|
16249
16285
|
if (!fs4.existsSync(swarmDir)) {
|
|
16250
16286
|
fs4.mkdirSync(swarmDir, { recursive: true });
|
|
16251
16287
|
}
|
|
16252
16288
|
const artifactFilename = "config-doctor.json";
|
|
16253
|
-
const artifactPath =
|
|
16289
|
+
const artifactPath = path10.join(swarmDir, artifactFilename);
|
|
16254
16290
|
const guiOutput = {
|
|
16255
16291
|
timestamp: result.timestamp,
|
|
16256
16292
|
summary: result.summary,
|
|
@@ -16318,6 +16354,7 @@ async function runConfigDoctorWithFixes(directory, config2, autoFix = false) {
|
|
|
16318
16354
|
}
|
|
16319
16355
|
var VALID_CONFIG_PATTERNS, DANGEROUS_PATH_SEGMENTS;
|
|
16320
16356
|
var init_config_doctor = __esm(() => {
|
|
16357
|
+
init_utils();
|
|
16321
16358
|
VALID_CONFIG_PATTERNS = [
|
|
16322
16359
|
/^\.config[\\/]opencode[\\/]opencode-swarm\.json$/,
|
|
16323
16360
|
/\.opencode[\\/]opencode-swarm\.json$/
|
|
@@ -16565,10 +16602,10 @@ function mergeDefs2(...defs) {
|
|
|
16565
16602
|
function cloneDef2(schema) {
|
|
16566
16603
|
return mergeDefs2(schema._zod.def);
|
|
16567
16604
|
}
|
|
16568
|
-
function getElementAtPath2(obj,
|
|
16569
|
-
if (!
|
|
16605
|
+
function getElementAtPath2(obj, path11) {
|
|
16606
|
+
if (!path11)
|
|
16570
16607
|
return obj;
|
|
16571
|
-
return
|
|
16608
|
+
return path11.reduce((acc, key) => acc?.[key], obj);
|
|
16572
16609
|
}
|
|
16573
16610
|
function promiseAllObject2(promisesObj) {
|
|
16574
16611
|
const keys = Object.keys(promisesObj);
|
|
@@ -16857,11 +16894,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
16857
16894
|
}
|
|
16858
16895
|
return false;
|
|
16859
16896
|
}
|
|
16860
|
-
function prefixIssues2(
|
|
16897
|
+
function prefixIssues2(path11, issues) {
|
|
16861
16898
|
return issues.map((iss) => {
|
|
16862
16899
|
var _a2;
|
|
16863
16900
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
16864
|
-
iss.path.unshift(
|
|
16901
|
+
iss.path.unshift(path11);
|
|
16865
16902
|
return iss;
|
|
16866
16903
|
});
|
|
16867
16904
|
}
|
|
@@ -17084,7 +17121,7 @@ function treeifyError2(error49, _mapper) {
|
|
|
17084
17121
|
return issue3.message;
|
|
17085
17122
|
};
|
|
17086
17123
|
const result = { errors: [] };
|
|
17087
|
-
const processError = (error50,
|
|
17124
|
+
const processError = (error50, path11 = []) => {
|
|
17088
17125
|
var _a2, _b;
|
|
17089
17126
|
for (const issue3 of error50.issues) {
|
|
17090
17127
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -17094,7 +17131,7 @@ function treeifyError2(error49, _mapper) {
|
|
|
17094
17131
|
} else if (issue3.code === "invalid_element") {
|
|
17095
17132
|
processError({ issues: issue3.issues }, issue3.path);
|
|
17096
17133
|
} else {
|
|
17097
|
-
const fullpath = [...
|
|
17134
|
+
const fullpath = [...path11, ...issue3.path];
|
|
17098
17135
|
if (fullpath.length === 0) {
|
|
17099
17136
|
result.errors.push(mapper(issue3));
|
|
17100
17137
|
continue;
|
|
@@ -17126,8 +17163,8 @@ function treeifyError2(error49, _mapper) {
|
|
|
17126
17163
|
}
|
|
17127
17164
|
function toDotPath2(_path) {
|
|
17128
17165
|
const segs = [];
|
|
17129
|
-
const
|
|
17130
|
-
for (const seg of
|
|
17166
|
+
const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
17167
|
+
for (const seg of path11) {
|
|
17131
17168
|
if (typeof seg === "number")
|
|
17132
17169
|
segs.push(`[${seg}]`);
|
|
17133
17170
|
else if (typeof seg === "symbol")
|
|
@@ -29139,7 +29176,7 @@ var init_lint = __esm(() => {
|
|
|
29139
29176
|
|
|
29140
29177
|
// src/tools/secretscan.ts
|
|
29141
29178
|
import * as fs5 from "fs";
|
|
29142
|
-
import * as
|
|
29179
|
+
import * as path11 from "path";
|
|
29143
29180
|
function calculateShannonEntropy(str) {
|
|
29144
29181
|
if (str.length === 0)
|
|
29145
29182
|
return 0;
|
|
@@ -29166,7 +29203,7 @@ function isHighEntropyString(str) {
|
|
|
29166
29203
|
function containsPathTraversal(str) {
|
|
29167
29204
|
if (/\.\.[/\\]/.test(str))
|
|
29168
29205
|
return true;
|
|
29169
|
-
const normalized =
|
|
29206
|
+
const normalized = path11.normalize(str);
|
|
29170
29207
|
if (/\.\.[/\\]/.test(normalized))
|
|
29171
29208
|
return true;
|
|
29172
29209
|
if (str.includes("%2e%2e") || str.includes("%2E%2E"))
|
|
@@ -29194,7 +29231,7 @@ function validateDirectoryInput(dir) {
|
|
|
29194
29231
|
return null;
|
|
29195
29232
|
}
|
|
29196
29233
|
function isBinaryFile(filePath, buffer) {
|
|
29197
|
-
const ext =
|
|
29234
|
+
const ext = path11.extname(filePath).toLowerCase();
|
|
29198
29235
|
if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
29199
29236
|
return true;
|
|
29200
29237
|
}
|
|
@@ -29331,9 +29368,9 @@ function isSymlinkLoop(realPath, visited) {
|
|
|
29331
29368
|
return false;
|
|
29332
29369
|
}
|
|
29333
29370
|
function isPathWithinScope(realPath, scanDir) {
|
|
29334
|
-
const resolvedScanDir =
|
|
29335
|
-
const resolvedRealPath =
|
|
29336
|
-
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir +
|
|
29371
|
+
const resolvedScanDir = path11.resolve(scanDir);
|
|
29372
|
+
const resolvedRealPath = path11.resolve(realPath);
|
|
29373
|
+
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(resolvedScanDir + "/") || resolvedRealPath.startsWith(resolvedScanDir + "\\");
|
|
29337
29374
|
}
|
|
29338
29375
|
function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
29339
29376
|
skippedDirs: 0,
|
|
@@ -29363,7 +29400,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
29363
29400
|
stats.skippedDirs++;
|
|
29364
29401
|
continue;
|
|
29365
29402
|
}
|
|
29366
|
-
const fullPath =
|
|
29403
|
+
const fullPath = path11.join(dir, entry);
|
|
29367
29404
|
let lstat;
|
|
29368
29405
|
try {
|
|
29369
29406
|
lstat = fs5.lstatSync(fullPath);
|
|
@@ -29394,7 +29431,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
29394
29431
|
const subFiles = findScannableFiles(fullPath, excludeDirs, scanDir, visited, stats);
|
|
29395
29432
|
files.push(...subFiles);
|
|
29396
29433
|
} else if (lstat.isFile()) {
|
|
29397
|
-
const ext =
|
|
29434
|
+
const ext = path11.extname(fullPath).toLowerCase();
|
|
29398
29435
|
if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
29399
29436
|
files.push(fullPath);
|
|
29400
29437
|
} else {
|
|
@@ -29660,7 +29697,7 @@ var init_secretscan = __esm(() => {
|
|
|
29660
29697
|
}
|
|
29661
29698
|
}
|
|
29662
29699
|
try {
|
|
29663
|
-
const scanDir =
|
|
29700
|
+
const scanDir = path11.resolve(directory);
|
|
29664
29701
|
if (!fs5.existsSync(scanDir)) {
|
|
29665
29702
|
const errorResult = {
|
|
29666
29703
|
error: "directory not found",
|
|
@@ -29787,88 +29824,13 @@ var init_secretscan = __esm(() => {
|
|
|
29787
29824
|
});
|
|
29788
29825
|
});
|
|
29789
29826
|
|
|
29790
|
-
// src/tools/test-runner.ts
|
|
29827
|
+
// src/tools/test-runner/constants.ts
|
|
29828
|
+
var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000;
|
|
29829
|
+
var init_constants = () => {};
|
|
29830
|
+
|
|
29831
|
+
// src/tools/test-runner/detect.ts
|
|
29791
29832
|
import * as fs6 from "fs";
|
|
29792
|
-
import * as
|
|
29793
|
-
function containsPathTraversal2(str) {
|
|
29794
|
-
if (/\.\.[/\\]/.test(str))
|
|
29795
|
-
return true;
|
|
29796
|
-
if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(str))
|
|
29797
|
-
return true;
|
|
29798
|
-
if (/%2e%2e/i.test(str))
|
|
29799
|
-
return true;
|
|
29800
|
-
if (/%2e\./i.test(str))
|
|
29801
|
-
return true;
|
|
29802
|
-
if (/%2e/i.test(str) && /\.\./.test(str))
|
|
29803
|
-
return true;
|
|
29804
|
-
if (/%252e%252e/i.test(str))
|
|
29805
|
-
return true;
|
|
29806
|
-
if (/\uff0e/.test(str))
|
|
29807
|
-
return true;
|
|
29808
|
-
if (/\u3002/.test(str))
|
|
29809
|
-
return true;
|
|
29810
|
-
if (/\uff65/.test(str))
|
|
29811
|
-
return true;
|
|
29812
|
-
if (/%2f/i.test(str))
|
|
29813
|
-
return true;
|
|
29814
|
-
if (/%5c/i.test(str))
|
|
29815
|
-
return true;
|
|
29816
|
-
return false;
|
|
29817
|
-
}
|
|
29818
|
-
function isAbsolutePath(str) {
|
|
29819
|
-
if (str.startsWith("/"))
|
|
29820
|
-
return true;
|
|
29821
|
-
if (/^[a-zA-Z]:[/\\]/.test(str))
|
|
29822
|
-
return true;
|
|
29823
|
-
if (/^\\\\/.test(str))
|
|
29824
|
-
return true;
|
|
29825
|
-
if (/^\\\\\.\\/.test(str))
|
|
29826
|
-
return true;
|
|
29827
|
-
return false;
|
|
29828
|
-
}
|
|
29829
|
-
function containsControlChars2(str) {
|
|
29830
|
-
return /[\x00-\x08\x0a\x0b\x0c\x0d\x0e-\x1f\x7f\x80-\x9f]/.test(str);
|
|
29831
|
-
}
|
|
29832
|
-
function containsPowerShellMetacharacters(str) {
|
|
29833
|
-
return POWERSHELL_METACHARACTERS.test(str);
|
|
29834
|
-
}
|
|
29835
|
-
function validateArgs2(args) {
|
|
29836
|
-
if (typeof args !== "object" || args === null)
|
|
29837
|
-
return false;
|
|
29838
|
-
const obj = args;
|
|
29839
|
-
if (obj.scope !== undefined) {
|
|
29840
|
-
if (obj.scope !== "all" && obj.scope !== "convention" && obj.scope !== "graph") {
|
|
29841
|
-
return false;
|
|
29842
|
-
}
|
|
29843
|
-
}
|
|
29844
|
-
if (obj.files !== undefined) {
|
|
29845
|
-
if (!Array.isArray(obj.files))
|
|
29846
|
-
return false;
|
|
29847
|
-
for (const f of obj.files) {
|
|
29848
|
-
if (typeof f !== "string")
|
|
29849
|
-
return false;
|
|
29850
|
-
if (isAbsolutePath(f))
|
|
29851
|
-
return false;
|
|
29852
|
-
if (containsPathTraversal2(f))
|
|
29853
|
-
return false;
|
|
29854
|
-
if (containsControlChars2(f))
|
|
29855
|
-
return false;
|
|
29856
|
-
if (containsPowerShellMetacharacters(f))
|
|
29857
|
-
return false;
|
|
29858
|
-
}
|
|
29859
|
-
}
|
|
29860
|
-
if (obj.coverage !== undefined) {
|
|
29861
|
-
if (typeof obj.coverage !== "boolean")
|
|
29862
|
-
return false;
|
|
29863
|
-
}
|
|
29864
|
-
if (obj.timeout_ms !== undefined) {
|
|
29865
|
-
if (typeof obj.timeout_ms !== "number")
|
|
29866
|
-
return false;
|
|
29867
|
-
if (obj.timeout_ms < 0 || obj.timeout_ms > MAX_TIMEOUT_MS)
|
|
29868
|
-
return false;
|
|
29869
|
-
}
|
|
29870
|
-
return true;
|
|
29871
|
-
}
|
|
29833
|
+
import * as path12 from "path";
|
|
29872
29834
|
function hasPackageJsonDependency(deps, ...patterns) {
|
|
29873
29835
|
for (const pattern of patterns) {
|
|
29874
29836
|
if (deps[pattern])
|
|
@@ -29883,7 +29845,7 @@ function hasDevDependency(devDeps, ...patterns) {
|
|
|
29883
29845
|
}
|
|
29884
29846
|
async function detectTestFramework() {
|
|
29885
29847
|
try {
|
|
29886
|
-
const packageJsonPath =
|
|
29848
|
+
const packageJsonPath = path12.join(process.cwd(), "package.json");
|
|
29887
29849
|
if (fs6.existsSync(packageJsonPath)) {
|
|
29888
29850
|
const content = fs6.readFileSync(packageJsonPath, "utf-8");
|
|
29889
29851
|
const pkg = JSON.parse(content);
|
|
@@ -29904,16 +29866,16 @@ async function detectTestFramework() {
|
|
|
29904
29866
|
return "jest";
|
|
29905
29867
|
if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
|
|
29906
29868
|
return "mocha";
|
|
29907
|
-
if (fs6.existsSync(
|
|
29869
|
+
if (fs6.existsSync(path12.join(process.cwd(), "bun.lockb")) || fs6.existsSync(path12.join(process.cwd(), "bun.lock"))) {
|
|
29908
29870
|
if (scripts.test?.includes("bun"))
|
|
29909
29871
|
return "bun";
|
|
29910
29872
|
}
|
|
29911
29873
|
}
|
|
29912
29874
|
} catch {}
|
|
29913
29875
|
try {
|
|
29914
|
-
const pyprojectTomlPath =
|
|
29915
|
-
const setupCfgPath =
|
|
29916
|
-
const requirementsTxtPath =
|
|
29876
|
+
const pyprojectTomlPath = path12.join(process.cwd(), "pyproject.toml");
|
|
29877
|
+
const setupCfgPath = path12.join(process.cwd(), "setup.cfg");
|
|
29878
|
+
const requirementsTxtPath = path12.join(process.cwd(), "requirements.txt");
|
|
29917
29879
|
if (fs6.existsSync(pyprojectTomlPath)) {
|
|
29918
29880
|
const content = fs6.readFileSync(pyprojectTomlPath, "utf-8");
|
|
29919
29881
|
if (content.includes("[tool.pytest"))
|
|
@@ -29933,7 +29895,7 @@ async function detectTestFramework() {
|
|
|
29933
29895
|
}
|
|
29934
29896
|
} catch {}
|
|
29935
29897
|
try {
|
|
29936
|
-
const cargoTomlPath =
|
|
29898
|
+
const cargoTomlPath = path12.join(process.cwd(), "Cargo.toml");
|
|
29937
29899
|
if (fs6.existsSync(cargoTomlPath)) {
|
|
29938
29900
|
const content = fs6.readFileSync(cargoTomlPath, "utf-8");
|
|
29939
29901
|
if (content.includes("[dev-dependencies]")) {
|
|
@@ -29944,15 +29906,20 @@ async function detectTestFramework() {
|
|
|
29944
29906
|
}
|
|
29945
29907
|
} catch {}
|
|
29946
29908
|
try {
|
|
29947
|
-
const pesterConfigPath =
|
|
29948
|
-
const pesterConfigJsonPath =
|
|
29949
|
-
const pesterPs1Path =
|
|
29909
|
+
const pesterConfigPath = path12.join(process.cwd(), "pester.config.ps1");
|
|
29910
|
+
const pesterConfigJsonPath = path12.join(process.cwd(), "pester.config.ps1.json");
|
|
29911
|
+
const pesterPs1Path = path12.join(process.cwd(), "tests.ps1");
|
|
29950
29912
|
if (fs6.existsSync(pesterConfigPath) || fs6.existsSync(pesterConfigJsonPath) || fs6.existsSync(pesterPs1Path)) {
|
|
29951
29913
|
return "pester";
|
|
29952
29914
|
}
|
|
29953
29915
|
} catch {}
|
|
29954
29916
|
return "none";
|
|
29955
29917
|
}
|
|
29918
|
+
var init_detect = () => {};
|
|
29919
|
+
|
|
29920
|
+
// src/tools/test-runner/discover.ts
|
|
29921
|
+
import * as fs7 from "fs";
|
|
29922
|
+
import * as path13 from "path";
|
|
29956
29923
|
function hasCompoundTestExtension(filename) {
|
|
29957
29924
|
const lower = filename.toLowerCase();
|
|
29958
29925
|
return COMPOUND_TEST_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
@@ -29960,26 +29927,26 @@ function hasCompoundTestExtension(filename) {
|
|
|
29960
29927
|
function getTestFilesFromConvention(sourceFiles) {
|
|
29961
29928
|
const testFiles = [];
|
|
29962
29929
|
for (const file3 of sourceFiles) {
|
|
29963
|
-
const
|
|
29964
|
-
const dirname4 =
|
|
29965
|
-
if (hasCompoundTestExtension(
|
|
29930
|
+
const basename3 = path13.basename(file3);
|
|
29931
|
+
const dirname4 = path13.dirname(file3);
|
|
29932
|
+
if (hasCompoundTestExtension(basename3) || basename3.includes(".spec.") || basename3.includes(".test.")) {
|
|
29966
29933
|
if (!testFiles.includes(file3)) {
|
|
29967
29934
|
testFiles.push(file3);
|
|
29968
29935
|
}
|
|
29969
29936
|
continue;
|
|
29970
29937
|
}
|
|
29971
29938
|
for (const pattern of TEST_PATTERNS) {
|
|
29972
|
-
const nameWithoutExt =
|
|
29973
|
-
const ext =
|
|
29939
|
+
const nameWithoutExt = basename3.replace(/\.[^.]+$/, "");
|
|
29940
|
+
const ext = path13.extname(basename3);
|
|
29974
29941
|
const possibleTestFiles = [
|
|
29975
|
-
|
|
29976
|
-
|
|
29977
|
-
|
|
29978
|
-
|
|
29979
|
-
|
|
29942
|
+
path13.join(dirname4, `${nameWithoutExt}.spec${ext}`),
|
|
29943
|
+
path13.join(dirname4, `${nameWithoutExt}.test${ext}`),
|
|
29944
|
+
path13.join(dirname4, "__tests__", `${nameWithoutExt}${ext}`),
|
|
29945
|
+
path13.join(dirname4, "tests", `${nameWithoutExt}${ext}`),
|
|
29946
|
+
path13.join(dirname4, "test", `${nameWithoutExt}${ext}`)
|
|
29980
29947
|
];
|
|
29981
29948
|
for (const testFile of possibleTestFiles) {
|
|
29982
|
-
if (
|
|
29949
|
+
if (fs7.existsSync(testFile) && !testFiles.includes(testFile)) {
|
|
29983
29950
|
testFiles.push(testFile);
|
|
29984
29951
|
}
|
|
29985
29952
|
}
|
|
@@ -29995,16 +29962,16 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
29995
29962
|
}
|
|
29996
29963
|
for (const testFile of candidateTestFiles) {
|
|
29997
29964
|
try {
|
|
29998
|
-
const content =
|
|
29999
|
-
const testDir =
|
|
29965
|
+
const content = fs7.readFileSync(testFile, "utf-8");
|
|
29966
|
+
const testDir = path13.dirname(testFile);
|
|
30000
29967
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
30001
29968
|
let match;
|
|
30002
29969
|
while ((match = importRegex.exec(content)) !== null) {
|
|
30003
29970
|
const importPath = match[1];
|
|
30004
29971
|
let resolvedImport;
|
|
30005
29972
|
if (importPath.startsWith(".")) {
|
|
30006
|
-
resolvedImport =
|
|
30007
|
-
const existingExt =
|
|
29973
|
+
resolvedImport = path13.resolve(testDir, importPath);
|
|
29974
|
+
const existingExt = path13.extname(resolvedImport);
|
|
30008
29975
|
if (!existingExt) {
|
|
30009
29976
|
for (const extToTry of [
|
|
30010
29977
|
".ts",
|
|
@@ -30015,7 +29982,7 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30015
29982
|
".cjs"
|
|
30016
29983
|
]) {
|
|
30017
29984
|
const withExt = resolvedImport + extToTry;
|
|
30018
|
-
if (sourceFiles.includes(withExt) ||
|
|
29985
|
+
if (sourceFiles.includes(withExt) || fs7.existsSync(withExt)) {
|
|
30019
29986
|
resolvedImport = withExt;
|
|
30020
29987
|
break;
|
|
30021
29988
|
}
|
|
@@ -30024,12 +29991,12 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30024
29991
|
} else {
|
|
30025
29992
|
continue;
|
|
30026
29993
|
}
|
|
30027
|
-
const importBasename =
|
|
30028
|
-
const importDir =
|
|
29994
|
+
const importBasename = path13.basename(resolvedImport, path13.extname(resolvedImport));
|
|
29995
|
+
const importDir = path13.dirname(resolvedImport);
|
|
30029
29996
|
for (const sourceFile of sourceFiles) {
|
|
30030
|
-
const sourceDir =
|
|
30031
|
-
const sourceBasename =
|
|
30032
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
29997
|
+
const sourceDir = path13.dirname(sourceFile);
|
|
29998
|
+
const sourceBasename = path13.basename(sourceFile, path13.extname(sourceFile));
|
|
29999
|
+
const isRelatedDir = importDir === sourceDir || importDir === path13.join(sourceDir, "__tests__") || importDir === path13.join(sourceDir, "tests") || importDir === path13.join(sourceDir, "test");
|
|
30033
30000
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
30034
30001
|
if (!testFiles.includes(testFile)) {
|
|
30035
30002
|
testFiles.push(testFile);
|
|
@@ -30042,8 +30009,8 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30042
30009
|
while ((match = requireRegex.exec(content)) !== null) {
|
|
30043
30010
|
const importPath = match[1];
|
|
30044
30011
|
if (importPath.startsWith(".")) {
|
|
30045
|
-
let resolvedImport =
|
|
30046
|
-
const existingExt =
|
|
30012
|
+
let resolvedImport = path13.resolve(testDir, importPath);
|
|
30013
|
+
const existingExt = path13.extname(resolvedImport);
|
|
30047
30014
|
if (!existingExt) {
|
|
30048
30015
|
for (const extToTry of [
|
|
30049
30016
|
".ts",
|
|
@@ -30054,18 +30021,18 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30054
30021
|
".cjs"
|
|
30055
30022
|
]) {
|
|
30056
30023
|
const withExt = resolvedImport + extToTry;
|
|
30057
|
-
if (sourceFiles.includes(withExt) ||
|
|
30024
|
+
if (sourceFiles.includes(withExt) || fs7.existsSync(withExt)) {
|
|
30058
30025
|
resolvedImport = withExt;
|
|
30059
30026
|
break;
|
|
30060
30027
|
}
|
|
30061
30028
|
}
|
|
30062
30029
|
}
|
|
30063
|
-
const importDir =
|
|
30064
|
-
const importBasename =
|
|
30030
|
+
const importDir = path13.dirname(resolvedImport);
|
|
30031
|
+
const importBasename = path13.basename(resolvedImport, path13.extname(resolvedImport));
|
|
30065
30032
|
for (const sourceFile of sourceFiles) {
|
|
30066
|
-
const sourceDir =
|
|
30067
|
-
const sourceBasename =
|
|
30068
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
30033
|
+
const sourceDir = path13.dirname(sourceFile);
|
|
30034
|
+
const sourceBasename = path13.basename(sourceFile, path13.extname(sourceFile));
|
|
30035
|
+
const isRelatedDir = importDir === sourceDir || importDir === path13.join(sourceDir, "__tests__") || importDir === path13.join(sourceDir, "tests") || importDir === path13.join(sourceDir, "test");
|
|
30069
30036
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
30070
30037
|
if (!testFiles.includes(testFile)) {
|
|
30071
30038
|
testFiles.push(testFile);
|
|
@@ -30079,40 +30046,118 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30079
30046
|
}
|
|
30080
30047
|
return testFiles;
|
|
30081
30048
|
}
|
|
30049
|
+
function findSourceFiles(dir, files = []) {
|
|
30050
|
+
let entries;
|
|
30051
|
+
try {
|
|
30052
|
+
entries = fs7.readdirSync(dir);
|
|
30053
|
+
} catch {
|
|
30054
|
+
return files;
|
|
30055
|
+
}
|
|
30056
|
+
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
30057
|
+
for (const entry of entries) {
|
|
30058
|
+
if (SKIP_DIRECTORIES.has(entry))
|
|
30059
|
+
continue;
|
|
30060
|
+
const fullPath = path13.join(dir, entry);
|
|
30061
|
+
let stat;
|
|
30062
|
+
try {
|
|
30063
|
+
stat = fs7.statSync(fullPath);
|
|
30064
|
+
} catch {
|
|
30065
|
+
continue;
|
|
30066
|
+
}
|
|
30067
|
+
if (stat.isDirectory()) {
|
|
30068
|
+
findSourceFiles(fullPath, files);
|
|
30069
|
+
} else if (stat.isFile()) {
|
|
30070
|
+
const ext = path13.extname(fullPath).toLowerCase();
|
|
30071
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
30072
|
+
files.push(fullPath);
|
|
30073
|
+
}
|
|
30074
|
+
}
|
|
30075
|
+
}
|
|
30076
|
+
return files;
|
|
30077
|
+
}
|
|
30078
|
+
var TEST_PATTERNS, COMPOUND_TEST_EXTENSIONS, SOURCE_EXTENSIONS, SKIP_DIRECTORIES;
|
|
30079
|
+
var init_discover = __esm(() => {
|
|
30080
|
+
TEST_PATTERNS = [
|
|
30081
|
+
{ test: ".spec.", source: "." },
|
|
30082
|
+
{ test: ".test.", source: "." },
|
|
30083
|
+
{ test: "/__tests__/", source: "/" },
|
|
30084
|
+
{ test: "/tests/", source: "/" },
|
|
30085
|
+
{ test: "/test/", source: "/" }
|
|
30086
|
+
];
|
|
30087
|
+
COMPOUND_TEST_EXTENSIONS = [
|
|
30088
|
+
".test.ts",
|
|
30089
|
+
".test.tsx",
|
|
30090
|
+
".test.js",
|
|
30091
|
+
".test.jsx",
|
|
30092
|
+
".spec.ts",
|
|
30093
|
+
".spec.tsx",
|
|
30094
|
+
".spec.js",
|
|
30095
|
+
".spec.jsx",
|
|
30096
|
+
".test.ps1",
|
|
30097
|
+
".spec.ps1"
|
|
30098
|
+
];
|
|
30099
|
+
SOURCE_EXTENSIONS = new Set([
|
|
30100
|
+
".ts",
|
|
30101
|
+
".tsx",
|
|
30102
|
+
".js",
|
|
30103
|
+
".jsx",
|
|
30104
|
+
".mjs",
|
|
30105
|
+
".cjs",
|
|
30106
|
+
".py",
|
|
30107
|
+
".rs",
|
|
30108
|
+
".ps1",
|
|
30109
|
+
".psm1"
|
|
30110
|
+
]);
|
|
30111
|
+
SKIP_DIRECTORIES = new Set([
|
|
30112
|
+
"node_modules",
|
|
30113
|
+
".git",
|
|
30114
|
+
"dist",
|
|
30115
|
+
"build",
|
|
30116
|
+
"out",
|
|
30117
|
+
"coverage",
|
|
30118
|
+
".next",
|
|
30119
|
+
".nuxt",
|
|
30120
|
+
".cache",
|
|
30121
|
+
"vendor",
|
|
30122
|
+
".svn",
|
|
30123
|
+
".hg",
|
|
30124
|
+
"__pycache__",
|
|
30125
|
+
".pytest_cache",
|
|
30126
|
+
"target"
|
|
30127
|
+
]);
|
|
30128
|
+
});
|
|
30129
|
+
|
|
30130
|
+
// src/tools/test-runner/run.ts
|
|
30082
30131
|
function buildTestCommand(framework, scope, files, coverage) {
|
|
30083
30132
|
switch (framework) {
|
|
30084
30133
|
case "bun": {
|
|
30085
30134
|
const args = ["bun", "test"];
|
|
30086
30135
|
if (coverage)
|
|
30087
30136
|
args.push("--coverage");
|
|
30088
|
-
if (scope !== "all" && files.length > 0)
|
|
30137
|
+
if (scope !== "all" && files.length > 0)
|
|
30089
30138
|
args.push(...files);
|
|
30090
|
-
}
|
|
30091
30139
|
return args;
|
|
30092
30140
|
}
|
|
30093
30141
|
case "vitest": {
|
|
30094
30142
|
const args = ["npx", "vitest", "run"];
|
|
30095
30143
|
if (coverage)
|
|
30096
30144
|
args.push("--coverage");
|
|
30097
|
-
if (scope !== "all" && files.length > 0)
|
|
30145
|
+
if (scope !== "all" && files.length > 0)
|
|
30098
30146
|
args.push(...files);
|
|
30099
|
-
}
|
|
30100
30147
|
return args;
|
|
30101
30148
|
}
|
|
30102
30149
|
case "jest": {
|
|
30103
30150
|
const args = ["npx", "jest"];
|
|
30104
30151
|
if (coverage)
|
|
30105
30152
|
args.push("--coverage");
|
|
30106
|
-
if (scope !== "all" && files.length > 0)
|
|
30153
|
+
if (scope !== "all" && files.length > 0)
|
|
30107
30154
|
args.push(...files);
|
|
30108
|
-
}
|
|
30109
30155
|
return args;
|
|
30110
30156
|
}
|
|
30111
30157
|
case "mocha": {
|
|
30112
30158
|
const args = ["npx", "mocha"];
|
|
30113
|
-
if (scope !== "all" && files.length > 0)
|
|
30159
|
+
if (scope !== "all" && files.length > 0)
|
|
30114
30160
|
args.push(...files);
|
|
30115
|
-
}
|
|
30116
30161
|
return args;
|
|
30117
30162
|
}
|
|
30118
30163
|
case "pytest": {
|
|
@@ -30120,16 +30165,14 @@ function buildTestCommand(framework, scope, files, coverage) {
|
|
|
30120
30165
|
const args = isWindows ? ["python", "-m", "pytest"] : ["python3", "-m", "pytest"];
|
|
30121
30166
|
if (coverage)
|
|
30122
30167
|
args.push("--cov=.", "--cov-report=term-missing");
|
|
30123
|
-
if (scope !== "all" && files.length > 0)
|
|
30168
|
+
if (scope !== "all" && files.length > 0)
|
|
30124
30169
|
args.push(...files);
|
|
30125
|
-
}
|
|
30126
30170
|
return args;
|
|
30127
30171
|
}
|
|
30128
30172
|
case "cargo": {
|
|
30129
30173
|
const args = ["cargo", "test"];
|
|
30130
|
-
if (scope !== "all" && files.length > 0)
|
|
30174
|
+
if (scope !== "all" && files.length > 0)
|
|
30131
30175
|
args.push(...files);
|
|
30132
|
-
}
|
|
30133
30176
|
return args;
|
|
30134
30177
|
}
|
|
30135
30178
|
case "pester": {
|
|
@@ -30138,8 +30181,7 @@ function buildTestCommand(framework, scope, files, coverage) {
|
|
|
30138
30181
|
const psCommand = `Invoke-Pester -Path @('${escapedFiles.join("','")}')`;
|
|
30139
30182
|
const utf16Bytes = Buffer.from(psCommand, "utf16le");
|
|
30140
30183
|
const base64Command = utf16Bytes.toString("base64");
|
|
30141
|
-
|
|
30142
|
-
return args;
|
|
30184
|
+
return ["pwsh", "-EncodedCommand", base64Command];
|
|
30143
30185
|
}
|
|
30144
30186
|
return ["pwsh", "-Command", "Invoke-Pester"];
|
|
30145
30187
|
}
|
|
@@ -30148,12 +30190,7 @@ function buildTestCommand(framework, scope, files, coverage) {
|
|
|
30148
30190
|
}
|
|
30149
30191
|
}
|
|
30150
30192
|
function parseTestOutput(framework, output) {
|
|
30151
|
-
const totals = {
|
|
30152
|
-
passed: 0,
|
|
30153
|
-
failed: 0,
|
|
30154
|
-
skipped: 0,
|
|
30155
|
-
total: 0
|
|
30156
|
-
};
|
|
30193
|
+
const totals = { passed: 0, failed: 0, skipped: 0, total: 0 };
|
|
30157
30194
|
let coveragePercent;
|
|
30158
30195
|
switch (framework) {
|
|
30159
30196
|
case "vitest":
|
|
@@ -30169,9 +30206,8 @@ function parseTestOutput(framework, output) {
|
|
|
30169
30206
|
totals.skipped = parsed.numPendingTests || 0;
|
|
30170
30207
|
totals.total = parsed.numTotalTests || 0;
|
|
30171
30208
|
}
|
|
30172
|
-
if (parsed.coverage !== undefined)
|
|
30209
|
+
if (parsed.coverage !== undefined)
|
|
30173
30210
|
coveragePercent = parsed.coverage;
|
|
30174
|
-
}
|
|
30175
30211
|
} catch {}
|
|
30176
30212
|
}
|
|
30177
30213
|
if (totals.total === 0) {
|
|
@@ -30187,9 +30223,8 @@ function parseTestOutput(framework, output) {
|
|
|
30187
30223
|
totals.total = totals.passed + totals.failed + totals.skipped;
|
|
30188
30224
|
}
|
|
30189
30225
|
const coverageMatch = output.match(/All files[^\d]*(\d+\.?\d*)\s*%/);
|
|
30190
|
-
if (!coveragePercent && coverageMatch)
|
|
30226
|
+
if (!coveragePercent && coverageMatch)
|
|
30191
30227
|
coveragePercent = parseFloat(coverageMatch[1]);
|
|
30192
|
-
}
|
|
30193
30228
|
break;
|
|
30194
30229
|
}
|
|
30195
30230
|
case "mocha": {
|
|
@@ -30217,9 +30252,8 @@ function parseTestOutput(framework, output) {
|
|
|
30217
30252
|
totals.skipped = parseInt(skipMatch[1], 10);
|
|
30218
30253
|
totals.total = totals.passed + totals.failed + totals.skipped;
|
|
30219
30254
|
const coverageMatch = output.match(/TOTAL\s+(\d+\.?\d*)\s*%/);
|
|
30220
|
-
if (coverageMatch)
|
|
30255
|
+
if (coverageMatch)
|
|
30221
30256
|
coveragePercent = parseFloat(coverageMatch[1]);
|
|
30222
|
-
}
|
|
30223
30257
|
break;
|
|
30224
30258
|
}
|
|
30225
30259
|
case "cargo": {
|
|
@@ -30275,10 +30309,7 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
|
|
|
30275
30309
|
}
|
|
30276
30310
|
const startTime = Date.now();
|
|
30277
30311
|
try {
|
|
30278
|
-
const proc = Bun.spawn(command, {
|
|
30279
|
-
stdout: "pipe",
|
|
30280
|
-
stderr: "pipe"
|
|
30281
|
-
});
|
|
30312
|
+
const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
|
|
30282
30313
|
const exitPromise = proc.exited;
|
|
30283
30314
|
const timeoutPromise = new Promise((resolve7) => setTimeout(() => {
|
|
30284
30315
|
proc.kill();
|
|
@@ -30291,18 +30322,16 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
|
|
|
30291
30322
|
new Response(proc.stderr).text()
|
|
30292
30323
|
]);
|
|
30293
30324
|
let output = stdout;
|
|
30294
|
-
if (stderr)
|
|
30325
|
+
if (stderr)
|
|
30295
30326
|
output += (output ? `
|
|
30296
30327
|
` : "") + stderr;
|
|
30297
|
-
}
|
|
30298
30328
|
const outputBytes = Buffer.byteLength(output, "utf-8");
|
|
30299
30329
|
if (outputBytes > MAX_OUTPUT_BYTES3) {
|
|
30300
30330
|
let truncIndex = MAX_OUTPUT_BYTES3;
|
|
30301
30331
|
while (truncIndex > 0) {
|
|
30302
30332
|
const truncated = output.slice(0, truncIndex);
|
|
30303
|
-
if (Buffer.byteLength(truncated, "utf-8") <= MAX_OUTPUT_BYTES3)
|
|
30333
|
+
if (Buffer.byteLength(truncated, "utf-8") <= MAX_OUTPUT_BYTES3)
|
|
30304
30334
|
break;
|
|
30305
|
-
}
|
|
30306
30335
|
truncIndex--;
|
|
30307
30336
|
}
|
|
30308
30337
|
output = output.slice(0, truncIndex) + `
|
|
@@ -30321,13 +30350,11 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
|
|
|
30321
30350
|
totals,
|
|
30322
30351
|
rawOutput: output
|
|
30323
30352
|
};
|
|
30324
|
-
if (coveragePercent !== undefined)
|
|
30353
|
+
if (coveragePercent !== undefined)
|
|
30325
30354
|
result.coveragePercent = coveragePercent;
|
|
30326
|
-
}
|
|
30327
30355
|
result.message = `${framework} tests passed (${totals.passed}/${totals.total})`;
|
|
30328
|
-
if (coveragePercent !== undefined)
|
|
30356
|
+
if (coveragePercent !== undefined)
|
|
30329
30357
|
result.message += ` with ${coveragePercent}% coverage`;
|
|
30330
|
-
}
|
|
30331
30358
|
return result;
|
|
30332
30359
|
} else {
|
|
30333
30360
|
const result = {
|
|
@@ -30342,9 +30369,8 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
|
|
|
30342
30369
|
error: `Tests failed with ${totals.failed} failures`,
|
|
30343
30370
|
message: `${framework} tests failed (${totals.failed}/${totals.total} failed)`
|
|
30344
30371
|
};
|
|
30345
|
-
if (coveragePercent !== undefined)
|
|
30372
|
+
if (coveragePercent !== undefined)
|
|
30346
30373
|
result.coveragePercent = coveragePercent;
|
|
30347
|
-
}
|
|
30348
30374
|
return result;
|
|
30349
30375
|
}
|
|
30350
30376
|
} catch (error93) {
|
|
@@ -30360,89 +30386,111 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
|
|
|
30360
30386
|
};
|
|
30361
30387
|
}
|
|
30362
30388
|
}
|
|
30363
|
-
|
|
30364
|
-
|
|
30365
|
-
|
|
30366
|
-
|
|
30367
|
-
|
|
30368
|
-
|
|
30369
|
-
|
|
30370
|
-
|
|
30371
|
-
|
|
30372
|
-
|
|
30373
|
-
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
|
|
30389
|
+
var init_run = __esm(() => {
|
|
30390
|
+
init_constants();
|
|
30391
|
+
});
|
|
30392
|
+
|
|
30393
|
+
// src/tools/test-runner/validate.ts
|
|
30394
|
+
function containsPathTraversal2(str) {
|
|
30395
|
+
if (/\.\.[/\\]/.test(str))
|
|
30396
|
+
return true;
|
|
30397
|
+
if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(str))
|
|
30398
|
+
return true;
|
|
30399
|
+
if (/%2e%2e/i.test(str))
|
|
30400
|
+
return true;
|
|
30401
|
+
if (/%2e\./i.test(str))
|
|
30402
|
+
return true;
|
|
30403
|
+
if (/%2e/i.test(str) && /\.\./.test(str))
|
|
30404
|
+
return true;
|
|
30405
|
+
if (/%252e%252e/i.test(str))
|
|
30406
|
+
return true;
|
|
30407
|
+
if (/\uff0e/.test(str))
|
|
30408
|
+
return true;
|
|
30409
|
+
if (/\u3002/.test(str))
|
|
30410
|
+
return true;
|
|
30411
|
+
if (/\uff65/.test(str))
|
|
30412
|
+
return true;
|
|
30413
|
+
if (/%2f/i.test(str))
|
|
30414
|
+
return true;
|
|
30415
|
+
if (/%5c/i.test(str))
|
|
30416
|
+
return true;
|
|
30417
|
+
return false;
|
|
30418
|
+
}
|
|
30419
|
+
function isAbsolutePath(str) {
|
|
30420
|
+
if (str.startsWith("/"))
|
|
30421
|
+
return true;
|
|
30422
|
+
if (/^[a-zA-Z]:[/\\]/.test(str))
|
|
30423
|
+
return true;
|
|
30424
|
+
if (/^\\\\/.test(str))
|
|
30425
|
+
return true;
|
|
30426
|
+
if (/^\\\\\.\\/.test(str))
|
|
30427
|
+
return true;
|
|
30428
|
+
return false;
|
|
30429
|
+
}
|
|
30430
|
+
function containsControlChars2(str) {
|
|
30431
|
+
return /[\x00-\x08\x0a\x0b\x0c\x0d\x0e-\x1f\x7f\x80-\x9f]/.test(str);
|
|
30432
|
+
}
|
|
30433
|
+
function containsPowerShellMetacharacters(str) {
|
|
30434
|
+
return POWERSHELL_METACHARACTERS.test(str);
|
|
30435
|
+
}
|
|
30436
|
+
function validateArgs2(args) {
|
|
30437
|
+
if (typeof args !== "object" || args === null)
|
|
30438
|
+
return false;
|
|
30439
|
+
const obj = args;
|
|
30440
|
+
if (obj.scope !== undefined) {
|
|
30441
|
+
if (obj.scope !== "all" && obj.scope !== "convention" && obj.scope !== "graph") {
|
|
30442
|
+
return false;
|
|
30380
30443
|
}
|
|
30381
|
-
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30444
|
+
}
|
|
30445
|
+
if (obj.files !== undefined) {
|
|
30446
|
+
if (!Array.isArray(obj.files))
|
|
30447
|
+
return false;
|
|
30448
|
+
for (const f of obj.files) {
|
|
30449
|
+
if (typeof f !== "string")
|
|
30450
|
+
return false;
|
|
30451
|
+
if (isAbsolutePath(f))
|
|
30452
|
+
return false;
|
|
30453
|
+
if (containsPathTraversal2(f))
|
|
30454
|
+
return false;
|
|
30455
|
+
if (containsControlChars2(f))
|
|
30456
|
+
return false;
|
|
30457
|
+
if (containsPowerShellMetacharacters(f))
|
|
30458
|
+
return false;
|
|
30388
30459
|
}
|
|
30389
30460
|
}
|
|
30390
|
-
|
|
30461
|
+
if (obj.coverage !== undefined) {
|
|
30462
|
+
if (typeof obj.coverage !== "boolean")
|
|
30463
|
+
return false;
|
|
30464
|
+
}
|
|
30465
|
+
if (obj.timeout_ms !== undefined) {
|
|
30466
|
+
if (typeof obj.timeout_ms !== "number")
|
|
30467
|
+
return false;
|
|
30468
|
+
if (obj.timeout_ms < 0 || obj.timeout_ms > MAX_TIMEOUT_MS)
|
|
30469
|
+
return false;
|
|
30470
|
+
}
|
|
30471
|
+
return true;
|
|
30391
30472
|
}
|
|
30392
|
-
var
|
|
30473
|
+
var POWERSHELL_METACHARACTERS;
|
|
30474
|
+
var init_validate = __esm(() => {
|
|
30475
|
+
init_constants();
|
|
30476
|
+
POWERSHELL_METACHARACTERS = /[|;&`$(){}[\]<>"'#*?\x00-\x1f]/;
|
|
30477
|
+
});
|
|
30478
|
+
|
|
30479
|
+
// src/tools/test-runner/index.ts
|
|
30480
|
+
import * as path14 from "path";
|
|
30481
|
+
var test_runner;
|
|
30393
30482
|
var init_test_runner = __esm(() => {
|
|
30394
30483
|
init_dist();
|
|
30395
|
-
|
|
30396
|
-
|
|
30397
|
-
|
|
30398
|
-
|
|
30399
|
-
|
|
30400
|
-
|
|
30401
|
-
|
|
30402
|
-
|
|
30403
|
-
COMPOUND_TEST_EXTENSIONS = [
|
|
30404
|
-
".test.ts",
|
|
30405
|
-
".test.tsx",
|
|
30406
|
-
".test.js",
|
|
30407
|
-
".test.jsx",
|
|
30408
|
-
".spec.ts",
|
|
30409
|
-
".spec.tsx",
|
|
30410
|
-
".spec.js",
|
|
30411
|
-
".spec.jsx",
|
|
30412
|
-
".test.ps1",
|
|
30413
|
-
".spec.ps1"
|
|
30414
|
-
];
|
|
30415
|
-
SOURCE_EXTENSIONS = new Set([
|
|
30416
|
-
".ts",
|
|
30417
|
-
".tsx",
|
|
30418
|
-
".js",
|
|
30419
|
-
".jsx",
|
|
30420
|
-
".mjs",
|
|
30421
|
-
".cjs",
|
|
30422
|
-
".py",
|
|
30423
|
-
".rs",
|
|
30424
|
-
".ps1",
|
|
30425
|
-
".psm1"
|
|
30426
|
-
]);
|
|
30427
|
-
SKIP_DIRECTORIES = new Set([
|
|
30428
|
-
"node_modules",
|
|
30429
|
-
".git",
|
|
30430
|
-
"dist",
|
|
30431
|
-
"build",
|
|
30432
|
-
"out",
|
|
30433
|
-
"coverage",
|
|
30434
|
-
".next",
|
|
30435
|
-
".nuxt",
|
|
30436
|
-
".cache",
|
|
30437
|
-
"vendor",
|
|
30438
|
-
".svn",
|
|
30439
|
-
".hg",
|
|
30440
|
-
"__pycache__",
|
|
30441
|
-
".pytest_cache",
|
|
30442
|
-
"target"
|
|
30443
|
-
]);
|
|
30484
|
+
init_constants();
|
|
30485
|
+
init_detect();
|
|
30486
|
+
init_discover();
|
|
30487
|
+
init_run();
|
|
30488
|
+
init_validate();
|
|
30489
|
+
init_constants();
|
|
30490
|
+
init_detect();
|
|
30491
|
+
init_run();
|
|
30444
30492
|
test_runner = tool({
|
|
30445
|
-
description: 'Run project tests with framework detection. Supports bun, vitest, jest, mocha, pytest, cargo, and pester. Returns deterministic normalized JSON with framework, scope, command, totals, coverage, duration, success status, and failures. Use scope "all" for full suite, "convention" to map source files to test files, or "graph" to find related tests via imports.',
|
|
30493
|
+
description: 'Run project tests with framework detection. Supports bun, vitest, jest, mocha, pytest, cargo, and pester. Returns deterministic normalized JSON with framework, scope, command, totals, coverage, duration, success status, and failures. Use scope "all" for full suite, "convention" to map source files to test files by naming, or "graph" to find related tests via imports.',
|
|
30446
30494
|
args: {
|
|
30447
30495
|
scope: tool.schema.enum(["all", "convention", "graph"]).optional().describe('Test scope: "all" runs full suite, "convention" maps source files to test files by naming, "graph" finds related tests via imports'),
|
|
30448
30496
|
files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to test (used with convention or graph scope)"),
|
|
@@ -30472,12 +30520,7 @@ var init_test_runner = __esm(() => {
|
|
|
30472
30520
|
scope,
|
|
30473
30521
|
error: "No test framework detected",
|
|
30474
30522
|
message: "No supported test framework found. Install bun, vitest, jest, mocha, pytest, cargo, or pester.",
|
|
30475
|
-
totals: {
|
|
30476
|
-
passed: 0,
|
|
30477
|
-
failed: 0,
|
|
30478
|
-
skipped: 0,
|
|
30479
|
-
total: 0
|
|
30480
|
-
}
|
|
30523
|
+
totals: { passed: 0, failed: 0, skipped: 0, total: 0 }
|
|
30481
30524
|
};
|
|
30482
30525
|
return JSON.stringify(result2, null, 2);
|
|
30483
30526
|
}
|
|
@@ -30487,16 +30530,10 @@ var init_test_runner = __esm(() => {
|
|
|
30487
30530
|
if (scope === "all") {
|
|
30488
30531
|
testFiles = [];
|
|
30489
30532
|
} else if (scope === "convention") {
|
|
30490
|
-
const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) =>
|
|
30491
|
-
const ext = path11.extname(f).toLowerCase();
|
|
30492
|
-
return SOURCE_EXTENSIONS.has(ext);
|
|
30493
|
-
}) : findSourceFiles(process.cwd());
|
|
30533
|
+
const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => SOURCE_EXTENSIONS.has(path14.extname(f).toLowerCase())) : findSourceFiles(process.cwd());
|
|
30494
30534
|
testFiles = getTestFilesFromConvention(sourceFiles);
|
|
30495
30535
|
} else if (scope === "graph") {
|
|
30496
|
-
const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) =>
|
|
30497
|
-
const ext = path11.extname(f).toLowerCase();
|
|
30498
|
-
return SOURCE_EXTENSIONS.has(ext);
|
|
30499
|
-
}) : findSourceFiles(process.cwd());
|
|
30536
|
+
const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => SOURCE_EXTENSIONS.has(path14.extname(f).toLowerCase())) : findSourceFiles(process.cwd());
|
|
30500
30537
|
const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
|
|
30501
30538
|
if (graphTestFiles.length > 0) {
|
|
30502
30539
|
testFiles = graphTestFiles;
|
|
@@ -30516,8 +30553,8 @@ var init_test_runner = __esm(() => {
|
|
|
30516
30553
|
});
|
|
30517
30554
|
|
|
30518
30555
|
// src/services/preflight-service.ts
|
|
30519
|
-
import * as
|
|
30520
|
-
import * as
|
|
30556
|
+
import * as fs8 from "fs";
|
|
30557
|
+
import * as path15 from "path";
|
|
30521
30558
|
function validateDirectoryPath(dir) {
|
|
30522
30559
|
if (!dir || typeof dir !== "string") {
|
|
30523
30560
|
throw new Error("Directory path is required");
|
|
@@ -30525,8 +30562,8 @@ function validateDirectoryPath(dir) {
|
|
|
30525
30562
|
if (dir.includes("..")) {
|
|
30526
30563
|
throw new Error("Directory path must not contain path traversal sequences");
|
|
30527
30564
|
}
|
|
30528
|
-
const normalized =
|
|
30529
|
-
const absolutePath =
|
|
30565
|
+
const normalized = path15.normalize(dir);
|
|
30566
|
+
const absolutePath = path15.isAbsolute(normalized) ? normalized : path15.resolve(normalized);
|
|
30530
30567
|
return absolutePath;
|
|
30531
30568
|
}
|
|
30532
30569
|
function validateTimeout(timeoutMs, defaultValue) {
|
|
@@ -30549,9 +30586,9 @@ function validateTimeout(timeoutMs, defaultValue) {
|
|
|
30549
30586
|
}
|
|
30550
30587
|
function getPackageVersion(dir) {
|
|
30551
30588
|
try {
|
|
30552
|
-
const packagePath =
|
|
30553
|
-
if (
|
|
30554
|
-
const content =
|
|
30589
|
+
const packagePath = path15.join(dir, "package.json");
|
|
30590
|
+
if (fs8.existsSync(packagePath)) {
|
|
30591
|
+
const content = fs8.readFileSync(packagePath, "utf-8");
|
|
30555
30592
|
const pkg = JSON.parse(content);
|
|
30556
30593
|
return pkg.version ?? null;
|
|
30557
30594
|
}
|
|
@@ -30560,9 +30597,9 @@ function getPackageVersion(dir) {
|
|
|
30560
30597
|
}
|
|
30561
30598
|
function getChangelogVersion(dir) {
|
|
30562
30599
|
try {
|
|
30563
|
-
const changelogPath =
|
|
30564
|
-
if (
|
|
30565
|
-
const content =
|
|
30600
|
+
const changelogPath = path15.join(dir, "CHANGELOG.md");
|
|
30601
|
+
if (fs8.existsSync(changelogPath)) {
|
|
30602
|
+
const content = fs8.readFileSync(changelogPath, "utf-8");
|
|
30566
30603
|
const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
|
|
30567
30604
|
if (match) {
|
|
30568
30605
|
return match[1];
|
|
@@ -30574,10 +30611,10 @@ function getChangelogVersion(dir) {
|
|
|
30574
30611
|
function getVersionFileVersion(dir) {
|
|
30575
30612
|
const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
|
|
30576
30613
|
for (const file3 of possibleFiles) {
|
|
30577
|
-
const filePath =
|
|
30578
|
-
if (
|
|
30614
|
+
const filePath = path15.join(dir, file3);
|
|
30615
|
+
if (fs8.existsSync(filePath)) {
|
|
30579
30616
|
try {
|
|
30580
|
-
const content =
|
|
30617
|
+
const content = fs8.readFileSync(filePath, "utf-8").trim();
|
|
30581
30618
|
const match = content.match(/(\d+\.\d+\.\d+)/);
|
|
30582
30619
|
if (match) {
|
|
30583
30620
|
return match[1];
|
|
@@ -31114,7 +31151,7 @@ function createPreflightIntegration(config3) {
|
|
|
31114
31151
|
statusArtifact = new AutomationStatusArtifact(swarmDir);
|
|
31115
31152
|
}
|
|
31116
31153
|
const preflightHandler = async (request) => {
|
|
31117
|
-
|
|
31154
|
+
log("[PreflightIntegration] Handling preflight request", {
|
|
31118
31155
|
requestId: request.id,
|
|
31119
31156
|
phase: request.currentPhase,
|
|
31120
31157
|
source: request.source
|
|
@@ -31123,13 +31160,13 @@ function createPreflightIntegration(config3) {
|
|
|
31123
31160
|
if (statusArtifact) {
|
|
31124
31161
|
const state = report.overall === "pass" ? "success" : "failure";
|
|
31125
31162
|
statusArtifact.recordOutcome(state, request.currentPhase, report.message);
|
|
31126
|
-
|
|
31163
|
+
log("[PreflightIntegration] Status artifact updated", {
|
|
31127
31164
|
state,
|
|
31128
31165
|
phase: request.currentPhase,
|
|
31129
31166
|
message: report.message
|
|
31130
31167
|
});
|
|
31131
31168
|
}
|
|
31132
|
-
|
|
31169
|
+
log("[PreflightIntegration] Preflight complete", {
|
|
31133
31170
|
requestId: request.id,
|
|
31134
31171
|
overall: report.overall,
|
|
31135
31172
|
message: report.message,
|
|
@@ -31152,10 +31189,11 @@ var init_preflight_integration = __esm(() => {
|
|
|
31152
31189
|
init_status_artifact();
|
|
31153
31190
|
init_trigger();
|
|
31154
31191
|
init_preflight_service();
|
|
31192
|
+
init_utils();
|
|
31155
31193
|
});
|
|
31156
31194
|
|
|
31157
31195
|
// src/index.ts
|
|
31158
|
-
import * as
|
|
31196
|
+
import * as path28 from "path";
|
|
31159
31197
|
|
|
31160
31198
|
// src/config/constants.ts
|
|
31161
31199
|
var QA_AGENTS = ["reviewer", "critic"];
|
|
@@ -31213,9 +31251,9 @@ var DEFAULT_SCORING_CONFIG = {
|
|
|
31213
31251
|
init_evidence_schema();
|
|
31214
31252
|
|
|
31215
31253
|
// src/config/loader.ts
|
|
31254
|
+
init_utils();
|
|
31216
31255
|
import * as fs from "fs";
|
|
31217
|
-
import * as
|
|
31218
|
-
import * as path from "path";
|
|
31256
|
+
import * as path2 from "path";
|
|
31219
31257
|
|
|
31220
31258
|
// src/config/schema.ts
|
|
31221
31259
|
init_zod();
|
|
@@ -31504,6 +31542,10 @@ var CheckpointConfigSchema = exports_external.object({
|
|
|
31504
31542
|
enabled: exports_external.boolean().default(true),
|
|
31505
31543
|
auto_checkpoint_threshold: exports_external.number().min(1).max(20).default(3)
|
|
31506
31544
|
});
|
|
31545
|
+
var GitingestConfigSchema = exports_external.object({
|
|
31546
|
+
enabled: exports_external.boolean().default(true),
|
|
31547
|
+
endpoint: exports_external.string().url().default("https://gitingest.com/api/ingest")
|
|
31548
|
+
});
|
|
31507
31549
|
var AutomationModeSchema = exports_external.enum(["manual", "hybrid", "auto"]);
|
|
31508
31550
|
var AutomationCapabilitiesSchema = exports_external.object({
|
|
31509
31551
|
plan_sync: exports_external.boolean().default(true),
|
|
@@ -31525,7 +31567,7 @@ var AutomationConfigSchemaBase = exports_external.object({
|
|
|
31525
31567
|
})
|
|
31526
31568
|
});
|
|
31527
31569
|
var AutomationConfigSchema = AutomationConfigSchemaBase;
|
|
31528
|
-
var
|
|
31570
|
+
var pluginConfigShape = {
|
|
31529
31571
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
31530
31572
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
31531
31573
|
max_iterations: exports_external.number().min(1).max(10).default(5),
|
|
@@ -31539,31 +31581,25 @@ var PluginConfigSchema = exports_external.object({
|
|
|
31539
31581
|
review_passes: ReviewPassesConfigSchema.optional(),
|
|
31540
31582
|
integration_analysis: IntegrationAnalysisConfigSchema.optional(),
|
|
31541
31583
|
docs: DocsConfigSchema.optional(),
|
|
31584
|
+
gitingest: GitingestConfigSchema.optional(),
|
|
31542
31585
|
ui_review: UIReviewConfigSchema.optional(),
|
|
31543
31586
|
compaction_advisory: CompactionAdvisoryConfigSchema.optional(),
|
|
31544
31587
|
lint: LintConfigSchema.optional(),
|
|
31545
31588
|
secretscan: SecretscanConfigSchema.optional(),
|
|
31546
31589
|
checkpoint: CheckpointConfigSchema.optional(),
|
|
31547
31590
|
automation: AutomationConfigSchema.optional()
|
|
31548
|
-
}
|
|
31591
|
+
};
|
|
31592
|
+
var PLUGIN_CONFIG_ALLOWED_KEYS = new Set(Object.keys(pluginConfigShape));
|
|
31593
|
+
var PluginConfigSchema = exports_external.object(pluginConfigShape);
|
|
31549
31594
|
|
|
31550
31595
|
// src/config/loader.ts
|
|
31551
31596
|
var CONFIG_FILENAME = "opencode-swarm.json";
|
|
31552
31597
|
var PROMPTS_DIR_NAME = "opencode-swarm";
|
|
31553
31598
|
var MAX_CONFIG_FILE_BYTES = 102400;
|
|
31554
|
-
function getUserConfigDir() {
|
|
31555
|
-
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
31556
|
-
}
|
|
31557
31599
|
function loadRawConfigFromPath(configPath) {
|
|
31558
31600
|
try {
|
|
31559
|
-
const stats = fs.statSync(configPath);
|
|
31560
|
-
if (stats.size > MAX_CONFIG_FILE_BYTES) {
|
|
31561
|
-
console.warn(`[opencode-swarm] Config file too large (max 100 KB): ${configPath}`);
|
|
31562
|
-
console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
|
|
31563
|
-
return { config: null, fileExisted: true, hadError: true };
|
|
31564
|
-
}
|
|
31565
31601
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
31566
|
-
if (content
|
|
31602
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_CONFIG_FILE_BYTES) {
|
|
31567
31603
|
console.warn(`[opencode-swarm] Config file too large after read (max 100 KB): ${configPath}`);
|
|
31568
31604
|
console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
|
|
31569
31605
|
return { config: null, fileExisted: true, hadError: true };
|
|
@@ -31579,10 +31615,10 @@ function loadRawConfigFromPath(configPath) {
|
|
|
31579
31615
|
fileExisted: true,
|
|
31580
31616
|
hadError: false
|
|
31581
31617
|
};
|
|
31582
|
-
} catch (
|
|
31583
|
-
const isFileNotFoundError =
|
|
31618
|
+
} catch (error49) {
|
|
31619
|
+
const isFileNotFoundError = error49 instanceof Error && "code" in error49 && error49.code === "ENOENT";
|
|
31584
31620
|
if (!isFileNotFoundError) {
|
|
31585
|
-
const errorMessage =
|
|
31621
|
+
const errorMessage = error49 instanceof Error ? error49.message : String(error49);
|
|
31586
31622
|
console.warn(`[opencode-swarm] \u26A0\uFE0F CONFIG LOAD FAILURE \u2014 config exists at ${configPath} but could not be loaded: ${errorMessage}`);
|
|
31587
31623
|
console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config load failed. Falling back to safe defaults with guardrails ENABLED.");
|
|
31588
31624
|
return { config: null, fileExisted: true, hadError: true };
|
|
@@ -31590,9 +31626,15 @@ function loadRawConfigFromPath(configPath) {
|
|
|
31590
31626
|
return { config: null, fileExisted: false, hadError: false };
|
|
31591
31627
|
}
|
|
31592
31628
|
}
|
|
31593
|
-
function
|
|
31594
|
-
const
|
|
31595
|
-
|
|
31629
|
+
function warnOnUnknownPluginConfigFields(config2, context) {
|
|
31630
|
+
const unknownKeys = Object.keys(config2).filter((key) => !PLUGIN_CONFIG_ALLOWED_KEYS.has(key)).sort();
|
|
31631
|
+
if (unknownKeys.length === 0)
|
|
31632
|
+
return;
|
|
31633
|
+
warn(`${context} contains unknown plugin config fields: ${unknownKeys.join(", ")}. They will be ignored.`);
|
|
31634
|
+
}
|
|
31635
|
+
function _loadPluginConfigInternal(directory) {
|
|
31636
|
+
const userConfigPath = path2.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
31637
|
+
const projectConfigPath = path2.join(directory, ".opencode", CONFIG_FILENAME);
|
|
31596
31638
|
const userResult = loadRawConfigFromPath(userConfigPath);
|
|
31597
31639
|
const projectResult = loadRawConfigFromPath(projectConfigPath);
|
|
31598
31640
|
const rawUserConfig = userResult.config;
|
|
@@ -31603,56 +31645,67 @@ function loadPluginConfig(directory) {
|
|
|
31603
31645
|
if (rawProjectConfig) {
|
|
31604
31646
|
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
31605
31647
|
}
|
|
31648
|
+
warnOnUnknownPluginConfigFields(mergedRaw, "Merged plugin configuration");
|
|
31606
31649
|
const result = PluginConfigSchema.safeParse(mergedRaw);
|
|
31607
31650
|
if (!result.success) {
|
|
31608
31651
|
if (rawUserConfig) {
|
|
31609
31652
|
const userParseResult = PluginConfigSchema.safeParse(rawUserConfig);
|
|
31610
31653
|
if (userParseResult.success) {
|
|
31611
31654
|
console.warn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
|
|
31612
|
-
return
|
|
31655
|
+
return {
|
|
31656
|
+
config: userParseResult.data,
|
|
31657
|
+
loadedFromFile,
|
|
31658
|
+
configHadErrors
|
|
31659
|
+
};
|
|
31613
31660
|
}
|
|
31614
31661
|
}
|
|
31615
31662
|
console.warn("[opencode-swarm] Merged config validation failed:");
|
|
31616
31663
|
console.warn(result.error.format());
|
|
31617
31664
|
console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Falling back to conservative defaults with guardrails ENABLED. Fix the config file to restore custom configuration.");
|
|
31618
|
-
return
|
|
31619
|
-
|
|
31620
|
-
|
|
31665
|
+
return {
|
|
31666
|
+
config: PluginConfigSchema.parse({
|
|
31667
|
+
guardrails: { enabled: true }
|
|
31668
|
+
}),
|
|
31669
|
+
loadedFromFile,
|
|
31670
|
+
configHadErrors
|
|
31671
|
+
};
|
|
31621
31672
|
}
|
|
31622
31673
|
if (loadedFromFile && configHadErrors) {
|
|
31623
|
-
return
|
|
31624
|
-
|
|
31625
|
-
|
|
31626
|
-
|
|
31674
|
+
return {
|
|
31675
|
+
config: PluginConfigSchema.parse({
|
|
31676
|
+
...mergedRaw,
|
|
31677
|
+
guardrails: { enabled: true }
|
|
31678
|
+
}),
|
|
31679
|
+
loadedFromFile,
|
|
31680
|
+
configHadErrors
|
|
31681
|
+
};
|
|
31627
31682
|
}
|
|
31628
|
-
return result.data;
|
|
31683
|
+
return { config: result.data, loadedFromFile, configHadErrors };
|
|
31684
|
+
}
|
|
31685
|
+
function loadPluginConfig(directory) {
|
|
31686
|
+
return _loadPluginConfigInternal(directory).config;
|
|
31629
31687
|
}
|
|
31630
31688
|
function loadPluginConfigWithMeta(directory) {
|
|
31631
|
-
const
|
|
31632
|
-
|
|
31633
|
-
const userResult = loadRawConfigFromPath(userConfigPath);
|
|
31634
|
-
const projectResult = loadRawConfigFromPath(projectConfigPath);
|
|
31635
|
-
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
31636
|
-
const config2 = loadPluginConfig(directory);
|
|
31637
|
-
return { config: config2, loadedFromFile };
|
|
31689
|
+
const result = _loadPluginConfigInternal(directory);
|
|
31690
|
+
return { config: result.config, loadedFromFile: result.loadedFromFile };
|
|
31638
31691
|
}
|
|
31639
31692
|
function loadAgentPrompt(agentName) {
|
|
31640
|
-
const promptsDir =
|
|
31693
|
+
const promptsDir = path2.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
|
|
31641
31694
|
const result = {};
|
|
31642
|
-
const promptPath =
|
|
31695
|
+
const promptPath = path2.join(promptsDir, `${agentName}.md`);
|
|
31643
31696
|
if (fs.existsSync(promptPath)) {
|
|
31644
31697
|
try {
|
|
31645
31698
|
result.prompt = fs.readFileSync(promptPath, "utf-8");
|
|
31646
|
-
} catch (
|
|
31647
|
-
console.warn(`[opencode-swarm] Error reading prompt file ${promptPath}:`,
|
|
31699
|
+
} catch (error49) {
|
|
31700
|
+
console.warn(`[opencode-swarm] Error reading prompt file ${promptPath}:`, error49 instanceof Error ? error49.message : String(error49));
|
|
31648
31701
|
}
|
|
31649
31702
|
}
|
|
31650
|
-
const appendPromptPath =
|
|
31703
|
+
const appendPromptPath = path2.join(promptsDir, `${agentName}_append.md`);
|
|
31651
31704
|
if (fs.existsSync(appendPromptPath)) {
|
|
31652
31705
|
try {
|
|
31653
31706
|
result.appendPrompt = fs.readFileSync(appendPromptPath, "utf-8");
|
|
31654
|
-
} catch (
|
|
31655
|
-
console.warn(`[opencode-swarm] Error reading append prompt ${appendPromptPath}:`,
|
|
31707
|
+
} catch (error49) {
|
|
31708
|
+
console.warn(`[opencode-swarm] Error reading append prompt ${appendPromptPath}:`, error49 instanceof Error ? error49.message : String(error49));
|
|
31656
31709
|
}
|
|
31657
31710
|
}
|
|
31658
31711
|
return result;
|
|
@@ -31661,6 +31714,14 @@ function loadAgentPrompt(agentName) {
|
|
|
31661
31714
|
// src/config/index.ts
|
|
31662
31715
|
init_plan_schema();
|
|
31663
31716
|
|
|
31717
|
+
// src/agents/model.ts
|
|
31718
|
+
function resolveModel(model) {
|
|
31719
|
+
if (!model || model === "current") {
|
|
31720
|
+
return;
|
|
31721
|
+
}
|
|
31722
|
+
return model;
|
|
31723
|
+
}
|
|
31724
|
+
|
|
31664
31725
|
// src/agents/architect.ts
|
|
31665
31726
|
var ARCHITECT_PROMPT = `You are Architect - orchestrator of a multi-agent swarm.
|
|
31666
31727
|
|
|
@@ -31929,6 +31990,7 @@ Swarm: {{SWARM_ID}}
|
|
|
31929
31990
|
- [pattern]: [usage]
|
|
31930
31991
|
\`\`\``;
|
|
31931
31992
|
function createArchitectAgent(model, customPrompt, customAppendPrompt) {
|
|
31993
|
+
const resolvedModel = resolveModel(model);
|
|
31932
31994
|
let prompt = ARCHITECT_PROMPT;
|
|
31933
31995
|
if (customPrompt) {
|
|
31934
31996
|
prompt = customPrompt;
|
|
@@ -31941,7 +32003,7 @@ ${customAppendPrompt}`;
|
|
|
31941
32003
|
name: "architect",
|
|
31942
32004
|
description: "Central orchestrator of the development pipeline. Analyzes requests, coordinates SME consultation, manages code generation, and triages QA feedback.",
|
|
31943
32005
|
config: {
|
|
31944
|
-
model,
|
|
32006
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
31945
32007
|
temperature: 0.1,
|
|
31946
32008
|
prompt
|
|
31947
32009
|
}
|
|
@@ -31975,6 +32037,7 @@ OUTPUT FORMAT:
|
|
|
31975
32037
|
DONE: [one-line summary]
|
|
31976
32038
|
CHANGED: [file]: [what changed]`;
|
|
31977
32039
|
function createCoderAgent(model, customPrompt, customAppendPrompt) {
|
|
32040
|
+
const resolvedModel = resolveModel(model);
|
|
31978
32041
|
let prompt = CODER_PROMPT;
|
|
31979
32042
|
if (customPrompt) {
|
|
31980
32043
|
prompt = customPrompt;
|
|
@@ -31987,7 +32050,7 @@ ${customAppendPrompt}`;
|
|
|
31987
32050
|
name: "coder",
|
|
31988
32051
|
description: "Production-quality code implementation specialist. Receives unified specifications and writes complete, working code.",
|
|
31989
32052
|
config: {
|
|
31990
|
-
model,
|
|
32053
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
31991
32054
|
temperature: 0.2,
|
|
31992
32055
|
prompt
|
|
31993
32056
|
}
|
|
@@ -32033,6 +32096,7 @@ RULES:
|
|
|
32033
32096
|
- Don't reject for style/formatting \u2014 focus on substance
|
|
32034
32097
|
- If the plan is fundamentally sound with only minor concerns, APPROVE it`;
|
|
32035
32098
|
function createCriticAgent(model, customPrompt, customAppendPrompt) {
|
|
32099
|
+
const resolvedModel = resolveModel(model);
|
|
32036
32100
|
let prompt = CRITIC_PROMPT;
|
|
32037
32101
|
if (customPrompt) {
|
|
32038
32102
|
prompt = customPrompt;
|
|
@@ -32045,7 +32109,7 @@ ${customAppendPrompt}`;
|
|
|
32045
32109
|
name: "critic",
|
|
32046
32110
|
description: "Plan critic. Reviews the architect's plan before implementation begins \u2014 checks completeness, feasibility, scope, dependencies, and flags AI-slop.",
|
|
32047
32111
|
config: {
|
|
32048
|
-
model,
|
|
32112
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32049
32113
|
temperature: 0.1,
|
|
32050
32114
|
prompt,
|
|
32051
32115
|
tools: {
|
|
@@ -32190,6 +32254,7 @@ RULES:
|
|
|
32190
32254
|
- Do NOT implement business logic \u2014 leave that for the coder
|
|
32191
32255
|
- Keep output under 3000 characters per component`;
|
|
32192
32256
|
function createDesignerAgent(model, customPrompt, customAppendPrompt) {
|
|
32257
|
+
const resolvedModel = resolveModel(model);
|
|
32193
32258
|
let prompt = DESIGNER_PROMPT;
|
|
32194
32259
|
if (customPrompt) {
|
|
32195
32260
|
prompt = customPrompt;
|
|
@@ -32202,7 +32267,7 @@ ${customAppendPrompt}`;
|
|
|
32202
32267
|
name: "designer",
|
|
32203
32268
|
description: "UI/UX design specification agent. Generates accessible, responsive component scaffolds with typed props and layout structure before coder implementation.",
|
|
32204
32269
|
config: {
|
|
32205
|
-
model,
|
|
32270
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32206
32271
|
temperature: 0.3,
|
|
32207
32272
|
prompt
|
|
32208
32273
|
}
|
|
@@ -32263,6 +32328,7 @@ ADDED: [list of new sections/files created]
|
|
|
32263
32328
|
REMOVED: [list of deprecated sections removed]
|
|
32264
32329
|
SUMMARY: [one-line description of doc changes]`;
|
|
32265
32330
|
function createDocsAgent(model, customPrompt, customAppendPrompt) {
|
|
32331
|
+
const resolvedModel = resolveModel(model);
|
|
32266
32332
|
let prompt = DOCS_PROMPT;
|
|
32267
32333
|
if (customPrompt) {
|
|
32268
32334
|
prompt = customPrompt;
|
|
@@ -32275,7 +32341,7 @@ ${customAppendPrompt}`;
|
|
|
32275
32341
|
name: "docs",
|
|
32276
32342
|
description: "Documentation synthesizer. Updates README, API docs, and guides to reflect code changes after each phase.",
|
|
32277
32343
|
config: {
|
|
32278
|
-
model,
|
|
32344
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32279
32345
|
temperature: 0.2,
|
|
32280
32346
|
prompt
|
|
32281
32347
|
}
|
|
@@ -32323,6 +32389,7 @@ DOMAINS: [relevant SME domains: powershell, security, python, etc.]
|
|
|
32323
32389
|
REVIEW NEEDED:
|
|
32324
32390
|
- [path]: [why, which SME]`;
|
|
32325
32391
|
function createExplorerAgent(model, customPrompt, customAppendPrompt) {
|
|
32392
|
+
const resolvedModel = resolveModel(model);
|
|
32326
32393
|
let prompt = EXPLORER_PROMPT;
|
|
32327
32394
|
if (customPrompt) {
|
|
32328
32395
|
prompt = customPrompt;
|
|
@@ -32335,7 +32402,7 @@ ${customAppendPrompt}`;
|
|
|
32335
32402
|
name: "explorer",
|
|
32336
32403
|
description: "Fast codebase discovery and analysis. Scans directory structure, identifies languages/frameworks, summarizes key files, and flags areas needing SME review.",
|
|
32337
32404
|
config: {
|
|
32338
|
-
model,
|
|
32405
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32339
32406
|
temperature: 0.1,
|
|
32340
32407
|
prompt,
|
|
32341
32408
|
tools: {
|
|
@@ -32381,6 +32448,7 @@ RISK LEVELS:
|
|
|
32381
32448
|
- HIGH: must fix
|
|
32382
32449
|
- CRITICAL: blocks approval`;
|
|
32383
32450
|
function createReviewerAgent(model, customPrompt, customAppendPrompt) {
|
|
32451
|
+
const resolvedModel = resolveModel(model);
|
|
32384
32452
|
let prompt = REVIEWER_PROMPT;
|
|
32385
32453
|
if (customPrompt) {
|
|
32386
32454
|
prompt = customPrompt;
|
|
@@ -32393,7 +32461,7 @@ ${customAppendPrompt}`;
|
|
|
32393
32461
|
name: "reviewer",
|
|
32394
32462
|
description: "Code reviewer. Verifies correctness, finds vulnerabilities, and checks quality across architect-specified dimensions.",
|
|
32395
32463
|
config: {
|
|
32396
|
-
model,
|
|
32464
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32397
32465
|
temperature: 0.1,
|
|
32398
32466
|
prompt,
|
|
32399
32467
|
tools: {
|
|
@@ -32432,6 +32500,7 @@ RULES:
|
|
|
32432
32500
|
- Be actionable: info Coder can use directly
|
|
32433
32501
|
- No code writing`;
|
|
32434
32502
|
function createSMEAgent(model, customPrompt, customAppendPrompt) {
|
|
32503
|
+
const resolvedModel = resolveModel(model);
|
|
32435
32504
|
let prompt = SME_PROMPT;
|
|
32436
32505
|
if (customPrompt) {
|
|
32437
32506
|
prompt = customPrompt;
|
|
@@ -32444,7 +32513,7 @@ ${customAppendPrompt}`;
|
|
|
32444
32513
|
name: "sme",
|
|
32445
32514
|
description: "Open-domain subject matter expert. Provides deep technical guidance on any domain the Architect requests \u2014 from security to iOS to Kubernetes.",
|
|
32446
32515
|
config: {
|
|
32447
|
-
model,
|
|
32516
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32448
32517
|
temperature: 0.2,
|
|
32449
32518
|
prompt,
|
|
32450
32519
|
tools: {
|
|
@@ -32518,6 +32587,7 @@ COVERAGE REPORTING:
|
|
|
32518
32587
|
- If COVERAGE_PCT < 70%, add a note: "COVERAGE_WARNING: Below 70% threshold \u2014 consider additional test cases for uncovered paths."
|
|
32519
32588
|
- The architect uses this to decide whether to request an additional test pass (Rule 10 / Phase 5 step 5h).`;
|
|
32520
32589
|
function createTestEngineerAgent(model, customPrompt, customAppendPrompt) {
|
|
32590
|
+
const resolvedModel = resolveModel(model);
|
|
32521
32591
|
let prompt = TEST_ENGINEER_PROMPT;
|
|
32522
32592
|
if (customPrompt) {
|
|
32523
32593
|
prompt = customPrompt;
|
|
@@ -32530,7 +32600,7 @@ ${customAppendPrompt}`;
|
|
|
32530
32600
|
name: "test_engineer",
|
|
32531
32601
|
description: "Testing and validation specialist. Generates test cases, runs them, and reports structured PASS/FAIL verdicts.",
|
|
32532
32602
|
config: {
|
|
32533
|
-
model,
|
|
32603
|
+
...resolvedModel !== undefined && { model: resolvedModel },
|
|
32534
32604
|
temperature: 0.2,
|
|
32535
32605
|
prompt
|
|
32536
32606
|
}
|
|
@@ -32550,8 +32620,9 @@ function stripSwarmPrefix(agentName, swarmPrefix) {
|
|
|
32550
32620
|
function getModelForAgent(agentName, swarmAgents, swarmPrefix) {
|
|
32551
32621
|
const baseAgentName = stripSwarmPrefix(agentName, swarmPrefix);
|
|
32552
32622
|
const explicit = swarmAgents?.[baseAgentName]?.model;
|
|
32553
|
-
if (explicit)
|
|
32554
|
-
return explicit;
|
|
32623
|
+
if (explicit !== undefined) {
|
|
32624
|
+
return resolveModel(explicit);
|
|
32625
|
+
}
|
|
32555
32626
|
return DEFAULT_MODELS[baseAgentName] ?? DEFAULT_MODELS.default;
|
|
32556
32627
|
}
|
|
32557
32628
|
function isAgentDisabled(agentName, swarmAgents, swarmPrefix) {
|
|
@@ -32722,8 +32793,8 @@ class CircuitBreaker {
|
|
|
32722
32793
|
async execute(fn) {
|
|
32723
32794
|
const state = this.getState();
|
|
32724
32795
|
if (state === "open") {
|
|
32725
|
-
const
|
|
32726
|
-
throw
|
|
32796
|
+
const error49 = new Error(`Circuit breaker '${this.name}' is open`);
|
|
32797
|
+
throw error49;
|
|
32727
32798
|
}
|
|
32728
32799
|
const startTime = Date.now();
|
|
32729
32800
|
try {
|
|
@@ -32731,10 +32802,10 @@ class CircuitBreaker {
|
|
|
32731
32802
|
const duration3 = Date.now() - startTime;
|
|
32732
32803
|
this.recordSuccess(duration3);
|
|
32733
32804
|
return result;
|
|
32734
|
-
} catch (
|
|
32805
|
+
} catch (error49) {
|
|
32735
32806
|
const duration3 = Date.now() - startTime;
|
|
32736
|
-
this.recordFailure(
|
|
32737
|
-
throw
|
|
32807
|
+
this.recordFailure(error49, duration3);
|
|
32808
|
+
throw error49;
|
|
32738
32809
|
}
|
|
32739
32810
|
}
|
|
32740
32811
|
async executeWithTimeout(fn) {
|
|
@@ -32748,9 +32819,9 @@ class CircuitBreaker {
|
|
|
32748
32819
|
fn().then((result) => {
|
|
32749
32820
|
clearTimeout(timeout);
|
|
32750
32821
|
resolve(result);
|
|
32751
|
-
}).catch((
|
|
32822
|
+
}).catch((error49) => {
|
|
32752
32823
|
clearTimeout(timeout);
|
|
32753
|
-
reject(
|
|
32824
|
+
reject(error49);
|
|
32754
32825
|
});
|
|
32755
32826
|
});
|
|
32756
32827
|
}
|
|
@@ -32764,7 +32835,7 @@ class CircuitBreaker {
|
|
|
32764
32835
|
}
|
|
32765
32836
|
this.onStateChange?.("callSuccess", { duration: duration3 });
|
|
32766
32837
|
}
|
|
32767
|
-
recordFailure(
|
|
32838
|
+
recordFailure(error49, duration3) {
|
|
32768
32839
|
this.failureCount++;
|
|
32769
32840
|
this.lastFailureTime = Date.now();
|
|
32770
32841
|
if (this.state === "half-open") {
|
|
@@ -32772,7 +32843,7 @@ class CircuitBreaker {
|
|
|
32772
32843
|
} else if (this.state === "closed" && this.failureCount >= this.config.failureThreshold) {
|
|
32773
32844
|
this.transitionTo("open");
|
|
32774
32845
|
}
|
|
32775
|
-
this.onStateChange?.("callFailure", { error:
|
|
32846
|
+
this.onStateChange?.("callFailure", { error: error49, duration: duration3 });
|
|
32776
32847
|
}
|
|
32777
32848
|
transitionTo(newState) {
|
|
32778
32849
|
const oldState = this.state;
|
|
@@ -33248,7 +33319,7 @@ function createAutomationManager(automationConfig) {
|
|
|
33248
33319
|
init_manager2();
|
|
33249
33320
|
init_utils();
|
|
33250
33321
|
import * as fs2 from "fs";
|
|
33251
|
-
import * as
|
|
33322
|
+
import * as path7 from "path";
|
|
33252
33323
|
|
|
33253
33324
|
class PlanSyncWorker {
|
|
33254
33325
|
directory;
|
|
@@ -33272,10 +33343,10 @@ class PlanSyncWorker {
|
|
|
33272
33343
|
this.onSyncComplete = options.onSyncComplete;
|
|
33273
33344
|
}
|
|
33274
33345
|
getSwarmDir() {
|
|
33275
|
-
return
|
|
33346
|
+
return path7.resolve(this.directory, ".swarm");
|
|
33276
33347
|
}
|
|
33277
33348
|
getPlanJsonPath() {
|
|
33278
|
-
return
|
|
33349
|
+
return path7.join(this.getSwarmDir(), "plan.json");
|
|
33279
33350
|
}
|
|
33280
33351
|
start() {
|
|
33281
33352
|
if (this.disposed) {
|
|
@@ -33510,7 +33581,7 @@ function handleAgentsCommand(agents, guardrails) {
|
|
|
33510
33581
|
}
|
|
33511
33582
|
const lines = [`## Registered Agents (${entries.length} total)`, ""];
|
|
33512
33583
|
for (const [key, agent] of entries) {
|
|
33513
|
-
const model = agent.config.model || "default";
|
|
33584
|
+
const model = agent.config.model === undefined ? "current session model" : agent.config.model || "default";
|
|
33514
33585
|
const temp = agent.config.temperature !== undefined ? agent.config.temperature.toString() : "default";
|
|
33515
33586
|
const tools = agent.config.tools || {};
|
|
33516
33587
|
const isReadOnly = tools.write === false || tools.edit === false;
|
|
@@ -33622,6 +33693,8 @@ async function handleArchiveCommand(directory, args) {
|
|
|
33622
33693
|
init_manager();
|
|
33623
33694
|
|
|
33624
33695
|
// src/state.ts
|
|
33696
|
+
var TOOL_AGGREGATE_CAP = 1000;
|
|
33697
|
+
var MAX_DELEGATION_CHAIN_SESSIONS = 1000;
|
|
33625
33698
|
var swarmState = {
|
|
33626
33699
|
activeToolCalls: new Map,
|
|
33627
33700
|
toolAggregates: new Map,
|
|
@@ -33630,6 +33703,14 @@ var swarmState = {
|
|
|
33630
33703
|
pendingEvents: 0,
|
|
33631
33704
|
agentSessions: new Map
|
|
33632
33705
|
};
|
|
33706
|
+
function enforceToolAggregateCapacity(key) {
|
|
33707
|
+
if (!swarmState.toolAggregates.has(key) && swarmState.toolAggregates.size >= TOOL_AGGREGATE_CAP) {
|
|
33708
|
+
const firstKey = swarmState.toolAggregates.keys().next().value;
|
|
33709
|
+
if (firstKey !== undefined) {
|
|
33710
|
+
swarmState.toolAggregates.delete(firstKey);
|
|
33711
|
+
}
|
|
33712
|
+
}
|
|
33713
|
+
}
|
|
33633
33714
|
function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
33634
33715
|
const now = Date.now();
|
|
33635
33716
|
const staleIds = [];
|
|
@@ -33640,6 +33721,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
|
33640
33721
|
}
|
|
33641
33722
|
for (const id of staleIds) {
|
|
33642
33723
|
swarmState.agentSessions.delete(id);
|
|
33724
|
+
swarmState.activeAgent.delete(id);
|
|
33725
|
+
swarmState.delegationChains.delete(id);
|
|
33643
33726
|
}
|
|
33644
33727
|
const sessionState = {
|
|
33645
33728
|
agentName,
|
|
@@ -33956,15 +34039,12 @@ async function handleBenchmarkCommand(directory, args) {
|
|
|
33956
34039
|
}
|
|
33957
34040
|
|
|
33958
34041
|
// src/commands/config.ts
|
|
33959
|
-
import * as
|
|
33960
|
-
|
|
33961
|
-
function getUserConfigDir2() {
|
|
33962
|
-
return process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config");
|
|
33963
|
-
}
|
|
34042
|
+
import * as path9 from "path";
|
|
34043
|
+
init_utils();
|
|
33964
34044
|
async function handleConfigCommand(directory, _args) {
|
|
33965
34045
|
const config2 = loadPluginConfig(directory);
|
|
33966
|
-
const userConfigPath =
|
|
33967
|
-
const projectConfigPath =
|
|
34046
|
+
const userConfigPath = path9.join(getUserConfigDir(), "opencode", "opencode-swarm.json");
|
|
34047
|
+
const projectConfigPath = path9.join(directory, ".opencode", "opencode-swarm.json");
|
|
33968
34048
|
const lines = [
|
|
33969
34049
|
"## Swarm Configuration",
|
|
33970
34050
|
"",
|
|
@@ -34389,6 +34469,56 @@ async function handleEvidenceSummaryCommand(directory) {
|
|
|
34389
34469
|
return lines.join(`
|
|
34390
34470
|
`);
|
|
34391
34471
|
}
|
|
34472
|
+
// package.json
|
|
34473
|
+
var package_default = {
|
|
34474
|
+
name: "opencode-swarm",
|
|
34475
|
+
version: "6.8.1",
|
|
34476
|
+
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
34477
|
+
main: "dist/index.js",
|
|
34478
|
+
types: "dist/index.d.ts",
|
|
34479
|
+
bin: {
|
|
34480
|
+
"opencode-swarm": "./dist/cli/index.js"
|
|
34481
|
+
},
|
|
34482
|
+
type: "module",
|
|
34483
|
+
license: "MIT",
|
|
34484
|
+
keywords: [
|
|
34485
|
+
"opencode",
|
|
34486
|
+
"opencode-plugin",
|
|
34487
|
+
"ai",
|
|
34488
|
+
"agents",
|
|
34489
|
+
"orchestration",
|
|
34490
|
+
"swarm",
|
|
34491
|
+
"multi-agent",
|
|
34492
|
+
"llm"
|
|
34493
|
+
],
|
|
34494
|
+
files: [
|
|
34495
|
+
"dist",
|
|
34496
|
+
"README.md",
|
|
34497
|
+
"LICENSE"
|
|
34498
|
+
],
|
|
34499
|
+
scripts: {
|
|
34500
|
+
clean: "rm -rf dist",
|
|
34501
|
+
build: "rm -rf dist && bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && tsc --emitDeclarationOnly",
|
|
34502
|
+
typecheck: "tsc --noEmit",
|
|
34503
|
+
test: "bun test",
|
|
34504
|
+
lint: "biome lint .",
|
|
34505
|
+
format: "biome format . --write",
|
|
34506
|
+
check: "biome check --write .",
|
|
34507
|
+
dev: "bun run build && opencode",
|
|
34508
|
+
prepublishOnly: "bun run build"
|
|
34509
|
+
},
|
|
34510
|
+
dependencies: {
|
|
34511
|
+
"@opencode-ai/plugin": "^1.1.53",
|
|
34512
|
+
"@opencode-ai/sdk": "^1.1.53",
|
|
34513
|
+
zod: "^4.1.8"
|
|
34514
|
+
},
|
|
34515
|
+
devDependencies: {
|
|
34516
|
+
"@biomejs/biome": "2.3.14",
|
|
34517
|
+
"bun-types": "latest",
|
|
34518
|
+
typescript: "^5.7.3"
|
|
34519
|
+
}
|
|
34520
|
+
};
|
|
34521
|
+
|
|
34392
34522
|
// src/services/export-service.ts
|
|
34393
34523
|
init_utils2();
|
|
34394
34524
|
init_manager2();
|
|
@@ -34397,7 +34527,7 @@ async function getExportData(directory) {
|
|
|
34397
34527
|
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
34398
34528
|
const contextContent = await readSwarmFileAsync(directory, "context.md");
|
|
34399
34529
|
return {
|
|
34400
|
-
version:
|
|
34530
|
+
version: package_default.version,
|
|
34401
34531
|
exported: new Date().toISOString(),
|
|
34402
34532
|
plan: planStructured || planContent,
|
|
34403
34533
|
context: contextContent
|
|
@@ -34693,7 +34823,7 @@ init_preflight_service();
|
|
|
34693
34823
|
|
|
34694
34824
|
// src/commands/reset.ts
|
|
34695
34825
|
init_utils2();
|
|
34696
|
-
import * as
|
|
34826
|
+
import * as fs9 from "fs";
|
|
34697
34827
|
async function handleResetCommand(directory, args) {
|
|
34698
34828
|
const hasConfirm = args.includes("--confirm");
|
|
34699
34829
|
if (!hasConfirm) {
|
|
@@ -34713,8 +34843,8 @@ async function handleResetCommand(directory, args) {
|
|
|
34713
34843
|
for (const filename of filesToReset) {
|
|
34714
34844
|
try {
|
|
34715
34845
|
const resolvedPath = validateSwarmPath(directory, filename);
|
|
34716
|
-
if (
|
|
34717
|
-
|
|
34846
|
+
if (fs9.existsSync(resolvedPath)) {
|
|
34847
|
+
fs9.unlinkSync(resolvedPath);
|
|
34718
34848
|
results.push(`- \u2705 Deleted ${filename}`);
|
|
34719
34849
|
} else {
|
|
34720
34850
|
results.push(`- \u23ED\uFE0F ${filename} not found (skipped)`);
|
|
@@ -34725,8 +34855,8 @@ async function handleResetCommand(directory, args) {
|
|
|
34725
34855
|
}
|
|
34726
34856
|
try {
|
|
34727
34857
|
const summariesPath = validateSwarmPath(directory, "summaries");
|
|
34728
|
-
if (
|
|
34729
|
-
|
|
34858
|
+
if (fs9.existsSync(summariesPath)) {
|
|
34859
|
+
fs9.rmSync(summariesPath, { recursive: true, force: true });
|
|
34730
34860
|
results.push("- \u2705 Deleted summaries/ directory");
|
|
34731
34861
|
} else {
|
|
34732
34862
|
results.push("- \u23ED\uFE0F summaries/ not found (skipped)");
|
|
@@ -34747,8 +34877,9 @@ async function handleResetCommand(directory, args) {
|
|
|
34747
34877
|
// src/summaries/manager.ts
|
|
34748
34878
|
init_utils2();
|
|
34749
34879
|
init_utils();
|
|
34750
|
-
import {
|
|
34751
|
-
import
|
|
34880
|
+
import { randomBytes } from "crypto";
|
|
34881
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync5 } from "fs";
|
|
34882
|
+
import * as path16 from "path";
|
|
34752
34883
|
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
34753
34884
|
function sanitizeSummaryId(id) {
|
|
34754
34885
|
if (!id || id.length === 0) {
|
|
@@ -34776,9 +34907,9 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
34776
34907
|
if (outputBytes > maxStoredBytes) {
|
|
34777
34908
|
throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
|
|
34778
34909
|
}
|
|
34779
|
-
const relativePath =
|
|
34910
|
+
const relativePath = path16.join("summaries", `${sanitizedId}.json`);
|
|
34780
34911
|
const summaryPath = validateSwarmPath(directory, relativePath);
|
|
34781
|
-
const summaryDir =
|
|
34912
|
+
const summaryDir = path16.dirname(summaryPath);
|
|
34782
34913
|
const entry = {
|
|
34783
34914
|
id: sanitizedId,
|
|
34784
34915
|
summaryText,
|
|
@@ -34788,7 +34919,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
34788
34919
|
};
|
|
34789
34920
|
const entryJson = JSON.stringify(entry);
|
|
34790
34921
|
mkdirSync5(summaryDir, { recursive: true });
|
|
34791
|
-
const tempPath =
|
|
34922
|
+
const tempPath = path16.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${randomBytes(16).toString("hex")}`);
|
|
34792
34923
|
try {
|
|
34793
34924
|
await Bun.write(tempPath, entryJson);
|
|
34794
34925
|
renameSync2(tempPath, summaryPath);
|
|
@@ -34801,7 +34932,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
34801
34932
|
}
|
|
34802
34933
|
async function loadFullOutput(directory, id) {
|
|
34803
34934
|
const sanitizedId = sanitizeSummaryId(id);
|
|
34804
|
-
const relativePath =
|
|
34935
|
+
const relativePath = path16.join("summaries", `${sanitizedId}.json`);
|
|
34805
34936
|
validateSwarmPath(directory, relativePath);
|
|
34806
34937
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
34807
34938
|
if (content === null) {
|
|
@@ -35275,6 +35406,7 @@ function createAgentActivityHooks(config3, directory) {
|
|
|
35275
35406
|
else
|
|
35276
35407
|
existing.failureCount++;
|
|
35277
35408
|
existing.totalDuration += duration5;
|
|
35409
|
+
enforceToolAggregateCapacity(key);
|
|
35278
35410
|
swarmState.toolAggregates.set(key, existing);
|
|
35279
35411
|
swarmState.pendingEvents++;
|
|
35280
35412
|
if (swarmState.pendingEvents >= 20) {
|
|
@@ -35305,8 +35437,8 @@ async function doFlush(directory) {
|
|
|
35305
35437
|
const activitySection = renderActivitySection();
|
|
35306
35438
|
const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
|
|
35307
35439
|
const flushedCount = swarmState.pendingEvents;
|
|
35308
|
-
const
|
|
35309
|
-
await Bun.write(
|
|
35440
|
+
const path17 = `${directory}/.swarm/context.md`;
|
|
35441
|
+
await Bun.write(path17, updated);
|
|
35310
35442
|
swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
|
|
35311
35443
|
} catch (error93) {
|
|
35312
35444
|
warn("Agent activity flush failed:", error93);
|
|
@@ -35578,6 +35710,11 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
|
35578
35710
|
to: agentName,
|
|
35579
35711
|
timestamp: now
|
|
35580
35712
|
};
|
|
35713
|
+
if (!swarmState.delegationChains.has(input.sessionID) && swarmState.delegationChains.size >= MAX_DELEGATION_CHAIN_SESSIONS) {
|
|
35714
|
+
const firstKey = swarmState.delegationChains.keys().next().value;
|
|
35715
|
+
if (firstKey !== undefined)
|
|
35716
|
+
swarmState.delegationChains.delete(firstKey);
|
|
35717
|
+
}
|
|
35581
35718
|
if (!swarmState.delegationChains.has(input.sessionID)) {
|
|
35582
35719
|
swarmState.delegationChains.set(input.sessionID, []);
|
|
35583
35720
|
}
|
|
@@ -35861,15 +35998,15 @@ ${originalText}`;
|
|
|
35861
35998
|
};
|
|
35862
35999
|
}
|
|
35863
36000
|
// src/hooks/system-enhancer.ts
|
|
35864
|
-
import * as
|
|
35865
|
-
import * as
|
|
36001
|
+
import * as fs11 from "fs";
|
|
36002
|
+
import * as path18 from "path";
|
|
35866
36003
|
init_manager2();
|
|
35867
36004
|
|
|
35868
36005
|
// src/services/decision-drift-analyzer.ts
|
|
35869
36006
|
init_utils2();
|
|
35870
36007
|
init_manager2();
|
|
35871
|
-
import * as
|
|
35872
|
-
import * as
|
|
36008
|
+
import * as fs10 from "fs";
|
|
36009
|
+
import * as path17 from "path";
|
|
35873
36010
|
var DEFAULT_DRIFT_CONFIG = {
|
|
35874
36011
|
staleThresholdPhases: 1,
|
|
35875
36012
|
detectContradictions: true,
|
|
@@ -36023,11 +36160,11 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
|
|
|
36023
36160
|
currentPhase = legacyPhase;
|
|
36024
36161
|
}
|
|
36025
36162
|
}
|
|
36026
|
-
const contextPath =
|
|
36163
|
+
const contextPath = path17.join(directory, ".swarm", "context.md");
|
|
36027
36164
|
let contextContent = "";
|
|
36028
36165
|
try {
|
|
36029
|
-
if (
|
|
36030
|
-
contextContent =
|
|
36166
|
+
if (fs10.existsSync(contextPath)) {
|
|
36167
|
+
contextContent = fs10.readFileSync(contextPath, "utf-8");
|
|
36031
36168
|
}
|
|
36032
36169
|
} catch {
|
|
36033
36170
|
return {
|
|
@@ -36206,6 +36343,265 @@ function estimateContentType(text) {
|
|
|
36206
36343
|
}
|
|
36207
36344
|
return "prose";
|
|
36208
36345
|
}
|
|
36346
|
+
async function loadPhaseTask(directory) {
|
|
36347
|
+
const plan = await loadPlan(directory);
|
|
36348
|
+
if (plan && plan.migration_status !== "migration_failed") {
|
|
36349
|
+
const phaseFromPlan = extractCurrentPhaseFromPlan(plan);
|
|
36350
|
+
const taskFromPlan = extractCurrentTaskFromPlan(plan);
|
|
36351
|
+
if (phaseFromPlan === null && taskFromPlan === null) {
|
|
36352
|
+
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
36353
|
+
if (planContent) {
|
|
36354
|
+
return {
|
|
36355
|
+
currentPhase: extractCurrentPhase(planContent),
|
|
36356
|
+
currentTask: extractCurrentTask(planContent)
|
|
36357
|
+
};
|
|
36358
|
+
}
|
|
36359
|
+
}
|
|
36360
|
+
return {
|
|
36361
|
+
currentPhase: phaseFromPlan,
|
|
36362
|
+
currentTask: taskFromPlan
|
|
36363
|
+
};
|
|
36364
|
+
} else {
|
|
36365
|
+
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
36366
|
+
if (planContent) {
|
|
36367
|
+
return {
|
|
36368
|
+
currentPhase: extractCurrentPhase(planContent),
|
|
36369
|
+
currentTask: extractCurrentTask(planContent)
|
|
36370
|
+
};
|
|
36371
|
+
}
|
|
36372
|
+
}
|
|
36373
|
+
return { currentPhase: null, currentTask: null };
|
|
36374
|
+
}
|
|
36375
|
+
async function loadContextDetails(directory, sessionId, config3) {
|
|
36376
|
+
const contextContent = await readSwarmFileAsync(directory, "context.md");
|
|
36377
|
+
const decisions = contextContent ? extractDecisions(contextContent, 200) : null;
|
|
36378
|
+
const activeAgentForSession = sessionId ? swarmState.activeAgent.get(sessionId) : undefined;
|
|
36379
|
+
const isArchitect = !activeAgentForSession || stripKnownSwarmPrefix(activeAgentForSession) === "architect";
|
|
36380
|
+
let agentContext = null;
|
|
36381
|
+
if (config3.hooks?.agent_activity !== false && sessionId && contextContent && activeAgentForSession) {
|
|
36382
|
+
const extracted = extractAgentContext(contextContent, activeAgentForSession, config3.hooks?.agent_awareness_max_chars ?? 300);
|
|
36383
|
+
if (extracted) {
|
|
36384
|
+
agentContext = `[SWARM AGENT CONTEXT] ${extracted}`;
|
|
36385
|
+
}
|
|
36386
|
+
}
|
|
36387
|
+
return { contextContent, decisions, agentContext, isArchitect };
|
|
36388
|
+
}
|
|
36389
|
+
function getConfigHints(config3) {
|
|
36390
|
+
const hints = [];
|
|
36391
|
+
if (config3.review_passes?.always_security_review) {
|
|
36392
|
+
hints.push("[SWARM CONFIG] Security review pass is MANDATORY for ALL tasks. Skip file-pattern check \u2014 always run security-only reviewer pass after general review APPROVED.");
|
|
36393
|
+
}
|
|
36394
|
+
if (config3.integration_analysis?.enabled === false) {
|
|
36395
|
+
hints.push("[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.");
|
|
36396
|
+
}
|
|
36397
|
+
if (config3.ui_review?.enabled) {
|
|
36398
|
+
hints.push("[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).");
|
|
36399
|
+
}
|
|
36400
|
+
if (config3.docs?.enabled === false) {
|
|
36401
|
+
hints.push("[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.");
|
|
36402
|
+
}
|
|
36403
|
+
if (config3.lint?.enabled === false) {
|
|
36404
|
+
hints.push("[SWARM CONFIG] Lint gate is DISABLED. Skip lint check/fix in QA sequence.");
|
|
36405
|
+
}
|
|
36406
|
+
if (config3.secretscan?.enabled === false) {
|
|
36407
|
+
hints.push("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
|
|
36408
|
+
}
|
|
36409
|
+
return hints;
|
|
36410
|
+
}
|
|
36411
|
+
async function getRetrospectiveHint(directory, isArchitect) {
|
|
36412
|
+
if (!isArchitect)
|
|
36413
|
+
return null;
|
|
36414
|
+
try {
|
|
36415
|
+
const evidenceDir = path18.join(directory, ".swarm", "evidence");
|
|
36416
|
+
if (!fs11.existsSync(evidenceDir))
|
|
36417
|
+
return null;
|
|
36418
|
+
const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36419
|
+
for (const file3 of files.slice(0, 5)) {
|
|
36420
|
+
let content;
|
|
36421
|
+
try {
|
|
36422
|
+
content = JSON.parse(fs11.readFileSync(path18.join(evidenceDir, file3), "utf-8"));
|
|
36423
|
+
} catch {
|
|
36424
|
+
continue;
|
|
36425
|
+
}
|
|
36426
|
+
if (content !== null && typeof content === "object" && content.type === "retrospective") {
|
|
36427
|
+
const retro = content;
|
|
36428
|
+
const hints = [];
|
|
36429
|
+
if (retro.reviewer_rejections > 2) {
|
|
36430
|
+
hints.push(`Phase ${retro.phase_number} had ${retro.reviewer_rejections} reviewer rejections.`);
|
|
36431
|
+
}
|
|
36432
|
+
if (retro.top_rejection_reasons.length > 0) {
|
|
36433
|
+
hints.push(`Common rejection reasons: ${retro.top_rejection_reasons.join(", ")}.`);
|
|
36434
|
+
}
|
|
36435
|
+
if (retro.lessons_learned.length > 0) {
|
|
36436
|
+
hints.push(`Lessons: ${retro.lessons_learned.join("; ")}.`);
|
|
36437
|
+
}
|
|
36438
|
+
if (hints.length > 0) {
|
|
36439
|
+
const retroHint = `[SWARM RETROSPECTIVE] From Phase ${retro.phase_number}: ${hints.join(" ")}`;
|
|
36440
|
+
const retroText = retroHint.length <= 800 ? retroHint : retroHint.substring(0, 800) + "...";
|
|
36441
|
+
return retroText;
|
|
36442
|
+
}
|
|
36443
|
+
break;
|
|
36444
|
+
}
|
|
36445
|
+
}
|
|
36446
|
+
} catch {}
|
|
36447
|
+
return null;
|
|
36448
|
+
}
|
|
36449
|
+
function getCompactionHint(sessionId, config3, isArchitect) {
|
|
36450
|
+
if (!isArchitect)
|
|
36451
|
+
return null;
|
|
36452
|
+
const compactionConfig = config3.compaction_advisory;
|
|
36453
|
+
if (compactionConfig?.enabled === false)
|
|
36454
|
+
return null;
|
|
36455
|
+
if (!sessionId)
|
|
36456
|
+
return null;
|
|
36457
|
+
const session = swarmState.agentSessions.get(sessionId);
|
|
36458
|
+
if (!session)
|
|
36459
|
+
return null;
|
|
36460
|
+
const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
|
|
36461
|
+
const thresholds = compactionConfig?.thresholds ?? [50, 75, 100, 125, 150];
|
|
36462
|
+
const lastHint = session.lastCompactionHint || 0;
|
|
36463
|
+
for (const threshold of thresholds) {
|
|
36464
|
+
if (totalToolCalls >= threshold && lastHint < threshold) {
|
|
36465
|
+
const messageTemplate = compactionConfig?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
|
|
36466
|
+
const message = messageTemplate.replace("${totalToolCalls}", String(totalToolCalls));
|
|
36467
|
+
session.lastCompactionHint = threshold;
|
|
36468
|
+
return message;
|
|
36469
|
+
}
|
|
36470
|
+
}
|
|
36471
|
+
return null;
|
|
36472
|
+
}
|
|
36473
|
+
async function getDecisionDriftHint(sessionId, config3, directory, isArchitect) {
|
|
36474
|
+
if (!isArchitect)
|
|
36475
|
+
return null;
|
|
36476
|
+
const automationCapabilities = config3.automation?.capabilities;
|
|
36477
|
+
if (automationCapabilities?.decision_drift_detection !== true)
|
|
36478
|
+
return null;
|
|
36479
|
+
if (!sessionId)
|
|
36480
|
+
return null;
|
|
36481
|
+
try {
|
|
36482
|
+
const driftResult = await analyzeDecisionDrift(directory);
|
|
36483
|
+
if (driftResult.hasDrift) {
|
|
36484
|
+
const driftText = formatDriftForContext(driftResult);
|
|
36485
|
+
if (driftText)
|
|
36486
|
+
return driftText;
|
|
36487
|
+
}
|
|
36488
|
+
} catch {}
|
|
36489
|
+
return null;
|
|
36490
|
+
}
|
|
36491
|
+
async function loadSwarmArtifacts(directory, sessionId, config3) {
|
|
36492
|
+
const [phaseTask, contextDetails] = await Promise.all([
|
|
36493
|
+
loadPhaseTask(directory),
|
|
36494
|
+
loadContextDetails(directory, sessionId, config3)
|
|
36495
|
+
]);
|
|
36496
|
+
return {
|
|
36497
|
+
phaseTask,
|
|
36498
|
+
contextDetails,
|
|
36499
|
+
scoringEnabled: config3.context_budget?.scoring?.enabled === true,
|
|
36500
|
+
maxInjectionTokens: config3.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY
|
|
36501
|
+
};
|
|
36502
|
+
}
|
|
36503
|
+
async function buildInjectionCandidates(artifacts, config3, sessionId, directory) {
|
|
36504
|
+
const { phaseTask, contextDetails } = artifacts;
|
|
36505
|
+
const candidates = [];
|
|
36506
|
+
let idCounter = 0;
|
|
36507
|
+
const addCandidate = (kind, text, priority, contentType = "prose", metadata) => {
|
|
36508
|
+
candidates.push({
|
|
36509
|
+
id: `candidate-${idCounter++}`,
|
|
36510
|
+
kind,
|
|
36511
|
+
text,
|
|
36512
|
+
tokens: estimateTokens(text),
|
|
36513
|
+
priority,
|
|
36514
|
+
metadata: { contentType, ...metadata }
|
|
36515
|
+
});
|
|
36516
|
+
};
|
|
36517
|
+
if (phaseTask.currentPhase) {
|
|
36518
|
+
addCandidate("phase", `[SWARM CONTEXT] Current phase: ${phaseTask.currentPhase}`, 1, estimateContentType(phaseTask.currentPhase));
|
|
36519
|
+
}
|
|
36520
|
+
if (phaseTask.currentTask) {
|
|
36521
|
+
addCandidate("task", `[SWARM CONTEXT] Current task: ${phaseTask.currentTask}`, 2, estimateContentType(phaseTask.currentTask), { isCurrentTask: true });
|
|
36522
|
+
}
|
|
36523
|
+
if (contextDetails.decisions) {
|
|
36524
|
+
addCandidate("decision", `[SWARM CONTEXT] Key decisions: ${contextDetails.decisions}`, 3, estimateContentType(contextDetails.decisions));
|
|
36525
|
+
}
|
|
36526
|
+
if (contextDetails.agentContext) {
|
|
36527
|
+
addCandidate("agent_context", contextDetails.agentContext, 4, estimateContentType(contextDetails.agentContext));
|
|
36528
|
+
}
|
|
36529
|
+
for (const hint of getConfigHints(config3)) {
|
|
36530
|
+
addCandidate("phase", hint, 1, "prose");
|
|
36531
|
+
}
|
|
36532
|
+
addCandidate("phase", "[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.", 2, "prose");
|
|
36533
|
+
if (contextDetails.isArchitect) {
|
|
36534
|
+
const retroText = await getRetrospectiveHint(directory, contextDetails.isArchitect);
|
|
36535
|
+
if (retroText) {
|
|
36536
|
+
addCandidate("phase", retroText, 2, "prose");
|
|
36537
|
+
}
|
|
36538
|
+
const compactionMessage = getCompactionHint(sessionId, config3, contextDetails.isArchitect);
|
|
36539
|
+
if (compactionMessage) {
|
|
36540
|
+
addCandidate("phase", compactionMessage, 1, "prose");
|
|
36541
|
+
}
|
|
36542
|
+
const driftText = await getDecisionDriftHint(sessionId, config3, directory, contextDetails.isArchitect);
|
|
36543
|
+
if (driftText) {
|
|
36544
|
+
addCandidate("phase", driftText, 2, "prose");
|
|
36545
|
+
}
|
|
36546
|
+
}
|
|
36547
|
+
const userScoringConfig = config3.context_budget?.scoring;
|
|
36548
|
+
const effectiveConfig = userScoringConfig?.weights ? {
|
|
36549
|
+
...DEFAULT_SCORING_CONFIG,
|
|
36550
|
+
...userScoringConfig,
|
|
36551
|
+
weights: userScoringConfig.weights
|
|
36552
|
+
} : DEFAULT_SCORING_CONFIG;
|
|
36553
|
+
return { candidates, effectiveConfig };
|
|
36554
|
+
}
|
|
36555
|
+
async function applyLegacyInjection(artifacts, config3, directory, sessionId, maxInjectionTokens, output) {
|
|
36556
|
+
const { phaseTask, contextDetails } = artifacts;
|
|
36557
|
+
let injectedTokens = 0;
|
|
36558
|
+
const tryInject = (text) => {
|
|
36559
|
+
const tokens = estimateTokens(text);
|
|
36560
|
+
if (injectedTokens + tokens <= maxInjectionTokens) {
|
|
36561
|
+
output.system.push(text);
|
|
36562
|
+
injectedTokens += tokens;
|
|
36563
|
+
}
|
|
36564
|
+
};
|
|
36565
|
+
if (phaseTask.currentPhase) {
|
|
36566
|
+
tryInject(`[SWARM CONTEXT] Current phase: ${phaseTask.currentPhase}`);
|
|
36567
|
+
}
|
|
36568
|
+
if (phaseTask.currentTask) {
|
|
36569
|
+
tryInject(`[SWARM CONTEXT] Current task: ${phaseTask.currentTask}`);
|
|
36570
|
+
}
|
|
36571
|
+
if (contextDetails.decisions) {
|
|
36572
|
+
tryInject(`[SWARM CONTEXT] Key decisions: ${contextDetails.decisions}`);
|
|
36573
|
+
}
|
|
36574
|
+
if (contextDetails.agentContext) {
|
|
36575
|
+
tryInject(contextDetails.agentContext);
|
|
36576
|
+
}
|
|
36577
|
+
tryInject("[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.");
|
|
36578
|
+
for (const hint of getConfigHints(config3)) {
|
|
36579
|
+
tryInject(hint);
|
|
36580
|
+
}
|
|
36581
|
+
if (contextDetails.isArchitect) {
|
|
36582
|
+
const retroText = await getRetrospectiveHint(directory, true);
|
|
36583
|
+
if (retroText)
|
|
36584
|
+
tryInject(retroText);
|
|
36585
|
+
const compactionMessage = getCompactionHint(sessionId, config3, contextDetails.isArchitect);
|
|
36586
|
+
if (compactionMessage)
|
|
36587
|
+
tryInject(compactionMessage);
|
|
36588
|
+
const driftText = await getDecisionDriftHint(sessionId, config3, directory, contextDetails.isArchitect);
|
|
36589
|
+
if (driftText)
|
|
36590
|
+
tryInject(driftText);
|
|
36591
|
+
}
|
|
36592
|
+
}
|
|
36593
|
+
async function applyScoringInjection(artifacts, config3, directory, sessionId, maxInjectionTokens, output) {
|
|
36594
|
+
const { candidates, effectiveConfig } = await buildInjectionCandidates(artifacts, config3, sessionId, directory);
|
|
36595
|
+
const ranked = rankCandidates(candidates, effectiveConfig);
|
|
36596
|
+
let injectedTokens = 0;
|
|
36597
|
+
for (const candidate of ranked) {
|
|
36598
|
+
if (injectedTokens + candidate.tokens > maxInjectionTokens) {
|
|
36599
|
+
continue;
|
|
36600
|
+
}
|
|
36601
|
+
output.system.push(candidate.text);
|
|
36602
|
+
injectedTokens += candidate.tokens;
|
|
36603
|
+
}
|
|
36604
|
+
}
|
|
36209
36605
|
function createSystemEnhancerHook(config3, directory) {
|
|
36210
36606
|
const enabled = config3.hooks?.system_enhancer !== false;
|
|
36211
36607
|
if (!enabled) {
|
|
@@ -36214,408 +36610,12 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36214
36610
|
return {
|
|
36215
36611
|
"experimental.chat.system.transform": safeHook(async (_input, output) => {
|
|
36216
36612
|
try {
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
}
|
|
36222
|
-
output.system.push(text);
|
|
36223
|
-
injectedTokens += tokens;
|
|
36224
|
-
};
|
|
36225
|
-
const maxInjectionTokens = config3.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY;
|
|
36226
|
-
let injectedTokens = 0;
|
|
36227
|
-
const contextContent = await readSwarmFileAsync(directory, "context.md");
|
|
36228
|
-
const scoringEnabled = config3.context_budget?.scoring?.enabled === true;
|
|
36229
|
-
if (!scoringEnabled) {
|
|
36230
|
-
const plan2 = await loadPlan(directory);
|
|
36231
|
-
if (plan2 && plan2.migration_status !== "migration_failed") {
|
|
36232
|
-
const currentPhase2 = extractCurrentPhaseFromPlan(plan2);
|
|
36233
|
-
if (currentPhase2) {
|
|
36234
|
-
tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
|
|
36235
|
-
}
|
|
36236
|
-
const currentTask2 = extractCurrentTaskFromPlan(plan2);
|
|
36237
|
-
if (currentTask2) {
|
|
36238
|
-
tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
|
|
36239
|
-
}
|
|
36240
|
-
} else {
|
|
36241
|
-
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
36242
|
-
if (planContent) {
|
|
36243
|
-
const currentPhase2 = extractCurrentPhase(planContent);
|
|
36244
|
-
if (currentPhase2) {
|
|
36245
|
-
tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
|
|
36246
|
-
}
|
|
36247
|
-
const currentTask2 = extractCurrentTask(planContent);
|
|
36248
|
-
if (currentTask2) {
|
|
36249
|
-
tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
|
|
36250
|
-
}
|
|
36251
|
-
}
|
|
36252
|
-
}
|
|
36253
|
-
if (contextContent) {
|
|
36254
|
-
const decisions = extractDecisions(contextContent, 200);
|
|
36255
|
-
if (decisions) {
|
|
36256
|
-
tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
|
|
36257
|
-
}
|
|
36258
|
-
if (config3.hooks?.agent_activity !== false && _input.sessionID) {
|
|
36259
|
-
const activeAgent = swarmState.activeAgent.get(_input.sessionID);
|
|
36260
|
-
if (activeAgent) {
|
|
36261
|
-
const agentContext = extractAgentContext(contextContent, activeAgent, config3.hooks?.agent_awareness_max_chars ?? 300);
|
|
36262
|
-
if (agentContext) {
|
|
36263
|
-
tryInject(`[SWARM AGENT CONTEXT] ${agentContext}`);
|
|
36264
|
-
}
|
|
36265
|
-
}
|
|
36266
|
-
}
|
|
36267
|
-
}
|
|
36268
|
-
tryInject("[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.");
|
|
36269
|
-
if (config3.review_passes?.always_security_review) {
|
|
36270
|
-
tryInject("[SWARM CONFIG] Security review pass is MANDATORY for ALL tasks. Skip file-pattern check \u2014 always run security-only reviewer pass after general review APPROVED.");
|
|
36271
|
-
}
|
|
36272
|
-
if (config3.integration_analysis?.enabled === false) {
|
|
36273
|
-
tryInject("[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.");
|
|
36274
|
-
}
|
|
36275
|
-
if (config3.ui_review?.enabled) {
|
|
36276
|
-
tryInject("[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).");
|
|
36277
|
-
}
|
|
36278
|
-
if (config3.docs?.enabled === false) {
|
|
36279
|
-
tryInject("[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.");
|
|
36280
|
-
}
|
|
36281
|
-
if (config3.lint?.enabled === false) {
|
|
36282
|
-
tryInject("[SWARM CONFIG] Lint gate is DISABLED. Skip lint check/fix in QA sequence.");
|
|
36283
|
-
}
|
|
36284
|
-
if (config3.secretscan?.enabled === false) {
|
|
36285
|
-
tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
|
|
36286
|
-
}
|
|
36287
|
-
const sessionId_retro = _input.sessionID;
|
|
36288
|
-
const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
|
|
36289
|
-
const isArchitect = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
|
|
36290
|
-
if (isArchitect) {
|
|
36291
|
-
try {
|
|
36292
|
-
const evidenceDir = path15.join(directory, ".swarm", "evidence");
|
|
36293
|
-
if (fs10.existsSync(evidenceDir)) {
|
|
36294
|
-
const files = fs10.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36295
|
-
for (const file3 of files.slice(0, 5)) {
|
|
36296
|
-
let content;
|
|
36297
|
-
try {
|
|
36298
|
-
content = JSON.parse(fs10.readFileSync(path15.join(evidenceDir, file3), "utf-8"));
|
|
36299
|
-
} catch {
|
|
36300
|
-
continue;
|
|
36301
|
-
}
|
|
36302
|
-
if (content !== null && typeof content === "object" && content.type === "retrospective") {
|
|
36303
|
-
const retro = content;
|
|
36304
|
-
const hints = [];
|
|
36305
|
-
if (retro.reviewer_rejections > 2) {
|
|
36306
|
-
hints.push(`Phase ${retro.phase_number} had ${retro.reviewer_rejections} reviewer rejections.`);
|
|
36307
|
-
}
|
|
36308
|
-
if (retro.top_rejection_reasons.length > 0) {
|
|
36309
|
-
hints.push(`Common rejection reasons: ${retro.top_rejection_reasons.join(", ")}.`);
|
|
36310
|
-
}
|
|
36311
|
-
if (retro.lessons_learned.length > 0) {
|
|
36312
|
-
hints.push(`Lessons: ${retro.lessons_learned.join("; ")}.`);
|
|
36313
|
-
}
|
|
36314
|
-
if (hints.length > 0) {
|
|
36315
|
-
const retroHint = `[SWARM RETROSPECTIVE] From Phase ${retro.phase_number}: ${hints.join(" ")}`;
|
|
36316
|
-
if (retroHint.length <= 800) {
|
|
36317
|
-
tryInject(retroHint);
|
|
36318
|
-
} else {
|
|
36319
|
-
tryInject(retroHint.substring(0, 800) + "...");
|
|
36320
|
-
}
|
|
36321
|
-
}
|
|
36322
|
-
break;
|
|
36323
|
-
}
|
|
36324
|
-
}
|
|
36325
|
-
}
|
|
36326
|
-
} catch {}
|
|
36327
|
-
const compactionConfig = config3.compaction_advisory;
|
|
36328
|
-
if (compactionConfig?.enabled !== false && sessionId_retro) {
|
|
36329
|
-
const session = swarmState.agentSessions.get(sessionId_retro);
|
|
36330
|
-
if (session) {
|
|
36331
|
-
const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
|
|
36332
|
-
const thresholds = compactionConfig?.thresholds ?? [
|
|
36333
|
-
50,
|
|
36334
|
-
75,
|
|
36335
|
-
100,
|
|
36336
|
-
125,
|
|
36337
|
-
150
|
|
36338
|
-
];
|
|
36339
|
-
const lastHint = session.lastCompactionHint || 0;
|
|
36340
|
-
for (const threshold of thresholds) {
|
|
36341
|
-
if (totalToolCalls >= threshold && lastHint < threshold) {
|
|
36342
|
-
const messageTemplate = compactionConfig?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
|
|
36343
|
-
const message = messageTemplate.replace("${totalToolCalls}", String(totalToolCalls));
|
|
36344
|
-
tryInject(message);
|
|
36345
|
-
session.lastCompactionHint = threshold;
|
|
36346
|
-
break;
|
|
36347
|
-
}
|
|
36348
|
-
}
|
|
36349
|
-
}
|
|
36350
|
-
}
|
|
36351
|
-
}
|
|
36352
|
-
const automationCapabilities = config3.automation?.capabilities;
|
|
36353
|
-
if (automationCapabilities?.decision_drift_detection === true && _input.sessionID) {
|
|
36354
|
-
const activeAgentForDrift = swarmState.activeAgent.get(_input.sessionID);
|
|
36355
|
-
const isArchitectForDrift = !activeAgentForDrift || stripKnownSwarmPrefix(activeAgentForDrift) === "architect";
|
|
36356
|
-
if (isArchitectForDrift) {
|
|
36357
|
-
try {
|
|
36358
|
-
const driftResult = await analyzeDecisionDrift(directory);
|
|
36359
|
-
if (driftResult.hasDrift) {
|
|
36360
|
-
const driftText = formatDriftForContext(driftResult);
|
|
36361
|
-
if (driftText) {
|
|
36362
|
-
tryInject(driftText);
|
|
36363
|
-
}
|
|
36364
|
-
}
|
|
36365
|
-
} catch {}
|
|
36366
|
-
}
|
|
36367
|
-
}
|
|
36368
|
-
return;
|
|
36369
|
-
}
|
|
36370
|
-
const userScoringConfig = config3.context_budget?.scoring;
|
|
36371
|
-
const candidates = [];
|
|
36372
|
-
let idCounter = 0;
|
|
36373
|
-
const effectiveConfig = userScoringConfig?.weights ? {
|
|
36374
|
-
...DEFAULT_SCORING_CONFIG,
|
|
36375
|
-
...userScoringConfig,
|
|
36376
|
-
weights: userScoringConfig.weights
|
|
36377
|
-
} : DEFAULT_SCORING_CONFIG;
|
|
36378
|
-
const plan = await loadPlan(directory);
|
|
36379
|
-
let currentPhase = null;
|
|
36380
|
-
let currentTask = null;
|
|
36381
|
-
if (plan && plan.migration_status !== "migration_failed") {
|
|
36382
|
-
currentPhase = extractCurrentPhaseFromPlan(plan);
|
|
36383
|
-
currentTask = extractCurrentTaskFromPlan(plan);
|
|
36613
|
+
const artifacts = await loadSwarmArtifacts(directory, _input.sessionID, config3);
|
|
36614
|
+
const { scoringEnabled, maxInjectionTokens } = artifacts;
|
|
36615
|
+
if (scoringEnabled) {
|
|
36616
|
+
await applyScoringInjection(artifacts, config3, directory, _input.sessionID, maxInjectionTokens, output);
|
|
36384
36617
|
} else {
|
|
36385
|
-
|
|
36386
|
-
if (planContent) {
|
|
36387
|
-
currentPhase = extractCurrentPhase(planContent);
|
|
36388
|
-
currentTask = extractCurrentTask(planContent);
|
|
36389
|
-
}
|
|
36390
|
-
}
|
|
36391
|
-
if (currentPhase) {
|
|
36392
|
-
const text = `[SWARM CONTEXT] Current phase: ${currentPhase}`;
|
|
36393
|
-
candidates.push({
|
|
36394
|
-
id: `candidate-${idCounter++}`,
|
|
36395
|
-
kind: "phase",
|
|
36396
|
-
text,
|
|
36397
|
-
tokens: estimateTokens(text),
|
|
36398
|
-
priority: 1,
|
|
36399
|
-
metadata: { contentType: estimateContentType(text) }
|
|
36400
|
-
});
|
|
36401
|
-
}
|
|
36402
|
-
if (currentTask) {
|
|
36403
|
-
const text = `[SWARM CONTEXT] Current task: ${currentTask}`;
|
|
36404
|
-
candidates.push({
|
|
36405
|
-
id: `candidate-${idCounter++}`,
|
|
36406
|
-
kind: "task",
|
|
36407
|
-
text,
|
|
36408
|
-
tokens: estimateTokens(text),
|
|
36409
|
-
priority: 2,
|
|
36410
|
-
metadata: {
|
|
36411
|
-
contentType: estimateContentType(text),
|
|
36412
|
-
isCurrentTask: true
|
|
36413
|
-
}
|
|
36414
|
-
});
|
|
36415
|
-
}
|
|
36416
|
-
if (contextContent) {
|
|
36417
|
-
const decisions = extractDecisions(contextContent, 200);
|
|
36418
|
-
if (decisions) {
|
|
36419
|
-
const text = `[SWARM CONTEXT] Key decisions: ${decisions}`;
|
|
36420
|
-
candidates.push({
|
|
36421
|
-
id: `candidate-${idCounter++}`,
|
|
36422
|
-
kind: "decision",
|
|
36423
|
-
text,
|
|
36424
|
-
tokens: estimateTokens(text),
|
|
36425
|
-
priority: 3,
|
|
36426
|
-
metadata: { contentType: estimateContentType(text) }
|
|
36427
|
-
});
|
|
36428
|
-
}
|
|
36429
|
-
if (config3.hooks?.agent_activity !== false && _input.sessionID) {
|
|
36430
|
-
const activeAgent = swarmState.activeAgent.get(_input.sessionID);
|
|
36431
|
-
if (activeAgent) {
|
|
36432
|
-
const agentContext = extractAgentContext(contextContent, activeAgent, config3.hooks?.agent_awareness_max_chars ?? 300);
|
|
36433
|
-
if (agentContext) {
|
|
36434
|
-
const text = `[SWARM AGENT CONTEXT] ${agentContext}`;
|
|
36435
|
-
candidates.push({
|
|
36436
|
-
id: `candidate-${idCounter++}`,
|
|
36437
|
-
kind: "agent_context",
|
|
36438
|
-
text,
|
|
36439
|
-
tokens: estimateTokens(text),
|
|
36440
|
-
priority: 4,
|
|
36441
|
-
metadata: { contentType: estimateContentType(text) }
|
|
36442
|
-
});
|
|
36443
|
-
}
|
|
36444
|
-
}
|
|
36445
|
-
}
|
|
36446
|
-
}
|
|
36447
|
-
if (config3.review_passes?.always_security_review) {
|
|
36448
|
-
const text = "[SWARM CONFIG] Security review pass is MANDATORY for ALL tasks. Skip file-pattern check \u2014 always run security-only reviewer pass after general review APPROVED.";
|
|
36449
|
-
candidates.push({
|
|
36450
|
-
id: `candidate-${idCounter++}`,
|
|
36451
|
-
kind: "phase",
|
|
36452
|
-
text,
|
|
36453
|
-
tokens: estimateTokens(text),
|
|
36454
|
-
priority: 1,
|
|
36455
|
-
metadata: { contentType: "prose" }
|
|
36456
|
-
});
|
|
36457
|
-
}
|
|
36458
|
-
if (config3.integration_analysis?.enabled === false) {
|
|
36459
|
-
const text = "[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.";
|
|
36460
|
-
candidates.push({
|
|
36461
|
-
id: `candidate-${idCounter++}`,
|
|
36462
|
-
kind: "phase",
|
|
36463
|
-
text,
|
|
36464
|
-
tokens: estimateTokens(text),
|
|
36465
|
-
priority: 1,
|
|
36466
|
-
metadata: { contentType: "prose" }
|
|
36467
|
-
});
|
|
36468
|
-
}
|
|
36469
|
-
if (config3.ui_review?.enabled) {
|
|
36470
|
-
const text = "[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).";
|
|
36471
|
-
candidates.push({
|
|
36472
|
-
id: `candidate-${idCounter++}`,
|
|
36473
|
-
kind: "phase",
|
|
36474
|
-
text,
|
|
36475
|
-
tokens: estimateTokens(text),
|
|
36476
|
-
priority: 1,
|
|
36477
|
-
metadata: { contentType: "prose" }
|
|
36478
|
-
});
|
|
36479
|
-
}
|
|
36480
|
-
if (config3.docs?.enabled === false) {
|
|
36481
|
-
const text = "[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.";
|
|
36482
|
-
candidates.push({
|
|
36483
|
-
id: `candidate-${idCounter++}`,
|
|
36484
|
-
kind: "phase",
|
|
36485
|
-
text,
|
|
36486
|
-
tokens: estimateTokens(text),
|
|
36487
|
-
priority: 1,
|
|
36488
|
-
metadata: { contentType: "prose" }
|
|
36489
|
-
});
|
|
36490
|
-
}
|
|
36491
|
-
if (config3.lint?.enabled === false) {
|
|
36492
|
-
const text = "[SWARM CONFIG] Lint gate is DISABLED. Skip lint check/fix in QA sequence.";
|
|
36493
|
-
candidates.push({
|
|
36494
|
-
id: `candidate-${idCounter++}`,
|
|
36495
|
-
kind: "phase",
|
|
36496
|
-
text,
|
|
36497
|
-
tokens: estimateTokens(text),
|
|
36498
|
-
priority: 1,
|
|
36499
|
-
metadata: { contentType: "prose" }
|
|
36500
|
-
});
|
|
36501
|
-
}
|
|
36502
|
-
if (config3.secretscan?.enabled === false) {
|
|
36503
|
-
const text = "[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.";
|
|
36504
|
-
candidates.push({
|
|
36505
|
-
id: `candidate-${idCounter++}`,
|
|
36506
|
-
kind: "phase",
|
|
36507
|
-
text,
|
|
36508
|
-
tokens: estimateTokens(text),
|
|
36509
|
-
priority: 1,
|
|
36510
|
-
metadata: { contentType: "prose" }
|
|
36511
|
-
});
|
|
36512
|
-
}
|
|
36513
|
-
const sessionId_retro_b = _input.sessionID;
|
|
36514
|
-
const activeAgent_retro_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
|
|
36515
|
-
const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
|
|
36516
|
-
if (isArchitect_b) {
|
|
36517
|
-
try {
|
|
36518
|
-
const evidenceDir_b = path15.join(directory, ".swarm", "evidence");
|
|
36519
|
-
if (fs10.existsSync(evidenceDir_b)) {
|
|
36520
|
-
const files_b = fs10.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36521
|
-
for (const file3 of files_b.slice(0, 5)) {
|
|
36522
|
-
let content_b;
|
|
36523
|
-
try {
|
|
36524
|
-
content_b = JSON.parse(fs10.readFileSync(path15.join(evidenceDir_b, file3), "utf-8"));
|
|
36525
|
-
} catch {
|
|
36526
|
-
continue;
|
|
36527
|
-
}
|
|
36528
|
-
if (content_b !== null && typeof content_b === "object" && content_b.type === "retrospective") {
|
|
36529
|
-
const retro_b = content_b;
|
|
36530
|
-
const hints_b = [];
|
|
36531
|
-
if (retro_b.reviewer_rejections > 2) {
|
|
36532
|
-
hints_b.push(`Phase ${retro_b.phase_number} had ${retro_b.reviewer_rejections} reviewer rejections.`);
|
|
36533
|
-
}
|
|
36534
|
-
if (retro_b.top_rejection_reasons.length > 0) {
|
|
36535
|
-
hints_b.push(`Common rejection reasons: ${retro_b.top_rejection_reasons.join(", ")}.`);
|
|
36536
|
-
}
|
|
36537
|
-
if (retro_b.lessons_learned.length > 0) {
|
|
36538
|
-
hints_b.push(`Lessons: ${retro_b.lessons_learned.join("; ")}.`);
|
|
36539
|
-
}
|
|
36540
|
-
if (hints_b.length > 0) {
|
|
36541
|
-
const retroHint_b = `[SWARM RETROSPECTIVE] From Phase ${retro_b.phase_number}: ${hints_b.join(" ")}`;
|
|
36542
|
-
const retroText = retroHint_b.length <= 800 ? retroHint_b : retroHint_b.substring(0, 800) + "...";
|
|
36543
|
-
candidates.push({
|
|
36544
|
-
id: `candidate-${idCounter++}`,
|
|
36545
|
-
kind: "phase",
|
|
36546
|
-
text: retroText,
|
|
36547
|
-
tokens: estimateTokens(retroText),
|
|
36548
|
-
priority: 2,
|
|
36549
|
-
metadata: { contentType: "prose" }
|
|
36550
|
-
});
|
|
36551
|
-
}
|
|
36552
|
-
break;
|
|
36553
|
-
}
|
|
36554
|
-
}
|
|
36555
|
-
}
|
|
36556
|
-
} catch {}
|
|
36557
|
-
const compactionConfig_b = config3.compaction_advisory;
|
|
36558
|
-
if (compactionConfig_b?.enabled !== false && sessionId_retro_b) {
|
|
36559
|
-
const session_b = swarmState.agentSessions.get(sessionId_retro_b);
|
|
36560
|
-
if (session_b) {
|
|
36561
|
-
const totalToolCalls_b = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
|
|
36562
|
-
const thresholds_b = compactionConfig_b?.thresholds ?? [
|
|
36563
|
-
50,
|
|
36564
|
-
75,
|
|
36565
|
-
100,
|
|
36566
|
-
125,
|
|
36567
|
-
150
|
|
36568
|
-
];
|
|
36569
|
-
const lastHint_b = session_b.lastCompactionHint || 0;
|
|
36570
|
-
for (const threshold of thresholds_b) {
|
|
36571
|
-
if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
|
|
36572
|
-
const messageTemplate_b = compactionConfig_b?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
|
|
36573
|
-
const compactionText = messageTemplate_b.replace("${totalToolCalls}", String(totalToolCalls_b));
|
|
36574
|
-
candidates.push({
|
|
36575
|
-
id: `candidate-${idCounter++}`,
|
|
36576
|
-
kind: "phase",
|
|
36577
|
-
text: compactionText,
|
|
36578
|
-
tokens: estimateTokens(compactionText),
|
|
36579
|
-
priority: 1,
|
|
36580
|
-
metadata: { contentType: "prose" }
|
|
36581
|
-
});
|
|
36582
|
-
session_b.lastCompactionHint = threshold;
|
|
36583
|
-
break;
|
|
36584
|
-
}
|
|
36585
|
-
}
|
|
36586
|
-
}
|
|
36587
|
-
}
|
|
36588
|
-
}
|
|
36589
|
-
const automationCapabilities_b = config3.automation?.capabilities;
|
|
36590
|
-
if (automationCapabilities_b?.decision_drift_detection === true && sessionId_retro_b) {
|
|
36591
|
-
const activeAgentForDrift_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
|
|
36592
|
-
const isArchitectForDrift_b = !activeAgentForDrift_b || stripKnownSwarmPrefix(activeAgentForDrift_b) === "architect";
|
|
36593
|
-
if (isArchitectForDrift_b) {
|
|
36594
|
-
try {
|
|
36595
|
-
const driftResult_b = await analyzeDecisionDrift(directory);
|
|
36596
|
-
if (driftResult_b.hasDrift) {
|
|
36597
|
-
const driftText_b = formatDriftForContext(driftResult_b);
|
|
36598
|
-
if (driftText_b) {
|
|
36599
|
-
candidates.push({
|
|
36600
|
-
id: `candidate-${idCounter++}`,
|
|
36601
|
-
kind: "phase",
|
|
36602
|
-
text: driftText_b,
|
|
36603
|
-
tokens: estimateTokens(driftText_b),
|
|
36604
|
-
priority: 2,
|
|
36605
|
-
metadata: { contentType: "prose" }
|
|
36606
|
-
});
|
|
36607
|
-
}
|
|
36608
|
-
}
|
|
36609
|
-
} catch {}
|
|
36610
|
-
}
|
|
36611
|
-
}
|
|
36612
|
-
const ranked = rankCandidates(candidates, effectiveConfig);
|
|
36613
|
-
for (const candidate of ranked) {
|
|
36614
|
-
if (injectedTokens + candidate.tokens > maxInjectionTokens) {
|
|
36615
|
-
continue;
|
|
36616
|
-
}
|
|
36617
|
-
output.system.push(candidate.text);
|
|
36618
|
-
injectedTokens += candidate.tokens;
|
|
36618
|
+
await applyLegacyInjection(artifacts, config3, directory, _input.sessionID, maxInjectionTokens, output);
|
|
36619
36619
|
}
|
|
36620
36620
|
} catch (error93) {
|
|
36621
36621
|
warn("System enhancer failed:", error93);
|
|
@@ -36814,8 +36814,8 @@ init_config_doctor();
|
|
|
36814
36814
|
// src/tools/checkpoint.ts
|
|
36815
36815
|
init_tool();
|
|
36816
36816
|
import { spawnSync } from "child_process";
|
|
36817
|
-
import * as
|
|
36818
|
-
import * as
|
|
36817
|
+
import * as fs12 from "fs";
|
|
36818
|
+
import * as path19 from "path";
|
|
36819
36819
|
var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
|
|
36820
36820
|
var MAX_LABEL_LENGTH = 100;
|
|
36821
36821
|
var GIT_TIMEOUT_MS = 30000;
|
|
@@ -36866,13 +36866,13 @@ function validateLabel(label) {
|
|
|
36866
36866
|
return null;
|
|
36867
36867
|
}
|
|
36868
36868
|
function getCheckpointLogPath() {
|
|
36869
|
-
return
|
|
36869
|
+
return path19.join(process.cwd(), CHECKPOINT_LOG_PATH);
|
|
36870
36870
|
}
|
|
36871
36871
|
function readCheckpointLog() {
|
|
36872
36872
|
const logPath = getCheckpointLogPath();
|
|
36873
36873
|
try {
|
|
36874
|
-
if (
|
|
36875
|
-
const content =
|
|
36874
|
+
if (fs12.existsSync(logPath)) {
|
|
36875
|
+
const content = fs12.readFileSync(logPath, "utf-8");
|
|
36876
36876
|
const parsed = JSON.parse(content);
|
|
36877
36877
|
if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
|
|
36878
36878
|
return { version: 1, checkpoints: [] };
|
|
@@ -36884,13 +36884,13 @@ function readCheckpointLog() {
|
|
|
36884
36884
|
}
|
|
36885
36885
|
function writeCheckpointLog(log2) {
|
|
36886
36886
|
const logPath = getCheckpointLogPath();
|
|
36887
|
-
const dir =
|
|
36888
|
-
if (!
|
|
36889
|
-
|
|
36887
|
+
const dir = path19.dirname(logPath);
|
|
36888
|
+
if (!fs12.existsSync(dir)) {
|
|
36889
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
36890
36890
|
}
|
|
36891
36891
|
const tempPath = `${logPath}.tmp`;
|
|
36892
|
-
|
|
36893
|
-
|
|
36892
|
+
fs12.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
|
|
36893
|
+
fs12.renameSync(tempPath, logPath);
|
|
36894
36894
|
}
|
|
36895
36895
|
function gitExec(args) {
|
|
36896
36896
|
const result = spawnSync("git", args, {
|
|
@@ -37090,8 +37090,8 @@ var checkpoint = tool({
|
|
|
37090
37090
|
});
|
|
37091
37091
|
// src/tools/complexity-hotspots.ts
|
|
37092
37092
|
init_dist();
|
|
37093
|
-
import * as
|
|
37094
|
-
import * as
|
|
37093
|
+
import * as fs13 from "fs";
|
|
37094
|
+
import * as path20 from "path";
|
|
37095
37095
|
var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
|
|
37096
37096
|
var DEFAULT_DAYS = 90;
|
|
37097
37097
|
var DEFAULT_TOP_N = 20;
|
|
@@ -37219,11 +37219,11 @@ function estimateComplexity(content) {
|
|
|
37219
37219
|
}
|
|
37220
37220
|
function getComplexityForFile(filePath) {
|
|
37221
37221
|
try {
|
|
37222
|
-
const stat =
|
|
37222
|
+
const stat = fs13.statSync(filePath);
|
|
37223
37223
|
if (stat.size > MAX_FILE_SIZE_BYTES2) {
|
|
37224
37224
|
return null;
|
|
37225
37225
|
}
|
|
37226
|
-
const content =
|
|
37226
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
37227
37227
|
return estimateComplexity(content);
|
|
37228
37228
|
} catch {
|
|
37229
37229
|
return null;
|
|
@@ -37234,7 +37234,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
37234
37234
|
const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
|
|
37235
37235
|
const filteredChurn = new Map;
|
|
37236
37236
|
for (const [file3, count] of churnMap) {
|
|
37237
|
-
const ext =
|
|
37237
|
+
const ext = path20.extname(file3).toLowerCase();
|
|
37238
37238
|
if (extSet.has(ext)) {
|
|
37239
37239
|
filteredChurn.set(file3, count);
|
|
37240
37240
|
}
|
|
@@ -37244,8 +37244,8 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
37244
37244
|
let analyzedFiles = 0;
|
|
37245
37245
|
for (const [file3, churnCount] of filteredChurn) {
|
|
37246
37246
|
let fullPath = file3;
|
|
37247
|
-
if (!
|
|
37248
|
-
fullPath =
|
|
37247
|
+
if (!fs13.existsSync(fullPath)) {
|
|
37248
|
+
fullPath = path20.join(cwd, file3);
|
|
37249
37249
|
}
|
|
37250
37250
|
const complexity = getComplexityForFile(fullPath);
|
|
37251
37251
|
if (complexity !== null) {
|
|
@@ -37403,14 +37403,14 @@ function validateBase(base) {
|
|
|
37403
37403
|
function validatePaths(paths) {
|
|
37404
37404
|
if (!paths)
|
|
37405
37405
|
return null;
|
|
37406
|
-
for (const
|
|
37407
|
-
if (!
|
|
37406
|
+
for (const path21 of paths) {
|
|
37407
|
+
if (!path21 || path21.length === 0) {
|
|
37408
37408
|
return "empty path not allowed";
|
|
37409
37409
|
}
|
|
37410
|
-
if (
|
|
37410
|
+
if (path21.length > MAX_PATH_LENGTH) {
|
|
37411
37411
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
37412
37412
|
}
|
|
37413
|
-
if (SHELL_METACHARACTERS2.test(
|
|
37413
|
+
if (SHELL_METACHARACTERS2.test(path21)) {
|
|
37414
37414
|
return "path contains shell metacharacters";
|
|
37415
37415
|
}
|
|
37416
37416
|
}
|
|
@@ -37473,8 +37473,8 @@ var diff = tool({
|
|
|
37473
37473
|
if (parts.length >= 3) {
|
|
37474
37474
|
const additions = parseInt(parts[0]) || 0;
|
|
37475
37475
|
const deletions = parseInt(parts[1]) || 0;
|
|
37476
|
-
const
|
|
37477
|
-
files.push({ path:
|
|
37476
|
+
const path21 = parts[2];
|
|
37477
|
+
files.push({ path: path21, additions, deletions });
|
|
37478
37478
|
}
|
|
37479
37479
|
}
|
|
37480
37480
|
const contractChanges = [];
|
|
@@ -37702,8 +37702,8 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
37702
37702
|
});
|
|
37703
37703
|
// src/tools/evidence-check.ts
|
|
37704
37704
|
init_dist();
|
|
37705
|
-
import * as
|
|
37706
|
-
import * as
|
|
37705
|
+
import * as fs14 from "fs";
|
|
37706
|
+
import * as path21 from "path";
|
|
37707
37707
|
var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
|
|
37708
37708
|
var MAX_EVIDENCE_FILES = 1000;
|
|
37709
37709
|
var EVIDENCE_DIR = ".swarm/evidence";
|
|
@@ -37726,9 +37726,9 @@ function validateRequiredTypes(input) {
|
|
|
37726
37726
|
return null;
|
|
37727
37727
|
}
|
|
37728
37728
|
function isPathWithinSwarm(filePath, cwd) {
|
|
37729
|
-
const normalizedCwd =
|
|
37730
|
-
const swarmPath =
|
|
37731
|
-
const normalizedPath =
|
|
37729
|
+
const normalizedCwd = path21.resolve(cwd);
|
|
37730
|
+
const swarmPath = path21.join(normalizedCwd, ".swarm");
|
|
37731
|
+
const normalizedPath = path21.resolve(filePath);
|
|
37732
37732
|
return normalizedPath.startsWith(swarmPath);
|
|
37733
37733
|
}
|
|
37734
37734
|
function parseCompletedTasks(planContent) {
|
|
@@ -37745,12 +37745,12 @@ function parseCompletedTasks(planContent) {
|
|
|
37745
37745
|
}
|
|
37746
37746
|
function readEvidenceFiles(evidenceDir, cwd) {
|
|
37747
37747
|
const evidence = [];
|
|
37748
|
-
if (!
|
|
37748
|
+
if (!fs14.existsSync(evidenceDir) || !fs14.statSync(evidenceDir).isDirectory()) {
|
|
37749
37749
|
return evidence;
|
|
37750
37750
|
}
|
|
37751
37751
|
let files;
|
|
37752
37752
|
try {
|
|
37753
|
-
files =
|
|
37753
|
+
files = fs14.readdirSync(evidenceDir);
|
|
37754
37754
|
} catch {
|
|
37755
37755
|
return evidence;
|
|
37756
37756
|
}
|
|
@@ -37759,14 +37759,14 @@ function readEvidenceFiles(evidenceDir, cwd) {
|
|
|
37759
37759
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
37760
37760
|
continue;
|
|
37761
37761
|
}
|
|
37762
|
-
const filePath =
|
|
37762
|
+
const filePath = path21.join(evidenceDir, filename);
|
|
37763
37763
|
try {
|
|
37764
|
-
const resolvedPath =
|
|
37765
|
-
const evidenceDirResolved =
|
|
37764
|
+
const resolvedPath = path21.resolve(filePath);
|
|
37765
|
+
const evidenceDirResolved = path21.resolve(evidenceDir);
|
|
37766
37766
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
37767
37767
|
continue;
|
|
37768
37768
|
}
|
|
37769
|
-
const stat =
|
|
37769
|
+
const stat = fs14.lstatSync(filePath);
|
|
37770
37770
|
if (!stat.isFile()) {
|
|
37771
37771
|
continue;
|
|
37772
37772
|
}
|
|
@@ -37775,7 +37775,7 @@ function readEvidenceFiles(evidenceDir, cwd) {
|
|
|
37775
37775
|
}
|
|
37776
37776
|
let fileStat;
|
|
37777
37777
|
try {
|
|
37778
|
-
fileStat =
|
|
37778
|
+
fileStat = fs14.statSync(filePath);
|
|
37779
37779
|
if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
|
|
37780
37780
|
continue;
|
|
37781
37781
|
}
|
|
@@ -37784,7 +37784,7 @@ function readEvidenceFiles(evidenceDir, cwd) {
|
|
|
37784
37784
|
}
|
|
37785
37785
|
let content;
|
|
37786
37786
|
try {
|
|
37787
|
-
content =
|
|
37787
|
+
content = fs14.readFileSync(filePath, "utf-8");
|
|
37788
37788
|
} catch {
|
|
37789
37789
|
continue;
|
|
37790
37790
|
}
|
|
@@ -37869,7 +37869,7 @@ var evidence_check = tool({
|
|
|
37869
37869
|
return JSON.stringify(errorResult, null, 2);
|
|
37870
37870
|
}
|
|
37871
37871
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
37872
|
-
const planPath =
|
|
37872
|
+
const planPath = path21.join(cwd, PLAN_FILE);
|
|
37873
37873
|
if (!isPathWithinSwarm(planPath, cwd)) {
|
|
37874
37874
|
const errorResult = {
|
|
37875
37875
|
error: "plan file path validation failed",
|
|
@@ -37883,7 +37883,7 @@ var evidence_check = tool({
|
|
|
37883
37883
|
}
|
|
37884
37884
|
let planContent;
|
|
37885
37885
|
try {
|
|
37886
|
-
planContent =
|
|
37886
|
+
planContent = fs14.readFileSync(planPath, "utf-8");
|
|
37887
37887
|
} catch {
|
|
37888
37888
|
const result2 = {
|
|
37889
37889
|
message: "No completed tasks found in plan.",
|
|
@@ -37901,7 +37901,7 @@ var evidence_check = tool({
|
|
|
37901
37901
|
};
|
|
37902
37902
|
return JSON.stringify(result2, null, 2);
|
|
37903
37903
|
}
|
|
37904
|
-
const evidenceDir =
|
|
37904
|
+
const evidenceDir = path21.join(cwd, EVIDENCE_DIR);
|
|
37905
37905
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
37906
37906
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
37907
37907
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -37917,8 +37917,8 @@ var evidence_check = tool({
|
|
|
37917
37917
|
});
|
|
37918
37918
|
// src/tools/file-extractor.ts
|
|
37919
37919
|
init_tool();
|
|
37920
|
-
import * as
|
|
37921
|
-
import * as
|
|
37920
|
+
import * as fs15 from "fs";
|
|
37921
|
+
import * as path22 from "path";
|
|
37922
37922
|
var EXT_MAP = {
|
|
37923
37923
|
python: ".py",
|
|
37924
37924
|
py: ".py",
|
|
@@ -37980,8 +37980,8 @@ var extract_code_blocks = tool({
|
|
|
37980
37980
|
execute: async (args) => {
|
|
37981
37981
|
const { content, output_dir, prefix } = args;
|
|
37982
37982
|
const targetDir = output_dir || process.cwd();
|
|
37983
|
-
if (!
|
|
37984
|
-
|
|
37983
|
+
if (!fs15.existsSync(targetDir)) {
|
|
37984
|
+
fs15.mkdirSync(targetDir, { recursive: true });
|
|
37985
37985
|
}
|
|
37986
37986
|
const pattern = /```(\w*)\n([\s\S]*?)```/g;
|
|
37987
37987
|
const matches = [...content.matchAll(pattern)];
|
|
@@ -37996,16 +37996,16 @@ var extract_code_blocks = tool({
|
|
|
37996
37996
|
if (prefix) {
|
|
37997
37997
|
filename = `${prefix}_${filename}`;
|
|
37998
37998
|
}
|
|
37999
|
-
let filepath =
|
|
38000
|
-
const base =
|
|
38001
|
-
const ext =
|
|
37999
|
+
let filepath = path22.join(targetDir, filename);
|
|
38000
|
+
const base = path22.basename(filepath, path22.extname(filepath));
|
|
38001
|
+
const ext = path22.extname(filepath);
|
|
38002
38002
|
let counter = 1;
|
|
38003
|
-
while (
|
|
38004
|
-
filepath =
|
|
38003
|
+
while (fs15.existsSync(filepath)) {
|
|
38004
|
+
filepath = path22.join(targetDir, `${base}_${counter}${ext}`);
|
|
38005
38005
|
counter++;
|
|
38006
38006
|
}
|
|
38007
38007
|
try {
|
|
38008
|
-
|
|
38008
|
+
fs15.writeFileSync(filepath, code.trim(), "utf-8");
|
|
38009
38009
|
savedFiles.push(filepath);
|
|
38010
38010
|
} catch (error93) {
|
|
38011
38011
|
errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
|
|
@@ -38034,13 +38034,15 @@ init_dist();
|
|
|
38034
38034
|
var GITINGEST_TIMEOUT_MS = 1e4;
|
|
38035
38035
|
var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
|
|
38036
38036
|
var GITINGEST_MAX_RETRIES = 2;
|
|
38037
|
+
var GITINGEST_DEFAULT_ENDPOINT = "https://gitingest.com/api/ingest";
|
|
38038
|
+
var DEFAULT_GITINGEST_CONFIG = GitingestConfigSchema.parse({});
|
|
38037
38039
|
var delay = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
38038
|
-
async function fetchGitingest(args) {
|
|
38040
|
+
async function fetchGitingest(args, endpoint = GITINGEST_DEFAULT_ENDPOINT) {
|
|
38039
38041
|
for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
|
|
38040
38042
|
try {
|
|
38041
38043
|
const controller = new AbortController;
|
|
38042
38044
|
const timeoutId = setTimeout(() => controller.abort(), GITINGEST_TIMEOUT_MS);
|
|
38043
|
-
const response = await fetch(
|
|
38045
|
+
const response = await fetch(endpoint, {
|
|
38044
38046
|
method: "POST",
|
|
38045
38047
|
headers: { "Content-Type": "application/json" },
|
|
38046
38048
|
body: JSON.stringify({
|
|
@@ -38100,21 +38102,28 @@ ${data.content}`;
|
|
|
38100
38102
|
throw new Error("gitingest request failed after retries");
|
|
38101
38103
|
}
|
|
38102
38104
|
var gitingest = tool({
|
|
38103
|
-
description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis.
|
|
38105
|
+
description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis. This third-party call sends repository content to gitingest.com, so enable it explicitly and ensure you have permission before sharing code externally.",
|
|
38104
38106
|
args: {
|
|
38105
38107
|
url: tool.schema.string().describe("GitHub repository URL (e.g., https://github.com/owner/repo)"),
|
|
38106
38108
|
maxFileSize: tool.schema.number().optional().describe("Maximum file size in bytes to include (default: 50000)"),
|
|
38107
38109
|
pattern: tool.schema.string().optional().describe("Glob pattern to filter files (e.g., '*.ts' or 'src/**/*.py')"),
|
|
38108
38110
|
patternType: tool.schema.enum(["include", "exclude"]).optional().describe("Whether pattern includes or excludes matching files (default: exclude)")
|
|
38109
38111
|
},
|
|
38110
|
-
async execute(args,
|
|
38111
|
-
|
|
38112
|
+
async execute(args, context) {
|
|
38113
|
+
const directory = context.directory ?? process.cwd();
|
|
38114
|
+
const pluginConfig = loadPluginConfig(directory);
|
|
38115
|
+
const gitingestConfig = pluginConfig.gitingest ?? DEFAULT_GITINGEST_CONFIG;
|
|
38116
|
+
if (gitingestConfig.enabled === false) {
|
|
38117
|
+
throw new Error("gitingest is disabled in your config. Set gitingest.enabled=true to re-enable it.");
|
|
38118
|
+
}
|
|
38119
|
+
const endpoint = gitingestConfig.endpoint ?? GITINGEST_DEFAULT_ENDPOINT;
|
|
38120
|
+
return fetchGitingest(args, endpoint);
|
|
38112
38121
|
}
|
|
38113
38122
|
});
|
|
38114
38123
|
// src/tools/imports.ts
|
|
38115
38124
|
init_dist();
|
|
38116
|
-
import * as
|
|
38117
|
-
import * as
|
|
38125
|
+
import * as fs16 from "fs";
|
|
38126
|
+
import * as path23 from "path";
|
|
38118
38127
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
38119
38128
|
var MAX_SYMBOL_LENGTH = 256;
|
|
38120
38129
|
var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
|
|
@@ -38168,7 +38177,7 @@ function validateSymbolInput(symbol3) {
|
|
|
38168
38177
|
return null;
|
|
38169
38178
|
}
|
|
38170
38179
|
function isBinaryFile2(filePath, buffer) {
|
|
38171
|
-
const ext =
|
|
38180
|
+
const ext = path23.extname(filePath).toLowerCase();
|
|
38172
38181
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
38173
38182
|
return false;
|
|
38174
38183
|
}
|
|
@@ -38192,15 +38201,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
38192
38201
|
const imports = [];
|
|
38193
38202
|
let resolvedTarget;
|
|
38194
38203
|
try {
|
|
38195
|
-
resolvedTarget =
|
|
38204
|
+
resolvedTarget = path23.resolve(targetFile);
|
|
38196
38205
|
} catch {
|
|
38197
38206
|
resolvedTarget = targetFile;
|
|
38198
38207
|
}
|
|
38199
|
-
const targetBasename =
|
|
38208
|
+
const targetBasename = path23.basename(targetFile, path23.extname(targetFile));
|
|
38200
38209
|
const targetWithExt = targetFile;
|
|
38201
38210
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
38202
|
-
const normalizedTargetWithExt =
|
|
38203
|
-
const normalizedTargetWithoutExt =
|
|
38211
|
+
const normalizedTargetWithExt = path23.normalize(targetWithExt).replace(/\\/g, "/");
|
|
38212
|
+
const normalizedTargetWithoutExt = path23.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
38204
38213
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
38205
38214
|
let match;
|
|
38206
38215
|
while ((match = importRegex.exec(content)) !== null) {
|
|
@@ -38224,9 +38233,9 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
38224
38233
|
}
|
|
38225
38234
|
const normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
38226
38235
|
let isMatch = false;
|
|
38227
|
-
const targetDir =
|
|
38228
|
-
const targetExt =
|
|
38229
|
-
const targetBasenameNoExt =
|
|
38236
|
+
const targetDir = path23.dirname(targetFile);
|
|
38237
|
+
const targetExt = path23.extname(targetFile);
|
|
38238
|
+
const targetBasenameNoExt = path23.basename(targetFile, targetExt);
|
|
38230
38239
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
38231
38240
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
38232
38241
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
@@ -38283,7 +38292,7 @@ var SKIP_DIRECTORIES2 = new Set([
|
|
|
38283
38292
|
function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
|
|
38284
38293
|
let entries;
|
|
38285
38294
|
try {
|
|
38286
|
-
entries =
|
|
38295
|
+
entries = fs16.readdirSync(dir);
|
|
38287
38296
|
} catch (e) {
|
|
38288
38297
|
stats.fileErrors.push({
|
|
38289
38298
|
path: dir,
|
|
@@ -38294,13 +38303,13 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
38294
38303
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
38295
38304
|
for (const entry of entries) {
|
|
38296
38305
|
if (SKIP_DIRECTORIES2.has(entry)) {
|
|
38297
|
-
stats.skippedDirs.push(
|
|
38306
|
+
stats.skippedDirs.push(path23.join(dir, entry));
|
|
38298
38307
|
continue;
|
|
38299
38308
|
}
|
|
38300
|
-
const fullPath =
|
|
38309
|
+
const fullPath = path23.join(dir, entry);
|
|
38301
38310
|
let stat;
|
|
38302
38311
|
try {
|
|
38303
|
-
stat =
|
|
38312
|
+
stat = fs16.statSync(fullPath);
|
|
38304
38313
|
} catch (e) {
|
|
38305
38314
|
stats.fileErrors.push({
|
|
38306
38315
|
path: fullPath,
|
|
@@ -38311,7 +38320,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
38311
38320
|
if (stat.isDirectory()) {
|
|
38312
38321
|
findSourceFiles2(fullPath, files, stats);
|
|
38313
38322
|
} else if (stat.isFile()) {
|
|
38314
|
-
const ext =
|
|
38323
|
+
const ext = path23.extname(fullPath).toLowerCase();
|
|
38315
38324
|
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
38316
38325
|
files.push(fullPath);
|
|
38317
38326
|
}
|
|
@@ -38367,8 +38376,8 @@ var imports = tool({
|
|
|
38367
38376
|
return JSON.stringify(errorResult, null, 2);
|
|
38368
38377
|
}
|
|
38369
38378
|
try {
|
|
38370
|
-
const targetFile =
|
|
38371
|
-
if (!
|
|
38379
|
+
const targetFile = path23.resolve(file3);
|
|
38380
|
+
if (!fs16.existsSync(targetFile)) {
|
|
38372
38381
|
const errorResult = {
|
|
38373
38382
|
error: `target file not found: ${file3}`,
|
|
38374
38383
|
target: file3,
|
|
@@ -38378,7 +38387,7 @@ var imports = tool({
|
|
|
38378
38387
|
};
|
|
38379
38388
|
return JSON.stringify(errorResult, null, 2);
|
|
38380
38389
|
}
|
|
38381
|
-
const targetStat =
|
|
38390
|
+
const targetStat = fs16.statSync(targetFile);
|
|
38382
38391
|
if (!targetStat.isFile()) {
|
|
38383
38392
|
const errorResult = {
|
|
38384
38393
|
error: "target must be a file, not a directory",
|
|
@@ -38389,7 +38398,7 @@ var imports = tool({
|
|
|
38389
38398
|
};
|
|
38390
38399
|
return JSON.stringify(errorResult, null, 2);
|
|
38391
38400
|
}
|
|
38392
|
-
const baseDir =
|
|
38401
|
+
const baseDir = path23.dirname(targetFile);
|
|
38393
38402
|
const scanStats = {
|
|
38394
38403
|
skippedDirs: [],
|
|
38395
38404
|
skippedFiles: 0,
|
|
@@ -38404,12 +38413,12 @@ var imports = tool({
|
|
|
38404
38413
|
if (consumers.length >= MAX_CONSUMERS)
|
|
38405
38414
|
break;
|
|
38406
38415
|
try {
|
|
38407
|
-
const stat =
|
|
38416
|
+
const stat = fs16.statSync(filePath);
|
|
38408
38417
|
if (stat.size > MAX_FILE_SIZE_BYTES4) {
|
|
38409
38418
|
skippedFileCount++;
|
|
38410
38419
|
continue;
|
|
38411
38420
|
}
|
|
38412
|
-
const buffer =
|
|
38421
|
+
const buffer = fs16.readFileSync(filePath);
|
|
38413
38422
|
if (isBinaryFile2(filePath, buffer)) {
|
|
38414
38423
|
skippedFileCount++;
|
|
38415
38424
|
continue;
|
|
@@ -38478,10 +38487,107 @@ init_lint();
|
|
|
38478
38487
|
|
|
38479
38488
|
// src/tools/pkg-audit.ts
|
|
38480
38489
|
init_dist();
|
|
38481
|
-
import * as
|
|
38482
|
-
import * as
|
|
38483
|
-
var MAX_OUTPUT_BYTES4 =
|
|
38490
|
+
import * as fs17 from "fs";
|
|
38491
|
+
import * as path24 from "path";
|
|
38492
|
+
var MAX_OUTPUT_BYTES4 = 5242880;
|
|
38484
38493
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
38494
|
+
async function runAuditCommand(command) {
|
|
38495
|
+
const proc = Bun.spawn(command, {
|
|
38496
|
+
stdout: "pipe",
|
|
38497
|
+
stderr: "pipe",
|
|
38498
|
+
cwd: process.cwd()
|
|
38499
|
+
});
|
|
38500
|
+
const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
|
|
38501
|
+
const capturePromise = Promise.all([
|
|
38502
|
+
new Response(proc.stdout).text(),
|
|
38503
|
+
new Response(proc.stderr).text()
|
|
38504
|
+
]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 }));
|
|
38505
|
+
const result = await Promise.race([capturePromise, timeoutPromise]);
|
|
38506
|
+
if (result === "timeout") {
|
|
38507
|
+
proc.kill();
|
|
38508
|
+
return { stdout: "", stderr: "", exitCode: -1, timedOut: true };
|
|
38509
|
+
}
|
|
38510
|
+
let { stdout, stderr } = result;
|
|
38511
|
+
if (stdout.length > MAX_OUTPUT_BYTES4) {
|
|
38512
|
+
stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
|
|
38513
|
+
}
|
|
38514
|
+
const exitCode = await proc.exited;
|
|
38515
|
+
return { stdout, stderr, exitCode, timedOut: false };
|
|
38516
|
+
}
|
|
38517
|
+
function buildTimeoutResult(ecosystem, command, message) {
|
|
38518
|
+
return {
|
|
38519
|
+
ecosystem,
|
|
38520
|
+
command,
|
|
38521
|
+
findings: [],
|
|
38522
|
+
criticalCount: 0,
|
|
38523
|
+
highCount: 0,
|
|
38524
|
+
totalCount: 0,
|
|
38525
|
+
clean: true,
|
|
38526
|
+
note: `${message} after ${AUDIT_TIMEOUT_MS / 1000}s`
|
|
38527
|
+
};
|
|
38528
|
+
}
|
|
38529
|
+
function handleAuditError(ecosystem, command, error93, notInstalledMatchers, notInstalledNote, errorPrefix) {
|
|
38530
|
+
const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
|
|
38531
|
+
if (notInstalledMatchers.some((re) => re.test(errorMessage))) {
|
|
38532
|
+
return {
|
|
38533
|
+
ecosystem,
|
|
38534
|
+
command,
|
|
38535
|
+
findings: [],
|
|
38536
|
+
criticalCount: 0,
|
|
38537
|
+
highCount: 0,
|
|
38538
|
+
totalCount: 0,
|
|
38539
|
+
clean: true,
|
|
38540
|
+
note: notInstalledNote
|
|
38541
|
+
};
|
|
38542
|
+
}
|
|
38543
|
+
return {
|
|
38544
|
+
ecosystem,
|
|
38545
|
+
command,
|
|
38546
|
+
findings: [],
|
|
38547
|
+
criticalCount: 0,
|
|
38548
|
+
highCount: 0,
|
|
38549
|
+
totalCount: 0,
|
|
38550
|
+
clean: true,
|
|
38551
|
+
note: `${errorPrefix}: ${errorMessage}`
|
|
38552
|
+
};
|
|
38553
|
+
}
|
|
38554
|
+
function buildCleanResult(ecosystem, command) {
|
|
38555
|
+
return {
|
|
38556
|
+
ecosystem,
|
|
38557
|
+
command,
|
|
38558
|
+
findings: [],
|
|
38559
|
+
criticalCount: 0,
|
|
38560
|
+
highCount: 0,
|
|
38561
|
+
totalCount: 0,
|
|
38562
|
+
clean: true
|
|
38563
|
+
};
|
|
38564
|
+
}
|
|
38565
|
+
function handleParseFailure(ecosystem, command, result, notePrefix, notInstalledNote, notInstalledMatchers) {
|
|
38566
|
+
const combined = `${result.stdout} ${result.stderr}`;
|
|
38567
|
+
if (notInstalledMatchers.some((re) => re.test(combined))) {
|
|
38568
|
+
return {
|
|
38569
|
+
ecosystem,
|
|
38570
|
+
command,
|
|
38571
|
+
findings: [],
|
|
38572
|
+
criticalCount: 0,
|
|
38573
|
+
highCount: 0,
|
|
38574
|
+
totalCount: 0,
|
|
38575
|
+
clean: true,
|
|
38576
|
+
note: notInstalledNote
|
|
38577
|
+
};
|
|
38578
|
+
}
|
|
38579
|
+
const snippet = result.stdout.trim() || result.stderr.trim();
|
|
38580
|
+
return {
|
|
38581
|
+
ecosystem,
|
|
38582
|
+
command,
|
|
38583
|
+
findings: [],
|
|
38584
|
+
criticalCount: 0,
|
|
38585
|
+
highCount: 0,
|
|
38586
|
+
totalCount: 0,
|
|
38587
|
+
clean: true,
|
|
38588
|
+
note: `${notePrefix}: ${snippet.slice(0, 200) || "unparseable output"}`
|
|
38589
|
+
};
|
|
38590
|
+
}
|
|
38485
38591
|
function isValidEcosystem(value) {
|
|
38486
38592
|
return typeof value === "string" && ["auto", "npm", "pip", "cargo"].includes(value);
|
|
38487
38593
|
}
|
|
@@ -38497,13 +38603,13 @@ function validateArgs3(args) {
|
|
|
38497
38603
|
function detectEcosystems() {
|
|
38498
38604
|
const ecosystems = [];
|
|
38499
38605
|
const cwd = process.cwd();
|
|
38500
|
-
if (
|
|
38606
|
+
if (fs17.existsSync(path24.join(cwd, "package.json"))) {
|
|
38501
38607
|
ecosystems.push("npm");
|
|
38502
38608
|
}
|
|
38503
|
-
if (
|
|
38609
|
+
if (fs17.existsSync(path24.join(cwd, "pyproject.toml")) || fs17.existsSync(path24.join(cwd, "requirements.txt"))) {
|
|
38504
38610
|
ecosystems.push("pip");
|
|
38505
38611
|
}
|
|
38506
|
-
if (
|
|
38612
|
+
if (fs17.existsSync(path24.join(cwd, "Cargo.toml"))) {
|
|
38507
38613
|
ecosystems.push("cargo");
|
|
38508
38614
|
}
|
|
38509
38615
|
return ecosystems;
|
|
@@ -38511,100 +38617,17 @@ function detectEcosystems() {
|
|
|
38511
38617
|
async function runNpmAudit() {
|
|
38512
38618
|
const command = ["npm", "audit", "--json"];
|
|
38513
38619
|
try {
|
|
38514
|
-
const
|
|
38515
|
-
|
|
38516
|
-
|
|
38517
|
-
cwd: process.cwd()
|
|
38518
|
-
});
|
|
38519
|
-
const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
|
|
38520
|
-
const result = await Promise.race([
|
|
38521
|
-
Promise.all([
|
|
38522
|
-
new Response(proc.stdout).text(),
|
|
38523
|
-
new Response(proc.stderr).text()
|
|
38524
|
-
]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
|
|
38525
|
-
timeoutPromise
|
|
38526
|
-
]);
|
|
38527
|
-
if (result === "timeout") {
|
|
38528
|
-
proc.kill();
|
|
38529
|
-
return {
|
|
38530
|
-
ecosystem: "npm",
|
|
38531
|
-
command,
|
|
38532
|
-
findings: [],
|
|
38533
|
-
criticalCount: 0,
|
|
38534
|
-
highCount: 0,
|
|
38535
|
-
totalCount: 0,
|
|
38536
|
-
clean: true,
|
|
38537
|
-
note: `npm audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
|
|
38538
|
-
};
|
|
38539
|
-
}
|
|
38540
|
-
let { stdout, stderr } = result;
|
|
38541
|
-
if (stdout.length > MAX_OUTPUT_BYTES4) {
|
|
38542
|
-
stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
|
|
38543
|
-
}
|
|
38544
|
-
const exitCode = await proc.exited;
|
|
38545
|
-
if (exitCode === 0) {
|
|
38546
|
-
return {
|
|
38547
|
-
ecosystem: "npm",
|
|
38548
|
-
command,
|
|
38549
|
-
findings: [],
|
|
38550
|
-
criticalCount: 0,
|
|
38551
|
-
highCount: 0,
|
|
38552
|
-
totalCount: 0,
|
|
38553
|
-
clean: true
|
|
38554
|
-
};
|
|
38620
|
+
const result = await runAuditCommand(command);
|
|
38621
|
+
if (result.timedOut) {
|
|
38622
|
+
return buildTimeoutResult("npm", command, "npm audit timed out");
|
|
38555
38623
|
}
|
|
38556
|
-
|
|
38557
|
-
const jsonMatch = stdout.match(/\{[\s\S]*\}/) || stderr.match(/\{[\s\S]*\}/);
|
|
38558
|
-
if (jsonMatch) {
|
|
38559
|
-
jsonOutput = jsonMatch[0];
|
|
38560
|
-
}
|
|
38561
|
-
const response = JSON.parse(jsonOutput);
|
|
38562
|
-
const findings = [];
|
|
38563
|
-
if (response.vulnerabilities) {
|
|
38564
|
-
for (const [pkgName, vuln] of Object.entries(response.vulnerabilities)) {
|
|
38565
|
-
let patchedVersion = null;
|
|
38566
|
-
if (vuln.fixAvailable && typeof vuln.fixAvailable === "object") {
|
|
38567
|
-
patchedVersion = vuln.fixAvailable.version;
|
|
38568
|
-
} else if (vuln.fixAvailable === true) {
|
|
38569
|
-
patchedVersion = "latest";
|
|
38570
|
-
}
|
|
38571
|
-
const severity = mapNpmSeverity(vuln.severity);
|
|
38572
|
-
findings.push({
|
|
38573
|
-
package: pkgName,
|
|
38574
|
-
installedVersion: vuln.range,
|
|
38575
|
-
patchedVersion,
|
|
38576
|
-
severity,
|
|
38577
|
-
title: vuln.title || `Vulnerability in ${pkgName}`,
|
|
38578
|
-
cve: vuln.cves && vuln.cves.length > 0 ? vuln.cves[0] : null,
|
|
38579
|
-
url: vuln.url || null
|
|
38580
|
-
});
|
|
38581
|
-
}
|
|
38582
|
-
}
|
|
38583
|
-
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
38584
|
-
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
38585
|
-
return {
|
|
38586
|
-
ecosystem: "npm",
|
|
38587
|
-
command,
|
|
38588
|
-
findings,
|
|
38589
|
-
criticalCount,
|
|
38590
|
-
highCount,
|
|
38591
|
-
totalCount: findings.length,
|
|
38592
|
-
clean: findings.length === 0
|
|
38593
|
-
};
|
|
38624
|
+
return parseNpmAuditResult(command, result);
|
|
38594
38625
|
} catch (error93) {
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
|
|
38598
|
-
|
|
38599
|
-
|
|
38600
|
-
findings: [],
|
|
38601
|
-
criticalCount: 0,
|
|
38602
|
-
highCount: 0,
|
|
38603
|
-
totalCount: 0,
|
|
38604
|
-
clean: true,
|
|
38605
|
-
note: "npm audit not available - npm may not be installed"
|
|
38606
|
-
};
|
|
38607
|
-
}
|
|
38626
|
+
return handleAuditError("npm", command, error93, [/audit/, /command not found/, /'npm' is not recognized/], "npm audit not available - npm may not be installed", "Error running npm audit");
|
|
38627
|
+
}
|
|
38628
|
+
}
|
|
38629
|
+
function parseNpmAuditResult(command, result) {
|
|
38630
|
+
if (result.exitCode === 0) {
|
|
38608
38631
|
return {
|
|
38609
38632
|
ecosystem: "npm",
|
|
38610
38633
|
command,
|
|
@@ -38612,10 +38635,45 @@ async function runNpmAudit() {
|
|
|
38612
38635
|
criticalCount: 0,
|
|
38613
38636
|
highCount: 0,
|
|
38614
38637
|
totalCount: 0,
|
|
38615
|
-
clean: true
|
|
38616
|
-
note: `Error running npm audit: ${errorMessage}`
|
|
38638
|
+
clean: true
|
|
38617
38639
|
};
|
|
38618
38640
|
}
|
|
38641
|
+
let jsonOutput = result.stdout;
|
|
38642
|
+
const match = jsonOutput.match(/\{[\s\S]*\}/) || result.stderr.match(/\{[\s\S]*\}/);
|
|
38643
|
+
if (match) {
|
|
38644
|
+
jsonOutput = match[0];
|
|
38645
|
+
}
|
|
38646
|
+
let response;
|
|
38647
|
+
try {
|
|
38648
|
+
response = JSON.parse(jsonOutput);
|
|
38649
|
+
} catch {
|
|
38650
|
+
return handleParseFailure("npm", command, result, "npm audit output could not be parsed", "npm audit not available - npm may not be installed", [/audit/, /command not found/, /'npm' is not recognized/]);
|
|
38651
|
+
}
|
|
38652
|
+
const findings = buildNpmFindings(response.vulnerabilities);
|
|
38653
|
+
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
38654
|
+
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
38655
|
+
return {
|
|
38656
|
+
ecosystem: "npm",
|
|
38657
|
+
command,
|
|
38658
|
+
findings,
|
|
38659
|
+
criticalCount,
|
|
38660
|
+
highCount,
|
|
38661
|
+
totalCount: findings.length,
|
|
38662
|
+
clean: findings.length === 0
|
|
38663
|
+
};
|
|
38664
|
+
}
|
|
38665
|
+
function buildNpmFindings(response) {
|
|
38666
|
+
if (!response)
|
|
38667
|
+
return [];
|
|
38668
|
+
return Object.entries(response).map(([pkgName, vuln]) => ({
|
|
38669
|
+
package: pkgName,
|
|
38670
|
+
installedVersion: vuln.range,
|
|
38671
|
+
patchedVersion: vuln.fixAvailable && typeof vuln.fixAvailable === "object" ? vuln.fixAvailable.version : vuln.fixAvailable === true ? "latest" : null,
|
|
38672
|
+
severity: mapNpmSeverity(vuln.severity),
|
|
38673
|
+
title: vuln.title || `Vulnerability in ${pkgName}`,
|
|
38674
|
+
cve: vuln.cves && vuln.cves.length > 0 ? vuln.cves[0] : null,
|
|
38675
|
+
url: vuln.url || null
|
|
38676
|
+
}));
|
|
38619
38677
|
}
|
|
38620
38678
|
function mapNpmSeverity(severity) {
|
|
38621
38679
|
switch (severity.toLowerCase()) {
|
|
@@ -38634,238 +38692,116 @@ function mapNpmSeverity(severity) {
|
|
|
38634
38692
|
async function runPipAudit() {
|
|
38635
38693
|
const command = ["pip-audit", "--format=json"];
|
|
38636
38694
|
try {
|
|
38637
|
-
const
|
|
38638
|
-
|
|
38639
|
-
|
|
38640
|
-
cwd: process.cwd()
|
|
38641
|
-
});
|
|
38642
|
-
const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
|
|
38643
|
-
const result = await Promise.race([
|
|
38644
|
-
Promise.all([
|
|
38645
|
-
new Response(proc.stdout).text(),
|
|
38646
|
-
new Response(proc.stderr).text()
|
|
38647
|
-
]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
|
|
38648
|
-
timeoutPromise
|
|
38649
|
-
]);
|
|
38650
|
-
if (result === "timeout") {
|
|
38651
|
-
proc.kill();
|
|
38652
|
-
return {
|
|
38653
|
-
ecosystem: "pip",
|
|
38654
|
-
command,
|
|
38655
|
-
findings: [],
|
|
38656
|
-
criticalCount: 0,
|
|
38657
|
-
highCount: 0,
|
|
38658
|
-
totalCount: 0,
|
|
38659
|
-
clean: true,
|
|
38660
|
-
note: `pip-audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
|
|
38661
|
-
};
|
|
38662
|
-
}
|
|
38663
|
-
let { stdout, stderr } = result;
|
|
38664
|
-
if (stdout.length > MAX_OUTPUT_BYTES4) {
|
|
38665
|
-
stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
|
|
38666
|
-
}
|
|
38667
|
-
const exitCode = await proc.exited;
|
|
38668
|
-
if (exitCode === 0 && !stdout.trim()) {
|
|
38669
|
-
return {
|
|
38670
|
-
ecosystem: "pip",
|
|
38671
|
-
command,
|
|
38672
|
-
findings: [],
|
|
38673
|
-
criticalCount: 0,
|
|
38674
|
-
highCount: 0,
|
|
38675
|
-
totalCount: 0,
|
|
38676
|
-
clean: true
|
|
38677
|
-
};
|
|
38678
|
-
}
|
|
38679
|
-
let packages = [];
|
|
38680
|
-
try {
|
|
38681
|
-
const parsed = JSON.parse(stdout);
|
|
38682
|
-
if (Array.isArray(parsed)) {
|
|
38683
|
-
packages = parsed;
|
|
38684
|
-
} else if (parsed.dependencies) {
|
|
38685
|
-
packages = parsed.dependencies;
|
|
38686
|
-
}
|
|
38687
|
-
} catch {
|
|
38688
|
-
if (stderr.includes("not installed") || stdout.includes("not installed") || stderr.includes("command not found")) {
|
|
38689
|
-
return {
|
|
38690
|
-
ecosystem: "pip",
|
|
38691
|
-
command,
|
|
38692
|
-
findings: [],
|
|
38693
|
-
criticalCount: 0,
|
|
38694
|
-
highCount: 0,
|
|
38695
|
-
totalCount: 0,
|
|
38696
|
-
clean: true,
|
|
38697
|
-
note: "pip-audit not installed. Install with: pip install pip-audit"
|
|
38698
|
-
};
|
|
38699
|
-
}
|
|
38700
|
-
return {
|
|
38701
|
-
ecosystem: "pip",
|
|
38702
|
-
command,
|
|
38703
|
-
findings: [],
|
|
38704
|
-
criticalCount: 0,
|
|
38705
|
-
highCount: 0,
|
|
38706
|
-
totalCount: 0,
|
|
38707
|
-
clean: true,
|
|
38708
|
-
note: `pip-audit output could not be parsed: ${stdout.slice(0, 200)}`
|
|
38709
|
-
};
|
|
38710
|
-
}
|
|
38711
|
-
const findings = [];
|
|
38712
|
-
for (const pkg of packages) {
|
|
38713
|
-
if (pkg.vulns && pkg.vulns.length > 0) {
|
|
38714
|
-
for (const vuln of pkg.vulns) {
|
|
38715
|
-
const severity = vuln.aliases && vuln.aliases.length > 0 ? "high" : "moderate";
|
|
38716
|
-
findings.push({
|
|
38717
|
-
package: pkg.name,
|
|
38718
|
-
installedVersion: pkg.version,
|
|
38719
|
-
patchedVersion: vuln.fix_versions && vuln.fix_versions.length > 0 ? vuln.fix_versions[0] : null,
|
|
38720
|
-
severity,
|
|
38721
|
-
title: vuln.id,
|
|
38722
|
-
cve: vuln.aliases && vuln.aliases.length > 0 ? vuln.aliases[0] : null,
|
|
38723
|
-
url: vuln.id.startsWith("CVE-") ? `https://nvd.nist.gov/vuln/detail/${vuln.id}` : null
|
|
38724
|
-
});
|
|
38725
|
-
}
|
|
38726
|
-
}
|
|
38695
|
+
const result = await runAuditCommand(command);
|
|
38696
|
+
if (result.timedOut) {
|
|
38697
|
+
return buildTimeoutResult("pip", command, "pip-audit timed out");
|
|
38727
38698
|
}
|
|
38728
|
-
|
|
38729
|
-
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
38730
|
-
return {
|
|
38731
|
-
ecosystem: "pip",
|
|
38732
|
-
command,
|
|
38733
|
-
findings,
|
|
38734
|
-
criticalCount,
|
|
38735
|
-
highCount,
|
|
38736
|
-
totalCount: findings.length,
|
|
38737
|
-
clean: findings.length === 0
|
|
38738
|
-
};
|
|
38699
|
+
return parsePipAuditResult(command, result);
|
|
38739
38700
|
} catch (error93) {
|
|
38740
|
-
|
|
38741
|
-
|
|
38742
|
-
|
|
38743
|
-
|
|
38744
|
-
|
|
38745
|
-
|
|
38746
|
-
|
|
38747
|
-
|
|
38748
|
-
|
|
38749
|
-
|
|
38750
|
-
|
|
38751
|
-
|
|
38701
|
+
return handleAuditError("pip", command, error93, [/not installed/, /command not found/, /pip-audit/], "pip-audit not installed. Install with: pip install pip-audit", "Error running pip-audit");
|
|
38702
|
+
}
|
|
38703
|
+
}
|
|
38704
|
+
function parsePipAuditResult(command, result) {
|
|
38705
|
+
if (result.exitCode === 0 && !result.stdout.trim()) {
|
|
38706
|
+
return buildCleanResult("pip", command);
|
|
38707
|
+
}
|
|
38708
|
+
let packages = [];
|
|
38709
|
+
try {
|
|
38710
|
+
const parsed = JSON.parse(result.stdout);
|
|
38711
|
+
if (Array.isArray(parsed)) {
|
|
38712
|
+
packages = parsed;
|
|
38713
|
+
} else if (parsed.dependencies) {
|
|
38714
|
+
packages = parsed.dependencies;
|
|
38715
|
+
}
|
|
38716
|
+
} catch {
|
|
38717
|
+
return handleParseFailure("pip", command, result, "pip-audit output could not be parsed", "pip-audit not installed. Install with: pip install pip-audit", [/not installed/, /command not found/, /pip-audit/]);
|
|
38718
|
+
}
|
|
38719
|
+
const findings = buildPipFindings(packages);
|
|
38720
|
+
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
38721
|
+
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
38722
|
+
return {
|
|
38723
|
+
ecosystem: "pip",
|
|
38724
|
+
command,
|
|
38725
|
+
findings,
|
|
38726
|
+
criticalCount,
|
|
38727
|
+
highCount,
|
|
38728
|
+
totalCount: findings.length,
|
|
38729
|
+
clean: findings.length === 0
|
|
38730
|
+
};
|
|
38731
|
+
}
|
|
38732
|
+
function buildPipFindings(packages) {
|
|
38733
|
+
const findings = [];
|
|
38734
|
+
for (const pkg of packages) {
|
|
38735
|
+
if (!pkg.vulns)
|
|
38736
|
+
continue;
|
|
38737
|
+
for (const vuln of pkg.vulns) {
|
|
38738
|
+
const severity = vuln.aliases && vuln.aliases.length > 0 ? "high" : "moderate";
|
|
38739
|
+
findings.push({
|
|
38740
|
+
package: pkg.name,
|
|
38741
|
+
installedVersion: pkg.version,
|
|
38742
|
+
patchedVersion: vuln.fix_versions && vuln.fix_versions.length > 0 ? vuln.fix_versions[0] : null,
|
|
38743
|
+
severity,
|
|
38744
|
+
title: vuln.id,
|
|
38745
|
+
cve: vuln.aliases && vuln.aliases.length > 0 ? vuln.aliases[0] : null,
|
|
38746
|
+
url: vuln.id.startsWith("CVE-") ? `https://nvd.nist.gov/vuln/detail/${vuln.id}` : null
|
|
38747
|
+
});
|
|
38752
38748
|
}
|
|
38753
|
-
return {
|
|
38754
|
-
ecosystem: "pip",
|
|
38755
|
-
command,
|
|
38756
|
-
findings: [],
|
|
38757
|
-
criticalCount: 0,
|
|
38758
|
-
highCount: 0,
|
|
38759
|
-
totalCount: 0,
|
|
38760
|
-
clean: true,
|
|
38761
|
-
note: `Error running pip-audit: ${errorMessage}`
|
|
38762
|
-
};
|
|
38763
38749
|
}
|
|
38750
|
+
return findings;
|
|
38764
38751
|
}
|
|
38765
38752
|
async function runCargoAudit() {
|
|
38766
38753
|
const command = ["cargo", "audit", "--json"];
|
|
38767
38754
|
try {
|
|
38768
|
-
const
|
|
38769
|
-
|
|
38770
|
-
|
|
38771
|
-
cwd: process.cwd()
|
|
38772
|
-
});
|
|
38773
|
-
const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
|
|
38774
|
-
const result = await Promise.race([
|
|
38775
|
-
Promise.all([
|
|
38776
|
-
new Response(proc.stdout).text(),
|
|
38777
|
-
new Response(proc.stderr).text()
|
|
38778
|
-
]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
|
|
38779
|
-
timeoutPromise
|
|
38780
|
-
]);
|
|
38781
|
-
if (result === "timeout") {
|
|
38782
|
-
proc.kill();
|
|
38783
|
-
return {
|
|
38784
|
-
ecosystem: "cargo",
|
|
38785
|
-
command,
|
|
38786
|
-
findings: [],
|
|
38787
|
-
criticalCount: 0,
|
|
38788
|
-
highCount: 0,
|
|
38789
|
-
totalCount: 0,
|
|
38790
|
-
clean: true,
|
|
38791
|
-
note: `cargo audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
|
|
38792
|
-
};
|
|
38793
|
-
}
|
|
38794
|
-
let { stdout, stderr } = result;
|
|
38795
|
-
if (stdout.length > MAX_OUTPUT_BYTES4) {
|
|
38796
|
-
stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
|
|
38797
|
-
}
|
|
38798
|
-
const exitCode = await proc.exited;
|
|
38799
|
-
if (exitCode === 0) {
|
|
38800
|
-
return {
|
|
38801
|
-
ecosystem: "cargo",
|
|
38802
|
-
command,
|
|
38803
|
-
findings: [],
|
|
38804
|
-
criticalCount: 0,
|
|
38805
|
-
highCount: 0,
|
|
38806
|
-
totalCount: 0,
|
|
38807
|
-
clean: true
|
|
38808
|
-
};
|
|
38809
|
-
}
|
|
38810
|
-
const findings = [];
|
|
38811
|
-
const lines = stdout.split(`
|
|
38812
|
-
`).filter((line) => line.trim());
|
|
38813
|
-
for (const line of lines) {
|
|
38814
|
-
try {
|
|
38815
|
-
const obj = JSON.parse(line);
|
|
38816
|
-
if (obj.vulnerabilities && obj.vulnerabilities.list) {
|
|
38817
|
-
for (const item of obj.vulnerabilities.list) {
|
|
38818
|
-
const cvss = item.advisory.cvss || 0;
|
|
38819
|
-
const severity = mapCargoSeverity(cvss);
|
|
38820
|
-
findings.push({
|
|
38821
|
-
package: item.advisory.package,
|
|
38822
|
-
installedVersion: item.package.version,
|
|
38823
|
-
patchedVersion: item.versions.patched && item.versions.patched.length > 0 ? item.versions.patched[0] : null,
|
|
38824
|
-
severity,
|
|
38825
|
-
title: item.advisory.title,
|
|
38826
|
-
cve: item.advisory.aliases && item.advisory.aliases.length > 0 ? item.advisory.aliases[0] : item.advisory.id ? item.advisory.id : null,
|
|
38827
|
-
url: item.advisory.url || null
|
|
38828
|
-
});
|
|
38829
|
-
}
|
|
38830
|
-
}
|
|
38831
|
-
} catch {}
|
|
38755
|
+
const result = await runAuditCommand(command);
|
|
38756
|
+
if (result.timedOut) {
|
|
38757
|
+
return buildTimeoutResult("cargo", command, "cargo audit timed out");
|
|
38832
38758
|
}
|
|
38833
|
-
|
|
38834
|
-
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
38835
|
-
return {
|
|
38836
|
-
ecosystem: "cargo",
|
|
38837
|
-
command,
|
|
38838
|
-
findings,
|
|
38839
|
-
criticalCount,
|
|
38840
|
-
highCount,
|
|
38841
|
-
totalCount: findings.length,
|
|
38842
|
-
clean: findings.length === 0
|
|
38843
|
-
};
|
|
38759
|
+
return parseCargoAuditResult(command, result);
|
|
38844
38760
|
} catch (error93) {
|
|
38845
|
-
|
|
38846
|
-
|
|
38847
|
-
|
|
38848
|
-
|
|
38849
|
-
|
|
38850
|
-
|
|
38851
|
-
|
|
38852
|
-
|
|
38853
|
-
|
|
38854
|
-
|
|
38855
|
-
|
|
38856
|
-
|
|
38857
|
-
|
|
38858
|
-
|
|
38859
|
-
|
|
38860
|
-
|
|
38861
|
-
|
|
38862
|
-
|
|
38863
|
-
|
|
38864
|
-
|
|
38865
|
-
|
|
38866
|
-
|
|
38867
|
-
|
|
38761
|
+
return handleAuditError("cargo", command, error93, [/not found/, /not recognized/, /cargo-audit/], "cargo-audit not installed. Install with: cargo install cargo-audit", "Error running cargo audit");
|
|
38762
|
+
}
|
|
38763
|
+
}
|
|
38764
|
+
function parseCargoAuditResult(command, result) {
|
|
38765
|
+
if (result.exitCode === 0) {
|
|
38766
|
+
return buildCleanResult("cargo", command);
|
|
38767
|
+
}
|
|
38768
|
+
const lines = result.stdout.split("\\n").filter((line) => line.trim());
|
|
38769
|
+
const findings = buildCargoFindings(lines);
|
|
38770
|
+
const criticalCount = findings.filter((f) => f.severity === "critical").length;
|
|
38771
|
+
const highCount = findings.filter((f) => f.severity === "high").length;
|
|
38772
|
+
return {
|
|
38773
|
+
ecosystem: "cargo",
|
|
38774
|
+
command,
|
|
38775
|
+
findings,
|
|
38776
|
+
criticalCount,
|
|
38777
|
+
highCount,
|
|
38778
|
+
totalCount: findings.length,
|
|
38779
|
+
clean: findings.length === 0
|
|
38780
|
+
};
|
|
38781
|
+
}
|
|
38782
|
+
function buildCargoFindings(lines) {
|
|
38783
|
+
const findings = [];
|
|
38784
|
+
for (const line of lines) {
|
|
38785
|
+
try {
|
|
38786
|
+
const obj = JSON.parse(line);
|
|
38787
|
+
if (obj.vulnerabilities && obj.vulnerabilities.list) {
|
|
38788
|
+
for (const item of obj.vulnerabilities.list) {
|
|
38789
|
+
const cvss = item.advisory.cvss || 0;
|
|
38790
|
+
const severity = mapCargoSeverity(cvss);
|
|
38791
|
+
findings.push({
|
|
38792
|
+
package: item.advisory.package,
|
|
38793
|
+
installedVersion: item.package.version,
|
|
38794
|
+
patchedVersion: item.versions.patched && item.versions.patched.length > 0 ? item.versions.patched[0] : null,
|
|
38795
|
+
severity,
|
|
38796
|
+
title: item.advisory.title,
|
|
38797
|
+
cve: item.advisory.aliases && item.advisory.aliases.length > 0 ? item.advisory.aliases[0] : item.advisory.id ? item.advisory.id : null,
|
|
38798
|
+
url: item.advisory.url || null
|
|
38799
|
+
});
|
|
38800
|
+
}
|
|
38801
|
+
}
|
|
38802
|
+
} catch {}
|
|
38868
38803
|
}
|
|
38804
|
+
return findings;
|
|
38869
38805
|
}
|
|
38870
38806
|
function mapCargoSeverity(cvss) {
|
|
38871
38807
|
if (cvss >= 9)
|
|
@@ -38984,8 +38920,8 @@ var retrieve_summary = tool({
|
|
|
38984
38920
|
});
|
|
38985
38921
|
// src/tools/schema-drift.ts
|
|
38986
38922
|
init_dist();
|
|
38987
|
-
import * as
|
|
38988
|
-
import * as
|
|
38923
|
+
import * as fs18 from "fs";
|
|
38924
|
+
import * as path25 from "path";
|
|
38989
38925
|
var SPEC_CANDIDATES = [
|
|
38990
38926
|
"openapi.json",
|
|
38991
38927
|
"openapi.yaml",
|
|
@@ -39017,28 +38953,28 @@ function normalizePath(p) {
|
|
|
39017
38953
|
}
|
|
39018
38954
|
function discoverSpecFile(cwd, specFileArg) {
|
|
39019
38955
|
if (specFileArg) {
|
|
39020
|
-
const resolvedPath =
|
|
39021
|
-
const normalizedCwd = cwd.endsWith(
|
|
38956
|
+
const resolvedPath = path25.resolve(cwd, specFileArg);
|
|
38957
|
+
const normalizedCwd = cwd.endsWith(path25.sep) ? cwd : cwd + path25.sep;
|
|
39022
38958
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
39023
38959
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
39024
38960
|
}
|
|
39025
|
-
const ext =
|
|
38961
|
+
const ext = path25.extname(resolvedPath).toLowerCase();
|
|
39026
38962
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
39027
38963
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
39028
38964
|
}
|
|
39029
|
-
const stats =
|
|
38965
|
+
const stats = fs18.statSync(resolvedPath);
|
|
39030
38966
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
39031
38967
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
39032
38968
|
}
|
|
39033
|
-
if (!
|
|
38969
|
+
if (!fs18.existsSync(resolvedPath)) {
|
|
39034
38970
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
39035
38971
|
}
|
|
39036
38972
|
return resolvedPath;
|
|
39037
38973
|
}
|
|
39038
38974
|
for (const candidate of SPEC_CANDIDATES) {
|
|
39039
|
-
const candidatePath =
|
|
39040
|
-
if (
|
|
39041
|
-
const stats =
|
|
38975
|
+
const candidatePath = path25.resolve(cwd, candidate);
|
|
38976
|
+
if (fs18.existsSync(candidatePath)) {
|
|
38977
|
+
const stats = fs18.statSync(candidatePath);
|
|
39042
38978
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
39043
38979
|
return candidatePath;
|
|
39044
38980
|
}
|
|
@@ -39047,8 +38983,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
39047
38983
|
return null;
|
|
39048
38984
|
}
|
|
39049
38985
|
function parseSpec(specFile) {
|
|
39050
|
-
const content =
|
|
39051
|
-
const ext =
|
|
38986
|
+
const content = fs18.readFileSync(specFile, "utf-8");
|
|
38987
|
+
const ext = path25.extname(specFile).toLowerCase();
|
|
39052
38988
|
if (ext === ".json") {
|
|
39053
38989
|
return parseJsonSpec(content);
|
|
39054
38990
|
}
|
|
@@ -39114,12 +39050,12 @@ function extractRoutes(cwd) {
|
|
|
39114
39050
|
function walkDir(dir) {
|
|
39115
39051
|
let entries;
|
|
39116
39052
|
try {
|
|
39117
|
-
entries =
|
|
39053
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
39118
39054
|
} catch {
|
|
39119
39055
|
return;
|
|
39120
39056
|
}
|
|
39121
39057
|
for (const entry of entries) {
|
|
39122
|
-
const fullPath =
|
|
39058
|
+
const fullPath = path25.join(dir, entry.name);
|
|
39123
39059
|
if (entry.isSymbolicLink()) {
|
|
39124
39060
|
continue;
|
|
39125
39061
|
}
|
|
@@ -39129,7 +39065,7 @@ function extractRoutes(cwd) {
|
|
|
39129
39065
|
}
|
|
39130
39066
|
walkDir(fullPath);
|
|
39131
39067
|
} else if (entry.isFile()) {
|
|
39132
|
-
const ext =
|
|
39068
|
+
const ext = path25.extname(entry.name).toLowerCase();
|
|
39133
39069
|
const baseName = entry.name.toLowerCase();
|
|
39134
39070
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
39135
39071
|
continue;
|
|
@@ -39147,7 +39083,7 @@ function extractRoutes(cwd) {
|
|
|
39147
39083
|
}
|
|
39148
39084
|
function extractRoutesFromFile(filePath) {
|
|
39149
39085
|
const routes = [];
|
|
39150
|
-
const content =
|
|
39086
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
39151
39087
|
const lines = content.split(/\r?\n/);
|
|
39152
39088
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
39153
39089
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -39294,8 +39230,8 @@ init_secretscan();
|
|
|
39294
39230
|
|
|
39295
39231
|
// src/tools/symbols.ts
|
|
39296
39232
|
init_tool();
|
|
39297
|
-
import * as
|
|
39298
|
-
import * as
|
|
39233
|
+
import * as fs19 from "fs";
|
|
39234
|
+
import * as path26 from "path";
|
|
39299
39235
|
var MAX_FILE_SIZE_BYTES5 = 1024 * 1024;
|
|
39300
39236
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
39301
39237
|
function containsControlCharacters(str) {
|
|
@@ -39324,11 +39260,11 @@ function containsWindowsAttacks(str) {
|
|
|
39324
39260
|
}
|
|
39325
39261
|
function isPathInWorkspace(filePath, workspace) {
|
|
39326
39262
|
try {
|
|
39327
|
-
const resolvedPath =
|
|
39328
|
-
const realWorkspace =
|
|
39329
|
-
const realResolvedPath =
|
|
39330
|
-
const relativePath =
|
|
39331
|
-
if (relativePath.startsWith("..") ||
|
|
39263
|
+
const resolvedPath = path26.resolve(workspace, filePath);
|
|
39264
|
+
const realWorkspace = fs19.realpathSync(workspace);
|
|
39265
|
+
const realResolvedPath = fs19.realpathSync(resolvedPath);
|
|
39266
|
+
const relativePath = path26.relative(realWorkspace, realResolvedPath);
|
|
39267
|
+
if (relativePath.startsWith("..") || path26.isAbsolute(relativePath)) {
|
|
39332
39268
|
return false;
|
|
39333
39269
|
}
|
|
39334
39270
|
return true;
|
|
@@ -39340,17 +39276,17 @@ function validatePathForRead(filePath, workspace) {
|
|
|
39340
39276
|
return isPathInWorkspace(filePath, workspace);
|
|
39341
39277
|
}
|
|
39342
39278
|
function extractTSSymbols(filePath, cwd) {
|
|
39343
|
-
const fullPath =
|
|
39279
|
+
const fullPath = path26.join(cwd, filePath);
|
|
39344
39280
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
39345
39281
|
return [];
|
|
39346
39282
|
}
|
|
39347
39283
|
let content;
|
|
39348
39284
|
try {
|
|
39349
|
-
const stats =
|
|
39285
|
+
const stats = fs19.statSync(fullPath);
|
|
39350
39286
|
if (stats.size > MAX_FILE_SIZE_BYTES5) {
|
|
39351
39287
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES5})`);
|
|
39352
39288
|
}
|
|
39353
|
-
content =
|
|
39289
|
+
content = fs19.readFileSync(fullPath, "utf-8");
|
|
39354
39290
|
} catch {
|
|
39355
39291
|
return [];
|
|
39356
39292
|
}
|
|
@@ -39492,17 +39428,17 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
39492
39428
|
});
|
|
39493
39429
|
}
|
|
39494
39430
|
function extractPythonSymbols(filePath, cwd) {
|
|
39495
|
-
const fullPath =
|
|
39431
|
+
const fullPath = path26.join(cwd, filePath);
|
|
39496
39432
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
39497
39433
|
return [];
|
|
39498
39434
|
}
|
|
39499
39435
|
let content;
|
|
39500
39436
|
try {
|
|
39501
|
-
const stats =
|
|
39437
|
+
const stats = fs19.statSync(fullPath);
|
|
39502
39438
|
if (stats.size > MAX_FILE_SIZE_BYTES5) {
|
|
39503
39439
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES5})`);
|
|
39504
39440
|
}
|
|
39505
|
-
content =
|
|
39441
|
+
content = fs19.readFileSync(fullPath, "utf-8");
|
|
39506
39442
|
} catch {
|
|
39507
39443
|
return [];
|
|
39508
39444
|
}
|
|
@@ -39574,7 +39510,7 @@ var symbols = tool({
|
|
|
39574
39510
|
}, null, 2);
|
|
39575
39511
|
}
|
|
39576
39512
|
const cwd = process.cwd();
|
|
39577
|
-
const ext =
|
|
39513
|
+
const ext = path26.extname(file3);
|
|
39578
39514
|
if (containsControlCharacters(file3)) {
|
|
39579
39515
|
return JSON.stringify({
|
|
39580
39516
|
file: file3,
|
|
@@ -39639,8 +39575,8 @@ init_test_runner();
|
|
|
39639
39575
|
|
|
39640
39576
|
// src/tools/todo-extract.ts
|
|
39641
39577
|
init_dist();
|
|
39642
|
-
import * as
|
|
39643
|
-
import * as
|
|
39578
|
+
import * as fs20 from "fs";
|
|
39579
|
+
import * as path27 from "path";
|
|
39644
39580
|
var MAX_TEXT_LENGTH = 200;
|
|
39645
39581
|
var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
|
|
39646
39582
|
var SUPPORTED_EXTENSIONS2 = new Set([
|
|
@@ -39711,9 +39647,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
39711
39647
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
39712
39648
|
}
|
|
39713
39649
|
try {
|
|
39714
|
-
const resolvedPath =
|
|
39715
|
-
const normalizedCwd =
|
|
39716
|
-
const normalizedResolved =
|
|
39650
|
+
const resolvedPath = path27.resolve(paths);
|
|
39651
|
+
const normalizedCwd = path27.resolve(cwd);
|
|
39652
|
+
const normalizedResolved = path27.resolve(resolvedPath);
|
|
39717
39653
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
39718
39654
|
return {
|
|
39719
39655
|
error: "paths must be within the current working directory",
|
|
@@ -39729,13 +39665,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
39729
39665
|
}
|
|
39730
39666
|
}
|
|
39731
39667
|
function isSupportedExtension(filePath) {
|
|
39732
|
-
const ext =
|
|
39668
|
+
const ext = path27.extname(filePath).toLowerCase();
|
|
39733
39669
|
return SUPPORTED_EXTENSIONS2.has(ext);
|
|
39734
39670
|
}
|
|
39735
39671
|
function findSourceFiles3(dir, files = []) {
|
|
39736
39672
|
let entries;
|
|
39737
39673
|
try {
|
|
39738
|
-
entries =
|
|
39674
|
+
entries = fs20.readdirSync(dir);
|
|
39739
39675
|
} catch {
|
|
39740
39676
|
return files;
|
|
39741
39677
|
}
|
|
@@ -39744,10 +39680,10 @@ function findSourceFiles3(dir, files = []) {
|
|
|
39744
39680
|
if (SKIP_DIRECTORIES3.has(entry)) {
|
|
39745
39681
|
continue;
|
|
39746
39682
|
}
|
|
39747
|
-
const fullPath =
|
|
39683
|
+
const fullPath = path27.join(dir, entry);
|
|
39748
39684
|
let stat;
|
|
39749
39685
|
try {
|
|
39750
|
-
stat =
|
|
39686
|
+
stat = fs20.statSync(fullPath);
|
|
39751
39687
|
} catch {
|
|
39752
39688
|
continue;
|
|
39753
39689
|
}
|
|
@@ -39840,7 +39776,7 @@ var todo_extract = tool({
|
|
|
39840
39776
|
return JSON.stringify(errorResult, null, 2);
|
|
39841
39777
|
}
|
|
39842
39778
|
const scanPath = resolvedPath;
|
|
39843
|
-
if (!
|
|
39779
|
+
if (!fs20.existsSync(scanPath)) {
|
|
39844
39780
|
const errorResult = {
|
|
39845
39781
|
error: `path not found: ${pathsInput}`,
|
|
39846
39782
|
total: 0,
|
|
@@ -39850,13 +39786,13 @@ var todo_extract = tool({
|
|
|
39850
39786
|
return JSON.stringify(errorResult, null, 2);
|
|
39851
39787
|
}
|
|
39852
39788
|
const filesToScan = [];
|
|
39853
|
-
const stat =
|
|
39789
|
+
const stat = fs20.statSync(scanPath);
|
|
39854
39790
|
if (stat.isFile()) {
|
|
39855
39791
|
if (isSupportedExtension(scanPath)) {
|
|
39856
39792
|
filesToScan.push(scanPath);
|
|
39857
39793
|
} else {
|
|
39858
39794
|
const errorResult = {
|
|
39859
|
-
error: `unsupported file extension: ${
|
|
39795
|
+
error: `unsupported file extension: ${path27.extname(scanPath)}`,
|
|
39860
39796
|
total: 0,
|
|
39861
39797
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
39862
39798
|
entries: []
|
|
@@ -39869,11 +39805,11 @@ var todo_extract = tool({
|
|
|
39869
39805
|
const allEntries = [];
|
|
39870
39806
|
for (const filePath of filesToScan) {
|
|
39871
39807
|
try {
|
|
39872
|
-
const fileStat =
|
|
39808
|
+
const fileStat = fs20.statSync(filePath);
|
|
39873
39809
|
if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
|
|
39874
39810
|
continue;
|
|
39875
39811
|
}
|
|
39876
|
-
const content =
|
|
39812
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
39877
39813
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
39878
39814
|
allEntries.push(...entries);
|
|
39879
39815
|
} catch {}
|
|
@@ -39944,7 +39880,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
39944
39880
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
39945
39881
|
preflightTriggerManager = new PTM(automationConfig);
|
|
39946
39882
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
39947
|
-
const swarmDir =
|
|
39883
|
+
const swarmDir = path28.resolve(ctx.directory, ".swarm");
|
|
39948
39884
|
statusArtifact = new ASA(swarmDir);
|
|
39949
39885
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
39950
39886
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|