opencode-swarm 6.10.0 → 6.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -474
- package/dist/background/trigger.d.ts +4 -0
- package/dist/hooks/pipeline-tracker.d.ts +5 -0
- package/dist/index.js +796 -259
- package/dist/sast/rules/index.d.ts +2 -2
- package/dist/state.d.ts +20 -0
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
2
|
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
|
-
};
|
|
18
3
|
var __export = (target, all) => {
|
|
19
4
|
for (var name2 in all)
|
|
20
5
|
__defProp(target, name2, {
|
|
@@ -15014,7 +14999,7 @@ async function buildPhaseSummary(phase) {
|
|
|
15014
14999
|
const taskIds = await listEvidenceTaskIds(".");
|
|
15015
15000
|
const phaseTaskIds = new Set(phase.tasks.map((t) => t.id));
|
|
15016
15001
|
const taskSummaries = [];
|
|
15017
|
-
const
|
|
15002
|
+
const _taskMap = new Map(phase.tasks.map((t) => [t.id, t]));
|
|
15018
15003
|
for (const task of phase.tasks) {
|
|
15019
15004
|
const summary = await buildTaskSummary(task, task.id);
|
|
15020
15005
|
taskSummaries.push(summary);
|
|
@@ -15670,6 +15655,9 @@ class PhaseBoundaryTrigger {
|
|
|
15670
15655
|
getCurrentPhase() {
|
|
15671
15656
|
return this.lastKnownPhase;
|
|
15672
15657
|
}
|
|
15658
|
+
get lastTriggeredPhaseValue() {
|
|
15659
|
+
return this.lastTriggeredPhase;
|
|
15660
|
+
}
|
|
15673
15661
|
detectBoundary(newPhase, completedTasks, totalTasks) {
|
|
15674
15662
|
if (newPhase === this.lastKnownPhase) {
|
|
15675
15663
|
return {
|
|
@@ -15847,7 +15835,7 @@ class PreflightTriggerManager {
|
|
|
15847
15835
|
enabled: this.isEnabled(),
|
|
15848
15836
|
mode,
|
|
15849
15837
|
currentPhase: this.trigger.getCurrentPhase(),
|
|
15850
|
-
lastTriggeredPhase: this.trigger
|
|
15838
|
+
lastTriggeredPhase: this.trigger.lastTriggeredPhaseValue,
|
|
15851
15839
|
pendingRequests: this.getQueueSize()
|
|
15852
15840
|
};
|
|
15853
15841
|
}
|
|
@@ -16053,7 +16041,7 @@ function readConfigFromFile(directory) {
|
|
|
16053
16041
|
return null;
|
|
16054
16042
|
}
|
|
16055
16043
|
}
|
|
16056
|
-
function validateConfigKey(path10, value,
|
|
16044
|
+
function validateConfigKey(path10, value, _config) {
|
|
16057
16045
|
const findings = [];
|
|
16058
16046
|
switch (path10) {
|
|
16059
16047
|
case "agents": {
|
|
@@ -29285,7 +29273,7 @@ async function runLint(linter, mode) {
|
|
|
29285
29273
|
` : "") + stderr;
|
|
29286
29274
|
}
|
|
29287
29275
|
if (output.length > MAX_OUTPUT_BYTES) {
|
|
29288
|
-
output = output.slice(0, MAX_OUTPUT_BYTES)
|
|
29276
|
+
output = `${output.slice(0, MAX_OUTPUT_BYTES)}
|
|
29289
29277
|
... (output truncated)`;
|
|
29290
29278
|
}
|
|
29291
29279
|
const result = {
|
|
@@ -29425,15 +29413,14 @@ function isBinaryFile(filePath, buffer) {
|
|
|
29425
29413
|
}
|
|
29426
29414
|
return nullCount > checkLen * BINARY_NULL_THRESHOLD;
|
|
29427
29415
|
}
|
|
29428
|
-
function scanLineForSecrets(line,
|
|
29416
|
+
function scanLineForSecrets(line, _lineNum) {
|
|
29429
29417
|
const results = [];
|
|
29430
29418
|
if (line.length > MAX_LINE_LENGTH) {
|
|
29431
29419
|
return results;
|
|
29432
29420
|
}
|
|
29433
29421
|
for (const pattern of SECRET_PATTERNS) {
|
|
29434
29422
|
pattern.regex.lastIndex = 0;
|
|
29435
|
-
let match;
|
|
29436
|
-
while ((match = pattern.regex.exec(line)) !== null) {
|
|
29423
|
+
for (let match = pattern.regex.exec(line);match !== null; match = pattern.regex.exec(line)) {
|
|
29437
29424
|
const fullMatch = match[0];
|
|
29438
29425
|
const redacted = pattern.redactTemplate(fullMatch);
|
|
29439
29426
|
results.push({
|
|
@@ -29544,7 +29531,7 @@ function isSymlinkLoop(realPath, visited) {
|
|
|
29544
29531
|
function isPathWithinScope(realPath, scanDir) {
|
|
29545
29532
|
const resolvedScanDir = path11.resolve(scanDir);
|
|
29546
29533
|
const resolvedRealPath = path11.resolve(realPath);
|
|
29547
|
-
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(resolvedScanDir
|
|
29534
|
+
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
|
|
29548
29535
|
}
|
|
29549
29536
|
function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
29550
29537
|
skippedDirs: 0,
|
|
@@ -29971,7 +29958,7 @@ var init_secretscan = __esm(() => {
|
|
|
29971
29958
|
parts2.push(`${skippedFiles + stats.fileErrors + stats.symlinkSkipped} files skipped (binary/oversized/symlinks/errors)`);
|
|
29972
29959
|
}
|
|
29973
29960
|
if (parts2.length > 0) {
|
|
29974
|
-
result.message = parts2.join("; ")
|
|
29961
|
+
result.message = `${parts2.join("; ")}.`;
|
|
29975
29962
|
}
|
|
29976
29963
|
let jsonOutput = JSON.stringify(result, null, 2);
|
|
29977
29964
|
if (jsonOutput.length > MAX_OUTPUT_BYTES2) {
|
|
@@ -30098,7 +30085,7 @@ async function detectTestFramework() {
|
|
|
30098
30085
|
if (fs7.existsSync(packageJsonPath)) {
|
|
30099
30086
|
const content = fs7.readFileSync(packageJsonPath, "utf-8");
|
|
30100
30087
|
const pkg = JSON.parse(content);
|
|
30101
|
-
const
|
|
30088
|
+
const _deps = pkg.dependencies || {};
|
|
30102
30089
|
const devDeps = pkg.devDependencies || {};
|
|
30103
30090
|
const scripts = pkg.scripts || {};
|
|
30104
30091
|
if (scripts.test?.includes("vitest"))
|
|
@@ -30179,7 +30166,7 @@ function getTestFilesFromConvention(sourceFiles) {
|
|
|
30179
30166
|
}
|
|
30180
30167
|
continue;
|
|
30181
30168
|
}
|
|
30182
|
-
for (const
|
|
30169
|
+
for (const _pattern of TEST_PATTERNS) {
|
|
30183
30170
|
const nameWithoutExt = basename2.replace(/\.[^.]+$/, "");
|
|
30184
30171
|
const ext = path12.extname(basename2);
|
|
30185
30172
|
const possibleTestFiles = [
|
|
@@ -30210,7 +30197,8 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30210
30197
|
const testDir = path12.dirname(testFile);
|
|
30211
30198
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
30212
30199
|
let match;
|
|
30213
|
-
|
|
30200
|
+
match = importRegex.exec(content);
|
|
30201
|
+
while (match !== null) {
|
|
30214
30202
|
const importPath = match[1];
|
|
30215
30203
|
let resolvedImport;
|
|
30216
30204
|
if (importPath.startsWith(".")) {
|
|
@@ -30248,9 +30236,11 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30248
30236
|
break;
|
|
30249
30237
|
}
|
|
30250
30238
|
}
|
|
30239
|
+
match = importRegex.exec(content);
|
|
30251
30240
|
}
|
|
30252
30241
|
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
30253
|
-
|
|
30242
|
+
match = requireRegex.exec(content);
|
|
30243
|
+
while (match !== null) {
|
|
30254
30244
|
const importPath = match[1];
|
|
30255
30245
|
if (importPath.startsWith(".")) {
|
|
30256
30246
|
let resolvedImport = path12.resolve(testDir, importPath);
|
|
@@ -30285,6 +30275,7 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30285
30275
|
}
|
|
30286
30276
|
}
|
|
30287
30277
|
}
|
|
30278
|
+
match = requireRegex.exec(content);
|
|
30288
30279
|
}
|
|
30289
30280
|
} catch {}
|
|
30290
30281
|
}
|
|
@@ -30516,7 +30507,7 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
|
|
|
30516
30507
|
}
|
|
30517
30508
|
truncIndex--;
|
|
30518
30509
|
}
|
|
30519
|
-
output = output.slice(0, truncIndex)
|
|
30510
|
+
output = `${output.slice(0, truncIndex)}
|
|
30520
30511
|
... (output truncated)`;
|
|
30521
30512
|
}
|
|
30522
30513
|
const { totals, coveragePercent } = parseTestOutput(framework, output);
|
|
@@ -30672,7 +30663,7 @@ var init_test_runner = __esm(() => {
|
|
|
30672
30663
|
return JSON.stringify(errorResult, null, 2);
|
|
30673
30664
|
}
|
|
30674
30665
|
const scope = args2.scope || "all";
|
|
30675
|
-
const
|
|
30666
|
+
const _files = args2.files || [];
|
|
30676
30667
|
const coverage = args2.coverage || false;
|
|
30677
30668
|
const timeout_ms = Math.min(args2.timeout_ms || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
30678
30669
|
const framework = await detectTestFramework();
|
|
@@ -30798,7 +30789,7 @@ function getVersionFileVersion(dir) {
|
|
|
30798
30789
|
}
|
|
30799
30790
|
return null;
|
|
30800
30791
|
}
|
|
30801
|
-
async function runVersionCheck(dir,
|
|
30792
|
+
async function runVersionCheck(dir, _timeoutMs) {
|
|
30802
30793
|
const startTime = Date.now();
|
|
30803
30794
|
try {
|
|
30804
30795
|
const packageVersion = getPackageVersion(dir);
|
|
@@ -30854,7 +30845,7 @@ async function runVersionCheck(dir, timeoutMs) {
|
|
|
30854
30845
|
};
|
|
30855
30846
|
}
|
|
30856
30847
|
}
|
|
30857
|
-
async function runLintCheck(
|
|
30848
|
+
async function runLintCheck(_dir, linter, timeoutMs) {
|
|
30858
30849
|
const startTime = Date.now();
|
|
30859
30850
|
try {
|
|
30860
30851
|
const lintPromise = runLint(linter, "check");
|
|
@@ -30921,7 +30912,7 @@ async function runLintCheck(dir, linter, timeoutMs) {
|
|
|
30921
30912
|
};
|
|
30922
30913
|
}
|
|
30923
30914
|
}
|
|
30924
|
-
async function runTestsCheck(
|
|
30915
|
+
async function runTestsCheck(_dir, scope, timeoutMs) {
|
|
30925
30916
|
const startTime = Date.now();
|
|
30926
30917
|
try {
|
|
30927
30918
|
const result = await runTests("none", scope, [], false, timeoutMs);
|
|
@@ -31366,7 +31357,7 @@ var init_preflight_integration = __esm(() => {
|
|
|
31366
31357
|
});
|
|
31367
31358
|
|
|
31368
31359
|
// src/index.ts
|
|
31369
|
-
import * as
|
|
31360
|
+
import * as path32 from "path";
|
|
31370
31361
|
|
|
31371
31362
|
// src/config/constants.ts
|
|
31372
31363
|
var QA_AGENTS = ["reviewer", "critic"];
|
|
@@ -31958,18 +31949,104 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
|
|
|
31958
31949
|
|
|
31959
31950
|
## RULES
|
|
31960
31951
|
|
|
31952
|
+
NAMESPACE RULE: "Phase N" and "Task N.M" ALWAYS refer to the PROJECT PLAN in .swarm/plan.md.
|
|
31953
|
+
Your operational modes (RESUME, CLARIFY, DISCOVER, CONSULT, PLAN, CRITIC-GATE, EXECUTE, PHASE-WRAP) are NEVER called "phases."
|
|
31954
|
+
Do not confuse your operational mode with the project's phase number.
|
|
31955
|
+
When you are in MODE: EXECUTE working on project Phase 3, Task 3.2 \u2014 your mode is EXECUTE. You are NOT in "Phase 3."
|
|
31956
|
+
Do not re-trigger DISCOVER or CONSULT because you noticed a project phase boundary.
|
|
31957
|
+
Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels into plan.md.
|
|
31958
|
+
|
|
31961
31959
|
1. DELEGATE all coding to {{AGENT_PREFIX}}coder. You do NOT write code.
|
|
31960
|
+
YOUR TOOLS: Task (delegation), diff, syntax_check, placeholder_scan, imports, lint, secretscan, sast_scan, build_check, pre_check_batch, quality_budget, symbols, complexity_hotspots, schema_drift, todo_extract, evidence_check, sbom_generate, checkpoint, pkg_audit, test_runner.
|
|
31961
|
+
CODER'S TOOLS: write, edit, patch, apply_patch, create_file, insert, replace \u2014 any tool that modifies file contents.
|
|
31962
|
+
If a tool modifies a file, it is a CODER tool. Delegate.
|
|
31962
31963
|
2. ONE agent per message. Send, STOP, wait for response.
|
|
31963
31964
|
3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
|
|
31965
|
+
BATCHING DETECTION \u2014 you are batching if your coder delegation contains ANY of:
|
|
31966
|
+
- The word "and" connecting two actions ("update X AND add Y")
|
|
31967
|
+
- Multiple FILE paths ("FILE: src/a.ts, src/b.ts, src/c.ts")
|
|
31968
|
+
- Multiple TASK objectives ("TASK: Refactor the processor and update the config")
|
|
31969
|
+
- Phrases like "also", "while you're at it", "additionally", "as well"
|
|
31970
|
+
|
|
31971
|
+
WHY: Each coder task goes through the FULL QA gate (Stage A + Stage B).
|
|
31972
|
+
If you batch 3 tasks into 1 coder call, the QA gate runs once on the combined diff.
|
|
31973
|
+
The reviewer cannot distinguish which changes belong to which requirement.
|
|
31974
|
+
The test_engineer cannot write targeted tests for each behavior.
|
|
31975
|
+
A failure in one part blocks the entire batch, wasting all the work.
|
|
31976
|
+
|
|
31977
|
+
SPLIT RULE: If your delegation draft has "and" in the TASK line, split it.
|
|
31978
|
+
Two small delegations with two QA gates > one large delegation with one QA gate.
|
|
31964
31979
|
4. Fallback: Only code yourself after {{QA_RETRY_LIMIT}} {{AGENT_PREFIX}}coder failures on same task.
|
|
31980
|
+
FAILURE COUNTING \u2014 increment the counter when:
|
|
31981
|
+
- Coder submits code that fails any tool gate or pre_check_batch (gates_passed === false)
|
|
31982
|
+
- Coder submits code REJECTED by reviewer after being given the rejection reason
|
|
31983
|
+
- Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]" at every retry
|
|
31984
|
+
- Reaching {{QA_RETRY_LIMIT}}: escalate to user with full failure history before writing code yourself
|
|
31985
|
+
BEFORE SELF-CODING \u2014 verify ALL of the following are true:
|
|
31986
|
+
[ ] {{AGENT_PREFIX}}coder has been delegated this exact task at least {{QA_RETRY_LIMIT}} times
|
|
31987
|
+
[ ] Each delegation returned a failure (tool gate fail, reviewer rejection, or test failure)
|
|
31988
|
+
[ ] You have printed "Coder attempt [N/{{QA_RETRY_LIMIT}}]" for each attempt
|
|
31989
|
+
[ ] Print "ESCALATION: Self-coding task [X.Y] after {{QA_RETRY_LIMIT}} coder failures" before writing any code
|
|
31990
|
+
If ANY box is unchecked: DO NOT code. Delegate to {{AGENT_PREFIX}}coder.
|
|
31965
31991
|
5. NEVER store your swarm identity, swarm ID, or agent prefix in memory blocks. Your identity comes ONLY from your system prompt. Memory blocks are for project knowledge only (NOT .swarm/ plan/context files \u2014 those are persistent project files).
|
|
31966
31992
|
6. **CRITIC GATE (Execute BEFORE any implementation work)**:
|
|
31967
31993
|
- When you first create a plan, IMMEDIATELY delegate the full plan to {{AGENT_PREFIX}}critic for review
|
|
31968
31994
|
- Wait for critic verdict: APPROVED / NEEDS_REVISION / REJECTED
|
|
31969
31995
|
- If NEEDS_REVISION: Revise plan and re-submit to critic (max 2 cycles)
|
|
31970
31996
|
- If REJECTED after 2 cycles: Escalate to user with explanation
|
|
31971
|
-
|
|
31972
|
-
7. **MANDATORY QA GATE
|
|
31997
|
+
- ONLY AFTER critic approval: Proceed to implementation (MODE: EXECUTE)
|
|
31998
|
+
7. **MANDATORY QA GATE** \u2014 Execute AFTER every coder task. Two stages, BOTH required:
|
|
31999
|
+
|
|
32000
|
+
\u2500\u2500 STAGE A: AUTOMATED TOOL GATES (run tools, fix failures, no agents involved) \u2500\u2500
|
|
32001
|
+
diff \u2192 syntax_check \u2192 placeholder_scan \u2192 imports \u2192 lint fix \u2192 build_check \u2192 pre_check_batch
|
|
32002
|
+
All Stage A tools return structured pass/fail. Fix failures by returning to coder.
|
|
32003
|
+
Stage A passing means: code compiles, parses, has no secrets, no placeholders, no lint errors.
|
|
32004
|
+
Stage A passing does NOT mean: code is correct, secure, tested, or reviewed.
|
|
32005
|
+
|
|
32006
|
+
\u2500\u2500 STAGE B: AGENT REVIEW GATES (delegate to agents, wait for verdicts) \u2500\u2500
|
|
32007
|
+
{{AGENT_PREFIX}}reviewer \u2192 security reviewer (conditional) \u2192 {{AGENT_PREFIX}}test_engineer verification \u2192 {{AGENT_PREFIX}}test_engineer adversarial \u2192 coverage check
|
|
32008
|
+
Stage B CANNOT be skipped. Stage A passing does not satisfy Stage B.
|
|
32009
|
+
Stage B is where logic errors, security flaws, edge cases, and behavioral bugs are caught.
|
|
32010
|
+
You MUST delegate to each Stage B agent and wait for their response.
|
|
32011
|
+
|
|
32012
|
+
A task is complete ONLY when BOTH stages pass.
|
|
32013
|
+
ANTI-EXEMPTION RULES \u2014 these thoughts are WRONG and must be ignored:
|
|
32014
|
+
\u2717 "It's a simple change" \u2192 gates are mandatory for ALL changes regardless of perceived complexity
|
|
32015
|
+
\u2717 "It's just a rename / refactor / config tweak" \u2192 same
|
|
32016
|
+
\u2717 "The code looks straightforward" \u2192 you are the author; authors are blind to their own mistakes
|
|
32017
|
+
\u2717 "I already reviewed it mentally" \u2192 mental review does not satisfy any gate
|
|
32018
|
+
\u2717 "It'll be fine" \u2192 this is how production data loss happens
|
|
32019
|
+
\u2717 "The tests will catch it" \u2192 tests do not run without being delegated to {{AGENT_PREFIX}}test_engineer
|
|
32020
|
+
\u2717 "It's just one file" \u2192 file count does not determine gate requirements
|
|
32021
|
+
\u2717 "pre_check_batch will catch any issues" \u2192 pre_check_batch only runs if you run it
|
|
32022
|
+
|
|
32023
|
+
There are NO simple changes. There are NO exceptions to the QA gate sequence.
|
|
32024
|
+
The gates exist because the author cannot objectively evaluate their own work.
|
|
32025
|
+
|
|
32026
|
+
PARTIAL GATE RATIONALIZATIONS \u2014 running SOME gates is NOT compliance:
|
|
32027
|
+
\u2717 "I ran pre_check_batch so the code is verified" \u2192 pre_check_batch does NOT replace {{AGENT_PREFIX}}reviewer or {{AGENT_PREFIX}}test_engineer
|
|
32028
|
+
\u2717 "syntax_check passed, good enough" \u2192 syntax_check catches syntax. Reviewer catches logic. Test_engineer catches behavior. All three are required.
|
|
32029
|
+
\u2717 "The mechanical gates passed, skip the agent gates" \u2192 agent reviews (reviewer, test_engineer) exist because automated tools miss logic errors, security flaws, and edge cases
|
|
32030
|
+
\u2717 "It's Phase 6+, the codebase is stable now" \u2192 complacency after successful phases is the #1 predictor of shipped bugs. Phase 6 needs MORE review, not less.
|
|
32031
|
+
\u2717 "I'll just run the fast gates" \u2192 speed of a gate does not determine whether it is required
|
|
32032
|
+
\u2717 "5 phases passed clean, this one will be fine" \u2192 past success does not predict future correctness
|
|
32033
|
+
|
|
32034
|
+
Running syntax_check + pre_check_batch without reviewer + test_engineer is a PARTIAL GATE VIOLATION.
|
|
32035
|
+
It is the same severity as skipping all gates. The QA gate is ALL steps or NONE.
|
|
32036
|
+
|
|
32037
|
+
ANTI-SELF-CODING RULES \u2014 these thoughts are WRONG and must be ignored:
|
|
32038
|
+
\u2717 "It's just a schema change / config flag / one-liner" \u2192 delegate to {{AGENT_PREFIX}}coder
|
|
32039
|
+
\u2717 "I already know what to write" \u2192 knowing what to write is planning. Writing it is coding. Delegate.
|
|
32040
|
+
\u2717 "It's faster if I just do it" \u2192 speed without QA gates is how bugs ship
|
|
32041
|
+
\u2717 "The coder succeeded on the last tasks, this one is trivial" \u2192 Rule 1 has no complexity exemption
|
|
32042
|
+
\u2717 "I'll just use apply_patch / edit / write directly" \u2192 these are coder tools, not architect tools
|
|
32043
|
+
\u2717 "It's just adding a column / field / import" \u2192 delegate to {{AGENT_PREFIX}}coder
|
|
32044
|
+
\u2717 "I'll do the simple parts, coder does the hard parts" \u2192 ALL parts go to coder. You are not a coder.
|
|
32045
|
+
|
|
32046
|
+
If you catch yourself reaching for a code editing tool: STOP. Delegate to {{AGENT_PREFIX}}coder.
|
|
32047
|
+
Zero {{AGENT_PREFIX}}coder failures on this task = zero justification for self-coding.
|
|
32048
|
+
Rule 4 requires {{QA_RETRY_LIMIT}} failures before you may code. Not 0. Not "it seemed simpler."
|
|
32049
|
+
Self-coding without {{QA_RETRY_LIMIT}} failures is a Rule 1 violation \u2014 identical severity to skipping QA gates.
|
|
31973
32050
|
- After coder completes: run \`diff\` tool. If \`hasContractChanges\` is true \u2192 delegate {{AGENT_PREFIX}}explorer for integration impact analysis. BREAKING \u2192 return to coder. COMPATIBLE \u2192 proceed.
|
|
31974
32051
|
- Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
|
|
31975
32052
|
- Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports check.
|
|
@@ -32107,7 +32184,7 @@ OUTPUT: Code scaffold for src/pages/Settings.tsx with component tree, typed prop
|
|
|
32107
32184
|
|
|
32108
32185
|
## WORKFLOW
|
|
32109
32186
|
|
|
32110
|
-
###
|
|
32187
|
+
### MODE: RESUME
|
|
32111
32188
|
If .swarm/plan.md exists:
|
|
32112
32189
|
1. Read plan.md header for "Swarm:" field
|
|
32113
32190
|
2. If Swarm field missing or matches "{{SWARM_ID}}" \u2192 Resume at current task
|
|
@@ -32118,14 +32195,14 @@ If .swarm/plan.md exists:
|
|
|
32118
32195
|
- Update context.md Swarm field to "{{SWARM_ID}}"
|
|
32119
32196
|
- Inform user: "Resuming project from [other] swarm. Cleared stale context. Ready to continue."
|
|
32120
32197
|
- Resume at current task
|
|
32121
|
-
If .swarm/plan.md does not exist \u2192 New project, proceed to
|
|
32198
|
+
If .swarm/plan.md does not exist \u2192 New project, proceed to MODE: CLARIFY
|
|
32122
32199
|
If new project: Run \`complexity_hotspots\` tool (90 days) to generate a risk map. Note modules with recommendation "security_review" or "full_gates" in context.md for stricter QA gates during Phase 5. Optionally run \`todo_extract\` to capture existing technical debt for plan consideration. After initial discovery, run \`sbom_generate\` with scope='all' to capture baseline dependency inventory (saved to .swarm/evidence/sbom/).
|
|
32123
32200
|
|
|
32124
|
-
###
|
|
32201
|
+
### MODE: CLARIFY
|
|
32125
32202
|
Ambiguous request \u2192 Ask up to 3 questions, wait for answers
|
|
32126
|
-
Clear request \u2192
|
|
32203
|
+
Clear request \u2192 MODE: DISCOVER
|
|
32127
32204
|
|
|
32128
|
-
###
|
|
32205
|
+
### MODE: DISCOVER
|
|
32129
32206
|
Delegate to {{AGENT_PREFIX}}explorer. Wait for response.
|
|
32130
32207
|
For complex tasks, make a second explorer call focused on risk/gap analysis:
|
|
32131
32208
|
- Hidden requirements, unstated assumptions, scope risks
|
|
@@ -32134,40 +32211,98 @@ After explorer returns:
|
|
|
32134
32211
|
- Run \`symbols\` tool on key files identified by explorer to understand public API surfaces
|
|
32135
32212
|
- Run \`complexity_hotspots\` if not already run in Phase 0 (check context.md for existing analysis). Note modules with recommendation "security_review" or "full_gates" in context.md.
|
|
32136
32213
|
|
|
32137
|
-
###
|
|
32214
|
+
### MODE: CONSULT
|
|
32138
32215
|
Check .swarm/context.md for cached guidance first.
|
|
32139
32216
|
Identify 1-3 relevant domains from the task requirements.
|
|
32140
32217
|
Call {{AGENT_PREFIX}}sme once per domain, serially. Max 3 SME calls per project phase.
|
|
32141
32218
|
Re-consult if a new domain emerges or if significant changes require fresh evaluation.
|
|
32142
32219
|
Cache guidance in context.md.
|
|
32143
32220
|
|
|
32144
|
-
###
|
|
32145
|
-
|
|
32221
|
+
### MODE: PLAN
|
|
32222
|
+
|
|
32223
|
+
Create .swarm/plan.md
|
|
32146
32224
|
- Phases with discrete tasks
|
|
32147
32225
|
- Dependencies (depends: X.Y)
|
|
32148
32226
|
- Acceptance criteria per task
|
|
32149
32227
|
|
|
32150
|
-
|
|
32228
|
+
TASK GRANULARITY RULES:
|
|
32229
|
+
- SMALL task: 1 file, 1 function/class/component, 1 logical concern. Delegate as-is.
|
|
32230
|
+
- MEDIUM task: If it touches >1 file, SPLIT into sequential file-scoped subtasks before writing to plan.
|
|
32231
|
+
- LARGE task: MUST be decomposed before writing to plan. A LARGE task in the plan is a planning error.
|
|
32232
|
+
- Litmus test: If you cannot write TASK + FILE + constraint in 3 bullet points, the task is too large. Split it.
|
|
32233
|
+
- NEVER write a task with compound verbs: "implement X and add Y and update Z" = 3 tasks, not 1. Split before writing to plan.
|
|
32234
|
+
- Coder receives ONE task. You make ALL scope decisions in the plan. Coder makes zero scope decisions.
|
|
32235
|
+
|
|
32236
|
+
Create .swarm/context.md
|
|
32151
32237
|
- Decisions, patterns, SME cache, file map
|
|
32152
32238
|
|
|
32153
|
-
###
|
|
32239
|
+
### MODE: CRITIC-GATE
|
|
32154
32240
|
Delegate plan to {{AGENT_PREFIX}}critic for review BEFORE any implementation begins.
|
|
32155
32241
|
- Send the full plan.md content and codebase context summary
|
|
32156
|
-
- **APPROVED** \u2192 Proceed to
|
|
32157
|
-
- **NEEDS_REVISION** \u2192 Revise the plan based on critic feedback, then resubmit (max 2
|
|
32242
|
+
- **APPROVED** \u2192 Proceed to MODE: EXECUTE
|
|
32243
|
+
- **NEEDS_REVISION** \u2192 Revise the plan based on critic feedback, then resubmit (max 2 cycles)
|
|
32158
32244
|
- **REJECTED** \u2192 Inform the user of fundamental issues and ask for guidance before proceeding
|
|
32159
32245
|
|
|
32160
|
-
|
|
32246
|
+
\u26D4 HARD STOP \u2014 Print this checklist before advancing to MODE: EXECUTE:
|
|
32247
|
+
[ ] {{AGENT_PREFIX}}critic returned a verdict
|
|
32248
|
+
[ ] APPROVED \u2192 proceed to MODE: EXECUTE
|
|
32249
|
+
[ ] NEEDS_REVISION \u2192 revised and resubmitted (attempt N of max 2)
|
|
32250
|
+
[ ] REJECTED (any cycle) \u2192 informed user. STOP.
|
|
32251
|
+
|
|
32252
|
+
You MUST NOT proceed to MODE: EXECUTE without printing this checklist with filled values.
|
|
32253
|
+
|
|
32254
|
+
CRITIC-GATE TRIGGER: Run ONCE when you first write the complete .swarm/plan.md.
|
|
32255
|
+
Do NOT re-run CRITIC-GATE before every project phase.
|
|
32256
|
+
If resuming a project with an existing approved plan, CRITIC-GATE is already satisfied.
|
|
32257
|
+
|
|
32258
|
+
### MODE: EXECUTE
|
|
32161
32259
|
For each task (respecting dependencies):
|
|
32162
32260
|
|
|
32261
|
+
RETRY PROTOCOL \u2014 when returning to coder after any gate failure:
|
|
32262
|
+
1. Provide structured rejection: "GATE FAILED: [gate name] | REASON: [details] | REQUIRED FIX: [specific action required]"
|
|
32263
|
+
2. Re-enter at step 5b ({{AGENT_PREFIX}}coder) with full failure context
|
|
32264
|
+
3. Resume execution at the failed step (do not restart from 5a)
|
|
32265
|
+
Exception: if coder modified files outside the original task scope, restart from step 5c
|
|
32266
|
+
4. Gates already PASSED may be skipped on retry if their input files are unchanged
|
|
32267
|
+
5. Print "Resuming at step [5X] after coder retry [N/{{QA_RETRY_LIMIT}}]" before re-executing
|
|
32268
|
+
|
|
32269
|
+
GATE FAILURE RESPONSE RULES \u2014 when ANY gate returns a failure:
|
|
32270
|
+
You MUST return to {{AGENT_PREFIX}}coder. You MUST NOT fix the code yourself.
|
|
32271
|
+
|
|
32272
|
+
WRONG responses to gate failure:
|
|
32273
|
+
\u2717 Editing the file yourself to fix the syntax error
|
|
32274
|
+
\u2717 Running a tool to auto-fix and moving on without coder
|
|
32275
|
+
\u2717 "Installing" or "configuring" tools to work around the failure
|
|
32276
|
+
\u2717 Treating the failure as an environment issue and proceeding
|
|
32277
|
+
\u2717 Deciding the failure is a false positive and skipping the gate
|
|
32278
|
+
|
|
32279
|
+
RIGHT response to gate failure:
|
|
32280
|
+
\u2713 Print "GATE FAILED: [gate name] | REASON: [details]"
|
|
32281
|
+
\u2713 Delegate to {{AGENT_PREFIX}}coder with:
|
|
32282
|
+
TASK: Fix [gate name] failure
|
|
32283
|
+
FILE: [affected file(s)]
|
|
32284
|
+
INPUT: [exact error output from the gate]
|
|
32285
|
+
CONSTRAINT: Fix ONLY the reported issue, do not modify other code
|
|
32286
|
+
\u2713 After coder returns, re-run the failed gate from the step that failed
|
|
32287
|
+
\u2713 Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]"
|
|
32288
|
+
|
|
32289
|
+
The ONLY exception: lint tool in fix mode (step 5g) auto-corrects by design.
|
|
32290
|
+
All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
|
|
32291
|
+
|
|
32163
32292
|
5a. **UI DESIGN GATE** (conditional \u2014 Rule 9): If task matches UI trigger \u2192 {{AGENT_PREFIX}}designer produces scaffold \u2192 pass scaffold to coder as INPUT. If no match \u2192 skip.
|
|
32164
32293
|
5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
|
|
32165
32294
|
5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
|
|
32295
|
+
\u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
|
|
32166
32296
|
5d. Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
|
|
32297
|
+
\u2192 REQUIRED: Print "syntaxcheck: [PASS | FAIL \u2014 N errors]"
|
|
32167
32298
|
5e. Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports.
|
|
32299
|
+
\u2192 REQUIRED: Print "placeholderscan: [PASS | FAIL \u2014 N findings]"
|
|
32168
32300
|
5f. Run \`imports\` tool for dependency audit. ISSUES \u2192 return to coder.
|
|
32301
|
+
\u2192 REQUIRED: Print "imports: [PASS | ISSUES \u2014 details]"
|
|
32169
32302
|
5g. Run \`lint\` tool with fix mode for auto-fixes. If issues remain \u2192 run \`lint\` tool with check mode. FAIL \u2192 return to coder.
|
|
32303
|
+
\u2192 REQUIRED: Print "lint: [PASS | FAIL \u2014 details]"
|
|
32170
32304
|
5h. Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
|
|
32305
|
+
\u2192 REQUIRED: Print "buildcheck: [PASS | FAIL | SKIPPED \u2014 no toolchain]"
|
|
32171
32306
|
5i. Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
|
|
32172
32307
|
- lint:check (code quality verification)
|
|
32173
32308
|
- secretscan (secret detection)
|
|
@@ -32176,14 +32311,66 @@ For each task (respecting dependencies):
|
|
|
32176
32311
|
\u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
|
|
32177
32312
|
\u2192 If gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to @coder with specific tool failures. Do NOT call @reviewer.
|
|
32178
32313
|
\u2192 If gates_passed === true: proceed to @reviewer.
|
|
32314
|
+
\u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | FAIL \u2014 [gate]: [details]]"
|
|
32315
|
+
|
|
32316
|
+
\u26A0\uFE0F pre_check_batch SCOPE BOUNDARY:
|
|
32317
|
+
pre_check_batch runs FOUR automated tools: lint:check, secretscan, sast_scan, quality_budget.
|
|
32318
|
+
pre_check_batch does NOT run and does NOT replace:
|
|
32319
|
+
- {{AGENT_PREFIX}}reviewer (logic review, correctness, edge cases, maintainability)
|
|
32320
|
+
- {{AGENT_PREFIX}}reviewer security-only pass (OWASP evaluation, auth/crypto review)
|
|
32321
|
+
- {{AGENT_PREFIX}}test_engineer verification tests (functional correctness)
|
|
32322
|
+
- {{AGENT_PREFIX}}test_engineer adversarial tests (attack vectors, boundary violations)
|
|
32323
|
+
- diff tool (contract change detection)
|
|
32324
|
+
- placeholder_scan (TODO/stub detection)
|
|
32325
|
+
- imports (dependency audit)
|
|
32326
|
+
gates_passed: true means "automated static checks passed."
|
|
32327
|
+
It does NOT mean "code is reviewed." It does NOT mean "code is tested."
|
|
32328
|
+
After pre_check_batch passes, you MUST STILL delegate to {{AGENT_PREFIX}}reviewer.
|
|
32329
|
+
Treating pre_check_batch as a substitute for reviewer is a PROCESS VIOLATION.
|
|
32330
|
+
|
|
32179
32331
|
5j. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
|
|
32332
|
+
\u2192 REQUIRED: Print "reviewer: [APPROVED | REJECTED \u2014 reason]"
|
|
32180
32333
|
5k. Security gate: if file matches security globs (auth, api, crypto, security, middleware, session, token, config/, env, credentials, authorization, roles, permissions, access) OR content has security keywords (see SECURITY_KEYWORDS list) OR secretscan has ANY findings OR sast_scan has ANY findings at or above threshold \u2192 MUST delegate {{AGENT_PREFIX}}reviewer security-only review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate to user.
|
|
32334
|
+
\u2192 REQUIRED: Print "security-reviewer: [TRIGGERED | NOT TRIGGERED \u2014 reason]"
|
|
32335
|
+
\u2192 If TRIGGERED: Print "security-reviewer: [APPROVED | REJECTED \u2014 reason]"
|
|
32181
32336
|
5l. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5g.
|
|
32337
|
+
\u2192 REQUIRED: Print "testengineer-verification: [PASS N/N | FAIL \u2014 details]"
|
|
32182
32338
|
5m. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5g.
|
|
32339
|
+
\u2192 REQUIRED: Print "testengineer-adversarial: [PASS | FAIL \u2014 details]"
|
|
32183
32340
|
5n. COVERAGE CHECK: If test_engineer reports coverage < 70% \u2192 delegate {{AGENT_PREFIX}}test_engineer for an additional test pass targeting uncovered paths. This is a soft guideline; use judgment for trivial tasks.
|
|
32341
|
+
|
|
32342
|
+
PRE-COMMIT RULE \u2014 Before ANY commit or push:
|
|
32343
|
+
You MUST answer YES to ALL of the following:
|
|
32344
|
+
[ ] Did {{AGENT_PREFIX}}reviewer run and return APPROVED? (not "I reviewed it" \u2014 the agent must have run)
|
|
32345
|
+
[ ] Did {{AGENT_PREFIX}}test_engineer run and return PASS? (not "the code looks correct" \u2014 the agent must have run)
|
|
32346
|
+
[ ] Did pre_check_batch run with gates_passed true?
|
|
32347
|
+
[ ] Did the diff step run?
|
|
32348
|
+
|
|
32349
|
+
If ANY box is unchecked: DO NOT COMMIT. Return to step 5b.
|
|
32350
|
+
There is no override. A commit without a completed QA gate is a workflow violation.
|
|
32351
|
+
|
|
32352
|
+
5o. \u26D4 TASK COMPLETION GATE \u2014 You MUST print this checklist with filled values before marking \u2713 in .swarm/plan.md:
|
|
32353
|
+
[TOOL] diff: PASS / SKIP \u2014 value: ___
|
|
32354
|
+
[TOOL] syntax_check: PASS \u2014 value: ___
|
|
32355
|
+
[TOOL] placeholder_scan: PASS \u2014 value: ___
|
|
32356
|
+
[TOOL] imports: PASS \u2014 value: ___
|
|
32357
|
+
[TOOL] lint: PASS \u2014 value: ___
|
|
32358
|
+
[TOOL] build_check: PASS / SKIPPED \u2014 value: ___
|
|
32359
|
+
[TOOL] pre_check_batch: PASS (lint:check \u2713 secretscan \u2713 sast_scan \u2713 quality_budget \u2713) \u2014 value: ___
|
|
32360
|
+
[GATE] reviewer: APPROVED \u2014 value: ___
|
|
32361
|
+
[GATE] security-reviewer: APPROVED / SKIPPED \u2014 value: ___
|
|
32362
|
+
[GATE] test_engineer-verification: PASS \u2014 value: ___
|
|
32363
|
+
[GATE] test_engineer-adversarial: PASS \u2014 value: ___
|
|
32364
|
+
[GATE] coverage: \u226570% / soft-skip \u2014 value: ___
|
|
32365
|
+
|
|
32366
|
+
You MUST NOT mark a task complete without printing this checklist with filled values.
|
|
32367
|
+
You MUST NOT fill "PASS" or "APPROVED" for a gate you did not actually run \u2014 that is fabrication.
|
|
32368
|
+
Any blank "value: ___" field = gate was not run = task is NOT complete.
|
|
32369
|
+
Filling this checklist from memory ("I think I ran it") is INVALID. Each value must come from actual tool/agent output in this session.
|
|
32370
|
+
|
|
32184
32371
|
5o. Update plan.md [x], proceed to next task.
|
|
32185
32372
|
|
|
32186
|
-
###
|
|
32373
|
+
### MODE: PHASE-WRAP
|
|
32187
32374
|
1. {{AGENT_PREFIX}}explorer - Rescan
|
|
32188
32375
|
2. {{AGENT_PREFIX}}docs - Update documentation for all changes in this phase. Provide:
|
|
32189
32376
|
- Complete list of files changed during this phase
|
|
@@ -32196,6 +32383,15 @@ For each task (respecting dependencies):
|
|
|
32196
32383
|
6. Summarize to user
|
|
32197
32384
|
7. Ask: "Ready for Phase [N+1]?"
|
|
32198
32385
|
|
|
32386
|
+
CATASTROPHIC VIOLATION CHECK \u2014 ask yourself at EVERY phase boundary (MODE: PHASE-WRAP):
|
|
32387
|
+
"Have I delegated to {{AGENT_PREFIX}}reviewer at least once this phase?"
|
|
32388
|
+
If the answer is NO: you have a catastrophic process violation.
|
|
32389
|
+
STOP. Do not proceed to the next phase. Inform the user:
|
|
32390
|
+
"\u26D4 PROCESS VIOLATION: Phase [N] completed with zero reviewer delegations.
|
|
32391
|
+
All code changes in this phase are unreviewed. Recommend retrospective review before proceeding."
|
|
32392
|
+
This is not optional. Zero reviewer calls in a phase is always a violation.
|
|
32393
|
+
There is no project where code ships without review.
|
|
32394
|
+
|
|
32199
32395
|
### Blockers
|
|
32200
32396
|
Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
|
|
32201
32397
|
|
|
@@ -32276,7 +32472,14 @@ RULES:
|
|
|
32276
32472
|
|
|
32277
32473
|
OUTPUT FORMAT:
|
|
32278
32474
|
DONE: [one-line summary]
|
|
32279
|
-
CHANGED: [file]: [what changed]
|
|
32475
|
+
CHANGED: [file]: [what changed]
|
|
32476
|
+
|
|
32477
|
+
AUTHOR BLINDNESS WARNING:
|
|
32478
|
+
Your output is NOT reviewed, tested, or approved until the Architect runs the full QA gate.
|
|
32479
|
+
Do NOT add commentary like "this looks good," "should be fine," or "ready for production."
|
|
32480
|
+
You wrote the code. You cannot objectively evaluate it. That is what the gates are for.
|
|
32481
|
+
Output only: DONE [one-line summary] / CHANGED [file] [what changed]
|
|
32482
|
+
`;
|
|
32280
32483
|
function createCoderAgent(model, customPrompt, customAppendPrompt) {
|
|
32281
32484
|
let prompt = CODER_PROMPT;
|
|
32282
32485
|
if (customPrompt) {
|
|
@@ -32315,10 +32518,11 @@ CONTEXT: [codebase summary, constraints]
|
|
|
32315
32518
|
REVIEW CHECKLIST:
|
|
32316
32519
|
- Completeness: Are all requirements addressed? Missing edge cases?
|
|
32317
32520
|
- Feasibility: Can each task actually be implemented as described? Are file paths real?
|
|
32318
|
-
- Scope: Is the plan doing too much or too little? Feature creep detection
|
|
32521
|
+
- Scope: Is the plan doing too much or too little? Feature creep detection?
|
|
32319
32522
|
- Dependencies: Are task dependencies correct? Will ordering work?
|
|
32320
32523
|
- Risk: Are high-risk changes identified? Is there a rollback path?
|
|
32321
32524
|
- AI-Slop Detection: Does the plan contain vague filler ("robust", "comprehensive", "leverage") without concrete specifics?
|
|
32525
|
+
- Task Atomicity: Does any single task touch 2+ files or contain compound verbs ("implement X and add Y and update Z")? Flag as MAJOR \u2014 oversized tasks blow coder's context and cause downstream gate failures. Suggested fix: Split into sequential single-file tasks before proceeding.
|
|
32322
32526
|
|
|
32323
32527
|
OUTPUT FORMAT:
|
|
32324
32528
|
VERDICT: APPROVED | NEEDS_REVISION | REJECTED
|
|
@@ -33644,7 +33848,7 @@ class PlanSyncWorker {
|
|
|
33644
33848
|
log("[PlanSyncWorker] Swarm directory does not exist yet");
|
|
33645
33849
|
return false;
|
|
33646
33850
|
}
|
|
33647
|
-
this.watcher = fs3.watch(swarmDir, { persistent: false }, (
|
|
33851
|
+
this.watcher = fs3.watch(swarmDir, { persistent: false }, (_eventType, filename) => {
|
|
33648
33852
|
if (this.disposed || this.status !== "running") {
|
|
33649
33853
|
return;
|
|
33650
33854
|
}
|
|
@@ -33787,7 +33991,7 @@ class PlanSyncWorker {
|
|
|
33787
33991
|
withTimeout(promise2, ms, timeoutMessage) {
|
|
33788
33992
|
return new Promise((resolve4, reject) => {
|
|
33789
33993
|
const timer = setTimeout(() => {
|
|
33790
|
-
reject(new Error(timeoutMessage
|
|
33994
|
+
reject(new Error(`${timeoutMessage} (${ms}ms)`));
|
|
33791
33995
|
}, ms);
|
|
33792
33996
|
promise2.then((result) => {
|
|
33793
33997
|
clearTimeout(timer);
|
|
@@ -33952,7 +34156,15 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
|
33952
34156
|
activeInvocationId: 0,
|
|
33953
34157
|
lastInvocationIdByAgent: {},
|
|
33954
34158
|
windows: {},
|
|
33955
|
-
lastCompactionHint: 0
|
|
34159
|
+
lastCompactionHint: 0,
|
|
34160
|
+
architectWriteCount: 0,
|
|
34161
|
+
lastCoderDelegationTaskId: null,
|
|
34162
|
+
gateLog: new Map,
|
|
34163
|
+
reviewerCallCount: new Map,
|
|
34164
|
+
lastGateFailure: null,
|
|
34165
|
+
partialGateWarningIssued: false,
|
|
34166
|
+
selfFixAttempted: false,
|
|
34167
|
+
catastrophicPhaseWarnings: new Set
|
|
33956
34168
|
};
|
|
33957
34169
|
swarmState.agentSessions.set(sessionId, sessionState);
|
|
33958
34170
|
swarmState.activeAgent.set(sessionId, agentName);
|
|
@@ -33979,6 +34191,30 @@ function ensureAgentSession(sessionId, agentName) {
|
|
|
33979
34191
|
if (session.lastCompactionHint === undefined) {
|
|
33980
34192
|
session.lastCompactionHint = 0;
|
|
33981
34193
|
}
|
|
34194
|
+
if (session.architectWriteCount === undefined) {
|
|
34195
|
+
session.architectWriteCount = 0;
|
|
34196
|
+
}
|
|
34197
|
+
if (session.lastCoderDelegationTaskId === undefined) {
|
|
34198
|
+
session.lastCoderDelegationTaskId = null;
|
|
34199
|
+
}
|
|
34200
|
+
if (!session.gateLog) {
|
|
34201
|
+
session.gateLog = new Map;
|
|
34202
|
+
}
|
|
34203
|
+
if (!session.reviewerCallCount) {
|
|
34204
|
+
session.reviewerCallCount = new Map;
|
|
34205
|
+
}
|
|
34206
|
+
if (session.lastGateFailure === undefined) {
|
|
34207
|
+
session.lastGateFailure = null;
|
|
34208
|
+
}
|
|
34209
|
+
if (session.partialGateWarningIssued === undefined) {
|
|
34210
|
+
session.partialGateWarningIssued = false;
|
|
34211
|
+
}
|
|
34212
|
+
if (session.selfFixAttempted === undefined) {
|
|
34213
|
+
session.selfFixAttempted = false;
|
|
34214
|
+
}
|
|
34215
|
+
if (!session.catastrophicPhaseWarnings) {
|
|
34216
|
+
session.catastrophicPhaseWarnings = new Set;
|
|
34217
|
+
}
|
|
33982
34218
|
session.lastToolCallTime = now;
|
|
33983
34219
|
return session;
|
|
33984
34220
|
}
|
|
@@ -34293,7 +34529,7 @@ async function handleBenchmarkCommand(directory, args2) {
|
|
|
34293
34529
|
}
|
|
34294
34530
|
lines.push("");
|
|
34295
34531
|
}
|
|
34296
|
-
if (qualityMetrics
|
|
34532
|
+
if (qualityMetrics?.hasEvidence) {
|
|
34297
34533
|
lines.push("### Quality Metrics");
|
|
34298
34534
|
lines.push(`- Complexity Delta: ${qualityMetrics.complexityDelta} (max: ${qualityMetrics.thresholds.maxComplexityDelta}) ${qualityMetrics.complexityDelta <= qualityMetrics.thresholds.maxComplexityDelta ? "\u2705" : "\u274C"}`);
|
|
34299
34535
|
lines.push(`- Public API Delta: ${qualityMetrics.publicApiDelta} (max: ${qualityMetrics.thresholds.maxPublicApiDelta}) ${qualityMetrics.publicApiDelta <= qualityMetrics.thresholds.maxPublicApiDelta ? "\u2705" : "\u274C"}`);
|
|
@@ -34635,12 +34871,12 @@ function formatEvidenceEntry(index, entry) {
|
|
|
34635
34871
|
const details = {};
|
|
34636
34872
|
if (entry.type === "review") {
|
|
34637
34873
|
const reviewEntry = entry;
|
|
34638
|
-
details
|
|
34639
|
-
details
|
|
34874
|
+
details.risk = reviewEntry.risk;
|
|
34875
|
+
details.issues = reviewEntry.issues?.length;
|
|
34640
34876
|
} else if (entry.type === "test") {
|
|
34641
34877
|
const testEntry = entry;
|
|
34642
|
-
details
|
|
34643
|
-
details
|
|
34878
|
+
details.tests_passed = testEntry.tests_passed;
|
|
34879
|
+
details.tests_failed = testEntry.tests_failed;
|
|
34644
34880
|
}
|
|
34645
34881
|
return {
|
|
34646
34882
|
index,
|
|
@@ -35872,6 +36108,10 @@ function createContextBudgetHandler(config3) {
|
|
|
35872
36108
|
};
|
|
35873
36109
|
}
|
|
35874
36110
|
// src/hooks/delegation-gate.ts
|
|
36111
|
+
function extractTaskLine(text) {
|
|
36112
|
+
const match = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
|
|
36113
|
+
return match ? match[1].trim() : null;
|
|
36114
|
+
}
|
|
35875
36115
|
function createDelegationGateHook(config3) {
|
|
35876
36116
|
const enabled = config3.hooks?.delegation_gate !== false;
|
|
35877
36117
|
const delegationMaxChars = config3.hooks?.delegation_max_chars ?? 4000;
|
|
@@ -35903,8 +36143,26 @@ function createDelegationGateHook(config3) {
|
|
|
35903
36143
|
return;
|
|
35904
36144
|
const textPart = lastUserMessage.parts[textPartIndex];
|
|
35905
36145
|
const text = textPart.text ?? "";
|
|
36146
|
+
const sessionID = lastUserMessage.info?.sessionID;
|
|
36147
|
+
const taskIdMatch = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
|
|
36148
|
+
const currentTaskId = taskIdMatch ? taskIdMatch[1].trim() : null;
|
|
35906
36149
|
const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
|
|
35907
|
-
|
|
36150
|
+
const isCoderDelegation = coderDelegationPattern.test(text);
|
|
36151
|
+
if (sessionID && isCoderDelegation && currentTaskId) {
|
|
36152
|
+
const session = ensureAgentSession(sessionID);
|
|
36153
|
+
session.lastCoderDelegationTaskId = currentTaskId;
|
|
36154
|
+
}
|
|
36155
|
+
if (sessionID && !isCoderDelegation && currentTaskId) {
|
|
36156
|
+
const session = ensureAgentSession(sessionID);
|
|
36157
|
+
if (session.architectWriteCount > 0 && session.lastCoderDelegationTaskId !== currentTaskId) {
|
|
36158
|
+
const warningText2 = `\u26A0\uFE0F DELEGATION VIOLATION: Code modifications detected for task ${currentTaskId} with zero coder delegations.
|
|
36159
|
+
Rule 1: DELEGATE all coding to coder. You do NOT write code.`;
|
|
36160
|
+
textPart.text = `${warningText2}
|
|
36161
|
+
|
|
36162
|
+
${text}`;
|
|
36163
|
+
}
|
|
36164
|
+
}
|
|
36165
|
+
if (!isCoderDelegation)
|
|
35908
36166
|
return;
|
|
35909
36167
|
const warnings = [];
|
|
35910
36168
|
if (text.length > delegationMaxChars) {
|
|
@@ -35918,12 +36176,18 @@ function createDelegationGateHook(config3) {
|
|
|
35918
36176
|
if (taskMatches && taskMatches.length > 1) {
|
|
35919
36177
|
warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
|
|
35920
36178
|
}
|
|
35921
|
-
const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with)
|
|
36179
|
+
const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with|while you'?re at it)[.,]?\b/gi;
|
|
35922
36180
|
const batchingMatches = text.match(batchingPattern);
|
|
35923
36181
|
if (batchingMatches && batchingMatches.length > 0) {
|
|
35924
|
-
warnings.push(
|
|
36182
|
+
warnings.push(`Batching language detected (${batchingMatches.join(", ")}). Break compound objectives into separate coder calls.`);
|
|
36183
|
+
}
|
|
36184
|
+
const taskLine = extractTaskLine(text);
|
|
36185
|
+
if (taskLine) {
|
|
36186
|
+
const andPattern = /\s+and\s+(update|add|remove|modify|refactor|implement|create|delete|fix|change|build|deploy|write|test|move|rename|extend|extract|convert|migrate|upgrade|replace)\b/i;
|
|
36187
|
+
if (andPattern.test(taskLine)) {
|
|
36188
|
+
warnings.push('TASK line contains "and" connecting separate actions');
|
|
36189
|
+
}
|
|
35925
36190
|
}
|
|
35926
|
-
const sessionID = lastUserMessage.info?.sessionID;
|
|
35927
36191
|
if (sessionID) {
|
|
35928
36192
|
const delegationChain = swarmState.delegationChains.get(sessionID);
|
|
35929
36193
|
if (delegationChain && delegationChain.length >= 2) {
|
|
@@ -35948,10 +36212,11 @@ function createDelegationGateHook(config3) {
|
|
|
35948
36212
|
}
|
|
35949
36213
|
if (warnings.length === 0)
|
|
35950
36214
|
return;
|
|
35951
|
-
const
|
|
35952
|
-
|
|
35953
|
-
|
|
35954
|
-
|
|
36215
|
+
const warningLines = warnings.map((w) => `Detected signal: ${w}`);
|
|
36216
|
+
const warningText = `\u26A0\uFE0F BATCH DETECTED: Your coder delegation appears to contain multiple tasks.
|
|
36217
|
+
Rule 3: ONE task per coder call. Split this into separate delegations.
|
|
36218
|
+
${warningLines.join(`
|
|
36219
|
+
`)}`;
|
|
35955
36220
|
const originalText = textPart.text ?? "";
|
|
35956
36221
|
textPart.text = `${warningText}
|
|
35957
36222
|
|
|
@@ -35964,7 +36229,7 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
|
35964
36229
|
const now = Date.now();
|
|
35965
36230
|
if (!input.agent || input.agent === "") {
|
|
35966
36231
|
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
35967
|
-
if (session2
|
|
36232
|
+
if (session2?.delegationActive) {
|
|
35968
36233
|
session2.delegationActive = false;
|
|
35969
36234
|
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
35970
36235
|
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
@@ -36000,7 +36265,85 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
|
36000
36265
|
};
|
|
36001
36266
|
}
|
|
36002
36267
|
// src/hooks/guardrails.ts
|
|
36268
|
+
import * as path15 from "path";
|
|
36269
|
+
init_manager2();
|
|
36003
36270
|
init_utils();
|
|
36271
|
+
function extractPhaseNumber(phaseString) {
|
|
36272
|
+
if (!phaseString)
|
|
36273
|
+
return 1;
|
|
36274
|
+
const match = phaseString.match(/^Phase (\d+):/);
|
|
36275
|
+
return match ? parseInt(match[1], 10) : 1;
|
|
36276
|
+
}
|
|
36277
|
+
function isWriteTool(toolName) {
|
|
36278
|
+
const normalized = toolName.replace(/^[^:]+[:.]/, "");
|
|
36279
|
+
const writeTools = [
|
|
36280
|
+
"write",
|
|
36281
|
+
"edit",
|
|
36282
|
+
"patch",
|
|
36283
|
+
"apply_patch",
|
|
36284
|
+
"create_file",
|
|
36285
|
+
"insert",
|
|
36286
|
+
"replace"
|
|
36287
|
+
];
|
|
36288
|
+
return writeTools.includes(normalized);
|
|
36289
|
+
}
|
|
36290
|
+
function isArchitect(sessionId) {
|
|
36291
|
+
const activeAgent = swarmState.activeAgent.get(sessionId);
|
|
36292
|
+
if (activeAgent) {
|
|
36293
|
+
const stripped = stripKnownSwarmPrefix(activeAgent);
|
|
36294
|
+
if (stripped === ORCHESTRATOR_NAME) {
|
|
36295
|
+
return true;
|
|
36296
|
+
}
|
|
36297
|
+
}
|
|
36298
|
+
const session = swarmState.agentSessions.get(sessionId);
|
|
36299
|
+
if (session) {
|
|
36300
|
+
const stripped = stripKnownSwarmPrefix(session.agentName);
|
|
36301
|
+
if (stripped === ORCHESTRATOR_NAME) {
|
|
36302
|
+
return true;
|
|
36303
|
+
}
|
|
36304
|
+
}
|
|
36305
|
+
return false;
|
|
36306
|
+
}
|
|
36307
|
+
function isOutsideSwarmDir(filePath) {
|
|
36308
|
+
if (!filePath)
|
|
36309
|
+
return false;
|
|
36310
|
+
const cwd = process.cwd();
|
|
36311
|
+
const swarmDir = path15.resolve(cwd, ".swarm");
|
|
36312
|
+
const resolved = path15.resolve(cwd, filePath);
|
|
36313
|
+
const relative2 = path15.relative(swarmDir, resolved);
|
|
36314
|
+
return relative2.startsWith("..") || path15.isAbsolute(relative2);
|
|
36315
|
+
}
|
|
36316
|
+
function isGateTool(toolName) {
|
|
36317
|
+
const normalized = toolName.replace(/^[^:]+[:.]/, "");
|
|
36318
|
+
const gateTools = [
|
|
36319
|
+
"diff",
|
|
36320
|
+
"syntax_check",
|
|
36321
|
+
"placeholder_scan",
|
|
36322
|
+
"imports",
|
|
36323
|
+
"lint",
|
|
36324
|
+
"build_check",
|
|
36325
|
+
"pre_check_batch",
|
|
36326
|
+
"secretscan",
|
|
36327
|
+
"sast_scan",
|
|
36328
|
+
"quality_budget"
|
|
36329
|
+
];
|
|
36330
|
+
return gateTools.includes(normalized);
|
|
36331
|
+
}
|
|
36332
|
+
function isAgentDelegation(toolName, args2) {
|
|
36333
|
+
const normalized = toolName.replace(/^[^:]+[:.]/, "");
|
|
36334
|
+
if (normalized !== "Task" && normalized !== "task") {
|
|
36335
|
+
return { isDelegation: false, targetAgent: null };
|
|
36336
|
+
}
|
|
36337
|
+
const argsObj = args2;
|
|
36338
|
+
if (!argsObj) {
|
|
36339
|
+
return { isDelegation: false, targetAgent: null };
|
|
36340
|
+
}
|
|
36341
|
+
const subagentType = argsObj.subagent_type;
|
|
36342
|
+
if (typeof subagentType === "string") {
|
|
36343
|
+
return { isDelegation: true, targetAgent: subagentType };
|
|
36344
|
+
}
|
|
36345
|
+
return { isDelegation: false, targetAgent: null };
|
|
36346
|
+
}
|
|
36004
36347
|
function createGuardrailsHooks(config3) {
|
|
36005
36348
|
if (config3.enabled === false) {
|
|
36006
36349
|
return {
|
|
@@ -36009,8 +36352,36 @@ function createGuardrailsHooks(config3) {
|
|
|
36009
36352
|
messagesTransform: async () => {}
|
|
36010
36353
|
};
|
|
36011
36354
|
}
|
|
36355
|
+
const inputArgsByCallID = new Map;
|
|
36012
36356
|
return {
|
|
36013
36357
|
toolBefore: async (input, output) => {
|
|
36358
|
+
if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
|
|
36359
|
+
const args2 = output.args;
|
|
36360
|
+
const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
|
|
36361
|
+
if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath)) {
|
|
36362
|
+
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
36363
|
+
if (session2) {
|
|
36364
|
+
session2.architectWriteCount++;
|
|
36365
|
+
warn("Architect direct code edit detected", {
|
|
36366
|
+
tool: input.tool,
|
|
36367
|
+
sessionID: input.sessionID,
|
|
36368
|
+
targetPath,
|
|
36369
|
+
writeCount: session2.architectWriteCount
|
|
36370
|
+
});
|
|
36371
|
+
if (session2.lastGateFailure && Date.now() - session2.lastGateFailure.timestamp < 120000) {
|
|
36372
|
+
const failedGate = session2.lastGateFailure.tool;
|
|
36373
|
+
const failedTaskId = session2.lastGateFailure.taskId;
|
|
36374
|
+
warn("Self-fix after gate failure detected", {
|
|
36375
|
+
failedGate,
|
|
36376
|
+
failedTaskId,
|
|
36377
|
+
currentTool: input.tool,
|
|
36378
|
+
sessionID: input.sessionID
|
|
36379
|
+
});
|
|
36380
|
+
session2.selfFixAttempted = true;
|
|
36381
|
+
}
|
|
36382
|
+
}
|
|
36383
|
+
}
|
|
36384
|
+
}
|
|
36014
36385
|
const rawActiveAgent = swarmState.activeAgent.get(input.sessionID);
|
|
36015
36386
|
const strippedAgent = rawActiveAgent ? stripKnownSwarmPrefix(rawActiveAgent) : undefined;
|
|
36016
36387
|
if (strippedAgent === ORCHESTRATOR_NAME) {
|
|
@@ -36138,8 +36509,45 @@ function createGuardrailsHooks(config3) {
|
|
|
36138
36509
|
window2.warningReason = reasons.join(", ");
|
|
36139
36510
|
}
|
|
36140
36511
|
}
|
|
36512
|
+
inputArgsByCallID.set(input.callID, output.args);
|
|
36141
36513
|
},
|
|
36142
36514
|
toolAfter: async (input, output) => {
|
|
36515
|
+
const session = swarmState.agentSessions.get(input.sessionID);
|
|
36516
|
+
if (session) {
|
|
36517
|
+
if (isGateTool(input.tool)) {
|
|
36518
|
+
const taskId = `${input.sessionID}:current`;
|
|
36519
|
+
if (!session.gateLog.has(taskId)) {
|
|
36520
|
+
session.gateLog.set(taskId, new Set);
|
|
36521
|
+
}
|
|
36522
|
+
session.gateLog.get(taskId)?.add(input.tool);
|
|
36523
|
+
const outputStr = typeof output.output === "string" ? output.output : "";
|
|
36524
|
+
const hasFailure = output.output === null || output.output === undefined || outputStr.includes("FAIL") || outputStr.includes("error") || outputStr.toLowerCase().includes("gates_passed: false");
|
|
36525
|
+
if (hasFailure) {
|
|
36526
|
+
session.lastGateFailure = {
|
|
36527
|
+
tool: input.tool,
|
|
36528
|
+
taskId,
|
|
36529
|
+
timestamp: Date.now()
|
|
36530
|
+
};
|
|
36531
|
+
} else {
|
|
36532
|
+
session.lastGateFailure = null;
|
|
36533
|
+
}
|
|
36534
|
+
}
|
|
36535
|
+
const inputArgs = inputArgsByCallID.get(input.callID);
|
|
36536
|
+
inputArgsByCallID.delete(input.callID);
|
|
36537
|
+
const delegation = isAgentDelegation(input.tool, inputArgs);
|
|
36538
|
+
if (delegation.isDelegation && (delegation.targetAgent === "reviewer" || delegation.targetAgent === "test_engineer")) {
|
|
36539
|
+
let currentPhase = 1;
|
|
36540
|
+
try {
|
|
36541
|
+
const plan = await loadPlan(process.cwd());
|
|
36542
|
+
if (plan) {
|
|
36543
|
+
const phaseString = extractCurrentPhaseFromPlan(plan);
|
|
36544
|
+
currentPhase = extractPhaseNumber(phaseString);
|
|
36545
|
+
}
|
|
36546
|
+
} catch {}
|
|
36547
|
+
const count = session.reviewerCallCount.get(currentPhase) ?? 0;
|
|
36548
|
+
session.reviewerCallCount.set(currentPhase, count + 1);
|
|
36549
|
+
}
|
|
36550
|
+
}
|
|
36143
36551
|
const window2 = getActiveWindow(input.sessionID);
|
|
36144
36552
|
if (!window2)
|
|
36145
36553
|
return;
|
|
@@ -36161,6 +36569,99 @@ function createGuardrailsHooks(config3) {
|
|
|
36161
36569
|
if (!sessionId) {
|
|
36162
36570
|
return;
|
|
36163
36571
|
}
|
|
36572
|
+
const session = swarmState.agentSessions.get(sessionId);
|
|
36573
|
+
const activeAgent = swarmState.activeAgent.get(sessionId);
|
|
36574
|
+
const isArchitectSession = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
|
|
36575
|
+
if (isArchitectSession && session && session.architectWriteCount > 0) {
|
|
36576
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
36577
|
+
if (textPart2) {
|
|
36578
|
+
textPart2.text = `\u26A0\uFE0F SELF-CODING DETECTED: You have used ${session.architectWriteCount} write-class tool(s) directly on non-.swarm/ files.
|
|
36579
|
+
` + `Rule 1 requires ALL coding to be delegated to @coder.
|
|
36580
|
+
` + `If you have not exhausted QA_RETRY_LIMIT coder failures on this task, STOP and delegate.
|
|
36581
|
+
|
|
36582
|
+
` + textPart2.text;
|
|
36583
|
+
}
|
|
36584
|
+
}
|
|
36585
|
+
if (isArchitectSession && session && session.selfFixAttempted && session.lastGateFailure && Date.now() - session.lastGateFailure.timestamp < 120000) {
|
|
36586
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
36587
|
+
if (textPart2 && !textPart2.text.includes("SELF-FIX DETECTED")) {
|
|
36588
|
+
textPart2.text = `\u26A0\uFE0F SELF-FIX DETECTED: Gate '${session.lastGateFailure.tool}' failed on task ${session.lastGateFailure.taskId}.
|
|
36589
|
+
` + `You are now using a write tool instead of delegating to @coder.
|
|
36590
|
+
` + `GATE FAILURE RESPONSE RULES require: return to coder with structured rejection.
|
|
36591
|
+
` + `Do NOT fix gate failures yourself.
|
|
36592
|
+
|
|
36593
|
+
` + textPart2.text;
|
|
36594
|
+
session.selfFixAttempted = false;
|
|
36595
|
+
}
|
|
36596
|
+
}
|
|
36597
|
+
const isArchitectSessionForGates = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
|
|
36598
|
+
if (isArchitectSessionForGates && session && session.gateLog.size > 0 && !session.partialGateWarningIssued) {
|
|
36599
|
+
const taskId = `${sessionId}:current`;
|
|
36600
|
+
const gates = session.gateLog.get(taskId);
|
|
36601
|
+
if (gates) {
|
|
36602
|
+
const REQUIRED_GATES = [
|
|
36603
|
+
"diff",
|
|
36604
|
+
"syntax_check",
|
|
36605
|
+
"placeholder_scan",
|
|
36606
|
+
"lint",
|
|
36607
|
+
"pre_check_batch"
|
|
36608
|
+
];
|
|
36609
|
+
const missingGates = [];
|
|
36610
|
+
for (const gate of REQUIRED_GATES) {
|
|
36611
|
+
if (!gates.has(gate)) {
|
|
36612
|
+
missingGates.push(gate);
|
|
36613
|
+
}
|
|
36614
|
+
}
|
|
36615
|
+
let currentPhaseForCheck = 1;
|
|
36616
|
+
try {
|
|
36617
|
+
const plan = await loadPlan(process.cwd());
|
|
36618
|
+
if (plan) {
|
|
36619
|
+
const phaseString = extractCurrentPhaseFromPlan(plan);
|
|
36620
|
+
currentPhaseForCheck = extractPhaseNumber(phaseString);
|
|
36621
|
+
}
|
|
36622
|
+
} catch {}
|
|
36623
|
+
const hasReviewerDelegation = (session.reviewerCallCount.get(currentPhaseForCheck) ?? 0) > 0;
|
|
36624
|
+
if (missingGates.length > 0 || !hasReviewerDelegation) {
|
|
36625
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
36626
|
+
if (textPart2 && !textPart2.text.includes("PARTIAL GATE VIOLATION")) {
|
|
36627
|
+
const missing = [...missingGates];
|
|
36628
|
+
if (!hasReviewerDelegation) {
|
|
36629
|
+
missing.push("reviewer/test_engineer (no delegations this phase)");
|
|
36630
|
+
}
|
|
36631
|
+
session.partialGateWarningIssued = true;
|
|
36632
|
+
textPart2.text = `\u26A0\uFE0F PARTIAL GATE VIOLATION: Task may be marked complete but missing gates: [${missing.join(", ")}].
|
|
36633
|
+
` + `The QA gate is ALL steps or NONE. Revert any \u2713 marks and run the missing gates.
|
|
36634
|
+
|
|
36635
|
+
` + textPart2.text;
|
|
36636
|
+
}
|
|
36637
|
+
}
|
|
36638
|
+
}
|
|
36639
|
+
}
|
|
36640
|
+
if (isArchitectSessionForGates && session && session.catastrophicPhaseWarnings) {
|
|
36641
|
+
try {
|
|
36642
|
+
const plan = await loadPlan(process.cwd());
|
|
36643
|
+
if (plan?.phases) {
|
|
36644
|
+
for (const phase of plan.phases) {
|
|
36645
|
+
if (phase.status === "complete") {
|
|
36646
|
+
const phaseNum = phase.id;
|
|
36647
|
+
if (!session.catastrophicPhaseWarnings.has(phaseNum)) {
|
|
36648
|
+
const reviewerCount = session.reviewerCallCount.get(phaseNum) ?? 0;
|
|
36649
|
+
if (reviewerCount === 0) {
|
|
36650
|
+
session.catastrophicPhaseWarnings.add(phaseNum);
|
|
36651
|
+
const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
36652
|
+
if (textPart2 && !textPart2.text.includes("CATASTROPHIC VIOLATION")) {
|
|
36653
|
+
textPart2.text = `[CATASTROPHIC VIOLATION: Phase ${phaseNum} completed with ZERO reviewer delegations.` + ` Every coder task requires reviewer approval. Recommend retrospective review of all Phase ${phaseNum} tasks.]
|
|
36654
|
+
|
|
36655
|
+
` + textPart2.text;
|
|
36656
|
+
}
|
|
36657
|
+
break;
|
|
36658
|
+
}
|
|
36659
|
+
}
|
|
36660
|
+
}
|
|
36661
|
+
}
|
|
36662
|
+
}
|
|
36663
|
+
} catch {}
|
|
36664
|
+
}
|
|
36164
36665
|
const targetWindow = getActiveWindow(sessionId);
|
|
36165
36666
|
if (!targetWindow || !targetWindow.warningIssued && !targetWindow.hardLimitHit) {
|
|
36166
36667
|
return;
|
|
@@ -36219,9 +36720,22 @@ function createPhaseMonitorHook(directory, preflightManager) {
|
|
|
36219
36720
|
return safeHook(handler);
|
|
36220
36721
|
}
|
|
36221
36722
|
// src/hooks/pipeline-tracker.ts
|
|
36723
|
+
init_manager2();
|
|
36222
36724
|
init_utils2();
|
|
36223
|
-
|
|
36224
|
-
|
|
36725
|
+
function parsePhaseNumber(phaseString) {
|
|
36726
|
+
if (!phaseString)
|
|
36727
|
+
return null;
|
|
36728
|
+
const match = phaseString.match(/^Phase (\d+):/);
|
|
36729
|
+
if (match) {
|
|
36730
|
+
return parseInt(match[1], 10);
|
|
36731
|
+
}
|
|
36732
|
+
return null;
|
|
36733
|
+
}
|
|
36734
|
+
function buildPhaseReminder(phaseNumber) {
|
|
36735
|
+
const phaseHeader = phaseNumber !== null ? ` (Phase ${phaseNumber})` : "";
|
|
36736
|
+
const complianceHeader = phaseNumber !== null ? `COMPLIANCE CHECK (Phase ${phaseNumber}):` : "COMPLIANCE CHECK:";
|
|
36737
|
+
return `<swarm_reminder>
|
|
36738
|
+
\u26A0\uFE0F ARCHITECT WORKFLOW REMINDER${phaseHeader}:
|
|
36225
36739
|
1. ANALYZE \u2192 Identify domains, create initial spec
|
|
36226
36740
|
2. SME_CONSULTATION \u2192 Delegate to @sme (one domain per call, max 3 calls)
|
|
36227
36741
|
3. COLLATE \u2192 Synthesize SME outputs into unified spec
|
|
@@ -36234,7 +36748,15 @@ DELEGATION RULES:
|
|
|
36234
36748
|
- SME: ONE domain per call (serial), max 3 per phase
|
|
36235
36749
|
- Reviewer: Specify CHECK dimensions relevant to the change
|
|
36236
36750
|
- Always wait for response before next delegation
|
|
36751
|
+
|
|
36752
|
+
${complianceHeader}
|
|
36753
|
+
- Reviewer delegation is MANDATORY for every coder task.
|
|
36754
|
+
- pre_check_batch is NOT a substitute for reviewer.
|
|
36755
|
+
- Stage A (tools) + Stage B (agents) = BOTH required.
|
|
36756
|
+
${phaseNumber !== null && phaseNumber >= 4 ? `
|
|
36757
|
+
\u26A0\uFE0F You are in Phase ${phaseNumber}. Compliance degrades with time. Do not skip reviewer or test_engineer.` : ""}
|
|
36237
36758
|
</swarm_reminder>`;
|
|
36759
|
+
}
|
|
36238
36760
|
function createPipelineTrackerHook(config3) {
|
|
36239
36761
|
const enabled = config3.inject_phase_reminders !== false;
|
|
36240
36762
|
if (!enabled) {
|
|
@@ -36263,8 +36785,19 @@ function createPipelineTrackerHook(config3) {
|
|
|
36263
36785
|
const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
|
|
36264
36786
|
if (textPartIndex === -1)
|
|
36265
36787
|
return;
|
|
36788
|
+
let phaseNumber = null;
|
|
36789
|
+
try {
|
|
36790
|
+
const plan = await loadPlan(process.cwd());
|
|
36791
|
+
if (plan) {
|
|
36792
|
+
const phaseString = extractCurrentPhaseFromPlan(plan);
|
|
36793
|
+
phaseNumber = parsePhaseNumber(phaseString);
|
|
36794
|
+
}
|
|
36795
|
+
} catch {
|
|
36796
|
+
phaseNumber = null;
|
|
36797
|
+
}
|
|
36798
|
+
const phaseReminder = buildPhaseReminder(phaseNumber);
|
|
36266
36799
|
const originalText = lastUserMessage.parts[textPartIndex].text ?? "";
|
|
36267
|
-
lastUserMessage.parts[textPartIndex].text = `${
|
|
36800
|
+
lastUserMessage.parts[textPartIndex].text = `${phaseReminder}
|
|
36268
36801
|
|
|
36269
36802
|
---
|
|
36270
36803
|
|
|
@@ -36274,14 +36807,14 @@ ${originalText}`;
|
|
|
36274
36807
|
}
|
|
36275
36808
|
// src/hooks/system-enhancer.ts
|
|
36276
36809
|
import * as fs11 from "fs";
|
|
36277
|
-
import * as
|
|
36810
|
+
import * as path17 from "path";
|
|
36278
36811
|
init_manager2();
|
|
36279
36812
|
|
|
36280
36813
|
// src/services/decision-drift-analyzer.ts
|
|
36281
36814
|
init_utils2();
|
|
36282
36815
|
init_manager2();
|
|
36283
36816
|
import * as fs10 from "fs";
|
|
36284
|
-
import * as
|
|
36817
|
+
import * as path16 from "path";
|
|
36285
36818
|
var DEFAULT_DRIFT_CONFIG = {
|
|
36286
36819
|
staleThresholdPhases: 1,
|
|
36287
36820
|
detectContradictions: true,
|
|
@@ -36435,7 +36968,7 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
|
|
|
36435
36968
|
currentPhase = legacyPhase;
|
|
36436
36969
|
}
|
|
36437
36970
|
}
|
|
36438
|
-
const contextPath =
|
|
36971
|
+
const contextPath = path16.join(directory, ".swarm", "context.md");
|
|
36439
36972
|
let contextContent = "";
|
|
36440
36973
|
try {
|
|
36441
36974
|
if (fs10.existsSync(contextPath)) {
|
|
@@ -36540,7 +37073,7 @@ function formatDriftForContext(result) {
|
|
|
36540
37073
|
const maxLength = 600;
|
|
36541
37074
|
let summary = result.summary;
|
|
36542
37075
|
if (summary.length > maxLength) {
|
|
36543
|
-
summary = summary.substring(0, maxLength - 3)
|
|
37076
|
+
summary = `${summary.substring(0, maxLength - 3)}...`;
|
|
36544
37077
|
}
|
|
36545
37078
|
return summary;
|
|
36546
37079
|
}
|
|
@@ -36708,16 +37241,16 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36708
37241
|
}
|
|
36709
37242
|
const sessionId_retro = _input.sessionID;
|
|
36710
37243
|
const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
|
|
36711
|
-
const
|
|
36712
|
-
if (
|
|
37244
|
+
const isArchitect2 = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
|
|
37245
|
+
if (isArchitect2) {
|
|
36713
37246
|
try {
|
|
36714
|
-
const evidenceDir =
|
|
37247
|
+
const evidenceDir = path17.join(directory, ".swarm", "evidence");
|
|
36715
37248
|
if (fs11.existsSync(evidenceDir)) {
|
|
36716
37249
|
const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36717
37250
|
for (const file3 of files.slice(0, 5)) {
|
|
36718
37251
|
let content;
|
|
36719
37252
|
try {
|
|
36720
|
-
content = JSON.parse(fs11.readFileSync(
|
|
37253
|
+
content = JSON.parse(fs11.readFileSync(path17.join(evidenceDir, file3), "utf-8"));
|
|
36721
37254
|
} catch {
|
|
36722
37255
|
continue;
|
|
36723
37256
|
}
|
|
@@ -36738,7 +37271,7 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36738
37271
|
if (retroHint.length <= 800) {
|
|
36739
37272
|
tryInject(retroHint);
|
|
36740
37273
|
} else {
|
|
36741
|
-
tryInject(retroHint.substring(0, 800)
|
|
37274
|
+
tryInject(`${retroHint.substring(0, 800)}...`);
|
|
36742
37275
|
}
|
|
36743
37276
|
}
|
|
36744
37277
|
break;
|
|
@@ -36761,8 +37294,9 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36761
37294
|
const lastHint = session.lastCompactionHint || 0;
|
|
36762
37295
|
for (const threshold of thresholds) {
|
|
36763
37296
|
if (totalToolCalls >= threshold && lastHint < threshold) {
|
|
36764
|
-
const
|
|
36765
|
-
const
|
|
37297
|
+
const totalToolCallsPlaceholder = "$" + "{totalToolCalls}";
|
|
37298
|
+
const messageTemplate = compactionConfig?.message ?? `[SWARM HINT] Session has ${totalToolCallsPlaceholder} tool calls. Consider compacting at next phase boundary to maintain context quality.`;
|
|
37299
|
+
const message = messageTemplate.replace(totalToolCallsPlaceholder, String(totalToolCalls));
|
|
36766
37300
|
tryInject(message);
|
|
36767
37301
|
session.lastCompactionHint = threshold;
|
|
36768
37302
|
break;
|
|
@@ -36951,13 +37485,13 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36951
37485
|
const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
|
|
36952
37486
|
if (isArchitect_b) {
|
|
36953
37487
|
try {
|
|
36954
|
-
const evidenceDir_b =
|
|
37488
|
+
const evidenceDir_b = path17.join(directory, ".swarm", "evidence");
|
|
36955
37489
|
if (fs11.existsSync(evidenceDir_b)) {
|
|
36956
37490
|
const files_b = fs11.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36957
37491
|
for (const file3 of files_b.slice(0, 5)) {
|
|
36958
37492
|
let content_b;
|
|
36959
37493
|
try {
|
|
36960
|
-
content_b = JSON.parse(fs11.readFileSync(
|
|
37494
|
+
content_b = JSON.parse(fs11.readFileSync(path17.join(evidenceDir_b, file3), "utf-8"));
|
|
36961
37495
|
} catch {
|
|
36962
37496
|
continue;
|
|
36963
37497
|
}
|
|
@@ -36975,7 +37509,7 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36975
37509
|
}
|
|
36976
37510
|
if (hints_b.length > 0) {
|
|
36977
37511
|
const retroHint_b = `[SWARM RETROSPECTIVE] From Phase ${retro_b.phase_number}: ${hints_b.join(" ")}`;
|
|
36978
|
-
const retroText = retroHint_b.length <= 800 ? retroHint_b : retroHint_b.substring(0, 800)
|
|
37512
|
+
const retroText = retroHint_b.length <= 800 ? retroHint_b : `${retroHint_b.substring(0, 800)}...`;
|
|
36979
37513
|
candidates.push({
|
|
36980
37514
|
id: `candidate-${idCounter++}`,
|
|
36981
37515
|
kind: "phase",
|
|
@@ -37005,8 +37539,9 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
37005
37539
|
const lastHint_b = session_b.lastCompactionHint || 0;
|
|
37006
37540
|
for (const threshold of thresholds_b) {
|
|
37007
37541
|
if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
|
|
37008
|
-
const
|
|
37009
|
-
const
|
|
37542
|
+
const totalToolCallsPlaceholder_b = "$" + "{totalToolCalls}";
|
|
37543
|
+
const messageTemplate_b = compactionConfig_b?.message ?? `[SWARM HINT] Session has ${totalToolCallsPlaceholder_b} tool calls. Consider compacting at next phase boundary to maintain context quality.`;
|
|
37544
|
+
const compactionText = messageTemplate_b.replace(totalToolCallsPlaceholder_b, String(totalToolCalls_b));
|
|
37010
37545
|
candidates.push({
|
|
37011
37546
|
id: `candidate-${idCounter++}`,
|
|
37012
37547
|
kind: "phase",
|
|
@@ -37209,7 +37744,7 @@ function createSummary(output, toolName, summaryId, maxSummaryChars) {
|
|
|
37209
37744
|
}
|
|
37210
37745
|
}
|
|
37211
37746
|
if (preview.length > maxPreviewChars) {
|
|
37212
|
-
preview = preview.substring(0, maxPreviewChars - 3)
|
|
37747
|
+
preview = `${preview.substring(0, maxPreviewChars - 3)}...`;
|
|
37213
37748
|
}
|
|
37214
37749
|
return `${headerLine}
|
|
37215
37750
|
${preview}
|
|
@@ -37253,7 +37788,7 @@ init_dist();
|
|
|
37253
37788
|
// src/build/discovery.ts
|
|
37254
37789
|
init_dist();
|
|
37255
37790
|
import * as fs12 from "fs";
|
|
37256
|
-
import * as
|
|
37791
|
+
import * as path18 from "path";
|
|
37257
37792
|
var ECOSYSTEMS = [
|
|
37258
37793
|
{
|
|
37259
37794
|
ecosystem: "node",
|
|
@@ -37367,15 +37902,15 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37367
37902
|
try {
|
|
37368
37903
|
const files = fs12.readdirSync(dir);
|
|
37369
37904
|
const matches = files.filter((f) => {
|
|
37370
|
-
const regex = new RegExp(
|
|
37905
|
+
const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
|
|
37371
37906
|
return regex.test(f);
|
|
37372
37907
|
});
|
|
37373
37908
|
if (matches.length > 0) {
|
|
37374
|
-
return
|
|
37909
|
+
return path18.join(dir, matches[0]);
|
|
37375
37910
|
}
|
|
37376
37911
|
} catch {}
|
|
37377
37912
|
} else {
|
|
37378
|
-
const filePath =
|
|
37913
|
+
const filePath = path18.join(workingDir, pattern);
|
|
37379
37914
|
if (fs12.existsSync(filePath)) {
|
|
37380
37915
|
return filePath;
|
|
37381
37916
|
}
|
|
@@ -37384,7 +37919,7 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37384
37919
|
return null;
|
|
37385
37920
|
}
|
|
37386
37921
|
function getRepoDefinedScripts(workingDir, scripts) {
|
|
37387
|
-
const packageJsonPath =
|
|
37922
|
+
const packageJsonPath = path18.join(workingDir, "package.json");
|
|
37388
37923
|
if (!fs12.existsSync(packageJsonPath)) {
|
|
37389
37924
|
return [];
|
|
37390
37925
|
}
|
|
@@ -37422,10 +37957,10 @@ function findAllBuildFiles(workingDir) {
|
|
|
37422
37957
|
for (const ecosystem of ECOSYSTEMS) {
|
|
37423
37958
|
for (const pattern of ecosystem.buildFiles) {
|
|
37424
37959
|
if (pattern.includes("*")) {
|
|
37425
|
-
const regex = new RegExp(
|
|
37960
|
+
const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
|
|
37426
37961
|
findFilesRecursive(workingDir, regex, allBuildFiles);
|
|
37427
37962
|
} else {
|
|
37428
|
-
const filePath =
|
|
37963
|
+
const filePath = path18.join(workingDir, pattern);
|
|
37429
37964
|
if (fs12.existsSync(filePath)) {
|
|
37430
37965
|
allBuildFiles.add(filePath);
|
|
37431
37966
|
}
|
|
@@ -37438,7 +37973,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
37438
37973
|
try {
|
|
37439
37974
|
const entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
37440
37975
|
for (const entry of entries) {
|
|
37441
|
-
const fullPath =
|
|
37976
|
+
const fullPath = path18.join(dir, entry.name);
|
|
37442
37977
|
if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
|
|
37443
37978
|
findFilesRecursive(fullPath, regex, results);
|
|
37444
37979
|
} else if (entry.isFile() && regex.test(entry.name)) {
|
|
@@ -37450,7 +37985,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
37450
37985
|
async function discoverBuildCommands(workingDir, options) {
|
|
37451
37986
|
const scope = options?.scope ?? "all";
|
|
37452
37987
|
const changedFiles = options?.changedFiles ?? [];
|
|
37453
|
-
const
|
|
37988
|
+
const _filesToCheck = filterByScope(workingDir, scope, changedFiles);
|
|
37454
37989
|
const commands = [];
|
|
37455
37990
|
const skipped = [];
|
|
37456
37991
|
for (const ecosystem of ECOSYSTEMS) {
|
|
@@ -37680,7 +38215,7 @@ var build_check = tool({
|
|
|
37680
38215
|
init_tool();
|
|
37681
38216
|
import { spawnSync } from "child_process";
|
|
37682
38217
|
import * as fs13 from "fs";
|
|
37683
|
-
import * as
|
|
38218
|
+
import * as path19 from "path";
|
|
37684
38219
|
var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
|
|
37685
38220
|
var MAX_LABEL_LENGTH = 100;
|
|
37686
38221
|
var GIT_TIMEOUT_MS = 30000;
|
|
@@ -37731,7 +38266,7 @@ function validateLabel(label) {
|
|
|
37731
38266
|
return null;
|
|
37732
38267
|
}
|
|
37733
38268
|
function getCheckpointLogPath() {
|
|
37734
|
-
return
|
|
38269
|
+
return path19.join(process.cwd(), CHECKPOINT_LOG_PATH);
|
|
37735
38270
|
}
|
|
37736
38271
|
function readCheckpointLog() {
|
|
37737
38272
|
const logPath = getCheckpointLogPath();
|
|
@@ -37749,7 +38284,7 @@ function readCheckpointLog() {
|
|
|
37749
38284
|
}
|
|
37750
38285
|
function writeCheckpointLog(log2) {
|
|
37751
38286
|
const logPath = getCheckpointLogPath();
|
|
37752
|
-
const dir =
|
|
38287
|
+
const dir = path19.dirname(logPath);
|
|
37753
38288
|
if (!fs13.existsSync(dir)) {
|
|
37754
38289
|
fs13.mkdirSync(dir, { recursive: true });
|
|
37755
38290
|
}
|
|
@@ -37956,7 +38491,7 @@ var checkpoint = tool({
|
|
|
37956
38491
|
// src/tools/complexity-hotspots.ts
|
|
37957
38492
|
init_dist();
|
|
37958
38493
|
import * as fs14 from "fs";
|
|
37959
|
-
import * as
|
|
38494
|
+
import * as path20 from "path";
|
|
37960
38495
|
var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
|
|
37961
38496
|
var DEFAULT_DAYS = 90;
|
|
37962
38497
|
var DEFAULT_TOP_N = 20;
|
|
@@ -38099,7 +38634,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
38099
38634
|
const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
|
|
38100
38635
|
const filteredChurn = new Map;
|
|
38101
38636
|
for (const [file3, count] of churnMap) {
|
|
38102
|
-
const ext =
|
|
38637
|
+
const ext = path20.extname(file3).toLowerCase();
|
|
38103
38638
|
if (extSet.has(ext)) {
|
|
38104
38639
|
filteredChurn.set(file3, count);
|
|
38105
38640
|
}
|
|
@@ -38110,7 +38645,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
38110
38645
|
for (const [file3, churnCount] of filteredChurn) {
|
|
38111
38646
|
let fullPath = file3;
|
|
38112
38647
|
if (!fs14.existsSync(fullPath)) {
|
|
38113
|
-
fullPath =
|
|
38648
|
+
fullPath = path20.join(cwd, file3);
|
|
38114
38649
|
}
|
|
38115
38650
|
const complexity = getComplexityForFile(fullPath);
|
|
38116
38651
|
if (complexity !== null) {
|
|
@@ -38268,14 +38803,14 @@ function validateBase(base) {
|
|
|
38268
38803
|
function validatePaths(paths) {
|
|
38269
38804
|
if (!paths)
|
|
38270
38805
|
return null;
|
|
38271
|
-
for (const
|
|
38272
|
-
if (!
|
|
38806
|
+
for (const path21 of paths) {
|
|
38807
|
+
if (!path21 || path21.length === 0) {
|
|
38273
38808
|
return "empty path not allowed";
|
|
38274
38809
|
}
|
|
38275
|
-
if (
|
|
38810
|
+
if (path21.length > MAX_PATH_LENGTH) {
|
|
38276
38811
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
38277
38812
|
}
|
|
38278
|
-
if (SHELL_METACHARACTERS2.test(
|
|
38813
|
+
if (SHELL_METACHARACTERS2.test(path21)) {
|
|
38279
38814
|
return "path contains shell metacharacters";
|
|
38280
38815
|
}
|
|
38281
38816
|
}
|
|
@@ -38290,7 +38825,7 @@ var diff = tool({
|
|
|
38290
38825
|
async execute(args2, _context) {
|
|
38291
38826
|
try {
|
|
38292
38827
|
const base = args2.base ?? "HEAD";
|
|
38293
|
-
const pathSpec = args2.paths?.length ?
|
|
38828
|
+
const pathSpec = args2.paths?.length ? `-- ${args2.paths.join(" ")}` : "";
|
|
38294
38829
|
const baseValidationError = validateBase(base);
|
|
38295
38830
|
if (baseValidationError) {
|
|
38296
38831
|
const errorResult = {
|
|
@@ -38319,11 +38854,11 @@ var diff = tool({
|
|
|
38319
38854
|
} else {
|
|
38320
38855
|
gitCmd = `git --no-pager diff ${base}`;
|
|
38321
38856
|
}
|
|
38322
|
-
const numstatOutput = execSync(gitCmd
|
|
38857
|
+
const numstatOutput = execSync(`${gitCmd} --numstat ${pathSpec}`, {
|
|
38323
38858
|
encoding: "utf-8",
|
|
38324
38859
|
timeout: DIFF_TIMEOUT_MS
|
|
38325
38860
|
});
|
|
38326
|
-
const fullDiffOutput = execSync(gitCmd
|
|
38861
|
+
const fullDiffOutput = execSync(`${gitCmd} -U3 ${pathSpec}`, {
|
|
38327
38862
|
encoding: "utf-8",
|
|
38328
38863
|
timeout: DIFF_TIMEOUT_MS,
|
|
38329
38864
|
maxBuffer: MAX_BUFFER_BYTES
|
|
@@ -38336,10 +38871,10 @@ var diff = tool({
|
|
|
38336
38871
|
continue;
|
|
38337
38872
|
const parts2 = line.split("\t");
|
|
38338
38873
|
if (parts2.length >= 3) {
|
|
38339
|
-
const additions = parseInt(parts2[0]) || 0;
|
|
38340
|
-
const deletions = parseInt(parts2[1]) || 0;
|
|
38341
|
-
const
|
|
38342
|
-
files.push({ path:
|
|
38874
|
+
const additions = parseInt(parts2[0], 10) || 0;
|
|
38875
|
+
const deletions = parseInt(parts2[1], 10) || 0;
|
|
38876
|
+
const path21 = parts2[2];
|
|
38877
|
+
files.push({ path: path21, additions, deletions });
|
|
38343
38878
|
}
|
|
38344
38879
|
}
|
|
38345
38880
|
const contractChanges = [];
|
|
@@ -38568,7 +39103,7 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
38568
39103
|
// src/tools/evidence-check.ts
|
|
38569
39104
|
init_dist();
|
|
38570
39105
|
import * as fs15 from "fs";
|
|
38571
|
-
import * as
|
|
39106
|
+
import * as path21 from "path";
|
|
38572
39107
|
var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
|
|
38573
39108
|
var MAX_EVIDENCE_FILES = 1000;
|
|
38574
39109
|
var EVIDENCE_DIR = ".swarm/evidence";
|
|
@@ -38591,16 +39126,15 @@ function validateRequiredTypes(input) {
|
|
|
38591
39126
|
return null;
|
|
38592
39127
|
}
|
|
38593
39128
|
function isPathWithinSwarm(filePath, cwd) {
|
|
38594
|
-
const normalizedCwd =
|
|
38595
|
-
const swarmPath =
|
|
38596
|
-
const normalizedPath =
|
|
39129
|
+
const normalizedCwd = path21.resolve(cwd);
|
|
39130
|
+
const swarmPath = path21.join(normalizedCwd, ".swarm");
|
|
39131
|
+
const normalizedPath = path21.resolve(filePath);
|
|
38597
39132
|
return normalizedPath.startsWith(swarmPath);
|
|
38598
39133
|
}
|
|
38599
39134
|
function parseCompletedTasks(planContent) {
|
|
38600
39135
|
const tasks = [];
|
|
38601
39136
|
const regex = /^-\s+\[x\]\s+(\d+\.\d+):\s+(.+)/gm;
|
|
38602
|
-
let match;
|
|
38603
|
-
while ((match = regex.exec(planContent)) !== null) {
|
|
39137
|
+
for (let match = regex.exec(planContent);match !== null; match = regex.exec(planContent)) {
|
|
38604
39138
|
const taskId = match[1];
|
|
38605
39139
|
let taskName = match[2].trim();
|
|
38606
39140
|
taskName = taskName.replace(/\s*\[(SMALL|MEDIUM|LARGE)\]\s*$/i, "").trim();
|
|
@@ -38608,7 +39142,7 @@ function parseCompletedTasks(planContent) {
|
|
|
38608
39142
|
}
|
|
38609
39143
|
return tasks;
|
|
38610
39144
|
}
|
|
38611
|
-
function readEvidenceFiles(evidenceDir,
|
|
39145
|
+
function readEvidenceFiles(evidenceDir, _cwd) {
|
|
38612
39146
|
const evidence = [];
|
|
38613
39147
|
if (!fs15.existsSync(evidenceDir) || !fs15.statSync(evidenceDir).isDirectory()) {
|
|
38614
39148
|
return evidence;
|
|
@@ -38624,10 +39158,10 @@ function readEvidenceFiles(evidenceDir, cwd) {
|
|
|
38624
39158
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
38625
39159
|
continue;
|
|
38626
39160
|
}
|
|
38627
|
-
const filePath =
|
|
39161
|
+
const filePath = path21.join(evidenceDir, filename);
|
|
38628
39162
|
try {
|
|
38629
|
-
const resolvedPath =
|
|
38630
|
-
const evidenceDirResolved =
|
|
39163
|
+
const resolvedPath = path21.resolve(filePath);
|
|
39164
|
+
const evidenceDirResolved = path21.resolve(evidenceDir);
|
|
38631
39165
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
38632
39166
|
continue;
|
|
38633
39167
|
}
|
|
@@ -38681,7 +39215,7 @@ function analyzeGaps(completedTasks, evidence, requiredTypes) {
|
|
|
38681
39215
|
for (const task of completedTasks) {
|
|
38682
39216
|
const taskEvidence = evidenceByTask.get(task.taskId) || new Set;
|
|
38683
39217
|
const requiredSet = new Set(requiredTypes.map((t) => t.toLowerCase()));
|
|
38684
|
-
const
|
|
39218
|
+
const _presentSet = new Set([...taskEvidence].filter((t) => requiredSet.has(t.toLowerCase())));
|
|
38685
39219
|
const missing = [];
|
|
38686
39220
|
const present = [];
|
|
38687
39221
|
for (const reqType of requiredTypes) {
|
|
@@ -38734,7 +39268,7 @@ var evidence_check = tool({
|
|
|
38734
39268
|
return JSON.stringify(errorResult, null, 2);
|
|
38735
39269
|
}
|
|
38736
39270
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
38737
|
-
const planPath =
|
|
39271
|
+
const planPath = path21.join(cwd, PLAN_FILE);
|
|
38738
39272
|
if (!isPathWithinSwarm(planPath, cwd)) {
|
|
38739
39273
|
const errorResult = {
|
|
38740
39274
|
error: "plan file path validation failed",
|
|
@@ -38766,7 +39300,7 @@ var evidence_check = tool({
|
|
|
38766
39300
|
};
|
|
38767
39301
|
return JSON.stringify(result2, null, 2);
|
|
38768
39302
|
}
|
|
38769
|
-
const evidenceDir =
|
|
39303
|
+
const evidenceDir = path21.join(cwd, EVIDENCE_DIR);
|
|
38770
39304
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
38771
39305
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
38772
39306
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -38783,7 +39317,7 @@ var evidence_check = tool({
|
|
|
38783
39317
|
// src/tools/file-extractor.ts
|
|
38784
39318
|
init_tool();
|
|
38785
39319
|
import * as fs16 from "fs";
|
|
38786
|
-
import * as
|
|
39320
|
+
import * as path22 from "path";
|
|
38787
39321
|
var EXT_MAP = {
|
|
38788
39322
|
python: ".py",
|
|
38789
39323
|
py: ".py",
|
|
@@ -38861,12 +39395,12 @@ var extract_code_blocks = tool({
|
|
|
38861
39395
|
if (prefix) {
|
|
38862
39396
|
filename = `${prefix}_${filename}`;
|
|
38863
39397
|
}
|
|
38864
|
-
let filepath =
|
|
38865
|
-
const base =
|
|
38866
|
-
const ext =
|
|
39398
|
+
let filepath = path22.join(targetDir, filename);
|
|
39399
|
+
const base = path22.basename(filepath, path22.extname(filepath));
|
|
39400
|
+
const ext = path22.extname(filepath);
|
|
38867
39401
|
let counter = 1;
|
|
38868
39402
|
while (fs16.existsSync(filepath)) {
|
|
38869
|
-
filepath =
|
|
39403
|
+
filepath = path22.join(targetDir, `${base}_${counter}${ext}`);
|
|
38870
39404
|
counter++;
|
|
38871
39405
|
}
|
|
38872
39406
|
try {
|
|
@@ -38899,7 +39433,7 @@ init_dist();
|
|
|
38899
39433
|
var GITINGEST_TIMEOUT_MS = 1e4;
|
|
38900
39434
|
var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
|
|
38901
39435
|
var GITINGEST_MAX_RETRIES = 2;
|
|
38902
|
-
var delay = (ms) => new Promise((
|
|
39436
|
+
var delay = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
38903
39437
|
async function fetchGitingest(args2) {
|
|
38904
39438
|
for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
|
|
38905
39439
|
try {
|
|
@@ -38979,7 +39513,7 @@ var gitingest = tool({
|
|
|
38979
39513
|
// src/tools/imports.ts
|
|
38980
39514
|
init_dist();
|
|
38981
39515
|
import * as fs17 from "fs";
|
|
38982
|
-
import * as
|
|
39516
|
+
import * as path23 from "path";
|
|
38983
39517
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
38984
39518
|
var MAX_SYMBOL_LENGTH = 256;
|
|
38985
39519
|
var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
|
|
@@ -39033,7 +39567,7 @@ function validateSymbolInput(symbol3) {
|
|
|
39033
39567
|
return null;
|
|
39034
39568
|
}
|
|
39035
39569
|
function isBinaryFile2(filePath, buffer) {
|
|
39036
|
-
const ext =
|
|
39570
|
+
const ext = path23.extname(filePath).toLowerCase();
|
|
39037
39571
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
39038
39572
|
return false;
|
|
39039
39573
|
}
|
|
@@ -39055,20 +39589,19 @@ function isBinaryFile2(filePath, buffer) {
|
|
|
39055
39589
|
}
|
|
39056
39590
|
function parseImports(content, targetFile, targetSymbol) {
|
|
39057
39591
|
const imports = [];
|
|
39058
|
-
let
|
|
39592
|
+
let _resolvedTarget;
|
|
39059
39593
|
try {
|
|
39060
|
-
|
|
39594
|
+
_resolvedTarget = path23.resolve(targetFile);
|
|
39061
39595
|
} catch {
|
|
39062
|
-
|
|
39596
|
+
_resolvedTarget = targetFile;
|
|
39063
39597
|
}
|
|
39064
|
-
const targetBasename =
|
|
39598
|
+
const targetBasename = path23.basename(targetFile, path23.extname(targetFile));
|
|
39065
39599
|
const targetWithExt = targetFile;
|
|
39066
39600
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
39067
|
-
const normalizedTargetWithExt =
|
|
39068
|
-
const normalizedTargetWithoutExt =
|
|
39601
|
+
const normalizedTargetWithExt = path23.normalize(targetWithExt).replace(/\\/g, "/");
|
|
39602
|
+
const normalizedTargetWithoutExt = path23.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
39069
39603
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
39070
|
-
let match;
|
|
39071
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
39604
|
+
for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
|
|
39072
39605
|
const modulePath = match[1] || match[2] || match[3];
|
|
39073
39606
|
if (!modulePath)
|
|
39074
39607
|
continue;
|
|
@@ -39087,15 +39620,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
39087
39620
|
} else if (matchedString.includes("require(")) {
|
|
39088
39621
|
importType = "require";
|
|
39089
39622
|
}
|
|
39090
|
-
const
|
|
39623
|
+
const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
39091
39624
|
let isMatch = false;
|
|
39092
|
-
const
|
|
39093
|
-
const targetExt =
|
|
39094
|
-
const targetBasenameNoExt =
|
|
39625
|
+
const _targetDir = path23.dirname(targetFile);
|
|
39626
|
+
const targetExt = path23.extname(targetFile);
|
|
39627
|
+
const targetBasenameNoExt = path23.basename(targetFile, targetExt);
|
|
39095
39628
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
39096
39629
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
39097
39630
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
39098
|
-
if (modulePath === targetBasename || modulePath === targetBasenameNoExt || modulePath ===
|
|
39631
|
+
if (modulePath === targetBasename || modulePath === targetBasenameNoExt || modulePath === `./${targetBasename}` || modulePath === `./${targetBasenameNoExt}` || modulePath === `../${targetBasename}` || modulePath === `../${targetBasenameNoExt}` || moduleNormalized === normalizedTargetWithExt || moduleNormalized === normalizedTargetWithoutExt || modulePath.endsWith(`/${targetBasename}`) || modulePath.endsWith(`\\${targetBasename}`) || modulePath.endsWith(`/${targetBasenameNoExt}`) || modulePath.endsWith(`\\${targetBasenameNoExt}`) || moduleNameNoExt === targetBasenameNoExt || `./${moduleNameNoExt}` === targetBasenameNoExt || moduleName === targetBasename || moduleName === targetBasenameNoExt) {
|
|
39099
39632
|
isMatch = true;
|
|
39100
39633
|
}
|
|
39101
39634
|
if (isMatch && targetSymbol) {
|
|
@@ -39159,10 +39692,10 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
39159
39692
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
39160
39693
|
for (const entry of entries) {
|
|
39161
39694
|
if (SKIP_DIRECTORIES2.has(entry)) {
|
|
39162
|
-
stats.skippedDirs.push(
|
|
39695
|
+
stats.skippedDirs.push(path23.join(dir, entry));
|
|
39163
39696
|
continue;
|
|
39164
39697
|
}
|
|
39165
|
-
const fullPath =
|
|
39698
|
+
const fullPath = path23.join(dir, entry);
|
|
39166
39699
|
let stat;
|
|
39167
39700
|
try {
|
|
39168
39701
|
stat = fs17.statSync(fullPath);
|
|
@@ -39176,7 +39709,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
39176
39709
|
if (stat.isDirectory()) {
|
|
39177
39710
|
findSourceFiles2(fullPath, files, stats);
|
|
39178
39711
|
} else if (stat.isFile()) {
|
|
39179
|
-
const ext =
|
|
39712
|
+
const ext = path23.extname(fullPath).toLowerCase();
|
|
39180
39713
|
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
39181
39714
|
files.push(fullPath);
|
|
39182
39715
|
}
|
|
@@ -39232,7 +39765,7 @@ var imports = tool({
|
|
|
39232
39765
|
return JSON.stringify(errorResult, null, 2);
|
|
39233
39766
|
}
|
|
39234
39767
|
try {
|
|
39235
|
-
const targetFile =
|
|
39768
|
+
const targetFile = path23.resolve(file3);
|
|
39236
39769
|
if (!fs17.existsSync(targetFile)) {
|
|
39237
39770
|
const errorResult = {
|
|
39238
39771
|
error: `target file not found: ${file3}`,
|
|
@@ -39254,7 +39787,7 @@ var imports = tool({
|
|
|
39254
39787
|
};
|
|
39255
39788
|
return JSON.stringify(errorResult, null, 2);
|
|
39256
39789
|
}
|
|
39257
|
-
const baseDir =
|
|
39790
|
+
const baseDir = path23.dirname(targetFile);
|
|
39258
39791
|
const scanStats = {
|
|
39259
39792
|
skippedDirs: [],
|
|
39260
39793
|
skippedFiles: 0,
|
|
@@ -39296,7 +39829,7 @@ var imports = tool({
|
|
|
39296
39829
|
raw: imp.raw
|
|
39297
39830
|
});
|
|
39298
39831
|
}
|
|
39299
|
-
} catch (
|
|
39832
|
+
} catch (_e) {
|
|
39300
39833
|
skippedFileCount++;
|
|
39301
39834
|
}
|
|
39302
39835
|
}
|
|
@@ -39322,7 +39855,7 @@ var imports = tool({
|
|
|
39322
39855
|
}
|
|
39323
39856
|
}
|
|
39324
39857
|
if (parts2.length > 0) {
|
|
39325
|
-
result.message = parts2.join("; ")
|
|
39858
|
+
result.message = `${parts2.join("; ")}.`;
|
|
39326
39859
|
}
|
|
39327
39860
|
return JSON.stringify(result, null, 2);
|
|
39328
39861
|
} catch (e) {
|
|
@@ -39344,7 +39877,7 @@ init_lint();
|
|
|
39344
39877
|
// src/tools/pkg-audit.ts
|
|
39345
39878
|
init_dist();
|
|
39346
39879
|
import * as fs18 from "fs";
|
|
39347
|
-
import * as
|
|
39880
|
+
import * as path24 from "path";
|
|
39348
39881
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
39349
39882
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
39350
39883
|
function isValidEcosystem(value) {
|
|
@@ -39362,13 +39895,13 @@ function validateArgs3(args2) {
|
|
|
39362
39895
|
function detectEcosystems() {
|
|
39363
39896
|
const ecosystems = [];
|
|
39364
39897
|
const cwd = process.cwd();
|
|
39365
|
-
if (fs18.existsSync(
|
|
39898
|
+
if (fs18.existsSync(path24.join(cwd, "package.json"))) {
|
|
39366
39899
|
ecosystems.push("npm");
|
|
39367
39900
|
}
|
|
39368
|
-
if (fs18.existsSync(
|
|
39901
|
+
if (fs18.existsSync(path24.join(cwd, "pyproject.toml")) || fs18.existsSync(path24.join(cwd, "requirements.txt"))) {
|
|
39369
39902
|
ecosystems.push("pip");
|
|
39370
39903
|
}
|
|
39371
|
-
if (fs18.existsSync(
|
|
39904
|
+
if (fs18.existsSync(path24.join(cwd, "Cargo.toml"))) {
|
|
39372
39905
|
ecosystems.push("cargo");
|
|
39373
39906
|
}
|
|
39374
39907
|
return ecosystems;
|
|
@@ -39381,7 +39914,7 @@ async function runNpmAudit() {
|
|
|
39381
39914
|
stderr: "pipe",
|
|
39382
39915
|
cwd: process.cwd()
|
|
39383
39916
|
});
|
|
39384
|
-
const timeoutPromise = new Promise((
|
|
39917
|
+
const timeoutPromise = new Promise((resolve11) => setTimeout(() => resolve11("timeout"), AUDIT_TIMEOUT_MS));
|
|
39385
39918
|
const result = await Promise.race([
|
|
39386
39919
|
Promise.all([
|
|
39387
39920
|
new Response(proc.stdout).text(),
|
|
@@ -39504,7 +40037,7 @@ async function runPipAudit() {
|
|
|
39504
40037
|
stderr: "pipe",
|
|
39505
40038
|
cwd: process.cwd()
|
|
39506
40039
|
});
|
|
39507
|
-
const timeoutPromise = new Promise((
|
|
40040
|
+
const timeoutPromise = new Promise((resolve11) => setTimeout(() => resolve11("timeout"), AUDIT_TIMEOUT_MS));
|
|
39508
40041
|
const result = await Promise.race([
|
|
39509
40042
|
Promise.all([
|
|
39510
40043
|
new Response(proc.stdout).text(),
|
|
@@ -39635,12 +40168,12 @@ async function runCargoAudit() {
|
|
|
39635
40168
|
stderr: "pipe",
|
|
39636
40169
|
cwd: process.cwd()
|
|
39637
40170
|
});
|
|
39638
|
-
const timeoutPromise = new Promise((
|
|
40171
|
+
const timeoutPromise = new Promise((resolve11) => setTimeout(() => resolve11("timeout"), AUDIT_TIMEOUT_MS));
|
|
39639
40172
|
const result = await Promise.race([
|
|
39640
40173
|
Promise.all([
|
|
39641
40174
|
new Response(proc.stdout).text(),
|
|
39642
40175
|
new Response(proc.stderr).text()
|
|
39643
|
-
]).then(([stdout2,
|
|
40176
|
+
]).then(([stdout2, stderr]) => ({ stdout: stdout2, stderr })),
|
|
39644
40177
|
timeoutPromise
|
|
39645
40178
|
]);
|
|
39646
40179
|
if (result === "timeout") {
|
|
@@ -39656,7 +40189,7 @@ async function runCargoAudit() {
|
|
|
39656
40189
|
note: `cargo audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
|
|
39657
40190
|
};
|
|
39658
40191
|
}
|
|
39659
|
-
let { stdout, stderr } = result;
|
|
40192
|
+
let { stdout, stderr: _stderr } = result;
|
|
39660
40193
|
if (stdout.length > MAX_OUTPUT_BYTES5) {
|
|
39661
40194
|
stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
|
|
39662
40195
|
}
|
|
@@ -39678,7 +40211,7 @@ async function runCargoAudit() {
|
|
|
39678
40211
|
for (const line of lines) {
|
|
39679
40212
|
try {
|
|
39680
40213
|
const obj = JSON.parse(line);
|
|
39681
|
-
if (obj.vulnerabilities
|
|
40214
|
+
if (obj.vulnerabilities?.list) {
|
|
39682
40215
|
for (const item of obj.vulnerabilities.list) {
|
|
39683
40216
|
const cvss = item.advisory.cvss || 0;
|
|
39684
40217
|
const severity = mapCargoSeverity(cvss);
|
|
@@ -41253,8 +41786,8 @@ var Module2 = (() => {
|
|
|
41253
41786
|
var moduleRtn;
|
|
41254
41787
|
var Module = moduleArg;
|
|
41255
41788
|
var readyPromiseResolve, readyPromiseReject;
|
|
41256
|
-
var readyPromise = new Promise((
|
|
41257
|
-
readyPromiseResolve =
|
|
41789
|
+
var readyPromise = new Promise((resolve11, reject) => {
|
|
41790
|
+
readyPromiseResolve = resolve11;
|
|
41258
41791
|
readyPromiseReject = reject;
|
|
41259
41792
|
});
|
|
41260
41793
|
var ENVIRONMENT_IS_WEB = typeof window == "object";
|
|
@@ -41276,11 +41809,11 @@ var Module2 = (() => {
|
|
|
41276
41809
|
throw toThrow;
|
|
41277
41810
|
}, "quit_");
|
|
41278
41811
|
var scriptDirectory = "";
|
|
41279
|
-
function locateFile(
|
|
41812
|
+
function locateFile(path25) {
|
|
41280
41813
|
if (Module["locateFile"]) {
|
|
41281
|
-
return Module["locateFile"](
|
|
41814
|
+
return Module["locateFile"](path25, scriptDirectory);
|
|
41282
41815
|
}
|
|
41283
|
-
return scriptDirectory +
|
|
41816
|
+
return scriptDirectory + path25;
|
|
41284
41817
|
}
|
|
41285
41818
|
__name(locateFile, "locateFile");
|
|
41286
41819
|
var readAsync, readBinary;
|
|
@@ -41334,13 +41867,13 @@ var Module2 = (() => {
|
|
|
41334
41867
|
}
|
|
41335
41868
|
readAsync = /* @__PURE__ */ __name(async (url3) => {
|
|
41336
41869
|
if (isFileURI(url3)) {
|
|
41337
|
-
return new Promise((
|
|
41870
|
+
return new Promise((resolve11, reject) => {
|
|
41338
41871
|
var xhr = new XMLHttpRequest;
|
|
41339
41872
|
xhr.open("GET", url3, true);
|
|
41340
41873
|
xhr.responseType = "arraybuffer";
|
|
41341
41874
|
xhr.onload = () => {
|
|
41342
41875
|
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
|
41343
|
-
|
|
41876
|
+
resolve11(xhr.response);
|
|
41344
41877
|
return;
|
|
41345
41878
|
}
|
|
41346
41879
|
reject(xhr.status);
|
|
@@ -41560,10 +42093,10 @@ var Module2 = (() => {
|
|
|
41560
42093
|
__name(receiveInstantiationResult, "receiveInstantiationResult");
|
|
41561
42094
|
var info2 = getWasmImports();
|
|
41562
42095
|
if (Module["instantiateWasm"]) {
|
|
41563
|
-
return new Promise((
|
|
42096
|
+
return new Promise((resolve11, reject) => {
|
|
41564
42097
|
Module["instantiateWasm"](info2, (mod, inst) => {
|
|
41565
42098
|
receiveInstance(mod, inst);
|
|
41566
|
-
|
|
42099
|
+
resolve11(mod.exports);
|
|
41567
42100
|
});
|
|
41568
42101
|
});
|
|
41569
42102
|
}
|
|
@@ -43094,7 +43627,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
|
|
|
43094
43627
|
]);
|
|
43095
43628
|
// src/tools/pre-check-batch.ts
|
|
43096
43629
|
init_dist();
|
|
43097
|
-
import * as
|
|
43630
|
+
import * as path27 from "path";
|
|
43098
43631
|
|
|
43099
43632
|
// node_modules/yocto-queue/index.js
|
|
43100
43633
|
class Node2 {
|
|
@@ -43185,26 +43718,26 @@ function pLimit(concurrency) {
|
|
|
43185
43718
|
activeCount--;
|
|
43186
43719
|
resumeNext();
|
|
43187
43720
|
};
|
|
43188
|
-
const run2 = async (function_,
|
|
43721
|
+
const run2 = async (function_, resolve11, arguments_2) => {
|
|
43189
43722
|
const result = (async () => function_(...arguments_2))();
|
|
43190
|
-
|
|
43723
|
+
resolve11(result);
|
|
43191
43724
|
try {
|
|
43192
43725
|
await result;
|
|
43193
43726
|
} catch {}
|
|
43194
43727
|
next();
|
|
43195
43728
|
};
|
|
43196
|
-
const enqueue = (function_,
|
|
43729
|
+
const enqueue = (function_, resolve11, reject, arguments_2) => {
|
|
43197
43730
|
const queueItem = { reject };
|
|
43198
43731
|
new Promise((internalResolve) => {
|
|
43199
43732
|
queueItem.run = internalResolve;
|
|
43200
43733
|
queue.enqueue(queueItem);
|
|
43201
|
-
}).then(run2.bind(undefined, function_,
|
|
43734
|
+
}).then(run2.bind(undefined, function_, resolve11, arguments_2));
|
|
43202
43735
|
if (activeCount < concurrency) {
|
|
43203
43736
|
resumeNext();
|
|
43204
43737
|
}
|
|
43205
43738
|
};
|
|
43206
|
-
const generator = (function_, ...arguments_2) => new Promise((
|
|
43207
|
-
enqueue(function_,
|
|
43739
|
+
const generator = (function_, ...arguments_2) => new Promise((resolve11, reject) => {
|
|
43740
|
+
enqueue(function_, resolve11, reject, arguments_2);
|
|
43208
43741
|
});
|
|
43209
43742
|
Object.defineProperties(generator, {
|
|
43210
43743
|
activeCount: {
|
|
@@ -43261,7 +43794,7 @@ init_manager();
|
|
|
43261
43794
|
|
|
43262
43795
|
// src/quality/metrics.ts
|
|
43263
43796
|
import * as fs19 from "fs";
|
|
43264
|
-
import * as
|
|
43797
|
+
import * as path25 from "path";
|
|
43265
43798
|
var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
|
|
43266
43799
|
var MIN_DUPLICATION_LINES = 10;
|
|
43267
43800
|
function estimateCyclomaticComplexity(content) {
|
|
@@ -43313,7 +43846,7 @@ async function computeComplexityDelta(files, workingDir) {
|
|
|
43313
43846
|
let totalComplexity = 0;
|
|
43314
43847
|
const analyzedFiles = [];
|
|
43315
43848
|
for (const file3 of files) {
|
|
43316
|
-
const fullPath =
|
|
43849
|
+
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
43317
43850
|
if (!fs19.existsSync(fullPath)) {
|
|
43318
43851
|
continue;
|
|
43319
43852
|
}
|
|
@@ -43436,7 +43969,7 @@ function countGoExports(content) {
|
|
|
43436
43969
|
function getExportCountForFile(filePath) {
|
|
43437
43970
|
try {
|
|
43438
43971
|
const content = fs19.readFileSync(filePath, "utf-8");
|
|
43439
|
-
const ext =
|
|
43972
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
43440
43973
|
switch (ext) {
|
|
43441
43974
|
case ".ts":
|
|
43442
43975
|
case ".tsx":
|
|
@@ -43462,7 +43995,7 @@ async function computePublicApiDelta(files, workingDir) {
|
|
|
43462
43995
|
let totalExports = 0;
|
|
43463
43996
|
const analyzedFiles = [];
|
|
43464
43997
|
for (const file3 of files) {
|
|
43465
|
-
const fullPath =
|
|
43998
|
+
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
43466
43999
|
if (!fs19.existsSync(fullPath)) {
|
|
43467
44000
|
continue;
|
|
43468
44001
|
}
|
|
@@ -43496,7 +44029,7 @@ async function computeDuplicationRatio(files, workingDir) {
|
|
|
43496
44029
|
let duplicateLines = 0;
|
|
43497
44030
|
const analyzedFiles = [];
|
|
43498
44031
|
for (const file3 of files) {
|
|
43499
|
-
const fullPath =
|
|
44032
|
+
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
43500
44033
|
if (!fs19.existsSync(fullPath)) {
|
|
43501
44034
|
continue;
|
|
43502
44035
|
}
|
|
@@ -43529,8 +44062,8 @@ function countCodeLines(content) {
|
|
|
43529
44062
|
return lines.length;
|
|
43530
44063
|
}
|
|
43531
44064
|
function isTestFile(filePath) {
|
|
43532
|
-
const basename5 =
|
|
43533
|
-
const _ext =
|
|
44065
|
+
const basename5 = path25.basename(filePath);
|
|
44066
|
+
const _ext = path25.extname(filePath).toLowerCase();
|
|
43534
44067
|
const testPatterns = [
|
|
43535
44068
|
".test.",
|
|
43536
44069
|
".spec.",
|
|
@@ -43572,7 +44105,7 @@ function shouldExcludeFile(filePath, excludeGlobs) {
|
|
|
43572
44105
|
async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
43573
44106
|
let testLines = 0;
|
|
43574
44107
|
let codeLines = 0;
|
|
43575
|
-
const srcDir =
|
|
44108
|
+
const srcDir = path25.join(workingDir, "src");
|
|
43576
44109
|
if (fs19.existsSync(srcDir)) {
|
|
43577
44110
|
await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
43578
44111
|
codeLines += lines;
|
|
@@ -43580,14 +44113,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
43580
44113
|
}
|
|
43581
44114
|
const possibleSrcDirs = ["lib", "app", "source", "core"];
|
|
43582
44115
|
for (const dir of possibleSrcDirs) {
|
|
43583
|
-
const dirPath =
|
|
44116
|
+
const dirPath = path25.join(workingDir, dir);
|
|
43584
44117
|
if (fs19.existsSync(dirPath)) {
|
|
43585
44118
|
await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
43586
44119
|
codeLines += lines;
|
|
43587
44120
|
});
|
|
43588
44121
|
}
|
|
43589
44122
|
}
|
|
43590
|
-
const testsDir =
|
|
44123
|
+
const testsDir = path25.join(workingDir, "tests");
|
|
43591
44124
|
if (fs19.existsSync(testsDir)) {
|
|
43592
44125
|
await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
43593
44126
|
testLines += lines;
|
|
@@ -43595,7 +44128,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
43595
44128
|
}
|
|
43596
44129
|
const possibleTestDirs = ["test", "__tests__", "specs"];
|
|
43597
44130
|
for (const dir of possibleTestDirs) {
|
|
43598
|
-
const dirPath =
|
|
44131
|
+
const dirPath = path25.join(workingDir, dir);
|
|
43599
44132
|
if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
|
|
43600
44133
|
await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
43601
44134
|
testLines += lines;
|
|
@@ -43610,7 +44143,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
|
|
|
43610
44143
|
try {
|
|
43611
44144
|
const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
|
|
43612
44145
|
for (const entry of entries) {
|
|
43613
|
-
const fullPath =
|
|
44146
|
+
const fullPath = path25.join(dirPath, entry.name);
|
|
43614
44147
|
if (entry.isDirectory()) {
|
|
43615
44148
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
|
|
43616
44149
|
continue;
|
|
@@ -43618,7 +44151,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
|
|
|
43618
44151
|
await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
|
|
43619
44152
|
} else if (entry.isFile()) {
|
|
43620
44153
|
const relativePath = fullPath.replace(`${process.cwd()}/`, "");
|
|
43621
|
-
const ext =
|
|
44154
|
+
const ext = path25.extname(entry.name).toLowerCase();
|
|
43622
44155
|
const validExts = [
|
|
43623
44156
|
".ts",
|
|
43624
44157
|
".tsx",
|
|
@@ -43873,7 +44406,7 @@ async function qualityBudget(input, directory) {
|
|
|
43873
44406
|
// src/tools/sast-scan.ts
|
|
43874
44407
|
init_manager();
|
|
43875
44408
|
import * as fs20 from "fs";
|
|
43876
|
-
import * as
|
|
44409
|
+
import * as path26 from "path";
|
|
43877
44410
|
import { extname as extname7 } from "path";
|
|
43878
44411
|
|
|
43879
44412
|
// src/sast/rules/c.ts
|
|
@@ -44501,9 +45034,9 @@ function findPatternMatches(content, pattern) {
|
|
|
44501
45034
|
`);
|
|
44502
45035
|
for (let lineNum = 0;lineNum < lines.length; lineNum++) {
|
|
44503
45036
|
const line = lines[lineNum];
|
|
44504
|
-
const workPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags
|
|
44505
|
-
let match;
|
|
44506
|
-
while (
|
|
45037
|
+
const workPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
45038
|
+
let match = workPattern.exec(line);
|
|
45039
|
+
while (match !== null) {
|
|
44507
45040
|
matches.push({
|
|
44508
45041
|
text: match[0],
|
|
44509
45042
|
line: lineNum + 1,
|
|
@@ -44512,6 +45045,7 @@ function findPatternMatches(content, pattern) {
|
|
|
44512
45045
|
if (match[0].length === 0) {
|
|
44513
45046
|
workPattern.lastIndex++;
|
|
44514
45047
|
}
|
|
45048
|
+
match = workPattern.exec(line);
|
|
44515
45049
|
}
|
|
44516
45050
|
}
|
|
44517
45051
|
return matches;
|
|
@@ -44558,7 +45092,7 @@ function executeRulesSync(filePath, content, language) {
|
|
|
44558
45092
|
// src/sast/semgrep.ts
|
|
44559
45093
|
import { execFile, execFileSync, spawn } from "child_process";
|
|
44560
45094
|
import { promisify } from "util";
|
|
44561
|
-
var
|
|
45095
|
+
var _execFileAsync = promisify(execFile);
|
|
44562
45096
|
var semgrepAvailableCache = null;
|
|
44563
45097
|
var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
|
|
44564
45098
|
var DEFAULT_TIMEOUT_MS3 = 30000;
|
|
@@ -44620,7 +45154,7 @@ function mapSemgrepSeverity(severity) {
|
|
|
44620
45154
|
}
|
|
44621
45155
|
}
|
|
44622
45156
|
async function executeWithTimeout(command, args2, options) {
|
|
44623
|
-
return new Promise((
|
|
45157
|
+
return new Promise((resolve11) => {
|
|
44624
45158
|
const child = spawn(command, args2, {
|
|
44625
45159
|
shell: false,
|
|
44626
45160
|
cwd: options.cwd
|
|
@@ -44629,7 +45163,7 @@ async function executeWithTimeout(command, args2, options) {
|
|
|
44629
45163
|
let stderr = "";
|
|
44630
45164
|
const timeout = setTimeout(() => {
|
|
44631
45165
|
child.kill("SIGTERM");
|
|
44632
|
-
|
|
45166
|
+
resolve11({
|
|
44633
45167
|
stdout,
|
|
44634
45168
|
stderr: "Process timed out",
|
|
44635
45169
|
exitCode: 124
|
|
@@ -44643,7 +45177,7 @@ async function executeWithTimeout(command, args2, options) {
|
|
|
44643
45177
|
});
|
|
44644
45178
|
child.on("close", (code) => {
|
|
44645
45179
|
clearTimeout(timeout);
|
|
44646
|
-
|
|
45180
|
+
resolve11({
|
|
44647
45181
|
stdout,
|
|
44648
45182
|
stderr,
|
|
44649
45183
|
exitCode: code ?? 0
|
|
@@ -44651,7 +45185,7 @@ async function executeWithTimeout(command, args2, options) {
|
|
|
44651
45185
|
});
|
|
44652
45186
|
child.on("error", (err2) => {
|
|
44653
45187
|
clearTimeout(timeout);
|
|
44654
|
-
|
|
45188
|
+
resolve11({
|
|
44655
45189
|
stdout,
|
|
44656
45190
|
stderr: err2.message,
|
|
44657
45191
|
exitCode: 1
|
|
@@ -44679,7 +45213,7 @@ async function runSemgrep(options) {
|
|
|
44679
45213
|
};
|
|
44680
45214
|
}
|
|
44681
45215
|
const args2 = [
|
|
44682
|
-
|
|
45216
|
+
`--config=./${rulesDir}`,
|
|
44683
45217
|
"--json",
|
|
44684
45218
|
"--quiet",
|
|
44685
45219
|
...files
|
|
@@ -44822,25 +45356,25 @@ async function sastScan(input, directory, config3) {
|
|
|
44822
45356
|
}
|
|
44823
45357
|
const allFindings = [];
|
|
44824
45358
|
let filesScanned = 0;
|
|
44825
|
-
let
|
|
45359
|
+
let _filesSkipped = 0;
|
|
44826
45360
|
const semgrepAvailable = isSemgrepAvailable();
|
|
44827
45361
|
const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
|
|
44828
45362
|
const filesByLanguage = new Map;
|
|
44829
45363
|
for (const filePath of changed_files) {
|
|
44830
|
-
const resolvedPath =
|
|
45364
|
+
const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
|
|
44831
45365
|
if (!fs20.existsSync(resolvedPath)) {
|
|
44832
|
-
|
|
45366
|
+
_filesSkipped++;
|
|
44833
45367
|
continue;
|
|
44834
45368
|
}
|
|
44835
45369
|
const skipResult = shouldSkipFile(resolvedPath);
|
|
44836
45370
|
if (skipResult.skip) {
|
|
44837
|
-
|
|
45371
|
+
_filesSkipped++;
|
|
44838
45372
|
continue;
|
|
44839
45373
|
}
|
|
44840
45374
|
const ext = extname7(resolvedPath).toLowerCase();
|
|
44841
45375
|
const langDef = getLanguageForExtension(ext);
|
|
44842
45376
|
if (!langDef) {
|
|
44843
|
-
|
|
45377
|
+
_filesSkipped++;
|
|
44844
45378
|
continue;
|
|
44845
45379
|
}
|
|
44846
45380
|
const language = langDef.id;
|
|
@@ -44936,10 +45470,10 @@ function validatePath(inputPath, baseDir) {
|
|
|
44936
45470
|
if (!inputPath || inputPath.length === 0) {
|
|
44937
45471
|
return "path is required";
|
|
44938
45472
|
}
|
|
44939
|
-
const resolved =
|
|
44940
|
-
const baseResolved =
|
|
44941
|
-
const
|
|
44942
|
-
if (
|
|
45473
|
+
const resolved = path27.resolve(baseDir, inputPath);
|
|
45474
|
+
const baseResolved = path27.resolve(baseDir);
|
|
45475
|
+
const relative3 = path27.relative(baseResolved, resolved);
|
|
45476
|
+
if (relative3.startsWith("..") || path27.isAbsolute(relative3)) {
|
|
44943
45477
|
return "path traversal detected";
|
|
44944
45478
|
}
|
|
44945
45479
|
return null;
|
|
@@ -45061,7 +45595,7 @@ async function runPreCheckBatch(input) {
|
|
|
45061
45595
|
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
45062
45596
|
continue;
|
|
45063
45597
|
}
|
|
45064
|
-
changedFiles.push(
|
|
45598
|
+
changedFiles.push(path27.resolve(directory, file3));
|
|
45065
45599
|
}
|
|
45066
45600
|
} else {
|
|
45067
45601
|
changedFiles = [];
|
|
@@ -45252,7 +45786,7 @@ var retrieve_summary = tool({
|
|
|
45252
45786
|
init_dist();
|
|
45253
45787
|
init_manager();
|
|
45254
45788
|
import * as fs21 from "fs";
|
|
45255
|
-
import * as
|
|
45789
|
+
import * as path28 from "path";
|
|
45256
45790
|
|
|
45257
45791
|
// src/sbom/detectors/dart.ts
|
|
45258
45792
|
function parsePubspecLock(content) {
|
|
@@ -46099,7 +46633,7 @@ function findManifestFiles(rootDir) {
|
|
|
46099
46633
|
try {
|
|
46100
46634
|
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
46101
46635
|
for (const entry of entries) {
|
|
46102
|
-
const fullPath =
|
|
46636
|
+
const fullPath = path28.join(dir, entry.name);
|
|
46103
46637
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
46104
46638
|
continue;
|
|
46105
46639
|
}
|
|
@@ -46109,7 +46643,7 @@ function findManifestFiles(rootDir) {
|
|
|
46109
46643
|
for (const pattern of patterns) {
|
|
46110
46644
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
46111
46645
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
46112
|
-
manifestFiles.push(
|
|
46646
|
+
manifestFiles.push(path28.relative(cwd, fullPath));
|
|
46113
46647
|
break;
|
|
46114
46648
|
}
|
|
46115
46649
|
}
|
|
@@ -46122,18 +46656,18 @@ function findManifestFiles(rootDir) {
|
|
|
46122
46656
|
}
|
|
46123
46657
|
function findManifestFilesInDirs(directories, workingDir) {
|
|
46124
46658
|
const found = [];
|
|
46125
|
-
const
|
|
46659
|
+
const _cwd = process.cwd();
|
|
46126
46660
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
46127
46661
|
for (const dir of directories) {
|
|
46128
46662
|
try {
|
|
46129
46663
|
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
46130
46664
|
for (const entry of entries) {
|
|
46131
|
-
const fullPath =
|
|
46665
|
+
const fullPath = path28.join(dir, entry.name);
|
|
46132
46666
|
if (entry.isFile()) {
|
|
46133
46667
|
for (const pattern of patterns) {
|
|
46134
46668
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
46135
46669
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
46136
|
-
found.push(
|
|
46670
|
+
found.push(path28.relative(workingDir, fullPath));
|
|
46137
46671
|
break;
|
|
46138
46672
|
}
|
|
46139
46673
|
}
|
|
@@ -46146,11 +46680,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
46146
46680
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
46147
46681
|
const dirs = new Set;
|
|
46148
46682
|
for (const file3 of changedFiles) {
|
|
46149
|
-
let currentDir =
|
|
46683
|
+
let currentDir = path28.dirname(file3);
|
|
46150
46684
|
while (true) {
|
|
46151
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
46152
|
-
dirs.add(
|
|
46153
|
-
const parent =
|
|
46685
|
+
if (currentDir && currentDir !== "." && currentDir !== path28.sep) {
|
|
46686
|
+
dirs.add(path28.join(workingDir, currentDir));
|
|
46687
|
+
const parent = path28.dirname(currentDir);
|
|
46154
46688
|
if (parent === currentDir)
|
|
46155
46689
|
break;
|
|
46156
46690
|
currentDir = parent;
|
|
@@ -46257,7 +46791,7 @@ var sbom_generate = tool({
|
|
|
46257
46791
|
const processedFiles = [];
|
|
46258
46792
|
for (const manifestFile of manifestFiles) {
|
|
46259
46793
|
try {
|
|
46260
|
-
const fullPath =
|
|
46794
|
+
const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
|
|
46261
46795
|
if (!fs21.existsSync(fullPath)) {
|
|
46262
46796
|
continue;
|
|
46263
46797
|
}
|
|
@@ -46274,7 +46808,7 @@ var sbom_generate = tool({
|
|
|
46274
46808
|
const bom = generateCycloneDX(allComponents);
|
|
46275
46809
|
const bomJson = serializeCycloneDX(bom);
|
|
46276
46810
|
const filename = generateSbomFilename();
|
|
46277
|
-
const outputPath =
|
|
46811
|
+
const outputPath = path28.join(outputDir, filename);
|
|
46278
46812
|
fs21.writeFileSync(outputPath, bomJson, "utf-8");
|
|
46279
46813
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
46280
46814
|
try {
|
|
@@ -46317,7 +46851,7 @@ var sbom_generate = tool({
|
|
|
46317
46851
|
// src/tools/schema-drift.ts
|
|
46318
46852
|
init_dist();
|
|
46319
46853
|
import * as fs22 from "fs";
|
|
46320
|
-
import * as
|
|
46854
|
+
import * as path29 from "path";
|
|
46321
46855
|
var SPEC_CANDIDATES = [
|
|
46322
46856
|
"openapi.json",
|
|
46323
46857
|
"openapi.yaml",
|
|
@@ -46349,12 +46883,12 @@ function normalizePath(p) {
|
|
|
46349
46883
|
}
|
|
46350
46884
|
function discoverSpecFile(cwd, specFileArg) {
|
|
46351
46885
|
if (specFileArg) {
|
|
46352
|
-
const resolvedPath =
|
|
46353
|
-
const normalizedCwd = cwd.endsWith(
|
|
46886
|
+
const resolvedPath = path29.resolve(cwd, specFileArg);
|
|
46887
|
+
const normalizedCwd = cwd.endsWith(path29.sep) ? cwd : cwd + path29.sep;
|
|
46354
46888
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
46355
46889
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
46356
46890
|
}
|
|
46357
|
-
const ext =
|
|
46891
|
+
const ext = path29.extname(resolvedPath).toLowerCase();
|
|
46358
46892
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
46359
46893
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
46360
46894
|
}
|
|
@@ -46368,7 +46902,7 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
46368
46902
|
return resolvedPath;
|
|
46369
46903
|
}
|
|
46370
46904
|
for (const candidate of SPEC_CANDIDATES) {
|
|
46371
|
-
const candidatePath =
|
|
46905
|
+
const candidatePath = path29.resolve(cwd, candidate);
|
|
46372
46906
|
if (fs22.existsSync(candidatePath)) {
|
|
46373
46907
|
const stats = fs22.statSync(candidatePath);
|
|
46374
46908
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
@@ -46380,7 +46914,7 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
46380
46914
|
}
|
|
46381
46915
|
function parseSpec(specFile) {
|
|
46382
46916
|
const content = fs22.readFileSync(specFile, "utf-8");
|
|
46383
|
-
const ext =
|
|
46917
|
+
const ext = path29.extname(specFile).toLowerCase();
|
|
46384
46918
|
if (ext === ".json") {
|
|
46385
46919
|
return parseJsonSpec(content);
|
|
46386
46920
|
}
|
|
@@ -46422,18 +46956,18 @@ function parseYamlSpec(content) {
|
|
|
46422
46956
|
return paths;
|
|
46423
46957
|
}
|
|
46424
46958
|
const pathRegex = /^\s{2}(\/[^\s:]+):/gm;
|
|
46425
|
-
let match;
|
|
46426
|
-
while ((match = pathRegex.exec(content)) !== null) {
|
|
46959
|
+
for (let match = pathRegex.exec(content);match !== null; match = pathRegex.exec(content)) {
|
|
46427
46960
|
const pathKey = match[1];
|
|
46428
46961
|
const methodRegex = /^\s{4}(get|post|put|patch|delete|options|head):/gm;
|
|
46429
46962
|
const methods = [];
|
|
46430
|
-
let methodMatch;
|
|
46431
46963
|
const pathStart = match.index;
|
|
46432
46964
|
const nextPathMatch = content.substring(pathStart + 1).match(/^\s{2}\//m);
|
|
46433
46965
|
const pathEnd = nextPathMatch && nextPathMatch.index !== undefined ? pathStart + 1 + nextPathMatch.index : content.length;
|
|
46434
46966
|
const pathSection = content.substring(pathStart, pathEnd);
|
|
46435
|
-
|
|
46967
|
+
let methodMatch = methodRegex.exec(pathSection);
|
|
46968
|
+
while (methodMatch !== null) {
|
|
46436
46969
|
methods.push(methodMatch[1]);
|
|
46970
|
+
methodMatch = methodRegex.exec(pathSection);
|
|
46437
46971
|
}
|
|
46438
46972
|
if (methods.length > 0) {
|
|
46439
46973
|
paths.push({ path: pathKey, methods });
|
|
@@ -46451,7 +46985,7 @@ function extractRoutes(cwd) {
|
|
|
46451
46985
|
return;
|
|
46452
46986
|
}
|
|
46453
46987
|
for (const entry of entries) {
|
|
46454
|
-
const fullPath =
|
|
46988
|
+
const fullPath = path29.join(dir, entry.name);
|
|
46455
46989
|
if (entry.isSymbolicLink()) {
|
|
46456
46990
|
continue;
|
|
46457
46991
|
}
|
|
@@ -46461,7 +46995,7 @@ function extractRoutes(cwd) {
|
|
|
46461
46995
|
}
|
|
46462
46996
|
walkDir(fullPath);
|
|
46463
46997
|
} else if (entry.isFile()) {
|
|
46464
|
-
const ext =
|
|
46998
|
+
const ext = path29.extname(entry.name).toLowerCase();
|
|
46465
46999
|
const baseName = entry.name.toLowerCase();
|
|
46466
47000
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
46467
47001
|
continue;
|
|
@@ -46485,10 +47019,10 @@ function extractRoutesFromFile(filePath) {
|
|
|
46485
47019
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
46486
47020
|
for (let lineNum = 0;lineNum < lines.length; lineNum++) {
|
|
46487
47021
|
const line = lines[lineNum];
|
|
46488
|
-
let match;
|
|
47022
|
+
let match = expressRegex.exec(line);
|
|
46489
47023
|
expressRegex.lastIndex = 0;
|
|
46490
47024
|
flaskRegex.lastIndex = 0;
|
|
46491
|
-
while (
|
|
47025
|
+
while (match !== null) {
|
|
46492
47026
|
const method = match[1].toLowerCase();
|
|
46493
47027
|
const routePath = match[2];
|
|
46494
47028
|
routes.push({
|
|
@@ -46497,8 +47031,10 @@ function extractRoutesFromFile(filePath) {
|
|
|
46497
47031
|
file: filePath,
|
|
46498
47032
|
line: lineNum + 1
|
|
46499
47033
|
});
|
|
47034
|
+
match = expressRegex.exec(line);
|
|
46500
47035
|
}
|
|
46501
|
-
|
|
47036
|
+
match = flaskRegex.exec(line);
|
|
47037
|
+
while (match !== null) {
|
|
46502
47038
|
const routePath = match[1];
|
|
46503
47039
|
routes.push({
|
|
46504
47040
|
path: routePath,
|
|
@@ -46506,6 +47042,7 @@ function extractRoutesFromFile(filePath) {
|
|
|
46506
47042
|
file: filePath,
|
|
46507
47043
|
line: lineNum + 1
|
|
46508
47044
|
});
|
|
47045
|
+
match = flaskRegex.exec(line);
|
|
46509
47046
|
}
|
|
46510
47047
|
}
|
|
46511
47048
|
return routes;
|
|
@@ -46627,7 +47164,7 @@ init_secretscan();
|
|
|
46627
47164
|
// src/tools/symbols.ts
|
|
46628
47165
|
init_tool();
|
|
46629
47166
|
import * as fs23 from "fs";
|
|
46630
|
-
import * as
|
|
47167
|
+
import * as path30 from "path";
|
|
46631
47168
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
46632
47169
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
46633
47170
|
function containsControlCharacters(str) {
|
|
@@ -46656,11 +47193,11 @@ function containsWindowsAttacks(str) {
|
|
|
46656
47193
|
}
|
|
46657
47194
|
function isPathInWorkspace(filePath, workspace) {
|
|
46658
47195
|
try {
|
|
46659
|
-
const resolvedPath =
|
|
47196
|
+
const resolvedPath = path30.resolve(workspace, filePath);
|
|
46660
47197
|
const realWorkspace = fs23.realpathSync(workspace);
|
|
46661
47198
|
const realResolvedPath = fs23.realpathSync(resolvedPath);
|
|
46662
|
-
const relativePath =
|
|
46663
|
-
if (relativePath.startsWith("..") ||
|
|
47199
|
+
const relativePath = path30.relative(realWorkspace, realResolvedPath);
|
|
47200
|
+
if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
|
|
46664
47201
|
return false;
|
|
46665
47202
|
}
|
|
46666
47203
|
return true;
|
|
@@ -46672,7 +47209,7 @@ function validatePathForRead(filePath, workspace) {
|
|
|
46672
47209
|
return isPathInWorkspace(filePath, workspace);
|
|
46673
47210
|
}
|
|
46674
47211
|
function extractTSSymbols(filePath, cwd) {
|
|
46675
|
-
const fullPath =
|
|
47212
|
+
const fullPath = path30.join(cwd, filePath);
|
|
46676
47213
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
46677
47214
|
return [];
|
|
46678
47215
|
}
|
|
@@ -46703,7 +47240,7 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
46703
47240
|
jsdoc = jsdocLines.join(`
|
|
46704
47241
|
`).trim();
|
|
46705
47242
|
if (jsdoc.length > 300)
|
|
46706
|
-
jsdoc = jsdoc.substring(0, 300)
|
|
47243
|
+
jsdoc = `${jsdoc.substring(0, 300)}...`;
|
|
46707
47244
|
}
|
|
46708
47245
|
const fnMatch = line.match(/^export\s+(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)(?:\s*:\s*(.+?))?(?:\s*\{|$)/);
|
|
46709
47246
|
if (fnMatch) {
|
|
@@ -46824,7 +47361,7 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
46824
47361
|
});
|
|
46825
47362
|
}
|
|
46826
47363
|
function extractPythonSymbols(filePath, cwd) {
|
|
46827
|
-
const fullPath =
|
|
47364
|
+
const fullPath = path30.join(cwd, filePath);
|
|
46828
47365
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
46829
47366
|
return [];
|
|
46830
47367
|
}
|
|
@@ -46906,7 +47443,7 @@ var symbols = tool({
|
|
|
46906
47443
|
}, null, 2);
|
|
46907
47444
|
}
|
|
46908
47445
|
const cwd = process.cwd();
|
|
46909
|
-
const ext =
|
|
47446
|
+
const ext = path30.extname(file3);
|
|
46910
47447
|
if (containsControlCharacters(file3)) {
|
|
46911
47448
|
return JSON.stringify({
|
|
46912
47449
|
file: file3,
|
|
@@ -46975,7 +47512,7 @@ init_test_runner();
|
|
|
46975
47512
|
// src/tools/todo-extract.ts
|
|
46976
47513
|
init_dist();
|
|
46977
47514
|
import * as fs24 from "fs";
|
|
46978
|
-
import * as
|
|
47515
|
+
import * as path31 from "path";
|
|
46979
47516
|
var MAX_TEXT_LENGTH = 200;
|
|
46980
47517
|
var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
|
|
46981
47518
|
var SUPPORTED_EXTENSIONS2 = new Set([
|
|
@@ -47046,9 +47583,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
47046
47583
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
47047
47584
|
}
|
|
47048
47585
|
try {
|
|
47049
|
-
const resolvedPath =
|
|
47050
|
-
const normalizedCwd =
|
|
47051
|
-
const normalizedResolved =
|
|
47586
|
+
const resolvedPath = path31.resolve(paths);
|
|
47587
|
+
const normalizedCwd = path31.resolve(cwd);
|
|
47588
|
+
const normalizedResolved = path31.resolve(resolvedPath);
|
|
47052
47589
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
47053
47590
|
return {
|
|
47054
47591
|
error: "paths must be within the current working directory",
|
|
@@ -47064,7 +47601,7 @@ function validatePathsInput(paths, cwd) {
|
|
|
47064
47601
|
}
|
|
47065
47602
|
}
|
|
47066
47603
|
function isSupportedExtension(filePath) {
|
|
47067
|
-
const ext =
|
|
47604
|
+
const ext = path31.extname(filePath).toLowerCase();
|
|
47068
47605
|
return SUPPORTED_EXTENSIONS2.has(ext);
|
|
47069
47606
|
}
|
|
47070
47607
|
function findSourceFiles3(dir, files = []) {
|
|
@@ -47079,7 +47616,7 @@ function findSourceFiles3(dir, files = []) {
|
|
|
47079
47616
|
if (SKIP_DIRECTORIES3.has(entry)) {
|
|
47080
47617
|
continue;
|
|
47081
47618
|
}
|
|
47082
|
-
const fullPath =
|
|
47619
|
+
const fullPath = path31.join(dir, entry);
|
|
47083
47620
|
let stat;
|
|
47084
47621
|
try {
|
|
47085
47622
|
stat = fs24.statSync(fullPath);
|
|
@@ -47111,7 +47648,7 @@ function parseTodoComments(content, filePath, tagsSet) {
|
|
|
47111
47648
|
let text = line.substring(match.index + match[0].length).trim();
|
|
47112
47649
|
text = text.replace(/^[/*\-\s]+/, "");
|
|
47113
47650
|
if (text.length > MAX_TEXT_LENGTH) {
|
|
47114
|
-
text = text.substring(0, MAX_TEXT_LENGTH - 3)
|
|
47651
|
+
text = `${text.substring(0, MAX_TEXT_LENGTH - 3)}...`;
|
|
47115
47652
|
}
|
|
47116
47653
|
entries.push({
|
|
47117
47654
|
file: filePath,
|
|
@@ -47191,7 +47728,7 @@ var todo_extract = tool({
|
|
|
47191
47728
|
filesToScan.push(scanPath);
|
|
47192
47729
|
} else {
|
|
47193
47730
|
const errorResult = {
|
|
47194
|
-
error: `unsupported file extension: ${
|
|
47731
|
+
error: `unsupported file extension: ${path31.extname(scanPath)}`,
|
|
47195
47732
|
total: 0,
|
|
47196
47733
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
47197
47734
|
entries: []
|
|
@@ -47279,7 +47816,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
47279
47816
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
47280
47817
|
preflightTriggerManager = new PTM(automationConfig);
|
|
47281
47818
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
47282
|
-
const swarmDir =
|
|
47819
|
+
const swarmDir = path32.resolve(ctx.directory, ".swarm");
|
|
47283
47820
|
statusArtifact = new ASA(swarmDir);
|
|
47284
47821
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
47285
47822
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|