opencode-swarm 6.14.0 → 6.14.12
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/LICENSE +21 -21
- package/dist/cli/index.js +0 -0
- package/dist/config/schema.d.ts +14 -0
- package/dist/hooks/context-budget.d.ts +3 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/message-priority.d.ts +105 -0
- package/dist/hooks/model-limits.d.ts +96 -0
- package/dist/index.js +746 -109
- package/dist/lang/grammars/tree-sitter-bash.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-c-sharp.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-cpp.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-css.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-go.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-ini.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-java.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-php.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-powershell.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-python.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-regex.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/lang/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/lang/grammars/tree-sitter.wasm +0 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/save-plan.d.ts +54 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
2
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name2, newValue) {
|
|
5
|
+
this[name2] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
18
7
|
var __export = (target, all) => {
|
|
19
8
|
for (var name2 in all)
|
|
20
9
|
__defProp(target, name2, {
|
|
21
10
|
get: all[name2],
|
|
22
11
|
enumerable: true,
|
|
23
12
|
configurable: true,
|
|
24
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name2)
|
|
25
14
|
});
|
|
26
15
|
};
|
|
27
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -14352,6 +14341,12 @@ function validateSwarmPath(directory, filename) {
|
|
|
14352
14341
|
if (/\.\.[/\\]/.test(filename)) {
|
|
14353
14342
|
throw new Error("Invalid filename: path traversal detected");
|
|
14354
14343
|
}
|
|
14344
|
+
if (/^[A-Za-z]:[\\/]/.test(filename)) {
|
|
14345
|
+
throw new Error("Invalid filename: path escapes .swarm directory");
|
|
14346
|
+
}
|
|
14347
|
+
if (filename.startsWith("/")) {
|
|
14348
|
+
throw new Error("Invalid filename: path escapes .swarm directory");
|
|
14349
|
+
}
|
|
14355
14350
|
const baseDir = path2.normalize(path2.resolve(directory, ".swarm"));
|
|
14356
14351
|
const resolved = path2.normalize(path2.resolve(baseDir, filename));
|
|
14357
14352
|
if (process.platform === "win32") {
|
|
@@ -14816,7 +14811,7 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
14816
14811
|
}
|
|
14817
14812
|
continue;
|
|
14818
14813
|
}
|
|
14819
|
-
const phaseMatch = trimmed.match(
|
|
14814
|
+
const phaseMatch = trimmed.match(/^#{2,3}\s*Phase\s+(\d+)(?::\s*([^[]+))?\s*(?:\[([^\]]+)\])?/i);
|
|
14820
14815
|
if (phaseMatch) {
|
|
14821
14816
|
if (currentPhase !== null) {
|
|
14822
14817
|
phases.push(currentPhase);
|
|
@@ -14884,12 +14879,87 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
14884
14879
|
};
|
|
14885
14880
|
currentPhase.tasks.push(task);
|
|
14886
14881
|
}
|
|
14882
|
+
const numberedTaskMatch = trimmed.match(/^(\d+)\.\s+(.+?)(?:\s*\[(\w+)\])?$/);
|
|
14883
|
+
if (numberedTaskMatch && currentPhase !== null) {
|
|
14884
|
+
const taskId = `${currentPhase.id}.${currentPhase.tasks.length + 1}`;
|
|
14885
|
+
let description = numberedTaskMatch[2].trim();
|
|
14886
|
+
const sizeText = numberedTaskMatch[3]?.toLowerCase() || "small";
|
|
14887
|
+
const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
|
|
14888
|
+
const depends = [];
|
|
14889
|
+
if (dependsMatch) {
|
|
14890
|
+
const depsText = dependsMatch[1];
|
|
14891
|
+
depends.push(...depsText.split(",").map((d) => d.trim()));
|
|
14892
|
+
description = description.substring(0, dependsMatch.index).trim();
|
|
14893
|
+
}
|
|
14894
|
+
const sizeMap = {
|
|
14895
|
+
small: "small",
|
|
14896
|
+
medium: "medium",
|
|
14897
|
+
large: "large"
|
|
14898
|
+
};
|
|
14899
|
+
const task = {
|
|
14900
|
+
id: taskId,
|
|
14901
|
+
phase: currentPhase.id,
|
|
14902
|
+
status: "pending",
|
|
14903
|
+
size: sizeMap[sizeText] || "small",
|
|
14904
|
+
description,
|
|
14905
|
+
depends,
|
|
14906
|
+
acceptance: undefined,
|
|
14907
|
+
files_touched: [],
|
|
14908
|
+
evidence_path: undefined,
|
|
14909
|
+
blocked_reason: undefined
|
|
14910
|
+
};
|
|
14911
|
+
currentPhase.tasks.push(task);
|
|
14912
|
+
}
|
|
14913
|
+
const noPrefixTaskMatch = trimmed.match(/^-\s*\[([^\]]+)\]\s+(?!\d+\.\d+:)(.+?)(?:\s*\[(\w+)\])?(?:\s*-\s*(.+))?$/i);
|
|
14914
|
+
if (noPrefixTaskMatch && currentPhase !== null) {
|
|
14915
|
+
const checkbox = noPrefixTaskMatch[1].toLowerCase();
|
|
14916
|
+
const taskId = `${currentPhase.id}.${currentPhase.tasks.length + 1}`;
|
|
14917
|
+
let description = noPrefixTaskMatch[2].trim();
|
|
14918
|
+
const sizeText = noPrefixTaskMatch[3]?.toLowerCase() || "small";
|
|
14919
|
+
let blockedReason;
|
|
14920
|
+
const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
|
|
14921
|
+
const depends = [];
|
|
14922
|
+
if (dependsMatch) {
|
|
14923
|
+
const depsText = dependsMatch[1];
|
|
14924
|
+
depends.push(...depsText.split(",").map((d) => d.trim()));
|
|
14925
|
+
description = description.substring(0, dependsMatch.index).trim();
|
|
14926
|
+
}
|
|
14927
|
+
let status = "pending";
|
|
14928
|
+
if (checkbox === "x") {
|
|
14929
|
+
status = "completed";
|
|
14930
|
+
} else if (checkbox === "blocked") {
|
|
14931
|
+
status = "blocked";
|
|
14932
|
+
const blockedReasonMatch = noPrefixTaskMatch[4];
|
|
14933
|
+
if (blockedReasonMatch) {
|
|
14934
|
+
blockedReason = blockedReasonMatch.trim();
|
|
14935
|
+
}
|
|
14936
|
+
}
|
|
14937
|
+
const sizeMap = {
|
|
14938
|
+
small: "small",
|
|
14939
|
+
medium: "medium",
|
|
14940
|
+
large: "large"
|
|
14941
|
+
};
|
|
14942
|
+
const task = {
|
|
14943
|
+
id: taskId,
|
|
14944
|
+
phase: currentPhase.id,
|
|
14945
|
+
status,
|
|
14946
|
+
size: sizeMap[sizeText] || "small",
|
|
14947
|
+
description,
|
|
14948
|
+
depends,
|
|
14949
|
+
acceptance: undefined,
|
|
14950
|
+
files_touched: [],
|
|
14951
|
+
evidence_path: undefined,
|
|
14952
|
+
blocked_reason: blockedReason
|
|
14953
|
+
};
|
|
14954
|
+
currentPhase.tasks.push(task);
|
|
14955
|
+
}
|
|
14887
14956
|
}
|
|
14888
14957
|
if (currentPhase !== null) {
|
|
14889
14958
|
phases.push(currentPhase);
|
|
14890
14959
|
}
|
|
14891
14960
|
let migrationStatus = "migrated";
|
|
14892
14961
|
if (phases.length === 0) {
|
|
14962
|
+
console.warn(`migrateLegacyPlan: 0 phases parsed from ${lines.length} lines. First 3 lines: ${lines.slice(0, 3).join(" | ")}`);
|
|
14893
14963
|
migrationStatus = "migration_failed";
|
|
14894
14964
|
phases.push({
|
|
14895
14965
|
id: 1,
|
|
@@ -31440,7 +31510,7 @@ var init_preflight_integration = __esm(() => {
|
|
|
31440
31510
|
});
|
|
31441
31511
|
|
|
31442
31512
|
// src/index.ts
|
|
31443
|
-
import * as
|
|
31513
|
+
import * as path32 from "path";
|
|
31444
31514
|
|
|
31445
31515
|
// src/tools/tool-names.ts
|
|
31446
31516
|
var TOOL_NAMES = [
|
|
@@ -31467,7 +31537,8 @@ var TOOL_NAMES = [
|
|
|
31467
31537
|
"gitingest",
|
|
31468
31538
|
"retrieve_summary",
|
|
31469
31539
|
"extract_code_blocks",
|
|
31470
|
-
"phase_complete"
|
|
31540
|
+
"phase_complete",
|
|
31541
|
+
"save_plan"
|
|
31471
31542
|
];
|
|
31472
31543
|
var TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
31473
31544
|
|
|
@@ -31500,6 +31571,7 @@ var AGENT_TOOL_MAP = {
|
|
|
31500
31571
|
"pkg_audit",
|
|
31501
31572
|
"pre_check_batch",
|
|
31502
31573
|
"retrieve_summary",
|
|
31574
|
+
"save_plan",
|
|
31503
31575
|
"schema_drift",
|
|
31504
31576
|
"secretscan",
|
|
31505
31577
|
"symbols",
|
|
@@ -31728,7 +31800,14 @@ var ContextBudgetConfigSchema = exports_external.object({
|
|
|
31728
31800
|
critical_threshold: exports_external.number().min(0).max(1).default(0.9),
|
|
31729
31801
|
model_limits: exports_external.record(exports_external.string(), exports_external.number().min(1000)).default({ default: 128000 }),
|
|
31730
31802
|
max_injection_tokens: exports_external.number().min(100).max(50000).default(4000),
|
|
31731
|
-
|
|
31803
|
+
tracked_agents: exports_external.array(exports_external.string()).default(["architect"]),
|
|
31804
|
+
scoring: ScoringConfigSchema.optional(),
|
|
31805
|
+
enforce: exports_external.boolean().default(true),
|
|
31806
|
+
prune_target: exports_external.number().min(0).max(1).default(0.7),
|
|
31807
|
+
preserve_last_n_turns: exports_external.number().min(0).max(100).default(4),
|
|
31808
|
+
recent_window: exports_external.number().min(1).max(100).default(10),
|
|
31809
|
+
enforce_on_agent_switch: exports_external.boolean().default(true),
|
|
31810
|
+
tool_output_mask_threshold: exports_external.number().min(100).max(1e5).default(2000)
|
|
31732
31811
|
});
|
|
31733
31812
|
var EvidenceConfigSchema = exports_external.object({
|
|
31734
31813
|
enabled: exports_external.boolean().default(true),
|
|
@@ -32531,10 +32610,21 @@ This briefing is a HARD REQUIREMENT for ALL phases. Skipping it is a process vio
|
|
|
32531
32610
|
|
|
32532
32611
|
### MODE: PLAN
|
|
32533
32612
|
|
|
32534
|
-
|
|
32535
|
-
-
|
|
32536
|
-
-
|
|
32537
|
-
-
|
|
32613
|
+
Use the \`save_plan\` tool to create the implementation plan. Required parameters:
|
|
32614
|
+
- \`title\`: The real project name from the spec (NOT a placeholder like [Project])
|
|
32615
|
+
- \`swarm_id\`: The swarm identifier (e.g. "mega", "local", "paid")
|
|
32616
|
+
- \`phases\`: Array of phases, each with \`id\` (number), \`name\` (string), and \`tasks\` (array)
|
|
32617
|
+
- Each task needs: \`id\` (e.g. "1.1"), \`description\` (real content from spec \u2014 bracket placeholders like [task] will be REJECTED)
|
|
32618
|
+
- Optional task fields: \`size\` (small/medium/large), \`depends\` (array of task IDs), \`acceptance\` (string)
|
|
32619
|
+
|
|
32620
|
+
Example call:
|
|
32621
|
+
save_plan({ title: "My Real Project", swarm_id: "mega", phases: [{ id: 1, name: "Setup", tasks: [{ id: "1.1", description: "Install dependencies and configure TypeScript", size: "small" }] }] })
|
|
32622
|
+
|
|
32623
|
+
\u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
|
|
32624
|
+
TASK: Write the implementation plan to .swarm/plan.md
|
|
32625
|
+
FILE: .swarm/plan.md
|
|
32626
|
+
INPUT: [provide the complete plan content below]
|
|
32627
|
+
CONSTRAINT: Write EXACTLY the content provided. Do not modify, summarize, or interpret.
|
|
32538
32628
|
|
|
32539
32629
|
TASK GRANULARITY RULES:
|
|
32540
32630
|
- SMALL task: 1 file, 1 function/class/component, 1 logical concern. Delegate as-is.
|
|
@@ -32553,8 +32643,7 @@ PHASE COUNT GUIDANCE:
|
|
|
32553
32643
|
- Rationale: Retrospectives at phase boundaries capture lessons that improve subsequent
|
|
32554
32644
|
phases. A single-phase plan gets zero iterative learning benefit.
|
|
32555
32645
|
|
|
32556
|
-
|
|
32557
|
-
- Decisions, patterns, SME cache, file map
|
|
32646
|
+
Also create .swarm/context.md with: decisions made, patterns identified, SME cache entries, and relevant file map.
|
|
32558
32647
|
|
|
32559
32648
|
### MODE: CRITIC-GATE
|
|
32560
32649
|
Delegate plan to {{AGENT_PREFIX}}critic for review BEFORE any implementation begins.
|
|
@@ -32765,19 +32854,21 @@ Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
|
|
|
32765
32854
|
|
|
32766
32855
|
## FILES
|
|
32767
32856
|
|
|
32857
|
+
\u26A0\uFE0F FILE FORMAT RULES: Every value in angle brackets below MUST be real content derived from the spec or codebase analysis. NEVER write literal bracket-placeholder text like "[task]", "[Project]", "[date]", "[reason]" \u2014 those are template slots in this example, NOT values to reproduce. Status tags like [COMPLETE], [IN PROGRESS], [BLOCKED], [SMALL], [MEDIUM], [LARGE], and checkboxes [x]/[ ] are valid format elements and must be reproduced exactly.
|
|
32858
|
+
|
|
32768
32859
|
.swarm/plan.md:
|
|
32769
32860
|
\`\`\`
|
|
32770
|
-
#
|
|
32861
|
+
# <real project name derived from the spec>
|
|
32771
32862
|
Swarm: {{SWARM_ID}}
|
|
32772
|
-
Phase:
|
|
32863
|
+
Phase: <current phase number> | Updated: <today's date in ISO format>
|
|
32773
32864
|
|
|
32774
|
-
## Phase 1 [COMPLETE]
|
|
32775
|
-
- [x] 1.1:
|
|
32865
|
+
## Phase 1: <descriptive phase name> [COMPLETE]
|
|
32866
|
+
- [x] 1.1: <specific completed task description from spec> [SMALL]
|
|
32776
32867
|
|
|
32777
|
-
## Phase 2 [IN PROGRESS]
|
|
32778
|
-
- [x] 2.1:
|
|
32779
|
-
- [ ] 2.2:
|
|
32780
|
-
- [BLOCKED] 2.3:
|
|
32868
|
+
## Phase 2: <descriptive phase name> [IN PROGRESS]
|
|
32869
|
+
- [x] 2.1: <specific task description from spec> [MEDIUM]
|
|
32870
|
+
- [ ] 2.2: <specific task description from spec> (depends: 2.1) \u2190 CURRENT
|
|
32871
|
+
- [BLOCKED] 2.3: <specific task description from spec> - <reason for blockage>
|
|
32781
32872
|
\`\`\`
|
|
32782
32873
|
|
|
32783
32874
|
.swarm/context.md:
|
|
@@ -32786,14 +32877,14 @@ Phase: [N] | Updated: [date]
|
|
|
32786
32877
|
Swarm: {{SWARM_ID}}
|
|
32787
32878
|
|
|
32788
32879
|
## Decisions
|
|
32789
|
-
-
|
|
32880
|
+
- <specific technical decision made>: <rationale for the decision>
|
|
32790
32881
|
|
|
32791
32882
|
## SME Cache
|
|
32792
|
-
###
|
|
32793
|
-
-
|
|
32883
|
+
### <domain name e.g. security, cross-platform>
|
|
32884
|
+
- <specific guidance from the SME consultation>
|
|
32794
32885
|
|
|
32795
32886
|
## Patterns
|
|
32796
|
-
-
|
|
32887
|
+
- <pattern name>: <how and when to use it in this codebase>
|
|
32797
32888
|
\`\`\``;
|
|
32798
32889
|
function createArchitectAgent(model, customPrompt, customAppendPrompt) {
|
|
32799
32890
|
let prompt = ARCHITECT_PROMPT;
|
|
@@ -36620,8 +36711,232 @@ function createCompactionCustomizerHook(config3, directory) {
|
|
|
36620
36711
|
})
|
|
36621
36712
|
};
|
|
36622
36713
|
}
|
|
36714
|
+
// src/hooks/context-budget.ts
|
|
36715
|
+
init_utils();
|
|
36716
|
+
|
|
36717
|
+
// src/hooks/message-priority.ts
|
|
36718
|
+
var MessagePriority = {
|
|
36719
|
+
CRITICAL: 0,
|
|
36720
|
+
HIGH: 1,
|
|
36721
|
+
MEDIUM: 2,
|
|
36722
|
+
LOW: 3,
|
|
36723
|
+
DISPOSABLE: 4
|
|
36724
|
+
};
|
|
36725
|
+
function containsPlanContent(text) {
|
|
36726
|
+
if (!text)
|
|
36727
|
+
return false;
|
|
36728
|
+
const lowerText = text.toLowerCase();
|
|
36729
|
+
return lowerText.includes(".swarm/plan") || lowerText.includes(".swarm/context") || lowerText.includes("swarm/plan.md") || lowerText.includes("swarm/context.md");
|
|
36730
|
+
}
|
|
36731
|
+
function isToolResult(message) {
|
|
36732
|
+
if (!message?.info)
|
|
36733
|
+
return false;
|
|
36734
|
+
const role = message.info.role;
|
|
36735
|
+
const toolName = message.info.toolName;
|
|
36736
|
+
return role === "assistant" && !!toolName;
|
|
36737
|
+
}
|
|
36738
|
+
function isDuplicateToolRead(current, previous) {
|
|
36739
|
+
if (!current?.info || !previous?.info)
|
|
36740
|
+
return false;
|
|
36741
|
+
const currentTool = current.info.toolName;
|
|
36742
|
+
const previousTool = previous.info.toolName;
|
|
36743
|
+
if (currentTool !== previousTool)
|
|
36744
|
+
return false;
|
|
36745
|
+
const isReadTool = currentTool?.toLowerCase().includes("read") && previousTool?.toLowerCase().includes("read");
|
|
36746
|
+
if (!isReadTool)
|
|
36747
|
+
return false;
|
|
36748
|
+
const currentArgs = current.info.toolArgs;
|
|
36749
|
+
const previousArgs = previous.info.toolArgs;
|
|
36750
|
+
if (!currentArgs || !previousArgs)
|
|
36751
|
+
return false;
|
|
36752
|
+
const currentKeys = Object.keys(currentArgs);
|
|
36753
|
+
const previousKeys = Object.keys(previousArgs);
|
|
36754
|
+
if (currentKeys.length === 0 || previousKeys.length === 0)
|
|
36755
|
+
return false;
|
|
36756
|
+
const firstKey = currentKeys[0];
|
|
36757
|
+
return currentArgs[firstKey] === previousArgs[firstKey];
|
|
36758
|
+
}
|
|
36759
|
+
function isStaleError(text, turnsAgo) {
|
|
36760
|
+
if (!text)
|
|
36761
|
+
return false;
|
|
36762
|
+
if (turnsAgo <= 6)
|
|
36763
|
+
return false;
|
|
36764
|
+
const lowerText = text.toLowerCase();
|
|
36765
|
+
const errorPatterns = [
|
|
36766
|
+
"error:",
|
|
36767
|
+
"failed to",
|
|
36768
|
+
"could not",
|
|
36769
|
+
"unable to",
|
|
36770
|
+
"exception",
|
|
36771
|
+
"errno",
|
|
36772
|
+
"cannot read",
|
|
36773
|
+
"not found",
|
|
36774
|
+
"access denied",
|
|
36775
|
+
"timeout"
|
|
36776
|
+
];
|
|
36777
|
+
return errorPatterns.some((pattern) => lowerText.includes(pattern));
|
|
36778
|
+
}
|
|
36779
|
+
function extractMessageText(message) {
|
|
36780
|
+
if (!message?.parts || message.parts.length === 0)
|
|
36781
|
+
return "";
|
|
36782
|
+
return message.parts.map((part) => part?.text || "").join("");
|
|
36783
|
+
}
|
|
36784
|
+
function classifyMessage(message, index, totalMessages, recentWindowSize = 10) {
|
|
36785
|
+
const role = message?.info?.role;
|
|
36786
|
+
const text = extractMessageText(message);
|
|
36787
|
+
if (containsPlanContent(text)) {
|
|
36788
|
+
return MessagePriority.CRITICAL;
|
|
36789
|
+
}
|
|
36790
|
+
if (role === "system") {
|
|
36791
|
+
return MessagePriority.CRITICAL;
|
|
36792
|
+
}
|
|
36793
|
+
if (role === "user") {
|
|
36794
|
+
return MessagePriority.HIGH;
|
|
36795
|
+
}
|
|
36796
|
+
if (isToolResult(message)) {
|
|
36797
|
+
const positionFromEnd = totalMessages - 1 - index;
|
|
36798
|
+
if (positionFromEnd < recentWindowSize) {
|
|
36799
|
+
return MessagePriority.MEDIUM;
|
|
36800
|
+
}
|
|
36801
|
+
if (isStaleError(text, positionFromEnd)) {
|
|
36802
|
+
return MessagePriority.DISPOSABLE;
|
|
36803
|
+
}
|
|
36804
|
+
return MessagePriority.LOW;
|
|
36805
|
+
}
|
|
36806
|
+
if (role === "assistant") {
|
|
36807
|
+
const positionFromEnd = totalMessages - 1 - index;
|
|
36808
|
+
if (positionFromEnd < recentWindowSize) {
|
|
36809
|
+
return MessagePriority.MEDIUM;
|
|
36810
|
+
}
|
|
36811
|
+
if (isStaleError(text, positionFromEnd)) {
|
|
36812
|
+
return MessagePriority.DISPOSABLE;
|
|
36813
|
+
}
|
|
36814
|
+
return MessagePriority.LOW;
|
|
36815
|
+
}
|
|
36816
|
+
return MessagePriority.LOW;
|
|
36817
|
+
}
|
|
36818
|
+
function classifyMessages(messages, recentWindowSize = 10) {
|
|
36819
|
+
const results = [];
|
|
36820
|
+
const totalMessages = messages.length;
|
|
36821
|
+
for (let i2 = 0;i2 < messages.length; i2++) {
|
|
36822
|
+
const message = messages[i2];
|
|
36823
|
+
const priority = classifyMessage(message, i2, totalMessages, recentWindowSize);
|
|
36824
|
+
if (i2 > 0) {
|
|
36825
|
+
const current = messages[i2];
|
|
36826
|
+
const previous = messages[i2 - 1];
|
|
36827
|
+
if (isDuplicateToolRead(current, previous)) {
|
|
36828
|
+
if (results[i2 - 1] >= MessagePriority.MEDIUM) {
|
|
36829
|
+
results[i2 - 1] = MessagePriority.DISPOSABLE;
|
|
36830
|
+
}
|
|
36831
|
+
}
|
|
36832
|
+
}
|
|
36833
|
+
results.push(priority);
|
|
36834
|
+
}
|
|
36835
|
+
return results;
|
|
36836
|
+
}
|
|
36837
|
+
|
|
36838
|
+
// src/hooks/model-limits.ts
|
|
36839
|
+
init_utils();
|
|
36840
|
+
var NATIVE_MODEL_LIMITS = {
|
|
36841
|
+
"claude-sonnet-4": 200000,
|
|
36842
|
+
"claude-opus-4": 200000,
|
|
36843
|
+
"claude-haiku-4": 200000,
|
|
36844
|
+
"gpt-5": 400000,
|
|
36845
|
+
"gpt-5.1-codex": 400000,
|
|
36846
|
+
"gpt-5.1": 264000,
|
|
36847
|
+
"gpt-4.1": 1047576,
|
|
36848
|
+
"gemini-2.5-pro": 1048576,
|
|
36849
|
+
"gemini-2.5-flash": 1048576,
|
|
36850
|
+
o3: 200000,
|
|
36851
|
+
"o4-mini": 200000,
|
|
36852
|
+
"deepseek-r1": 163840,
|
|
36853
|
+
"deepseek-chat": 163840,
|
|
36854
|
+
"qwen3.5": 131072
|
|
36855
|
+
};
|
|
36856
|
+
var PROVIDER_CAPS = {
|
|
36857
|
+
copilot: 128000,
|
|
36858
|
+
"github-copilot": 128000
|
|
36859
|
+
};
|
|
36860
|
+
function extractModelInfo(messages) {
|
|
36861
|
+
if (!messages || messages.length === 0) {
|
|
36862
|
+
return {};
|
|
36863
|
+
}
|
|
36864
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
36865
|
+
const message = messages[i2];
|
|
36866
|
+
if (!message?.info)
|
|
36867
|
+
continue;
|
|
36868
|
+
if (message.info.role === "assistant") {
|
|
36869
|
+
const modelID = message.info.modelID;
|
|
36870
|
+
const providerID = message.info.providerID;
|
|
36871
|
+
if (modelID || providerID) {
|
|
36872
|
+
return {
|
|
36873
|
+
...modelID ? { modelID } : {},
|
|
36874
|
+
...providerID ? { providerID } : {}
|
|
36875
|
+
};
|
|
36876
|
+
}
|
|
36877
|
+
}
|
|
36878
|
+
}
|
|
36879
|
+
return {};
|
|
36880
|
+
}
|
|
36881
|
+
var loggedFirstCalls = new Set;
|
|
36882
|
+
function resolveModelLimit(modelID, providerID, configOverrides = {}) {
|
|
36883
|
+
const normalizedModelID = modelID ?? "";
|
|
36884
|
+
const normalizedProviderID = providerID ?? "";
|
|
36885
|
+
if (normalizedProviderID && normalizedModelID) {
|
|
36886
|
+
const providerModelKey = `${normalizedProviderID}/${normalizedModelID}`;
|
|
36887
|
+
if (configOverrides[providerModelKey] !== undefined) {
|
|
36888
|
+
logFirstCall(normalizedModelID, normalizedProviderID, "override(provider/model)", configOverrides[providerModelKey]);
|
|
36889
|
+
return configOverrides[providerModelKey];
|
|
36890
|
+
}
|
|
36891
|
+
}
|
|
36892
|
+
if (normalizedModelID && configOverrides[normalizedModelID] !== undefined) {
|
|
36893
|
+
logFirstCall(normalizedModelID, normalizedProviderID, "override(model)", configOverrides[normalizedModelID]);
|
|
36894
|
+
return configOverrides[normalizedModelID];
|
|
36895
|
+
}
|
|
36896
|
+
if (normalizedProviderID && PROVIDER_CAPS[normalizedProviderID] !== undefined) {
|
|
36897
|
+
const cap = PROVIDER_CAPS[normalizedProviderID];
|
|
36898
|
+
logFirstCall(normalizedModelID, normalizedProviderID, "provider_cap", cap);
|
|
36899
|
+
return cap;
|
|
36900
|
+
}
|
|
36901
|
+
if (normalizedModelID) {
|
|
36902
|
+
const matchedLimit = findNativeLimit(normalizedModelID);
|
|
36903
|
+
if (matchedLimit !== undefined) {
|
|
36904
|
+
logFirstCall(normalizedModelID, normalizedProviderID, "native", matchedLimit);
|
|
36905
|
+
return matchedLimit;
|
|
36906
|
+
}
|
|
36907
|
+
}
|
|
36908
|
+
if (configOverrides.default !== undefined) {
|
|
36909
|
+
logFirstCall(normalizedModelID, normalizedProviderID, "default_override", configOverrides.default);
|
|
36910
|
+
return configOverrides.default;
|
|
36911
|
+
}
|
|
36912
|
+
logFirstCall(normalizedModelID, normalizedProviderID, "fallback", 128000);
|
|
36913
|
+
return 128000;
|
|
36914
|
+
}
|
|
36915
|
+
function findNativeLimit(modelID) {
|
|
36916
|
+
if (NATIVE_MODEL_LIMITS[modelID] !== undefined) {
|
|
36917
|
+
return NATIVE_MODEL_LIMITS[modelID];
|
|
36918
|
+
}
|
|
36919
|
+
let bestMatch;
|
|
36920
|
+
for (const key of Object.keys(NATIVE_MODEL_LIMITS)) {
|
|
36921
|
+
if (modelID.startsWith(key)) {
|
|
36922
|
+
if (!bestMatch || key.length > bestMatch.length) {
|
|
36923
|
+
bestMatch = key;
|
|
36924
|
+
}
|
|
36925
|
+
}
|
|
36926
|
+
}
|
|
36927
|
+
return bestMatch ? NATIVE_MODEL_LIMITS[bestMatch] : undefined;
|
|
36928
|
+
}
|
|
36929
|
+
function logFirstCall(modelID, providerID, source, limit) {
|
|
36930
|
+
const key = `${modelID || "unknown"}::${providerID || "unknown"}`;
|
|
36931
|
+
if (!loggedFirstCalls.has(key)) {
|
|
36932
|
+
loggedFirstCalls.add(key);
|
|
36933
|
+
warn(`[model-limits] Resolved limit for ${modelID || "(no model)"}@${providerID || "(no provider)"}: ${limit} (source: ${source})`);
|
|
36934
|
+
}
|
|
36935
|
+
}
|
|
36936
|
+
|
|
36623
36937
|
// src/hooks/context-budget.ts
|
|
36624
36938
|
init_utils2();
|
|
36939
|
+
var lastSeenAgent;
|
|
36625
36940
|
function createContextBudgetHandler(config3) {
|
|
36626
36941
|
const enabled = config3.context_budget?.enabled !== false;
|
|
36627
36942
|
if (!enabled) {
|
|
@@ -36629,14 +36944,19 @@ function createContextBudgetHandler(config3) {
|
|
|
36629
36944
|
}
|
|
36630
36945
|
const warnThreshold = config3.context_budget?.warn_threshold ?? 0.7;
|
|
36631
36946
|
const criticalThreshold = config3.context_budget?.critical_threshold ?? 0.9;
|
|
36632
|
-
const
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
const modelLimit = modelLimits.default ?? 128000;
|
|
36636
|
-
return async (_input, output) => {
|
|
36947
|
+
const modelLimitsConfig = config3.context_budget?.model_limits ?? {};
|
|
36948
|
+
const loggedLimits = new Set;
|
|
36949
|
+
const handler = async (_input, output) => {
|
|
36637
36950
|
const messages = output?.messages;
|
|
36638
36951
|
if (!messages || messages.length === 0)
|
|
36639
36952
|
return;
|
|
36953
|
+
const { modelID, providerID } = extractModelInfo(messages);
|
|
36954
|
+
const modelLimit = resolveModelLimit(modelID, providerID, modelLimitsConfig);
|
|
36955
|
+
const cacheKey = `${modelID || "unknown"}::${providerID || "unknown"}`;
|
|
36956
|
+
if (!loggedLimits.has(cacheKey)) {
|
|
36957
|
+
loggedLimits.add(cacheKey);
|
|
36958
|
+
warn(`[swarm] Context budget: model=${modelID || "unknown"} provider=${providerID || "unknown"} limit=${modelLimit}`);
|
|
36959
|
+
}
|
|
36640
36960
|
let totalTokens = 0;
|
|
36641
36961
|
for (const message of messages) {
|
|
36642
36962
|
if (!message?.parts)
|
|
@@ -36648,6 +36968,79 @@ function createContextBudgetHandler(config3) {
|
|
|
36648
36968
|
}
|
|
36649
36969
|
}
|
|
36650
36970
|
const usagePercent = totalTokens / modelLimit;
|
|
36971
|
+
let baseAgent;
|
|
36972
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
36973
|
+
const msg = messages[i2];
|
|
36974
|
+
if (msg?.info?.role === "user" && msg?.info?.agent) {
|
|
36975
|
+
baseAgent = stripKnownSwarmPrefix(msg.info.agent);
|
|
36976
|
+
break;
|
|
36977
|
+
}
|
|
36978
|
+
}
|
|
36979
|
+
let ratio = usagePercent;
|
|
36980
|
+
if (lastSeenAgent !== undefined && baseAgent !== undefined && baseAgent !== lastSeenAgent) {
|
|
36981
|
+
const enforceOnSwitch = config3.context_budget?.enforce_on_agent_switch ?? true;
|
|
36982
|
+
if (enforceOnSwitch && usagePercent > (config3.context_budget?.warn_threshold ?? 0.7)) {
|
|
36983
|
+
warn(`[swarm] Agent switch detected: ${lastSeenAgent} \u2192 ${baseAgent}, enforcing context budget`, {
|
|
36984
|
+
from: lastSeenAgent,
|
|
36985
|
+
to: baseAgent
|
|
36986
|
+
});
|
|
36987
|
+
ratio = 1;
|
|
36988
|
+
}
|
|
36989
|
+
}
|
|
36990
|
+
lastSeenAgent = baseAgent;
|
|
36991
|
+
if (ratio >= criticalThreshold) {
|
|
36992
|
+
const enforce = config3.context_budget?.enforce ?? true;
|
|
36993
|
+
if (enforce) {
|
|
36994
|
+
const targetTokens = modelLimit * (config3.context_budget?.prune_target ?? 0.7);
|
|
36995
|
+
const recentWindow = config3.context_budget?.recent_window ?? 10;
|
|
36996
|
+
const priorities = classifyMessages(output.messages || [], recentWindow);
|
|
36997
|
+
const toolMaskThreshold = config3.context_budget?.tool_output_mask_threshold ?? 2000;
|
|
36998
|
+
let toolMaskFreedTokens = 0;
|
|
36999
|
+
const maskedIndices = new Set;
|
|
37000
|
+
for (let i2 = 0;i2 < (output.messages || []).length; i2++) {
|
|
37001
|
+
const msg = (output.messages || [])[i2];
|
|
37002
|
+
if (shouldMaskToolOutput(msg, i2, (output.messages || []).length, recentWindow, toolMaskThreshold)) {
|
|
37003
|
+
toolMaskFreedTokens += maskToolOutput(msg, toolMaskThreshold);
|
|
37004
|
+
maskedIndices.add(i2);
|
|
37005
|
+
}
|
|
37006
|
+
}
|
|
37007
|
+
if (toolMaskFreedTokens > 0) {
|
|
37008
|
+
totalTokens -= toolMaskFreedTokens;
|
|
37009
|
+
warn(`[swarm] Tool output masking: masked ${maskedIndices.size} tool results, freed ~${toolMaskFreedTokens} tokens`, {
|
|
37010
|
+
maskedCount: maskedIndices.size,
|
|
37011
|
+
freedTokens: toolMaskFreedTokens
|
|
37012
|
+
});
|
|
37013
|
+
}
|
|
37014
|
+
const preserveLastNTurns = config3.context_budget?.preserve_last_n_turns ?? 4;
|
|
37015
|
+
const removableMessages = identifyRemovableMessages(output.messages || [], priorities, preserveLastNTurns);
|
|
37016
|
+
let freedTokens = 0;
|
|
37017
|
+
const toRemove = new Set;
|
|
37018
|
+
for (const idx of removableMessages) {
|
|
37019
|
+
if (totalTokens - freedTokens <= targetTokens)
|
|
37020
|
+
break;
|
|
37021
|
+
toRemove.add(idx);
|
|
37022
|
+
freedTokens += estimateTokens(extractMessageText2(output.messages[idx]));
|
|
37023
|
+
}
|
|
37024
|
+
const beforeTokens = totalTokens;
|
|
37025
|
+
if (toRemove.size > 0) {
|
|
37026
|
+
const actualFreedTokens = applyObservationMasking(output.messages || [], toRemove);
|
|
37027
|
+
totalTokens -= actualFreedTokens;
|
|
37028
|
+
warn(`[swarm] Context enforcement: pruned ${toRemove.size} messages, freed ${actualFreedTokens} tokens (${beforeTokens}\u2192${totalTokens} of ${modelLimit})`, {
|
|
37029
|
+
pruned: toRemove.size,
|
|
37030
|
+
freedTokens: actualFreedTokens,
|
|
37031
|
+
before: beforeTokens,
|
|
37032
|
+
after: totalTokens,
|
|
37033
|
+
limit: modelLimit
|
|
37034
|
+
});
|
|
37035
|
+
} else if (removableMessages.length === 0 && totalTokens > targetTokens) {
|
|
37036
|
+
warn(`[swarm] Context enforcement: no removable messages found but still ${totalTokens} tokens (target: ${targetTokens})`, {
|
|
37037
|
+
currentTokens: totalTokens,
|
|
37038
|
+
targetTokens,
|
|
37039
|
+
limit: modelLimit
|
|
37040
|
+
});
|
|
37041
|
+
}
|
|
37042
|
+
}
|
|
37043
|
+
}
|
|
36651
37044
|
let lastUserMessageIndex = -1;
|
|
36652
37045
|
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
36653
37046
|
if (messages[i2]?.info?.role === "user") {
|
|
@@ -36660,8 +37053,10 @@ function createContextBudgetHandler(config3) {
|
|
|
36660
37053
|
const lastUserMessage = messages[lastUserMessageIndex];
|
|
36661
37054
|
if (!lastUserMessage?.parts)
|
|
36662
37055
|
return;
|
|
36663
|
-
const
|
|
36664
|
-
|
|
37056
|
+
const trackedAgents = config3.context_budget?.tracked_agents ?? [
|
|
37057
|
+
"architect"
|
|
37058
|
+
];
|
|
37059
|
+
if (baseAgent && !trackedAgents.includes(baseAgent))
|
|
36665
37060
|
return;
|
|
36666
37061
|
const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
|
|
36667
37062
|
if (textPartIndex === -1)
|
|
@@ -36682,6 +37077,110 @@ function createContextBudgetHandler(config3) {
|
|
|
36682
37077
|
lastUserMessage.parts[textPartIndex].text = `${warningText}${originalText}`;
|
|
36683
37078
|
}
|
|
36684
37079
|
};
|
|
37080
|
+
return handler;
|
|
37081
|
+
}
|
|
37082
|
+
function identifyRemovableMessages(messages, priorities, preserveLastNTurns) {
|
|
37083
|
+
let turnCount = 0;
|
|
37084
|
+
const protectedIndices = new Set;
|
|
37085
|
+
for (let i2 = messages.length - 1;i2 >= 0 && turnCount < preserveLastNTurns * 2; i2--) {
|
|
37086
|
+
const role = messages[i2]?.info?.role;
|
|
37087
|
+
if (role === "user" || role === "assistant") {
|
|
37088
|
+
protectedIndices.add(i2);
|
|
37089
|
+
if (role === "user")
|
|
37090
|
+
turnCount++;
|
|
37091
|
+
}
|
|
37092
|
+
}
|
|
37093
|
+
let lastUserIdx = -1;
|
|
37094
|
+
let lastAssistantIdx = -1;
|
|
37095
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
37096
|
+
const role = messages[i2]?.info?.role;
|
|
37097
|
+
if (role === "user" && lastUserIdx === -1) {
|
|
37098
|
+
lastUserIdx = i2;
|
|
37099
|
+
}
|
|
37100
|
+
if (role === "assistant" && lastAssistantIdx === -1) {
|
|
37101
|
+
lastAssistantIdx = i2;
|
|
37102
|
+
}
|
|
37103
|
+
if (lastUserIdx !== -1 && lastAssistantIdx !== -1)
|
|
37104
|
+
break;
|
|
37105
|
+
}
|
|
37106
|
+
if (lastUserIdx !== -1)
|
|
37107
|
+
protectedIndices.add(lastUserIdx);
|
|
37108
|
+
if (lastAssistantIdx !== -1)
|
|
37109
|
+
protectedIndices.add(lastAssistantIdx);
|
|
37110
|
+
const HIGH = MessagePriority.HIGH;
|
|
37111
|
+
const MEDIUM = MessagePriority.MEDIUM;
|
|
37112
|
+
const LOW = MessagePriority.LOW;
|
|
37113
|
+
const DISPOSABLE = MessagePriority.DISPOSABLE;
|
|
37114
|
+
const byPriority = [[], [], [], [], []];
|
|
37115
|
+
for (let i2 = 0;i2 < priorities.length; i2++) {
|
|
37116
|
+
const priority = priorities[i2];
|
|
37117
|
+
if (!protectedIndices.has(i2) && priority > HIGH) {
|
|
37118
|
+
byPriority[priority].push(i2);
|
|
37119
|
+
}
|
|
37120
|
+
}
|
|
37121
|
+
return [...byPriority[DISPOSABLE], ...byPriority[LOW], ...byPriority[MEDIUM]];
|
|
37122
|
+
}
|
|
37123
|
+
function applyObservationMasking(messages, toRemove) {
|
|
37124
|
+
let actualFreedTokens = 0;
|
|
37125
|
+
for (const idx of toRemove) {
|
|
37126
|
+
const msg = messages[idx];
|
|
37127
|
+
if (msg?.parts) {
|
|
37128
|
+
for (const part of msg.parts) {
|
|
37129
|
+
if (part.type === "text" && part.text) {
|
|
37130
|
+
const originalTokens = estimateTokens(part.text);
|
|
37131
|
+
const placeholder = `[Context pruned \u2014 message from turn ${idx}, ~${originalTokens} tokens freed. Use retrieve_summary if needed.]`;
|
|
37132
|
+
const maskedTokens = estimateTokens(placeholder);
|
|
37133
|
+
part.text = placeholder;
|
|
37134
|
+
actualFreedTokens += originalTokens - maskedTokens;
|
|
37135
|
+
}
|
|
37136
|
+
}
|
|
37137
|
+
}
|
|
37138
|
+
}
|
|
37139
|
+
return actualFreedTokens;
|
|
37140
|
+
}
|
|
37141
|
+
function extractMessageText2(msg) {
|
|
37142
|
+
if (!msg?.parts)
|
|
37143
|
+
return "";
|
|
37144
|
+
return msg.parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
37145
|
+
`);
|
|
37146
|
+
}
|
|
37147
|
+
function extractToolName(text) {
|
|
37148
|
+
const match = text.match(/^(read_file|write|edit|apply_patch|task|bun|npm|git|bash|glob|grep|mkdir|cp|mv|rm)\b/i);
|
|
37149
|
+
return match?.[1];
|
|
37150
|
+
}
|
|
37151
|
+
function shouldMaskToolOutput(msg, index, totalMessages, recentWindowSize, threshold) {
|
|
37152
|
+
if (!isToolResult(msg))
|
|
37153
|
+
return false;
|
|
37154
|
+
const text = extractMessageText2(msg);
|
|
37155
|
+
if (text.includes("[Tool output masked") || text.includes("[Context pruned")) {
|
|
37156
|
+
return false;
|
|
37157
|
+
}
|
|
37158
|
+
const toolName = extractToolName(text);
|
|
37159
|
+
if (toolName && ["retrieve_summary", "task"].includes(toolName.toLowerCase())) {
|
|
37160
|
+
return false;
|
|
37161
|
+
}
|
|
37162
|
+
const age = totalMessages - 1 - index;
|
|
37163
|
+
return age > recentWindowSize || text.length > threshold;
|
|
37164
|
+
}
|
|
37165
|
+
function maskToolOutput(msg, threshold) {
|
|
37166
|
+
if (!msg?.parts)
|
|
37167
|
+
return 0;
|
|
37168
|
+
let freedTokens = 0;
|
|
37169
|
+
for (const part of msg.parts) {
|
|
37170
|
+
if (part.type === "text" && part.text) {
|
|
37171
|
+
if (part.text.includes("[Tool output masked") || part.text.includes("[Context pruned")) {
|
|
37172
|
+
continue;
|
|
37173
|
+
}
|
|
37174
|
+
const originalTokens = estimateTokens(part.text);
|
|
37175
|
+
const toolName = extractToolName(part.text) || "unknown";
|
|
37176
|
+
const excerpt = part.text.substring(0, 200).replace(/\n/g, " ");
|
|
37177
|
+
const placeholder = `[Tool output masked \u2014 ${toolName} returned ~${originalTokens} tokens. First 200 chars: "${excerpt}..." Use retrieve_summary if needed.]`;
|
|
37178
|
+
const maskedTokens = estimateTokens(placeholder);
|
|
37179
|
+
part.text = placeholder;
|
|
37180
|
+
freedTokens += originalTokens - maskedTokens;
|
|
37181
|
+
}
|
|
37182
|
+
}
|
|
37183
|
+
return freedTokens;
|
|
36685
37184
|
}
|
|
36686
37185
|
// src/hooks/delegation-gate.ts
|
|
36687
37186
|
function extractTaskLine(text) {
|
|
@@ -36910,6 +37409,12 @@ function isSourceCodePath(filePath) {
|
|
|
36910
37409
|
];
|
|
36911
37410
|
return !nonSourcePatterns.some((pattern) => pattern.test(normalized));
|
|
36912
37411
|
}
|
|
37412
|
+
function hasTraversalSegments(filePath) {
|
|
37413
|
+
if (!filePath)
|
|
37414
|
+
return false;
|
|
37415
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
37416
|
+
return normalized.startsWith("..") || normalized.includes("/../") || normalized.endsWith("/..");
|
|
37417
|
+
}
|
|
36913
37418
|
function isGateTool(toolName) {
|
|
36914
37419
|
const normalized = toolName.replace(/^[^:]+[:.]/, "");
|
|
36915
37420
|
const gateTools = [
|
|
@@ -36952,10 +37457,43 @@ function createGuardrailsHooks(config3) {
|
|
|
36952
37457
|
const inputArgsByCallID = new Map;
|
|
36953
37458
|
return {
|
|
36954
37459
|
toolBefore: async (input, output) => {
|
|
36955
|
-
|
|
37460
|
+
const currentSession = swarmState.agentSessions.get(input.sessionID);
|
|
37461
|
+
if (currentSession?.delegationActive) {} else if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
|
|
36956
37462
|
const args2 = output.args;
|
|
36957
37463
|
const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
|
|
36958
|
-
if (
|
|
37464
|
+
if (!targetPath && (input.tool === "apply_patch" || input.tool === "patch")) {
|
|
37465
|
+
const patchText = args2?.input ?? args2?.patch ?? (Array.isArray(args2?.cmd) ? args2.cmd[1] : undefined);
|
|
37466
|
+
if (typeof patchText === "string") {
|
|
37467
|
+
const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
|
|
37468
|
+
const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
|
|
37469
|
+
const paths = new Set;
|
|
37470
|
+
let match;
|
|
37471
|
+
while ((match = patchPathPattern.exec(patchText)) !== null) {
|
|
37472
|
+
paths.add(match[1].trim());
|
|
37473
|
+
}
|
|
37474
|
+
while ((match = diffPathPattern.exec(patchText)) !== null) {
|
|
37475
|
+
const p = match[1].trim();
|
|
37476
|
+
if (p !== "/dev/null")
|
|
37477
|
+
paths.add(p);
|
|
37478
|
+
}
|
|
37479
|
+
for (const p of paths) {
|
|
37480
|
+
if (isOutsideSwarmDir(p) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
37481
|
+
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
37482
|
+
if (session2) {
|
|
37483
|
+
session2.architectWriteCount++;
|
|
37484
|
+
warn("Architect direct code edit detected via apply_patch", {
|
|
37485
|
+
tool: input.tool,
|
|
37486
|
+
sessionID: input.sessionID,
|
|
37487
|
+
targetPath: p,
|
|
37488
|
+
writeCount: session2.architectWriteCount
|
|
37489
|
+
});
|
|
37490
|
+
}
|
|
37491
|
+
break;
|
|
37492
|
+
}
|
|
37493
|
+
}
|
|
37494
|
+
}
|
|
37495
|
+
}
|
|
37496
|
+
if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath) && (isSourceCodePath(targetPath) || hasTraversalSegments(targetPath))) {
|
|
36959
37497
|
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
36960
37498
|
if (session2) {
|
|
36961
37499
|
session2.architectWriteCount++;
|
|
@@ -41017,10 +41555,108 @@ var phase_complete = tool({
|
|
|
41017
41555
|
return executePhaseComplete(phaseCompleteArgs);
|
|
41018
41556
|
}
|
|
41019
41557
|
});
|
|
41558
|
+
// src/tools/save-plan.ts
|
|
41559
|
+
init_tool();
|
|
41560
|
+
init_manager2();
|
|
41561
|
+
import * as path23 from "path";
|
|
41562
|
+
function detectPlaceholderContent(args2) {
|
|
41563
|
+
const issues = [];
|
|
41564
|
+
const placeholderPattern = /^\[\w[\w\s]*\]$/;
|
|
41565
|
+
if (placeholderPattern.test(args2.title.trim())) {
|
|
41566
|
+
issues.push(`Plan title appears to be a template placeholder: "${args2.title}"`);
|
|
41567
|
+
}
|
|
41568
|
+
for (const phase of args2.phases) {
|
|
41569
|
+
if (placeholderPattern.test(phase.name.trim())) {
|
|
41570
|
+
issues.push(`Phase ${phase.id} name appears to be a template placeholder: "${phase.name}"`);
|
|
41571
|
+
}
|
|
41572
|
+
for (const task of phase.tasks) {
|
|
41573
|
+
if (placeholderPattern.test(task.description.trim())) {
|
|
41574
|
+
issues.push(`Task ${task.id} description appears to be a template placeholder: "${task.description}"`);
|
|
41575
|
+
}
|
|
41576
|
+
}
|
|
41577
|
+
}
|
|
41578
|
+
return issues;
|
|
41579
|
+
}
|
|
41580
|
+
async function executeSavePlan(args2) {
|
|
41581
|
+
const placeholderIssues = detectPlaceholderContent(args2);
|
|
41582
|
+
if (placeholderIssues.length > 0) {
|
|
41583
|
+
return {
|
|
41584
|
+
success: false,
|
|
41585
|
+
message: "Plan rejected: contains template placeholder content",
|
|
41586
|
+
errors: placeholderIssues
|
|
41587
|
+
};
|
|
41588
|
+
}
|
|
41589
|
+
const plan = {
|
|
41590
|
+
schema_version: "1.0.0",
|
|
41591
|
+
title: args2.title,
|
|
41592
|
+
swarm: args2.swarm_id,
|
|
41593
|
+
migration_status: "native",
|
|
41594
|
+
current_phase: args2.phases[0]?.id,
|
|
41595
|
+
phases: args2.phases.map((phase) => {
|
|
41596
|
+
return {
|
|
41597
|
+
id: phase.id,
|
|
41598
|
+
name: phase.name,
|
|
41599
|
+
status: "pending",
|
|
41600
|
+
tasks: phase.tasks.map((task) => {
|
|
41601
|
+
return {
|
|
41602
|
+
id: task.id,
|
|
41603
|
+
phase: phase.id,
|
|
41604
|
+
status: "pending",
|
|
41605
|
+
size: task.size ?? "small",
|
|
41606
|
+
description: task.description,
|
|
41607
|
+
depends: task.depends ?? [],
|
|
41608
|
+
acceptance: task.acceptance,
|
|
41609
|
+
files_touched: []
|
|
41610
|
+
};
|
|
41611
|
+
})
|
|
41612
|
+
};
|
|
41613
|
+
})
|
|
41614
|
+
};
|
|
41615
|
+
const tasksCount = plan.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
|
|
41616
|
+
const dir = args2.working_directory ?? process.cwd();
|
|
41617
|
+
try {
|
|
41618
|
+
await savePlan(dir, plan);
|
|
41619
|
+
return {
|
|
41620
|
+
success: true,
|
|
41621
|
+
message: "Plan saved successfully",
|
|
41622
|
+
plan_path: path23.join(dir, ".swarm", "plan.json"),
|
|
41623
|
+
phases_count: plan.phases.length,
|
|
41624
|
+
tasks_count: tasksCount
|
|
41625
|
+
};
|
|
41626
|
+
} catch (error93) {
|
|
41627
|
+
return {
|
|
41628
|
+
success: false,
|
|
41629
|
+
message: "Failed to save plan",
|
|
41630
|
+
errors: [String(error93)]
|
|
41631
|
+
};
|
|
41632
|
+
}
|
|
41633
|
+
}
|
|
41634
|
+
var save_plan = tool({
|
|
41635
|
+
description: "Save a structured implementation plan to .swarm/plan.json and .swarm/plan.md. " + "Task descriptions and phase names MUST contain real content from the spec \u2014 " + "bracket placeholders like [task] or [Project] will be rejected.",
|
|
41636
|
+
args: {
|
|
41637
|
+
title: tool.schema.string().min(1).describe("Plan title \u2014 the REAL project name from the spec. NOT a placeholder like [Project]."),
|
|
41638
|
+
swarm_id: tool.schema.string().min(1).describe('Swarm identifier (e.g. "mega")'),
|
|
41639
|
+
phases: tool.schema.array(tool.schema.object({
|
|
41640
|
+
id: tool.schema.number().int().positive().describe("Phase number, starting at 1"),
|
|
41641
|
+
name: tool.schema.string().min(1).describe("Descriptive phase name derived from the spec"),
|
|
41642
|
+
tasks: tool.schema.array(tool.schema.object({
|
|
41643
|
+
id: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, 'Task ID must be in N.M format, e.g. "1.1"').describe('Task ID in N.M format, e.g. "1.1", "2.3"'),
|
|
41644
|
+
description: tool.schema.string().min(1).describe("Specific task description from the spec. NOT a placeholder like [task]."),
|
|
41645
|
+
size: tool.schema.enum(["small", "medium", "large"]).optional().describe("Task size estimate (default: small)"),
|
|
41646
|
+
depends: tool.schema.array(tool.schema.string()).optional().describe('Task IDs this task depends on, e.g. ["1.1", "1.2"]'),
|
|
41647
|
+
acceptance: tool.schema.string().optional().describe("Acceptance criteria for this task")
|
|
41648
|
+
})).min(1).describe("Tasks in this phase")
|
|
41649
|
+
})).min(1).describe("Implementation phases"),
|
|
41650
|
+
working_directory: tool.schema.string().optional().describe("Working directory (defaults to process.cwd())")
|
|
41651
|
+
},
|
|
41652
|
+
execute: async (args2) => {
|
|
41653
|
+
return JSON.stringify(await executeSavePlan(args2), null, 2);
|
|
41654
|
+
}
|
|
41655
|
+
});
|
|
41020
41656
|
// src/tools/pkg-audit.ts
|
|
41021
41657
|
init_dist();
|
|
41022
41658
|
import * as fs18 from "fs";
|
|
41023
|
-
import * as
|
|
41659
|
+
import * as path24 from "path";
|
|
41024
41660
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
41025
41661
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
41026
41662
|
function isValidEcosystem(value) {
|
|
@@ -41038,13 +41674,13 @@ function validateArgs3(args2) {
|
|
|
41038
41674
|
function detectEcosystems() {
|
|
41039
41675
|
const ecosystems = [];
|
|
41040
41676
|
const cwd = process.cwd();
|
|
41041
|
-
if (fs18.existsSync(
|
|
41677
|
+
if (fs18.existsSync(path24.join(cwd, "package.json"))) {
|
|
41042
41678
|
ecosystems.push("npm");
|
|
41043
41679
|
}
|
|
41044
|
-
if (fs18.existsSync(
|
|
41680
|
+
if (fs18.existsSync(path24.join(cwd, "pyproject.toml")) || fs18.existsSync(path24.join(cwd, "requirements.txt"))) {
|
|
41045
41681
|
ecosystems.push("pip");
|
|
41046
41682
|
}
|
|
41047
|
-
if (fs18.existsSync(
|
|
41683
|
+
if (fs18.existsSync(path24.join(cwd, "Cargo.toml"))) {
|
|
41048
41684
|
ecosystems.push("cargo");
|
|
41049
41685
|
}
|
|
41050
41686
|
return ecosystems;
|
|
@@ -42952,11 +43588,11 @@ var Module2 = (() => {
|
|
|
42952
43588
|
throw toThrow;
|
|
42953
43589
|
}, "quit_");
|
|
42954
43590
|
var scriptDirectory = "";
|
|
42955
|
-
function locateFile(
|
|
43591
|
+
function locateFile(path25) {
|
|
42956
43592
|
if (Module["locateFile"]) {
|
|
42957
|
-
return Module["locateFile"](
|
|
43593
|
+
return Module["locateFile"](path25, scriptDirectory);
|
|
42958
43594
|
}
|
|
42959
|
-
return scriptDirectory +
|
|
43595
|
+
return scriptDirectory + path25;
|
|
42960
43596
|
}
|
|
42961
43597
|
__name(locateFile, "locateFile");
|
|
42962
43598
|
var readAsync, readBinary;
|
|
@@ -44770,7 +45406,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
|
|
|
44770
45406
|
]);
|
|
44771
45407
|
// src/tools/pre-check-batch.ts
|
|
44772
45408
|
init_dist();
|
|
44773
|
-
import * as
|
|
45409
|
+
import * as path27 from "path";
|
|
44774
45410
|
|
|
44775
45411
|
// node_modules/yocto-queue/index.js
|
|
44776
45412
|
class Node2 {
|
|
@@ -44937,7 +45573,7 @@ init_manager();
|
|
|
44937
45573
|
|
|
44938
45574
|
// src/quality/metrics.ts
|
|
44939
45575
|
import * as fs19 from "fs";
|
|
44940
|
-
import * as
|
|
45576
|
+
import * as path25 from "path";
|
|
44941
45577
|
var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
|
|
44942
45578
|
var MIN_DUPLICATION_LINES = 10;
|
|
44943
45579
|
function estimateCyclomaticComplexity(content) {
|
|
@@ -44989,7 +45625,7 @@ async function computeComplexityDelta(files, workingDir) {
|
|
|
44989
45625
|
let totalComplexity = 0;
|
|
44990
45626
|
const analyzedFiles = [];
|
|
44991
45627
|
for (const file3 of files) {
|
|
44992
|
-
const fullPath =
|
|
45628
|
+
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
44993
45629
|
if (!fs19.existsSync(fullPath)) {
|
|
44994
45630
|
continue;
|
|
44995
45631
|
}
|
|
@@ -45112,7 +45748,7 @@ function countGoExports(content) {
|
|
|
45112
45748
|
function getExportCountForFile(filePath) {
|
|
45113
45749
|
try {
|
|
45114
45750
|
const content = fs19.readFileSync(filePath, "utf-8");
|
|
45115
|
-
const ext =
|
|
45751
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
45116
45752
|
switch (ext) {
|
|
45117
45753
|
case ".ts":
|
|
45118
45754
|
case ".tsx":
|
|
@@ -45138,7 +45774,7 @@ async function computePublicApiDelta(files, workingDir) {
|
|
|
45138
45774
|
let totalExports = 0;
|
|
45139
45775
|
const analyzedFiles = [];
|
|
45140
45776
|
for (const file3 of files) {
|
|
45141
|
-
const fullPath =
|
|
45777
|
+
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
45142
45778
|
if (!fs19.existsSync(fullPath)) {
|
|
45143
45779
|
continue;
|
|
45144
45780
|
}
|
|
@@ -45172,7 +45808,7 @@ async function computeDuplicationRatio(files, workingDir) {
|
|
|
45172
45808
|
let duplicateLines = 0;
|
|
45173
45809
|
const analyzedFiles = [];
|
|
45174
45810
|
for (const file3 of files) {
|
|
45175
|
-
const fullPath =
|
|
45811
|
+
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
45176
45812
|
if (!fs19.existsSync(fullPath)) {
|
|
45177
45813
|
continue;
|
|
45178
45814
|
}
|
|
@@ -45205,8 +45841,8 @@ function countCodeLines(content) {
|
|
|
45205
45841
|
return lines.length;
|
|
45206
45842
|
}
|
|
45207
45843
|
function isTestFile(filePath) {
|
|
45208
|
-
const basename5 =
|
|
45209
|
-
const _ext =
|
|
45844
|
+
const basename5 = path25.basename(filePath);
|
|
45845
|
+
const _ext = path25.extname(filePath).toLowerCase();
|
|
45210
45846
|
const testPatterns = [
|
|
45211
45847
|
".test.",
|
|
45212
45848
|
".spec.",
|
|
@@ -45248,7 +45884,7 @@ function shouldExcludeFile(filePath, excludeGlobs) {
|
|
|
45248
45884
|
async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
45249
45885
|
let testLines = 0;
|
|
45250
45886
|
let codeLines = 0;
|
|
45251
|
-
const srcDir =
|
|
45887
|
+
const srcDir = path25.join(workingDir, "src");
|
|
45252
45888
|
if (fs19.existsSync(srcDir)) {
|
|
45253
45889
|
await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
45254
45890
|
codeLines += lines;
|
|
@@ -45256,14 +45892,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
45256
45892
|
}
|
|
45257
45893
|
const possibleSrcDirs = ["lib", "app", "source", "core"];
|
|
45258
45894
|
for (const dir of possibleSrcDirs) {
|
|
45259
|
-
const dirPath =
|
|
45895
|
+
const dirPath = path25.join(workingDir, dir);
|
|
45260
45896
|
if (fs19.existsSync(dirPath)) {
|
|
45261
45897
|
await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
45262
45898
|
codeLines += lines;
|
|
45263
45899
|
});
|
|
45264
45900
|
}
|
|
45265
45901
|
}
|
|
45266
|
-
const testsDir =
|
|
45902
|
+
const testsDir = path25.join(workingDir, "tests");
|
|
45267
45903
|
if (fs19.existsSync(testsDir)) {
|
|
45268
45904
|
await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
45269
45905
|
testLines += lines;
|
|
@@ -45271,7 +45907,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
45271
45907
|
}
|
|
45272
45908
|
const possibleTestDirs = ["test", "__tests__", "specs"];
|
|
45273
45909
|
for (const dir of possibleTestDirs) {
|
|
45274
|
-
const dirPath =
|
|
45910
|
+
const dirPath = path25.join(workingDir, dir);
|
|
45275
45911
|
if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
|
|
45276
45912
|
await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
45277
45913
|
testLines += lines;
|
|
@@ -45286,7 +45922,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
|
|
|
45286
45922
|
try {
|
|
45287
45923
|
const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
|
|
45288
45924
|
for (const entry of entries) {
|
|
45289
|
-
const fullPath =
|
|
45925
|
+
const fullPath = path25.join(dirPath, entry.name);
|
|
45290
45926
|
if (entry.isDirectory()) {
|
|
45291
45927
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
|
|
45292
45928
|
continue;
|
|
@@ -45294,7 +45930,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
|
|
|
45294
45930
|
await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
|
|
45295
45931
|
} else if (entry.isFile()) {
|
|
45296
45932
|
const relativePath = fullPath.replace(`${process.cwd()}/`, "");
|
|
45297
|
-
const ext =
|
|
45933
|
+
const ext = path25.extname(entry.name).toLowerCase();
|
|
45298
45934
|
const validExts = [
|
|
45299
45935
|
".ts",
|
|
45300
45936
|
".tsx",
|
|
@@ -45549,7 +46185,7 @@ async function qualityBudget(input, directory) {
|
|
|
45549
46185
|
// src/tools/sast-scan.ts
|
|
45550
46186
|
init_manager();
|
|
45551
46187
|
import * as fs20 from "fs";
|
|
45552
|
-
import * as
|
|
46188
|
+
import * as path26 from "path";
|
|
45553
46189
|
import { extname as extname7 } from "path";
|
|
45554
46190
|
|
|
45555
46191
|
// src/sast/rules/c.ts
|
|
@@ -46504,7 +47140,7 @@ async function sastScan(input, directory, config3) {
|
|
|
46504
47140
|
const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
|
|
46505
47141
|
const filesByLanguage = new Map;
|
|
46506
47142
|
for (const filePath of changed_files) {
|
|
46507
|
-
const resolvedPath =
|
|
47143
|
+
const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
|
|
46508
47144
|
if (!fs20.existsSync(resolvedPath)) {
|
|
46509
47145
|
_filesSkipped++;
|
|
46510
47146
|
continue;
|
|
@@ -46613,10 +47249,10 @@ function validatePath(inputPath, baseDir) {
|
|
|
46613
47249
|
if (!inputPath || inputPath.length === 0) {
|
|
46614
47250
|
return "path is required";
|
|
46615
47251
|
}
|
|
46616
|
-
const resolved =
|
|
46617
|
-
const baseResolved =
|
|
46618
|
-
const relative3 =
|
|
46619
|
-
if (relative3.startsWith("..") ||
|
|
47252
|
+
const resolved = path27.resolve(baseDir, inputPath);
|
|
47253
|
+
const baseResolved = path27.resolve(baseDir);
|
|
47254
|
+
const relative3 = path27.relative(baseResolved, resolved);
|
|
47255
|
+
if (relative3.startsWith("..") || path27.isAbsolute(relative3)) {
|
|
46620
47256
|
return "path traversal detected";
|
|
46621
47257
|
}
|
|
46622
47258
|
return null;
|
|
@@ -46738,7 +47374,7 @@ async function runPreCheckBatch(input) {
|
|
|
46738
47374
|
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
46739
47375
|
continue;
|
|
46740
47376
|
}
|
|
46741
|
-
changedFiles.push(
|
|
47377
|
+
changedFiles.push(path27.resolve(directory, file3));
|
|
46742
47378
|
}
|
|
46743
47379
|
} else {
|
|
46744
47380
|
changedFiles = [];
|
|
@@ -46929,7 +47565,7 @@ var retrieve_summary = tool({
|
|
|
46929
47565
|
init_dist();
|
|
46930
47566
|
init_manager();
|
|
46931
47567
|
import * as fs21 from "fs";
|
|
46932
|
-
import * as
|
|
47568
|
+
import * as path28 from "path";
|
|
46933
47569
|
|
|
46934
47570
|
// src/sbom/detectors/dart.ts
|
|
46935
47571
|
function parsePubspecLock(content) {
|
|
@@ -47776,7 +48412,7 @@ function findManifestFiles(rootDir) {
|
|
|
47776
48412
|
try {
|
|
47777
48413
|
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
47778
48414
|
for (const entry of entries) {
|
|
47779
|
-
const fullPath =
|
|
48415
|
+
const fullPath = path28.join(dir, entry.name);
|
|
47780
48416
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
47781
48417
|
continue;
|
|
47782
48418
|
}
|
|
@@ -47786,7 +48422,7 @@ function findManifestFiles(rootDir) {
|
|
|
47786
48422
|
for (const pattern of patterns) {
|
|
47787
48423
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
47788
48424
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
47789
|
-
manifestFiles.push(
|
|
48425
|
+
manifestFiles.push(path28.relative(cwd, fullPath));
|
|
47790
48426
|
break;
|
|
47791
48427
|
}
|
|
47792
48428
|
}
|
|
@@ -47805,12 +48441,12 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
47805
48441
|
try {
|
|
47806
48442
|
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
47807
48443
|
for (const entry of entries) {
|
|
47808
|
-
const fullPath =
|
|
48444
|
+
const fullPath = path28.join(dir, entry.name);
|
|
47809
48445
|
if (entry.isFile()) {
|
|
47810
48446
|
for (const pattern of patterns) {
|
|
47811
48447
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
47812
48448
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
47813
|
-
found.push(
|
|
48449
|
+
found.push(path28.relative(workingDir, fullPath));
|
|
47814
48450
|
break;
|
|
47815
48451
|
}
|
|
47816
48452
|
}
|
|
@@ -47823,11 +48459,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
47823
48459
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
47824
48460
|
const dirs = new Set;
|
|
47825
48461
|
for (const file3 of changedFiles) {
|
|
47826
|
-
let currentDir =
|
|
48462
|
+
let currentDir = path28.dirname(file3);
|
|
47827
48463
|
while (true) {
|
|
47828
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
47829
|
-
dirs.add(
|
|
47830
|
-
const parent =
|
|
48464
|
+
if (currentDir && currentDir !== "." && currentDir !== path28.sep) {
|
|
48465
|
+
dirs.add(path28.join(workingDir, currentDir));
|
|
48466
|
+
const parent = path28.dirname(currentDir);
|
|
47831
48467
|
if (parent === currentDir)
|
|
47832
48468
|
break;
|
|
47833
48469
|
currentDir = parent;
|
|
@@ -47934,7 +48570,7 @@ var sbom_generate = tool({
|
|
|
47934
48570
|
const processedFiles = [];
|
|
47935
48571
|
for (const manifestFile of manifestFiles) {
|
|
47936
48572
|
try {
|
|
47937
|
-
const fullPath =
|
|
48573
|
+
const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
|
|
47938
48574
|
if (!fs21.existsSync(fullPath)) {
|
|
47939
48575
|
continue;
|
|
47940
48576
|
}
|
|
@@ -47951,7 +48587,7 @@ var sbom_generate = tool({
|
|
|
47951
48587
|
const bom = generateCycloneDX(allComponents);
|
|
47952
48588
|
const bomJson = serializeCycloneDX(bom);
|
|
47953
48589
|
const filename = generateSbomFilename();
|
|
47954
|
-
const outputPath =
|
|
48590
|
+
const outputPath = path28.join(outputDir, filename);
|
|
47955
48591
|
fs21.writeFileSync(outputPath, bomJson, "utf-8");
|
|
47956
48592
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
47957
48593
|
try {
|
|
@@ -47994,7 +48630,7 @@ var sbom_generate = tool({
|
|
|
47994
48630
|
// src/tools/schema-drift.ts
|
|
47995
48631
|
init_dist();
|
|
47996
48632
|
import * as fs22 from "fs";
|
|
47997
|
-
import * as
|
|
48633
|
+
import * as path29 from "path";
|
|
47998
48634
|
var SPEC_CANDIDATES = [
|
|
47999
48635
|
"openapi.json",
|
|
48000
48636
|
"openapi.yaml",
|
|
@@ -48026,12 +48662,12 @@ function normalizePath(p) {
|
|
|
48026
48662
|
}
|
|
48027
48663
|
function discoverSpecFile(cwd, specFileArg) {
|
|
48028
48664
|
if (specFileArg) {
|
|
48029
|
-
const resolvedPath =
|
|
48030
|
-
const normalizedCwd = cwd.endsWith(
|
|
48665
|
+
const resolvedPath = path29.resolve(cwd, specFileArg);
|
|
48666
|
+
const normalizedCwd = cwd.endsWith(path29.sep) ? cwd : cwd + path29.sep;
|
|
48031
48667
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
48032
48668
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
48033
48669
|
}
|
|
48034
|
-
const ext =
|
|
48670
|
+
const ext = path29.extname(resolvedPath).toLowerCase();
|
|
48035
48671
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
48036
48672
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
48037
48673
|
}
|
|
@@ -48045,7 +48681,7 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
48045
48681
|
return resolvedPath;
|
|
48046
48682
|
}
|
|
48047
48683
|
for (const candidate of SPEC_CANDIDATES) {
|
|
48048
|
-
const candidatePath =
|
|
48684
|
+
const candidatePath = path29.resolve(cwd, candidate);
|
|
48049
48685
|
if (fs22.existsSync(candidatePath)) {
|
|
48050
48686
|
const stats = fs22.statSync(candidatePath);
|
|
48051
48687
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
@@ -48057,7 +48693,7 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
48057
48693
|
}
|
|
48058
48694
|
function parseSpec(specFile) {
|
|
48059
48695
|
const content = fs22.readFileSync(specFile, "utf-8");
|
|
48060
|
-
const ext =
|
|
48696
|
+
const ext = path29.extname(specFile).toLowerCase();
|
|
48061
48697
|
if (ext === ".json") {
|
|
48062
48698
|
return parseJsonSpec(content);
|
|
48063
48699
|
}
|
|
@@ -48128,7 +48764,7 @@ function extractRoutes(cwd) {
|
|
|
48128
48764
|
return;
|
|
48129
48765
|
}
|
|
48130
48766
|
for (const entry of entries) {
|
|
48131
|
-
const fullPath =
|
|
48767
|
+
const fullPath = path29.join(dir, entry.name);
|
|
48132
48768
|
if (entry.isSymbolicLink()) {
|
|
48133
48769
|
continue;
|
|
48134
48770
|
}
|
|
@@ -48138,7 +48774,7 @@ function extractRoutes(cwd) {
|
|
|
48138
48774
|
}
|
|
48139
48775
|
walkDir(fullPath);
|
|
48140
48776
|
} else if (entry.isFile()) {
|
|
48141
|
-
const ext =
|
|
48777
|
+
const ext = path29.extname(entry.name).toLowerCase();
|
|
48142
48778
|
const baseName = entry.name.toLowerCase();
|
|
48143
48779
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
48144
48780
|
continue;
|
|
@@ -48307,7 +48943,7 @@ init_secretscan();
|
|
|
48307
48943
|
// src/tools/symbols.ts
|
|
48308
48944
|
init_tool();
|
|
48309
48945
|
import * as fs23 from "fs";
|
|
48310
|
-
import * as
|
|
48946
|
+
import * as path30 from "path";
|
|
48311
48947
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
48312
48948
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
48313
48949
|
function containsControlCharacters(str) {
|
|
@@ -48336,11 +48972,11 @@ function containsWindowsAttacks(str) {
|
|
|
48336
48972
|
}
|
|
48337
48973
|
function isPathInWorkspace(filePath, workspace) {
|
|
48338
48974
|
try {
|
|
48339
|
-
const resolvedPath =
|
|
48975
|
+
const resolvedPath = path30.resolve(workspace, filePath);
|
|
48340
48976
|
const realWorkspace = fs23.realpathSync(workspace);
|
|
48341
48977
|
const realResolvedPath = fs23.realpathSync(resolvedPath);
|
|
48342
|
-
const relativePath =
|
|
48343
|
-
if (relativePath.startsWith("..") ||
|
|
48978
|
+
const relativePath = path30.relative(realWorkspace, realResolvedPath);
|
|
48979
|
+
if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
|
|
48344
48980
|
return false;
|
|
48345
48981
|
}
|
|
48346
48982
|
return true;
|
|
@@ -48352,7 +48988,7 @@ function validatePathForRead(filePath, workspace) {
|
|
|
48352
48988
|
return isPathInWorkspace(filePath, workspace);
|
|
48353
48989
|
}
|
|
48354
48990
|
function extractTSSymbols(filePath, cwd) {
|
|
48355
|
-
const fullPath =
|
|
48991
|
+
const fullPath = path30.join(cwd, filePath);
|
|
48356
48992
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
48357
48993
|
return [];
|
|
48358
48994
|
}
|
|
@@ -48504,7 +49140,7 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
48504
49140
|
});
|
|
48505
49141
|
}
|
|
48506
49142
|
function extractPythonSymbols(filePath, cwd) {
|
|
48507
|
-
const fullPath =
|
|
49143
|
+
const fullPath = path30.join(cwd, filePath);
|
|
48508
49144
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
48509
49145
|
return [];
|
|
48510
49146
|
}
|
|
@@ -48586,7 +49222,7 @@ var symbols = tool({
|
|
|
48586
49222
|
}, null, 2);
|
|
48587
49223
|
}
|
|
48588
49224
|
const cwd = process.cwd();
|
|
48589
|
-
const ext =
|
|
49225
|
+
const ext = path30.extname(file3);
|
|
48590
49226
|
if (containsControlCharacters(file3)) {
|
|
48591
49227
|
return JSON.stringify({
|
|
48592
49228
|
file: file3,
|
|
@@ -48655,7 +49291,7 @@ init_test_runner();
|
|
|
48655
49291
|
// src/tools/todo-extract.ts
|
|
48656
49292
|
init_dist();
|
|
48657
49293
|
import * as fs24 from "fs";
|
|
48658
|
-
import * as
|
|
49294
|
+
import * as path31 from "path";
|
|
48659
49295
|
var MAX_TEXT_LENGTH = 200;
|
|
48660
49296
|
var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
|
|
48661
49297
|
var SUPPORTED_EXTENSIONS2 = new Set([
|
|
@@ -48726,9 +49362,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
48726
49362
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
48727
49363
|
}
|
|
48728
49364
|
try {
|
|
48729
|
-
const resolvedPath =
|
|
48730
|
-
const normalizedCwd =
|
|
48731
|
-
const normalizedResolved =
|
|
49365
|
+
const resolvedPath = path31.resolve(paths);
|
|
49366
|
+
const normalizedCwd = path31.resolve(cwd);
|
|
49367
|
+
const normalizedResolved = path31.resolve(resolvedPath);
|
|
48732
49368
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
48733
49369
|
return {
|
|
48734
49370
|
error: "paths must be within the current working directory",
|
|
@@ -48744,7 +49380,7 @@ function validatePathsInput(paths, cwd) {
|
|
|
48744
49380
|
}
|
|
48745
49381
|
}
|
|
48746
49382
|
function isSupportedExtension(filePath) {
|
|
48747
|
-
const ext =
|
|
49383
|
+
const ext = path31.extname(filePath).toLowerCase();
|
|
48748
49384
|
return SUPPORTED_EXTENSIONS2.has(ext);
|
|
48749
49385
|
}
|
|
48750
49386
|
function findSourceFiles3(dir, files = []) {
|
|
@@ -48759,7 +49395,7 @@ function findSourceFiles3(dir, files = []) {
|
|
|
48759
49395
|
if (SKIP_DIRECTORIES3.has(entry)) {
|
|
48760
49396
|
continue;
|
|
48761
49397
|
}
|
|
48762
|
-
const fullPath =
|
|
49398
|
+
const fullPath = path31.join(dir, entry);
|
|
48763
49399
|
let stat;
|
|
48764
49400
|
try {
|
|
48765
49401
|
stat = fs24.statSync(fullPath);
|
|
@@ -48871,7 +49507,7 @@ var todo_extract = tool({
|
|
|
48871
49507
|
filesToScan.push(scanPath);
|
|
48872
49508
|
} else {
|
|
48873
49509
|
const errorResult = {
|
|
48874
|
-
error: `unsupported file extension: ${
|
|
49510
|
+
error: `unsupported file extension: ${path31.extname(scanPath)}`,
|
|
48875
49511
|
total: 0,
|
|
48876
49512
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
48877
49513
|
entries: []
|
|
@@ -48986,7 +49622,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
48986
49622
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
48987
49623
|
preflightTriggerManager = new PTM(automationConfig);
|
|
48988
49624
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
48989
|
-
const swarmDir =
|
|
49625
|
+
const swarmDir = path32.resolve(ctx.directory, ".swarm");
|
|
48990
49626
|
statusArtifact = new ASA(swarmDir);
|
|
48991
49627
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
48992
49628
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -49092,6 +49728,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
49092
49728
|
phase_complete,
|
|
49093
49729
|
pre_check_batch,
|
|
49094
49730
|
retrieve_summary,
|
|
49731
|
+
save_plan,
|
|
49095
49732
|
schema_drift,
|
|
49096
49733
|
secretscan,
|
|
49097
49734
|
symbols,
|