opencode-swarm 6.13.0 → 6.13.2
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 +23 -0
- package/dist/cli/index.js +7 -19
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +48 -0
- package/dist/hooks/adversarial-detector.d.ts +15 -0
- package/dist/index.js +418 -87
- package/dist/state.d.ts +13 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/phase-complete.d.ts +9 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -310,6 +310,8 @@ Per-agent overrides:
|
|
|
310
310
|
| build_check | Runs your project's native build/typecheck |
|
|
311
311
|
| quality_budget | Enforces complexity, duplication, and test ratio limits |
|
|
312
312
|
| pre_check_batch | Runs lint, secretscan, SAST, and quality budget in parallel (~15s vs ~60s sequential) |
|
|
313
|
+
| phase_complete | Enforces phase completion, verifies required agents, logs events, and resets state |
|
|
314
|
+
|
|
313
315
|
|
|
314
316
|
All tools run locally. No Docker, no network calls, no external APIs.
|
|
315
317
|
|
|
@@ -586,11 +588,30 @@ The following tools can be assigned to agents via overrides:
|
|
|
586
588
|
| `symbols` | Extract exported symbols |
|
|
587
589
|
| `test_runner` | Run project tests |
|
|
588
590
|
| `todo_extract` | Extract TODO/FIXME comments |
|
|
591
|
+
| `phase_complete` | Enforces phase completion, verifies required agents, logs events, resets state |
|
|
589
592
|
|
|
590
593
|
---
|
|
591
594
|
|
|
592
595
|
## Recent Changes
|
|
593
596
|
|
|
597
|
+
### v6.13.2 — Pipeline Enforcement
|
|
598
|
+
|
|
599
|
+
This release adds enforcement-layer tooling and self-healing guardrails:
|
|
600
|
+
|
|
601
|
+
- **`phase_complete` tool**: Verifies all required agents were dispatched before a phase closes; emits events to `.swarm/events.jsonl`; configurable `enforce`/`warn` policy
|
|
602
|
+
- **Summarization loop fix**: `exempt_tools` config prevents `retrieve_summary` and `task` outputs from being re-summarized (fixes Issue #8)
|
|
603
|
+
- **Same-model adversarial detection**: Warns when coder and reviewer share the same model; `warn`/`gate`/`ignore` policy
|
|
604
|
+
- **Architect test guardrail (HF-1b)**: Prevents architect from running full `bun test` suite — must target specific files one at a time
|
|
605
|
+
- **Docs**: `docs/swarm-briefing.md` (LLM pipeline briefing), Task Field Reference in `docs/planning.md`
|
|
606
|
+
|
|
607
|
+
### v6.13.1 — Consolidation & Defaults Fix
|
|
608
|
+
|
|
609
|
+
- **`consolidateSystemMessages`**: Merges multiple system messages into one at index 0
|
|
610
|
+
- **Test isolation helpers**: `createIsolatedTestEnv` and `assertSafeForWrite`
|
|
611
|
+
- **Coder self-verify guardrail (HF-1)**: Coder and test_engineer agents blocked from running build/test/lint
|
|
612
|
+
- **`/swarm` template fix**: `{{arguments}}` → `$ARGUMENTS`
|
|
613
|
+
- **DEFAULT_MODELS update**: `claude-sonnet-4-5` → `claude-sonnet-4-20250514`, `gemini-2.0-flash` → `gemini-2.5-flash`
|
|
614
|
+
|
|
594
615
|
### v6.13.0 — Context Efficiency
|
|
595
616
|
|
|
596
617
|
This release focuses on reducing context usage and improving mode-conditional behavior:
|
|
@@ -650,6 +671,8 @@ Upcoming: v6.14 focuses on further context optimization and agent coordination i
|
|
|
650
671
|
- [Design Rationale](docs/design-rationale.md)
|
|
651
672
|
- [Installation Guide](docs/installation.md)
|
|
652
673
|
- [Linux + Docker Desktop Install Guide](docs/installation-linux-docker.md)
|
|
674
|
+
- [Pre-Swarm Planning Guide](docs/planning.md)
|
|
675
|
+
- [Swarm Briefing for LLMs](docs/swarm-briefing.md)
|
|
653
676
|
|
|
654
677
|
---
|
|
655
678
|
|
package/dist/cli/index.js
CHANGED
|
@@ -52,26 +52,14 @@ async function install() {
|
|
|
52
52
|
console.log("\u2713 Disabled default OpenCode agents (explore, general)");
|
|
53
53
|
if (!fs.existsSync(PLUGIN_CONFIG_PATH)) {
|
|
54
54
|
const defaultConfig = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
reviewer: { model: "google/gemini-3-flash" },
|
|
62
|
-
test_engineer: { model: "google/gemini-3-flash" }
|
|
63
|
-
},
|
|
64
|
-
hybrid: {
|
|
65
|
-
architect: { model: "anthropic/claude-sonnet-4.5" },
|
|
66
|
-
coder: { model: "ollama/qwen3:72b" },
|
|
67
|
-
sme: { model: "npu/qwen3:14b" },
|
|
68
|
-
reviewer: { model: "npu/qwen3:14b" },
|
|
69
|
-
test_engineer: { model: "npu/qwen3:14b" }
|
|
70
|
-
}
|
|
55
|
+
agents: {
|
|
56
|
+
architect: { model: "anthropic/claude-sonnet-4-20250514" },
|
|
57
|
+
coder: { model: "anthropic/claude-sonnet-4-20250514" },
|
|
58
|
+
sme: { model: "google/gemini-2.5-flash" },
|
|
59
|
+
reviewer: { model: "google/gemini-2.5-flash" },
|
|
60
|
+
test_engineer: { model: "google/gemini-2.5-flash" }
|
|
71
61
|
},
|
|
72
|
-
|
|
73
|
-
max_iterations: 5,
|
|
74
|
-
inject_phase_reminders: true
|
|
62
|
+
max_iterations: 5
|
|
75
63
|
};
|
|
76
64
|
saveJson(PLUGIN_CONFIG_PATH, defaultConfig);
|
|
77
65
|
console.log("\u2713 Created default plugin config at:", PLUGIN_CONFIG_PATH);
|
package/dist/config/index.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ export { ApprovalEvidenceSchema, BaseEvidenceSchema, DiffEvidenceSchema, EVIDENC
|
|
|
5
5
|
export { loadAgentPrompt, loadPluginConfig, loadPluginConfigWithMeta, } from './loader';
|
|
6
6
|
export type { MigrationStatus, Phase, PhaseStatus, Plan, Task, TaskSize, TaskStatus, } from './plan-schema';
|
|
7
7
|
export { MigrationStatusSchema, PhaseSchema, PhaseStatusSchema, PlanSchema, TaskSchema, TaskSizeSchema, TaskStatusSchema, } from './plan-schema';
|
|
8
|
-
export type { AgentOverrideConfig, AutomationCapabilities, AutomationConfig, AutomationMode, PipelineConfig, PluginConfig, SwarmConfig, } from './schema';
|
|
9
|
-
export { AgentOverrideConfigSchema, AutomationCapabilitiesSchema, AutomationConfigSchema, AutomationModeSchema, PipelineConfigSchema, PluginConfigSchema, SwarmConfigSchema, } from './schema';
|
|
8
|
+
export type { AgentOverrideConfig, AutomationCapabilities, AutomationConfig, AutomationMode, PhaseCompleteConfig, PipelineConfig, PluginConfig, SwarmConfig, } from './schema';
|
|
9
|
+
export { AgentOverrideConfigSchema, AutomationCapabilitiesSchema, AutomationConfigSchema, AutomationModeSchema, PhaseCompleteConfigSchema, PipelineConfigSchema, PluginConfigSchema, SwarmConfigSchema, } from './schema';
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -190,12 +190,27 @@ export declare const PipelineConfigSchema: z.ZodObject<{
|
|
|
190
190
|
parallel_precheck: z.ZodDefault<z.ZodBoolean>;
|
|
191
191
|
}, z.core.$strip>;
|
|
192
192
|
export type PipelineConfig = z.infer<typeof PipelineConfigSchema>;
|
|
193
|
+
export declare const PhaseCompleteConfigSchema: z.ZodObject<{
|
|
194
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
195
|
+
required_agents: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
196
|
+
reviewer: "reviewer";
|
|
197
|
+
coder: "coder";
|
|
198
|
+
test_engineer: "test_engineer";
|
|
199
|
+
}>>>;
|
|
200
|
+
require_docs: z.ZodDefault<z.ZodBoolean>;
|
|
201
|
+
policy: z.ZodDefault<z.ZodEnum<{
|
|
202
|
+
enforce: "enforce";
|
|
203
|
+
warn: "warn";
|
|
204
|
+
}>>;
|
|
205
|
+
}, z.core.$strip>;
|
|
206
|
+
export type PhaseCompleteConfig = z.infer<typeof PhaseCompleteConfigSchema>;
|
|
193
207
|
export declare const SummaryConfigSchema: z.ZodObject<{
|
|
194
208
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
195
209
|
threshold_bytes: z.ZodDefault<z.ZodNumber>;
|
|
196
210
|
max_summary_chars: z.ZodDefault<z.ZodNumber>;
|
|
197
211
|
max_stored_bytes: z.ZodDefault<z.ZodNumber>;
|
|
198
212
|
retention_days: z.ZodDefault<z.ZodNumber>;
|
|
213
|
+
exempt_tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
199
214
|
}, z.core.$strip>;
|
|
200
215
|
export type SummaryConfig = z.infer<typeof SummaryConfigSchema>;
|
|
201
216
|
export declare const ReviewPassesConfigSchema: z.ZodObject<{
|
|
@@ -203,6 +218,16 @@ export declare const ReviewPassesConfigSchema: z.ZodObject<{
|
|
|
203
218
|
security_globs: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
204
219
|
}, z.core.$strip>;
|
|
205
220
|
export type ReviewPassesConfig = z.infer<typeof ReviewPassesConfigSchema>;
|
|
221
|
+
export declare const AdversarialDetectionConfigSchema: z.ZodObject<{
|
|
222
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
223
|
+
policy: z.ZodDefault<z.ZodEnum<{
|
|
224
|
+
warn: "warn";
|
|
225
|
+
gate: "gate";
|
|
226
|
+
ignore: "ignore";
|
|
227
|
+
}>>;
|
|
228
|
+
pairs: z.ZodDefault<z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString], null>>>;
|
|
229
|
+
}, z.core.$strip>;
|
|
230
|
+
export type AdversarialDetectionConfig = z.infer<typeof AdversarialDetectionConfigSchema>;
|
|
206
231
|
export declare const IntegrationAnalysisConfigSchema: z.ZodObject<{
|
|
207
232
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
208
233
|
}, z.core.$strip>;
|
|
@@ -367,6 +392,19 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
367
392
|
pipeline: z.ZodOptional<z.ZodObject<{
|
|
368
393
|
parallel_precheck: z.ZodDefault<z.ZodBoolean>;
|
|
369
394
|
}, z.core.$strip>>;
|
|
395
|
+
phase_complete: z.ZodOptional<z.ZodObject<{
|
|
396
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
397
|
+
required_agents: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
398
|
+
reviewer: "reviewer";
|
|
399
|
+
coder: "coder";
|
|
400
|
+
test_engineer: "test_engineer";
|
|
401
|
+
}>>>;
|
|
402
|
+
require_docs: z.ZodDefault<z.ZodBoolean>;
|
|
403
|
+
policy: z.ZodDefault<z.ZodEnum<{
|
|
404
|
+
enforce: "enforce";
|
|
405
|
+
warn: "warn";
|
|
406
|
+
}>>;
|
|
407
|
+
}, z.core.$strip>>;
|
|
370
408
|
qa_retry_limit: z.ZodDefault<z.ZodNumber>;
|
|
371
409
|
inject_phase_reminders: z.ZodDefault<z.ZodBoolean>;
|
|
372
410
|
hooks: z.ZodOptional<z.ZodObject<{
|
|
@@ -479,11 +517,21 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
479
517
|
max_summary_chars: z.ZodDefault<z.ZodNumber>;
|
|
480
518
|
max_stored_bytes: z.ZodDefault<z.ZodNumber>;
|
|
481
519
|
retention_days: z.ZodDefault<z.ZodNumber>;
|
|
520
|
+
exempt_tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
482
521
|
}, z.core.$strip>>;
|
|
483
522
|
review_passes: z.ZodOptional<z.ZodObject<{
|
|
484
523
|
always_security_review: z.ZodDefault<z.ZodBoolean>;
|
|
485
524
|
security_globs: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
486
525
|
}, z.core.$strip>>;
|
|
526
|
+
adversarial_detection: z.ZodOptional<z.ZodObject<{
|
|
527
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
528
|
+
policy: z.ZodDefault<z.ZodEnum<{
|
|
529
|
+
warn: "warn";
|
|
530
|
+
gate: "gate";
|
|
531
|
+
ignore: "ignore";
|
|
532
|
+
}>>;
|
|
533
|
+
pairs: z.ZodDefault<z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodString], null>>>;
|
|
534
|
+
}, z.core.$strip>>;
|
|
487
535
|
integration_analysis: z.ZodOptional<z.ZodObject<{
|
|
488
536
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
489
537
|
}, z.core.$strip>>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PluginConfig } from '../config';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the model for a given agent by checking config overrides,
|
|
4
|
+
* swarm configurations, and falling back to defaults.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveAgentModel(agentName: string, config: PluginConfig): string;
|
|
7
|
+
/**
|
|
8
|
+
* Detect if two agents share the same model (adversarial pair).
|
|
9
|
+
* Returns the shared model string if matched, null otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectAdversarialPair(agentA: string, agentB: string, config: PluginConfig): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Format an adversarial warning message based on policy.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatAdversarialWarning(agentA: string, agentB: string, sharedModel: string, policy: string): string;
|
package/dist/index.js
CHANGED
|
@@ -31449,7 +31449,8 @@ var TOOL_NAMES = [
|
|
|
31449
31449
|
"detect_domains",
|
|
31450
31450
|
"gitingest",
|
|
31451
31451
|
"retrieve_summary",
|
|
31452
|
-
"extract_code_blocks"
|
|
31452
|
+
"extract_code_blocks",
|
|
31453
|
+
"phase_complete"
|
|
31453
31454
|
];
|
|
31454
31455
|
var TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
31455
31456
|
|
|
@@ -31565,16 +31566,16 @@ for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
|
31565
31566
|
}
|
|
31566
31567
|
}
|
|
31567
31568
|
var DEFAULT_MODELS = {
|
|
31568
|
-
architect: "anthropic/claude-sonnet-4-
|
|
31569
|
-
explorer: "google/gemini-2.
|
|
31570
|
-
coder: "anthropic/claude-sonnet-4-
|
|
31571
|
-
test_engineer: "google/gemini-2.
|
|
31572
|
-
sme: "google/gemini-2.
|
|
31573
|
-
reviewer: "google/gemini-2.
|
|
31574
|
-
critic: "google/gemini-2.
|
|
31575
|
-
docs: "google/gemini-2.
|
|
31576
|
-
designer: "google/gemini-2.
|
|
31577
|
-
default: "google/gemini-2.
|
|
31569
|
+
architect: "anthropic/claude-sonnet-4-20250514",
|
|
31570
|
+
explorer: "google/gemini-2.5-flash",
|
|
31571
|
+
coder: "anthropic/claude-sonnet-4-20250514",
|
|
31572
|
+
test_engineer: "google/gemini-2.5-flash",
|
|
31573
|
+
sme: "google/gemini-2.5-flash",
|
|
31574
|
+
reviewer: "google/gemini-2.5-flash",
|
|
31575
|
+
critic: "google/gemini-2.5-flash",
|
|
31576
|
+
docs: "google/gemini-2.5-flash",
|
|
31577
|
+
designer: "google/gemini-2.5-flash",
|
|
31578
|
+
default: "google/gemini-2.5-flash"
|
|
31578
31579
|
};
|
|
31579
31580
|
var DEFAULT_SCORING_CONFIG = {
|
|
31580
31581
|
enabled: false,
|
|
@@ -31785,12 +31786,19 @@ var GateConfigSchema = exports_external.object({
|
|
|
31785
31786
|
var PipelineConfigSchema = exports_external.object({
|
|
31786
31787
|
parallel_precheck: exports_external.boolean().default(true)
|
|
31787
31788
|
});
|
|
31789
|
+
var PhaseCompleteConfigSchema = exports_external.object({
|
|
31790
|
+
enabled: exports_external.boolean().default(true),
|
|
31791
|
+
required_agents: exports_external.array(exports_external.enum(["coder", "reviewer", "test_engineer"])).default(["coder", "reviewer", "test_engineer"]),
|
|
31792
|
+
require_docs: exports_external.boolean().default(true),
|
|
31793
|
+
policy: exports_external.enum(["enforce", "warn"]).default("enforce")
|
|
31794
|
+
});
|
|
31788
31795
|
var SummaryConfigSchema = exports_external.object({
|
|
31789
31796
|
enabled: exports_external.boolean().default(true),
|
|
31790
31797
|
threshold_bytes: exports_external.number().min(1024).max(1048576).default(20480),
|
|
31791
31798
|
max_summary_chars: exports_external.number().min(100).max(5000).default(1000),
|
|
31792
31799
|
max_stored_bytes: exports_external.number().min(10240).max(104857600).default(10485760),
|
|
31793
|
-
retention_days: exports_external.number().min(1).max(365).default(7)
|
|
31800
|
+
retention_days: exports_external.number().min(1).max(365).default(7),
|
|
31801
|
+
exempt_tools: exports_external.array(exports_external.string()).default(["retrieve_summary", "task"])
|
|
31794
31802
|
});
|
|
31795
31803
|
var ReviewPassesConfigSchema = exports_external.object({
|
|
31796
31804
|
always_security_review: exports_external.boolean().default(false),
|
|
@@ -31804,6 +31812,11 @@ var ReviewPassesConfigSchema = exports_external.object({
|
|
|
31804
31812
|
"**/token/**"
|
|
31805
31813
|
])
|
|
31806
31814
|
});
|
|
31815
|
+
var AdversarialDetectionConfigSchema = exports_external.object({
|
|
31816
|
+
enabled: exports_external.boolean().default(true),
|
|
31817
|
+
policy: exports_external.enum(["warn", "gate", "ignore"]).default("warn"),
|
|
31818
|
+
pairs: exports_external.array(exports_external.tuple([exports_external.string(), exports_external.string()])).default([["coder", "reviewer"]])
|
|
31819
|
+
});
|
|
31807
31820
|
var IntegrationAnalysisConfigSchema = exports_external.object({
|
|
31808
31821
|
enabled: exports_external.boolean().default(true)
|
|
31809
31822
|
});
|
|
@@ -32035,6 +32048,7 @@ var PluginConfigSchema = exports_external.object({
|
|
|
32035
32048
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
32036
32049
|
max_iterations: exports_external.number().min(1).max(10).default(5),
|
|
32037
32050
|
pipeline: PipelineConfigSchema.optional(),
|
|
32051
|
+
phase_complete: PhaseCompleteConfigSchema.optional(),
|
|
32038
32052
|
qa_retry_limit: exports_external.number().min(1).max(10).default(3),
|
|
32039
32053
|
inject_phase_reminders: exports_external.boolean().default(true),
|
|
32040
32054
|
hooks: HooksConfigSchema.optional(),
|
|
@@ -32046,6 +32060,7 @@ var PluginConfigSchema = exports_external.object({
|
|
|
32046
32060
|
evidence: EvidenceConfigSchema.optional(),
|
|
32047
32061
|
summaries: SummaryConfigSchema.optional(),
|
|
32048
32062
|
review_passes: ReviewPassesConfigSchema.optional(),
|
|
32063
|
+
adversarial_detection: AdversarialDetectionConfigSchema.optional(),
|
|
32049
32064
|
integration_analysis: IntegrationAnalysisConfigSchema.optional(),
|
|
32050
32065
|
docs: DocsConfigSchema.optional(),
|
|
32051
32066
|
ui_review: UIReviewConfigSchema.optional(),
|
|
@@ -32108,6 +32123,22 @@ function loadRawConfigFromPath(configPath) {
|
|
|
32108
32123
|
return { config: null, fileExisted: false, hadError: false };
|
|
32109
32124
|
}
|
|
32110
32125
|
}
|
|
32126
|
+
function migratePresetsConfig(raw) {
|
|
32127
|
+
if (raw.presets && typeof raw.presets === "object" && !raw.agents) {
|
|
32128
|
+
const presetName = raw.preset || "remote";
|
|
32129
|
+
const presets = raw.presets;
|
|
32130
|
+
const activePreset = presets[presetName] || Object.values(presets)[0];
|
|
32131
|
+
if (activePreset && typeof activePreset === "object") {
|
|
32132
|
+
const migrated = { ...raw, agents: activePreset };
|
|
32133
|
+
delete migrated.preset;
|
|
32134
|
+
delete migrated.presets;
|
|
32135
|
+
delete migrated.swarm_mode;
|
|
32136
|
+
console.warn("[opencode-swarm] Migrated v6.12 presets config to agents format. Consider updating your opencode-swarm.json.");
|
|
32137
|
+
return migrated;
|
|
32138
|
+
}
|
|
32139
|
+
}
|
|
32140
|
+
return raw;
|
|
32141
|
+
}
|
|
32111
32142
|
function loadPluginConfig(directory) {
|
|
32112
32143
|
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
32113
32144
|
const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
|
|
@@ -32121,6 +32152,7 @@ function loadPluginConfig(directory) {
|
|
|
32121
32152
|
if (rawProjectConfig) {
|
|
32122
32153
|
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
32123
32154
|
}
|
|
32155
|
+
mergedRaw = migratePresetsConfig(mergedRaw);
|
|
32124
32156
|
const result = PluginConfigSchema.safeParse(mergedRaw);
|
|
32125
32157
|
if (!result.success) {
|
|
32126
32158
|
if (rawUserConfig) {
|
|
@@ -34434,7 +34466,10 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
|
34434
34466
|
lastGateFailure: null,
|
|
34435
34467
|
partialGateWarningIssued: false,
|
|
34436
34468
|
selfFixAttempted: false,
|
|
34437
|
-
catastrophicPhaseWarnings: new Set
|
|
34469
|
+
catastrophicPhaseWarnings: new Set,
|
|
34470
|
+
lastPhaseCompleteTimestamp: 0,
|
|
34471
|
+
lastPhaseCompletePhase: 0,
|
|
34472
|
+
phaseAgentsDispatched: new Set
|
|
34438
34473
|
};
|
|
34439
34474
|
swarmState.agentSessions.set(sessionId, sessionState);
|
|
34440
34475
|
swarmState.activeAgent.set(sessionId, agentName);
|
|
@@ -34485,6 +34520,15 @@ function ensureAgentSession(sessionId, agentName) {
|
|
|
34485
34520
|
if (!session.catastrophicPhaseWarnings) {
|
|
34486
34521
|
session.catastrophicPhaseWarnings = new Set;
|
|
34487
34522
|
}
|
|
34523
|
+
if (session.lastPhaseCompleteTimestamp === undefined) {
|
|
34524
|
+
session.lastPhaseCompleteTimestamp = 0;
|
|
34525
|
+
}
|
|
34526
|
+
if (session.lastPhaseCompletePhase === undefined) {
|
|
34527
|
+
session.lastPhaseCompletePhase = 0;
|
|
34528
|
+
}
|
|
34529
|
+
if (!session.phaseAgentsDispatched) {
|
|
34530
|
+
session.phaseAgentsDispatched = new Set;
|
|
34531
|
+
}
|
|
34488
34532
|
session.lastToolCallTime = now;
|
|
34489
34533
|
return session;
|
|
34490
34534
|
}
|
|
@@ -34553,6 +34597,17 @@ function pruneOldWindows(sessionId, maxAgeMs = 24 * 60 * 60 * 1000, maxWindows =
|
|
|
34553
34597
|
const toKeep = sorted.slice(0, maxWindows);
|
|
34554
34598
|
session.windows = Object.fromEntries(toKeep);
|
|
34555
34599
|
}
|
|
34600
|
+
function recordPhaseAgentDispatch(sessionId, agentName) {
|
|
34601
|
+
const session = swarmState.agentSessions.get(sessionId);
|
|
34602
|
+
if (!session) {
|
|
34603
|
+
return;
|
|
34604
|
+
}
|
|
34605
|
+
if (!session.phaseAgentsDispatched) {
|
|
34606
|
+
session.phaseAgentsDispatched = new Set;
|
|
34607
|
+
}
|
|
34608
|
+
const normalizedName = stripKnownSwarmPrefix(agentName);
|
|
34609
|
+
session.phaseAgentsDispatched.add(normalizedName);
|
|
34610
|
+
}
|
|
34556
34611
|
|
|
34557
34612
|
// src/commands/benchmark.ts
|
|
34558
34613
|
init_utils();
|
|
@@ -36663,6 +36718,7 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
|
|
|
36663
36718
|
const isArchitect = strippedAgent === ORCHESTRATOR_NAME;
|
|
36664
36719
|
const session = ensureAgentSession(input.sessionID, agentName);
|
|
36665
36720
|
session.delegationActive = !isArchitect;
|
|
36721
|
+
recordPhaseAgentDispatch(input.sessionID, agentName);
|
|
36666
36722
|
if (!isArchitect && guardrailsEnabled) {
|
|
36667
36723
|
beginInvocation(input.sessionID, agentName);
|
|
36668
36724
|
}
|
|
@@ -37114,8 +37170,8 @@ function hashArgs(args2) {
|
|
|
37114
37170
|
// src/hooks/messages-transform.ts
|
|
37115
37171
|
function consolidateSystemMessages(messages) {
|
|
37116
37172
|
if (messages.length > 0 && messages[0].role === "system" && messages[0].content !== undefined && typeof messages[0].content === "string" && messages[0].content.trim().length > 0) {
|
|
37117
|
-
const
|
|
37118
|
-
if (
|
|
37173
|
+
const totalSystemCount = messages.filter((m) => m.role === "system").length;
|
|
37174
|
+
if (totalSystemCount === 1) {
|
|
37119
37175
|
return [...messages];
|
|
37120
37176
|
}
|
|
37121
37177
|
}
|
|
@@ -37123,24 +37179,32 @@ function consolidateSystemMessages(messages) {
|
|
|
37123
37179
|
const systemContents = [];
|
|
37124
37180
|
for (let i2 = 0;i2 < messages.length; i2++) {
|
|
37125
37181
|
const message = messages[i2];
|
|
37126
|
-
if (message.role !== "system")
|
|
37182
|
+
if (message.role !== "system")
|
|
37127
37183
|
continue;
|
|
37128
|
-
|
|
37129
|
-
if (message.tool_call_id !== undefined || message.name !== undefined) {
|
|
37130
|
-
continue;
|
|
37131
|
-
}
|
|
37132
|
-
if (typeof message.content !== "string") {
|
|
37133
|
-
continue;
|
|
37134
|
-
}
|
|
37135
|
-
const trimmedContent = message.content.trim();
|
|
37136
|
-
if (trimmedContent.length === 0) {
|
|
37184
|
+
if (message.tool_call_id !== undefined || message.name !== undefined)
|
|
37137
37185
|
continue;
|
|
37186
|
+
let textContent = null;
|
|
37187
|
+
if (typeof message.content === "string") {
|
|
37188
|
+
const trimmed = message.content.trim();
|
|
37189
|
+
if (trimmed.length > 0)
|
|
37190
|
+
textContent = trimmed;
|
|
37191
|
+
} else if (Array.isArray(message.content)) {
|
|
37192
|
+
const texts = message.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter((t) => t.length > 0);
|
|
37193
|
+
if (texts.length > 0)
|
|
37194
|
+
textContent = texts.join(`
|
|
37195
|
+
`);
|
|
37138
37196
|
}
|
|
37139
37197
|
systemMessageIndices.push(i2);
|
|
37140
|
-
|
|
37198
|
+
if (textContent) {
|
|
37199
|
+
systemContents.push(textContent);
|
|
37200
|
+
}
|
|
37141
37201
|
}
|
|
37142
37202
|
if (systemContents.length === 0) {
|
|
37143
|
-
return
|
|
37203
|
+
return messages.filter((m, idx) => {
|
|
37204
|
+
if (m.role !== "system")
|
|
37205
|
+
return true;
|
|
37206
|
+
return idx === 0;
|
|
37207
|
+
});
|
|
37144
37208
|
}
|
|
37145
37209
|
const mergedSystemContent = systemContents.join(`
|
|
37146
37210
|
|
|
@@ -37150,14 +37214,23 @@ function consolidateSystemMessages(messages) {
|
|
|
37150
37214
|
result.push({
|
|
37151
37215
|
role: "system",
|
|
37152
37216
|
content: mergedSystemContent,
|
|
37153
|
-
...Object.fromEntries(Object.entries(firstSystemMessage).filter(([key]) => key !== "role" && key !== "content"))
|
|
37217
|
+
...Object.fromEntries(Object.entries(firstSystemMessage).filter(([key]) => key !== "role" && key !== "content" && key !== "name" && key !== "tool_call_id"))
|
|
37154
37218
|
});
|
|
37155
37219
|
for (let i2 = 0;i2 < messages.length; i2++) {
|
|
37156
|
-
|
|
37157
|
-
|
|
37220
|
+
const message = messages[i2];
|
|
37221
|
+
if (systemMessageIndices.includes(i2)) {
|
|
37222
|
+
continue;
|
|
37158
37223
|
}
|
|
37224
|
+
if (message.role === "system" && typeof message.content === "string" && message.content.trim().length === 0) {
|
|
37225
|
+
continue;
|
|
37226
|
+
}
|
|
37227
|
+
result.push({ ...message });
|
|
37159
37228
|
}
|
|
37160
|
-
return result
|
|
37229
|
+
return result.filter((msg, idx) => {
|
|
37230
|
+
if (idx === 0)
|
|
37231
|
+
return true;
|
|
37232
|
+
return msg.role !== "system";
|
|
37233
|
+
});
|
|
37161
37234
|
}
|
|
37162
37235
|
// src/hooks/phase-monitor.ts
|
|
37163
37236
|
init_manager2();
|
|
@@ -37552,6 +37625,39 @@ init_preflight_service();
|
|
|
37552
37625
|
// src/hooks/system-enhancer.ts
|
|
37553
37626
|
init_utils();
|
|
37554
37627
|
|
|
37628
|
+
// src/hooks/adversarial-detector.ts
|
|
37629
|
+
function safeGet(obj, key) {
|
|
37630
|
+
if (!obj || !Object.hasOwn(obj, key))
|
|
37631
|
+
return;
|
|
37632
|
+
return obj[key];
|
|
37633
|
+
}
|
|
37634
|
+
function resolveAgentModel(agentName, config3) {
|
|
37635
|
+
const baseName = stripKnownSwarmPrefix(agentName).toLowerCase();
|
|
37636
|
+
const agentOverride = safeGet(config3.agents, baseName)?.model;
|
|
37637
|
+
if (agentOverride)
|
|
37638
|
+
return agentOverride;
|
|
37639
|
+
if (config3.swarms) {
|
|
37640
|
+
for (const swarm of Object.values(config3.swarms)) {
|
|
37641
|
+
const swarmModel = safeGet(swarm.agents, baseName)?.model;
|
|
37642
|
+
if (swarmModel)
|
|
37643
|
+
return swarmModel;
|
|
37644
|
+
}
|
|
37645
|
+
}
|
|
37646
|
+
const defaultModel = safeGet(DEFAULT_MODELS, baseName);
|
|
37647
|
+
return defaultModel ?? DEFAULT_MODELS.default;
|
|
37648
|
+
}
|
|
37649
|
+
function detectAdversarialPair(agentA, agentB, config3) {
|
|
37650
|
+
const modelA = resolveAgentModel(agentA, config3).toLowerCase();
|
|
37651
|
+
const modelB = resolveAgentModel(agentB, config3).toLowerCase();
|
|
37652
|
+
return modelA === modelB ? modelA : null;
|
|
37653
|
+
}
|
|
37654
|
+
function formatAdversarialWarning(agentA, agentB, sharedModel, policy) {
|
|
37655
|
+
if (policy === "gate") {
|
|
37656
|
+
return `\u26A0\uFE0F GATE POLICY: Same-model adversarial pair detected. Agent ${agentA} and checker ${agentB} both use model ${sharedModel}. This requires extra scrutiny \u2014 escalate if issues are found.`;
|
|
37657
|
+
}
|
|
37658
|
+
return `\u26A0\uFE0F Same-model adversarial pair detected. Agent ${agentA} and checker ${agentB} both use model ${sharedModel}. Review may lack independence.`;
|
|
37659
|
+
}
|
|
37660
|
+
|
|
37555
37661
|
// src/hooks/context-scoring.ts
|
|
37556
37662
|
function calculateAgeFactor(ageHours, config3) {
|
|
37557
37663
|
if (ageHours <= 0) {
|
|
@@ -37694,6 +37800,35 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
37694
37800
|
if (config3.secretscan?.enabled === false) {
|
|
37695
37801
|
tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
|
|
37696
37802
|
}
|
|
37803
|
+
const activeAgent_hf1 = swarmState.activeAgent.get(_input.sessionID ?? "");
|
|
37804
|
+
const baseRole = activeAgent_hf1 ? stripKnownSwarmPrefix(activeAgent_hf1) : null;
|
|
37805
|
+
if (baseRole === "coder" || baseRole === "test_engineer") {
|
|
37806
|
+
tryInject("[SWARM CONFIG] You must NOT run build, test, lint, or type-check commands (npm run build, bun test, npx tsc, eslint, etc.). Make ONLY the code changes specified in your task. Verification is handled by the reviewer agent \u2014 do not self-verify. If your task explicitly asks you to run a specific command, that is the only exception.");
|
|
37807
|
+
}
|
|
37808
|
+
if (baseRole === "architect" || baseRole === null) {
|
|
37809
|
+
tryInject("[SWARM CONFIG] You must NEVER run the full test suite or batch test files. If you need to verify changes, run ONLY the specific test files for code YOU modified in this session \u2014 one file at a time, strictly serial. Do not run tests from directories or files unrelated to your changes. Do not run bun test without an explicit file path. When possible, delegate test execution to the test_engineer agent instead of running tests yourself.");
|
|
37810
|
+
}
|
|
37811
|
+
if (config3.adversarial_detection?.enabled !== false) {
|
|
37812
|
+
const activeAgent_adv = swarmState.activeAgent.get(_input.sessionID ?? "");
|
|
37813
|
+
if (activeAgent_adv) {
|
|
37814
|
+
const baseRole_adv = stripKnownSwarmPrefix(activeAgent_adv);
|
|
37815
|
+
const pairs_adv = config3.adversarial_detection?.pairs ?? [
|
|
37816
|
+
["coder", "reviewer"]
|
|
37817
|
+
];
|
|
37818
|
+
const policy_adv = config3.adversarial_detection?.policy ?? "warn";
|
|
37819
|
+
for (const [agentA, agentB] of pairs_adv) {
|
|
37820
|
+
if (baseRole_adv === agentB) {
|
|
37821
|
+
const sharedModel = detectAdversarialPair(agentA, agentB, config3);
|
|
37822
|
+
if (sharedModel) {
|
|
37823
|
+
const warningText = formatAdversarialWarning(agentA, agentB, sharedModel, policy_adv);
|
|
37824
|
+
if (policy_adv !== "ignore") {
|
|
37825
|
+
tryInject(`[SWARM CONFIG] ${warningText}`);
|
|
37826
|
+
}
|
|
37827
|
+
}
|
|
37828
|
+
}
|
|
37829
|
+
}
|
|
37830
|
+
}
|
|
37831
|
+
}
|
|
37697
37832
|
if (mode !== "DISCOVER") {
|
|
37698
37833
|
const sessionId_preflight = _input.sessionID;
|
|
37699
37834
|
const activeAgent_preflight = swarmState.activeAgent.get(sessionId_preflight ?? "");
|
|
@@ -37955,6 +38090,34 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
37955
38090
|
metadata: { contentType: "prose" }
|
|
37956
38091
|
});
|
|
37957
38092
|
}
|
|
38093
|
+
if (config3.adversarial_detection?.enabled !== false) {
|
|
38094
|
+
const activeAgent_adv_b = swarmState.activeAgent.get(_input.sessionID ?? "");
|
|
38095
|
+
if (activeAgent_adv_b) {
|
|
38096
|
+
const baseRole_adv_b = stripKnownSwarmPrefix(activeAgent_adv_b);
|
|
38097
|
+
const pairs_adv_b = config3.adversarial_detection?.pairs ?? [
|
|
38098
|
+
["coder", "reviewer"]
|
|
38099
|
+
];
|
|
38100
|
+
const policy_adv_b = config3.adversarial_detection?.policy ?? "warn";
|
|
38101
|
+
for (const [agentA_b, agentB_b] of pairs_adv_b) {
|
|
38102
|
+
if (baseRole_adv_b === agentB_b) {
|
|
38103
|
+
const sharedModel_b = detectAdversarialPair(agentA_b, agentB_b, config3);
|
|
38104
|
+
if (sharedModel_b) {
|
|
38105
|
+
const warningText_b = formatAdversarialWarning(agentA_b, agentB_b, sharedModel_b, policy_adv_b);
|
|
38106
|
+
if (policy_adv_b !== "ignore") {
|
|
38107
|
+
candidates.push({
|
|
38108
|
+
id: `candidate-${idCounter++}`,
|
|
38109
|
+
kind: "agent_context",
|
|
38110
|
+
text: `[SWARM CONFIG] ${warningText_b}`,
|
|
38111
|
+
tokens: estimateTokens(warningText_b),
|
|
38112
|
+
priority: 2,
|
|
38113
|
+
metadata: { contentType: "prose" }
|
|
38114
|
+
});
|
|
38115
|
+
}
|
|
38116
|
+
}
|
|
38117
|
+
}
|
|
38118
|
+
}
|
|
38119
|
+
}
|
|
38120
|
+
}
|
|
37958
38121
|
const sessionId_preflight_b = _input.sessionID;
|
|
37959
38122
|
const activeAgent_preflight_b = swarmState.activeAgent.get(sessionId_preflight_b ?? "");
|
|
37960
38123
|
const isArchitectForPreflight_b = !activeAgent_preflight_b || stripKnownSwarmPrefix(activeAgent_preflight_b) === "architect";
|
|
@@ -38273,6 +38436,10 @@ function createToolSummarizerHook(config3, directory) {
|
|
|
38273
38436
|
if (typeof output.output !== "string" || output.output.length === 0) {
|
|
38274
38437
|
return;
|
|
38275
38438
|
}
|
|
38439
|
+
const exemptTools = config3.exempt_tools ?? ["retrieve_summary", "task"];
|
|
38440
|
+
if (exemptTools.includes(input.tool)) {
|
|
38441
|
+
return;
|
|
38442
|
+
}
|
|
38276
38443
|
if (!shouldSummarize(output.output, config3.threshold_bytes)) {
|
|
38277
38444
|
return;
|
|
38278
38445
|
}
|
|
@@ -40385,9 +40552,172 @@ var imports = tool({
|
|
|
40385
40552
|
// src/tools/index.ts
|
|
40386
40553
|
init_lint();
|
|
40387
40554
|
|
|
40555
|
+
// src/tools/phase-complete.ts
|
|
40556
|
+
init_tool();
|
|
40557
|
+
import * as fs18 from "fs";
|
|
40558
|
+
init_utils2();
|
|
40559
|
+
function getDelegationsSince(sessionID, sinceTimestamp) {
|
|
40560
|
+
const chain = swarmState.delegationChains.get(sessionID);
|
|
40561
|
+
if (!chain) {
|
|
40562
|
+
return [];
|
|
40563
|
+
}
|
|
40564
|
+
if (sinceTimestamp === 0) {
|
|
40565
|
+
return chain;
|
|
40566
|
+
}
|
|
40567
|
+
return chain.filter((entry) => entry.timestamp > sinceTimestamp);
|
|
40568
|
+
}
|
|
40569
|
+
function normalizeAgentsFromDelegations(delegations) {
|
|
40570
|
+
const agents = new Set;
|
|
40571
|
+
for (const delegation of delegations) {
|
|
40572
|
+
const normalizedFrom = stripKnownSwarmPrefix(delegation.from);
|
|
40573
|
+
const normalizedTo = stripKnownSwarmPrefix(delegation.to);
|
|
40574
|
+
agents.add(normalizedFrom);
|
|
40575
|
+
agents.add(normalizedTo);
|
|
40576
|
+
}
|
|
40577
|
+
return agents;
|
|
40578
|
+
}
|
|
40579
|
+
async function executePhaseComplete(args2) {
|
|
40580
|
+
const phase = Number(args2.phase);
|
|
40581
|
+
const summary = args2.summary;
|
|
40582
|
+
const sessionID = args2.sessionID;
|
|
40583
|
+
if (Number.isNaN(phase) || phase < 1) {
|
|
40584
|
+
return JSON.stringify({
|
|
40585
|
+
success: false,
|
|
40586
|
+
phase,
|
|
40587
|
+
message: "Invalid phase number",
|
|
40588
|
+
agentsDispatched: [],
|
|
40589
|
+
warnings: ["Phase must be a positive number"]
|
|
40590
|
+
}, null, 2);
|
|
40591
|
+
}
|
|
40592
|
+
if (!sessionID) {
|
|
40593
|
+
return JSON.stringify({
|
|
40594
|
+
success: false,
|
|
40595
|
+
phase,
|
|
40596
|
+
message: "Session ID is required",
|
|
40597
|
+
agentsDispatched: [],
|
|
40598
|
+
warnings: [
|
|
40599
|
+
"sessionID parameter is required for phase completion tracking"
|
|
40600
|
+
]
|
|
40601
|
+
}, null, 2);
|
|
40602
|
+
}
|
|
40603
|
+
const session = ensureAgentSession(sessionID);
|
|
40604
|
+
const lastCompletionTimestamp = session.lastPhaseCompleteTimestamp ?? 0;
|
|
40605
|
+
const recentDelegations = getDelegationsSince(sessionID, lastCompletionTimestamp);
|
|
40606
|
+
const delegationAgents = normalizeAgentsFromDelegations(recentDelegations);
|
|
40607
|
+
const trackedAgents = session.phaseAgentsDispatched ?? new Set;
|
|
40608
|
+
const allAgents = new Set([...delegationAgents, ...trackedAgents]);
|
|
40609
|
+
const agentsDispatched = Array.from(allAgents).sort();
|
|
40610
|
+
const directory = process.cwd();
|
|
40611
|
+
const { config: config3 } = loadPluginConfigWithMeta(directory);
|
|
40612
|
+
let phaseCompleteConfig;
|
|
40613
|
+
try {
|
|
40614
|
+
phaseCompleteConfig = PhaseCompleteConfigSchema.parse(config3.phase_complete ?? {});
|
|
40615
|
+
} catch (parseError) {
|
|
40616
|
+
return JSON.stringify({
|
|
40617
|
+
success: false,
|
|
40618
|
+
phase,
|
|
40619
|
+
status: "incomplete",
|
|
40620
|
+
message: `Invalid phase_complete configuration: ${parseError instanceof Error ? parseError.message : "Unknown error"}`,
|
|
40621
|
+
agentsDispatched,
|
|
40622
|
+
agentsMissing: [],
|
|
40623
|
+
warnings: ["Configuration validation failed"]
|
|
40624
|
+
}, null, 2);
|
|
40625
|
+
}
|
|
40626
|
+
if (phaseCompleteConfig.enabled === false) {
|
|
40627
|
+
return JSON.stringify({
|
|
40628
|
+
success: true,
|
|
40629
|
+
phase,
|
|
40630
|
+
status: "disabled",
|
|
40631
|
+
message: `Phase ${phase} complete (enforcement disabled)`,
|
|
40632
|
+
agentsDispatched,
|
|
40633
|
+
agentsMissing: [],
|
|
40634
|
+
warnings: []
|
|
40635
|
+
}, null, 2);
|
|
40636
|
+
}
|
|
40637
|
+
const effectiveRequired = [...phaseCompleteConfig.required_agents];
|
|
40638
|
+
if (phaseCompleteConfig.require_docs && !effectiveRequired.includes("docs")) {
|
|
40639
|
+
effectiveRequired.push("docs");
|
|
40640
|
+
}
|
|
40641
|
+
const agentsMissing = effectiveRequired.filter((req) => !allAgents.has(req));
|
|
40642
|
+
const warnings = [];
|
|
40643
|
+
let success3 = true;
|
|
40644
|
+
let status = "success";
|
|
40645
|
+
const safeSummary = summary?.trim().slice(0, 500);
|
|
40646
|
+
let message = safeSummary ? `Phase ${phase} completed: ${safeSummary}` : `Phase ${phase} completed`;
|
|
40647
|
+
if (agentsMissing.length > 0) {
|
|
40648
|
+
if (phaseCompleteConfig.policy === "enforce") {
|
|
40649
|
+
success3 = false;
|
|
40650
|
+
status = "incomplete";
|
|
40651
|
+
message = `Phase ${phase} incomplete: missing required agents: ${agentsMissing.join(", ")}`;
|
|
40652
|
+
} else {
|
|
40653
|
+
status = "warned";
|
|
40654
|
+
warnings.push(`Warning: phase ${phase} missing required agents: ${agentsMissing.join(", ")}`);
|
|
40655
|
+
}
|
|
40656
|
+
}
|
|
40657
|
+
const now = Date.now();
|
|
40658
|
+
const durationMs = now - lastCompletionTimestamp;
|
|
40659
|
+
const event = {
|
|
40660
|
+
event: "phase_complete",
|
|
40661
|
+
phase,
|
|
40662
|
+
timestamp: new Date(now).toISOString(),
|
|
40663
|
+
agents_dispatched: agentsDispatched,
|
|
40664
|
+
agents_missing: agentsMissing,
|
|
40665
|
+
status,
|
|
40666
|
+
summary: safeSummary ?? null
|
|
40667
|
+
};
|
|
40668
|
+
try {
|
|
40669
|
+
const eventsPath = validateSwarmPath(directory, "events.jsonl");
|
|
40670
|
+
fs18.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
40671
|
+
`, "utf-8");
|
|
40672
|
+
} catch (writeError) {
|
|
40673
|
+
warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
40674
|
+
}
|
|
40675
|
+
if (success3) {
|
|
40676
|
+
session.phaseAgentsDispatched = new Set;
|
|
40677
|
+
session.lastPhaseCompleteTimestamp = now;
|
|
40678
|
+
session.lastPhaseCompletePhase = phase;
|
|
40679
|
+
}
|
|
40680
|
+
const result = {
|
|
40681
|
+
success: success3,
|
|
40682
|
+
phase,
|
|
40683
|
+
status,
|
|
40684
|
+
message,
|
|
40685
|
+
agentsDispatched,
|
|
40686
|
+
agentsMissing,
|
|
40687
|
+
warnings
|
|
40688
|
+
};
|
|
40689
|
+
return JSON.stringify({ ...result, timestamp: event.timestamp, duration_ms: durationMs }, null, 2);
|
|
40690
|
+
}
|
|
40691
|
+
var phase_complete = tool({
|
|
40692
|
+
description: "Mark a phase as complete and track which agents were dispatched. " + "Used for phase completion gating and tracking. " + "Accepts phase number and optional summary. Returns list of agents that were dispatched.",
|
|
40693
|
+
args: {
|
|
40694
|
+
phase: tool.schema.number().describe("The phase number being completed (e.g., 1, 2, 3)"),
|
|
40695
|
+
summary: tool.schema.string().optional().describe("Optional summary of what was accomplished in this phase"),
|
|
40696
|
+
sessionID: tool.schema.string().optional().describe("Session ID for tracking state (auto-provided by plugin context)")
|
|
40697
|
+
},
|
|
40698
|
+
execute: async (args2) => {
|
|
40699
|
+
let phaseCompleteArgs;
|
|
40700
|
+
try {
|
|
40701
|
+
phaseCompleteArgs = {
|
|
40702
|
+
phase: Number(args2.phase),
|
|
40703
|
+
summary: args2.summary !== undefined ? String(args2.summary) : undefined,
|
|
40704
|
+
sessionID: args2.sessionID !== undefined ? String(args2.sessionID) : undefined
|
|
40705
|
+
};
|
|
40706
|
+
} catch {
|
|
40707
|
+
return JSON.stringify({
|
|
40708
|
+
success: false,
|
|
40709
|
+
phase: 0,
|
|
40710
|
+
message: "Invalid arguments",
|
|
40711
|
+
agentsDispatched: [],
|
|
40712
|
+
warnings: ["Failed to parse arguments"]
|
|
40713
|
+
}, null, 2);
|
|
40714
|
+
}
|
|
40715
|
+
return executePhaseComplete(phaseCompleteArgs);
|
|
40716
|
+
}
|
|
40717
|
+
});
|
|
40388
40718
|
// src/tools/pkg-audit.ts
|
|
40389
40719
|
init_dist();
|
|
40390
|
-
import * as
|
|
40720
|
+
import * as fs19 from "fs";
|
|
40391
40721
|
import * as path24 from "path";
|
|
40392
40722
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
40393
40723
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
@@ -40406,13 +40736,13 @@ function validateArgs3(args2) {
|
|
|
40406
40736
|
function detectEcosystems() {
|
|
40407
40737
|
const ecosystems = [];
|
|
40408
40738
|
const cwd = process.cwd();
|
|
40409
|
-
if (
|
|
40739
|
+
if (fs19.existsSync(path24.join(cwd, "package.json"))) {
|
|
40410
40740
|
ecosystems.push("npm");
|
|
40411
40741
|
}
|
|
40412
|
-
if (
|
|
40742
|
+
if (fs19.existsSync(path24.join(cwd, "pyproject.toml")) || fs19.existsSync(path24.join(cwd, "requirements.txt"))) {
|
|
40413
40743
|
ecosystems.push("pip");
|
|
40414
40744
|
}
|
|
40415
|
-
if (
|
|
40745
|
+
if (fs19.existsSync(path24.join(cwd, "Cargo.toml"))) {
|
|
40416
40746
|
ecosystems.push("cargo");
|
|
40417
40747
|
}
|
|
40418
40748
|
return ecosystems;
|
|
@@ -44304,7 +44634,7 @@ init_lint();
|
|
|
44304
44634
|
init_manager();
|
|
44305
44635
|
|
|
44306
44636
|
// src/quality/metrics.ts
|
|
44307
|
-
import * as
|
|
44637
|
+
import * as fs20 from "fs";
|
|
44308
44638
|
import * as path25 from "path";
|
|
44309
44639
|
var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
|
|
44310
44640
|
var MIN_DUPLICATION_LINES = 10;
|
|
@@ -44343,11 +44673,11 @@ function estimateCyclomaticComplexity(content) {
|
|
|
44343
44673
|
}
|
|
44344
44674
|
function getComplexityForFile2(filePath) {
|
|
44345
44675
|
try {
|
|
44346
|
-
const stat =
|
|
44676
|
+
const stat = fs20.statSync(filePath);
|
|
44347
44677
|
if (stat.size > MAX_FILE_SIZE_BYTES5) {
|
|
44348
44678
|
return null;
|
|
44349
44679
|
}
|
|
44350
|
-
const content =
|
|
44680
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
44351
44681
|
return estimateCyclomaticComplexity(content);
|
|
44352
44682
|
} catch {
|
|
44353
44683
|
return null;
|
|
@@ -44358,7 +44688,7 @@ async function computeComplexityDelta(files, workingDir) {
|
|
|
44358
44688
|
const analyzedFiles = [];
|
|
44359
44689
|
for (const file3 of files) {
|
|
44360
44690
|
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
44361
|
-
if (!
|
|
44691
|
+
if (!fs20.existsSync(fullPath)) {
|
|
44362
44692
|
continue;
|
|
44363
44693
|
}
|
|
44364
44694
|
const complexity = getComplexityForFile2(fullPath);
|
|
@@ -44479,7 +44809,7 @@ function countGoExports(content) {
|
|
|
44479
44809
|
}
|
|
44480
44810
|
function getExportCountForFile(filePath) {
|
|
44481
44811
|
try {
|
|
44482
|
-
const content =
|
|
44812
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
44483
44813
|
const ext = path25.extname(filePath).toLowerCase();
|
|
44484
44814
|
switch (ext) {
|
|
44485
44815
|
case ".ts":
|
|
@@ -44507,7 +44837,7 @@ async function computePublicApiDelta(files, workingDir) {
|
|
|
44507
44837
|
const analyzedFiles = [];
|
|
44508
44838
|
for (const file3 of files) {
|
|
44509
44839
|
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
44510
|
-
if (!
|
|
44840
|
+
if (!fs20.existsSync(fullPath)) {
|
|
44511
44841
|
continue;
|
|
44512
44842
|
}
|
|
44513
44843
|
const exports = getExportCountForFile(fullPath);
|
|
@@ -44541,15 +44871,15 @@ async function computeDuplicationRatio(files, workingDir) {
|
|
|
44541
44871
|
const analyzedFiles = [];
|
|
44542
44872
|
for (const file3 of files) {
|
|
44543
44873
|
const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
|
|
44544
|
-
if (!
|
|
44874
|
+
if (!fs20.existsSync(fullPath)) {
|
|
44545
44875
|
continue;
|
|
44546
44876
|
}
|
|
44547
44877
|
try {
|
|
44548
|
-
const stat =
|
|
44878
|
+
const stat = fs20.statSync(fullPath);
|
|
44549
44879
|
if (stat.size > MAX_FILE_SIZE_BYTES5) {
|
|
44550
44880
|
continue;
|
|
44551
44881
|
}
|
|
44552
|
-
const content =
|
|
44882
|
+
const content = fs20.readFileSync(fullPath, "utf-8");
|
|
44553
44883
|
const lines = content.split(`
|
|
44554
44884
|
`).filter((line) => line.trim().length > 0);
|
|
44555
44885
|
if (lines.length < MIN_DUPLICATION_LINES) {
|
|
@@ -44617,7 +44947,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
44617
44947
|
let testLines = 0;
|
|
44618
44948
|
let codeLines = 0;
|
|
44619
44949
|
const srcDir = path25.join(workingDir, "src");
|
|
44620
|
-
if (
|
|
44950
|
+
if (fs20.existsSync(srcDir)) {
|
|
44621
44951
|
await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
44622
44952
|
codeLines += lines;
|
|
44623
44953
|
});
|
|
@@ -44625,14 +44955,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
44625
44955
|
const possibleSrcDirs = ["lib", "app", "source", "core"];
|
|
44626
44956
|
for (const dir of possibleSrcDirs) {
|
|
44627
44957
|
const dirPath = path25.join(workingDir, dir);
|
|
44628
|
-
if (
|
|
44958
|
+
if (fs20.existsSync(dirPath)) {
|
|
44629
44959
|
await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
44630
44960
|
codeLines += lines;
|
|
44631
44961
|
});
|
|
44632
44962
|
}
|
|
44633
44963
|
}
|
|
44634
44964
|
const testsDir = path25.join(workingDir, "tests");
|
|
44635
|
-
if (
|
|
44965
|
+
if (fs20.existsSync(testsDir)) {
|
|
44636
44966
|
await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
44637
44967
|
testLines += lines;
|
|
44638
44968
|
});
|
|
@@ -44640,7 +44970,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
44640
44970
|
const possibleTestDirs = ["test", "__tests__", "specs"];
|
|
44641
44971
|
for (const dir of possibleTestDirs) {
|
|
44642
44972
|
const dirPath = path25.join(workingDir, dir);
|
|
44643
|
-
if (
|
|
44973
|
+
if (fs20.existsSync(dirPath) && dirPath !== testsDir) {
|
|
44644
44974
|
await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
44645
44975
|
testLines += lines;
|
|
44646
44976
|
});
|
|
@@ -44652,7 +44982,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
|
44652
44982
|
}
|
|
44653
44983
|
async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
|
|
44654
44984
|
try {
|
|
44655
|
-
const entries =
|
|
44985
|
+
const entries = fs20.readdirSync(dirPath, { withFileTypes: true });
|
|
44656
44986
|
for (const entry of entries) {
|
|
44657
44987
|
const fullPath = path25.join(dirPath, entry.name);
|
|
44658
44988
|
if (entry.isDirectory()) {
|
|
@@ -44700,7 +45030,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
|
|
|
44700
45030
|
continue;
|
|
44701
45031
|
}
|
|
44702
45032
|
try {
|
|
44703
|
-
const content =
|
|
45033
|
+
const content = fs20.readFileSync(fullPath, "utf-8");
|
|
44704
45034
|
const lines = countCodeLines(content);
|
|
44705
45035
|
callback(lines);
|
|
44706
45036
|
} catch {}
|
|
@@ -44916,7 +45246,7 @@ async function qualityBudget(input, directory) {
|
|
|
44916
45246
|
|
|
44917
45247
|
// src/tools/sast-scan.ts
|
|
44918
45248
|
init_manager();
|
|
44919
|
-
import * as
|
|
45249
|
+
import * as fs21 from "fs";
|
|
44920
45250
|
import * as path26 from "path";
|
|
44921
45251
|
import { extname as extname7 } from "path";
|
|
44922
45252
|
|
|
@@ -45780,17 +46110,17 @@ var SEVERITY_ORDER = {
|
|
|
45780
46110
|
};
|
|
45781
46111
|
function shouldSkipFile(filePath) {
|
|
45782
46112
|
try {
|
|
45783
|
-
const stats =
|
|
46113
|
+
const stats = fs21.statSync(filePath);
|
|
45784
46114
|
if (stats.size > MAX_FILE_SIZE_BYTES6) {
|
|
45785
46115
|
return { skip: true, reason: "file too large" };
|
|
45786
46116
|
}
|
|
45787
46117
|
if (stats.size === 0) {
|
|
45788
46118
|
return { skip: true, reason: "empty file" };
|
|
45789
46119
|
}
|
|
45790
|
-
const fd =
|
|
46120
|
+
const fd = fs21.openSync(filePath, "r");
|
|
45791
46121
|
const buffer = Buffer.alloc(8192);
|
|
45792
|
-
const bytesRead =
|
|
45793
|
-
|
|
46122
|
+
const bytesRead = fs21.readSync(fd, buffer, 0, 8192, 0);
|
|
46123
|
+
fs21.closeSync(fd);
|
|
45794
46124
|
if (bytesRead > 0) {
|
|
45795
46125
|
let nullCount = 0;
|
|
45796
46126
|
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
@@ -45829,7 +46159,7 @@ function countBySeverity(findings) {
|
|
|
45829
46159
|
}
|
|
45830
46160
|
function scanFileWithTierA(filePath, language) {
|
|
45831
46161
|
try {
|
|
45832
|
-
const content =
|
|
46162
|
+
const content = fs21.readFileSync(filePath, "utf-8");
|
|
45833
46163
|
const findings = executeRulesSync(filePath, content, language);
|
|
45834
46164
|
return findings.map((f) => ({
|
|
45835
46165
|
rule_id: f.rule_id,
|
|
@@ -45873,7 +46203,7 @@ async function sastScan(input, directory, config3) {
|
|
|
45873
46203
|
const filesByLanguage = new Map;
|
|
45874
46204
|
for (const filePath of changed_files) {
|
|
45875
46205
|
const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
|
|
45876
|
-
if (!
|
|
46206
|
+
if (!fs21.existsSync(resolvedPath)) {
|
|
45877
46207
|
_filesSkipped++;
|
|
45878
46208
|
continue;
|
|
45879
46209
|
}
|
|
@@ -46296,7 +46626,7 @@ var retrieve_summary = tool({
|
|
|
46296
46626
|
// src/tools/sbom-generate.ts
|
|
46297
46627
|
init_dist();
|
|
46298
46628
|
init_manager();
|
|
46299
|
-
import * as
|
|
46629
|
+
import * as fs22 from "fs";
|
|
46300
46630
|
import * as path28 from "path";
|
|
46301
46631
|
|
|
46302
46632
|
// src/sbom/detectors/dart.ts
|
|
@@ -47142,7 +47472,7 @@ function findManifestFiles(rootDir) {
|
|
|
47142
47472
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
47143
47473
|
function searchDir(dir) {
|
|
47144
47474
|
try {
|
|
47145
|
-
const entries =
|
|
47475
|
+
const entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
47146
47476
|
for (const entry of entries) {
|
|
47147
47477
|
const fullPath = path28.join(dir, entry.name);
|
|
47148
47478
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
@@ -47171,7 +47501,7 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
47171
47501
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
47172
47502
|
for (const dir of directories) {
|
|
47173
47503
|
try {
|
|
47174
|
-
const entries =
|
|
47504
|
+
const entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
47175
47505
|
for (const entry of entries) {
|
|
47176
47506
|
const fullPath = path28.join(dir, entry.name);
|
|
47177
47507
|
if (entry.isFile()) {
|
|
@@ -47209,7 +47539,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
47209
47539
|
}
|
|
47210
47540
|
function ensureOutputDir(outputDir) {
|
|
47211
47541
|
try {
|
|
47212
|
-
|
|
47542
|
+
fs22.mkdirSync(outputDir, { recursive: true });
|
|
47213
47543
|
} catch (error93) {
|
|
47214
47544
|
if (!error93 || error93.code !== "EEXIST") {
|
|
47215
47545
|
throw error93;
|
|
@@ -47303,10 +47633,10 @@ var sbom_generate = tool({
|
|
|
47303
47633
|
for (const manifestFile of manifestFiles) {
|
|
47304
47634
|
try {
|
|
47305
47635
|
const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
|
|
47306
|
-
if (!
|
|
47636
|
+
if (!fs22.existsSync(fullPath)) {
|
|
47307
47637
|
continue;
|
|
47308
47638
|
}
|
|
47309
|
-
const content =
|
|
47639
|
+
const content = fs22.readFileSync(fullPath, "utf-8");
|
|
47310
47640
|
const components = detectComponents(manifestFile, content);
|
|
47311
47641
|
processedFiles.push(manifestFile);
|
|
47312
47642
|
if (components.length > 0) {
|
|
@@ -47320,7 +47650,7 @@ var sbom_generate = tool({
|
|
|
47320
47650
|
const bomJson = serializeCycloneDX(bom);
|
|
47321
47651
|
const filename = generateSbomFilename();
|
|
47322
47652
|
const outputPath = path28.join(outputDir, filename);
|
|
47323
|
-
|
|
47653
|
+
fs22.writeFileSync(outputPath, bomJson, "utf-8");
|
|
47324
47654
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
47325
47655
|
try {
|
|
47326
47656
|
const timestamp = new Date().toISOString();
|
|
@@ -47361,7 +47691,7 @@ var sbom_generate = tool({
|
|
|
47361
47691
|
});
|
|
47362
47692
|
// src/tools/schema-drift.ts
|
|
47363
47693
|
init_dist();
|
|
47364
|
-
import * as
|
|
47694
|
+
import * as fs23 from "fs";
|
|
47365
47695
|
import * as path29 from "path";
|
|
47366
47696
|
var SPEC_CANDIDATES = [
|
|
47367
47697
|
"openapi.json",
|
|
@@ -47403,19 +47733,19 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
47403
47733
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
47404
47734
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
47405
47735
|
}
|
|
47406
|
-
const stats =
|
|
47736
|
+
const stats = fs23.statSync(resolvedPath);
|
|
47407
47737
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
47408
47738
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
47409
47739
|
}
|
|
47410
|
-
if (!
|
|
47740
|
+
if (!fs23.existsSync(resolvedPath)) {
|
|
47411
47741
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
47412
47742
|
}
|
|
47413
47743
|
return resolvedPath;
|
|
47414
47744
|
}
|
|
47415
47745
|
for (const candidate of SPEC_CANDIDATES) {
|
|
47416
47746
|
const candidatePath = path29.resolve(cwd, candidate);
|
|
47417
|
-
if (
|
|
47418
|
-
const stats =
|
|
47747
|
+
if (fs23.existsSync(candidatePath)) {
|
|
47748
|
+
const stats = fs23.statSync(candidatePath);
|
|
47419
47749
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
47420
47750
|
return candidatePath;
|
|
47421
47751
|
}
|
|
@@ -47424,7 +47754,7 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
47424
47754
|
return null;
|
|
47425
47755
|
}
|
|
47426
47756
|
function parseSpec(specFile) {
|
|
47427
|
-
const content =
|
|
47757
|
+
const content = fs23.readFileSync(specFile, "utf-8");
|
|
47428
47758
|
const ext = path29.extname(specFile).toLowerCase();
|
|
47429
47759
|
if (ext === ".json") {
|
|
47430
47760
|
return parseJsonSpec(content);
|
|
@@ -47491,7 +47821,7 @@ function extractRoutes(cwd) {
|
|
|
47491
47821
|
function walkDir(dir) {
|
|
47492
47822
|
let entries;
|
|
47493
47823
|
try {
|
|
47494
|
-
entries =
|
|
47824
|
+
entries = fs23.readdirSync(dir, { withFileTypes: true });
|
|
47495
47825
|
} catch {
|
|
47496
47826
|
return;
|
|
47497
47827
|
}
|
|
@@ -47524,7 +47854,7 @@ function extractRoutes(cwd) {
|
|
|
47524
47854
|
}
|
|
47525
47855
|
function extractRoutesFromFile(filePath) {
|
|
47526
47856
|
const routes = [];
|
|
47527
|
-
const content =
|
|
47857
|
+
const content = fs23.readFileSync(filePath, "utf-8");
|
|
47528
47858
|
const lines = content.split(/\r?\n/);
|
|
47529
47859
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
47530
47860
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -47674,7 +48004,7 @@ init_secretscan();
|
|
|
47674
48004
|
|
|
47675
48005
|
// src/tools/symbols.ts
|
|
47676
48006
|
init_tool();
|
|
47677
|
-
import * as
|
|
48007
|
+
import * as fs24 from "fs";
|
|
47678
48008
|
import * as path30 from "path";
|
|
47679
48009
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
47680
48010
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
@@ -47705,8 +48035,8 @@ function containsWindowsAttacks(str) {
|
|
|
47705
48035
|
function isPathInWorkspace(filePath, workspace) {
|
|
47706
48036
|
try {
|
|
47707
48037
|
const resolvedPath = path30.resolve(workspace, filePath);
|
|
47708
|
-
const realWorkspace =
|
|
47709
|
-
const realResolvedPath =
|
|
48038
|
+
const realWorkspace = fs24.realpathSync(workspace);
|
|
48039
|
+
const realResolvedPath = fs24.realpathSync(resolvedPath);
|
|
47710
48040
|
const relativePath = path30.relative(realWorkspace, realResolvedPath);
|
|
47711
48041
|
if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
|
|
47712
48042
|
return false;
|
|
@@ -47726,11 +48056,11 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
47726
48056
|
}
|
|
47727
48057
|
let content;
|
|
47728
48058
|
try {
|
|
47729
|
-
const stats =
|
|
48059
|
+
const stats = fs24.statSync(fullPath);
|
|
47730
48060
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
47731
48061
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
47732
48062
|
}
|
|
47733
|
-
content =
|
|
48063
|
+
content = fs24.readFileSync(fullPath, "utf-8");
|
|
47734
48064
|
} catch {
|
|
47735
48065
|
return [];
|
|
47736
48066
|
}
|
|
@@ -47878,11 +48208,11 @@ function extractPythonSymbols(filePath, cwd) {
|
|
|
47878
48208
|
}
|
|
47879
48209
|
let content;
|
|
47880
48210
|
try {
|
|
47881
|
-
const stats =
|
|
48211
|
+
const stats = fs24.statSync(fullPath);
|
|
47882
48212
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
47883
48213
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
47884
48214
|
}
|
|
47885
|
-
content =
|
|
48215
|
+
content = fs24.readFileSync(fullPath, "utf-8");
|
|
47886
48216
|
} catch {
|
|
47887
48217
|
return [];
|
|
47888
48218
|
}
|
|
@@ -48022,7 +48352,7 @@ init_test_runner();
|
|
|
48022
48352
|
|
|
48023
48353
|
// src/tools/todo-extract.ts
|
|
48024
48354
|
init_dist();
|
|
48025
|
-
import * as
|
|
48355
|
+
import * as fs25 from "fs";
|
|
48026
48356
|
import * as path31 from "path";
|
|
48027
48357
|
var MAX_TEXT_LENGTH = 200;
|
|
48028
48358
|
var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
|
|
@@ -48118,7 +48448,7 @@ function isSupportedExtension(filePath) {
|
|
|
48118
48448
|
function findSourceFiles3(dir, files = []) {
|
|
48119
48449
|
let entries;
|
|
48120
48450
|
try {
|
|
48121
|
-
entries =
|
|
48451
|
+
entries = fs25.readdirSync(dir);
|
|
48122
48452
|
} catch {
|
|
48123
48453
|
return files;
|
|
48124
48454
|
}
|
|
@@ -48130,7 +48460,7 @@ function findSourceFiles3(dir, files = []) {
|
|
|
48130
48460
|
const fullPath = path31.join(dir, entry);
|
|
48131
48461
|
let stat;
|
|
48132
48462
|
try {
|
|
48133
|
-
stat =
|
|
48463
|
+
stat = fs25.statSync(fullPath);
|
|
48134
48464
|
} catch {
|
|
48135
48465
|
continue;
|
|
48136
48466
|
}
|
|
@@ -48223,7 +48553,7 @@ var todo_extract = tool({
|
|
|
48223
48553
|
return JSON.stringify(errorResult, null, 2);
|
|
48224
48554
|
}
|
|
48225
48555
|
const scanPath = resolvedPath;
|
|
48226
|
-
if (!
|
|
48556
|
+
if (!fs25.existsSync(scanPath)) {
|
|
48227
48557
|
const errorResult = {
|
|
48228
48558
|
error: `path not found: ${pathsInput}`,
|
|
48229
48559
|
total: 0,
|
|
@@ -48233,7 +48563,7 @@ var todo_extract = tool({
|
|
|
48233
48563
|
return JSON.stringify(errorResult, null, 2);
|
|
48234
48564
|
}
|
|
48235
48565
|
const filesToScan = [];
|
|
48236
|
-
const stat =
|
|
48566
|
+
const stat = fs25.statSync(scanPath);
|
|
48237
48567
|
if (stat.isFile()) {
|
|
48238
48568
|
if (isSupportedExtension(scanPath)) {
|
|
48239
48569
|
filesToScan.push(scanPath);
|
|
@@ -48252,11 +48582,11 @@ var todo_extract = tool({
|
|
|
48252
48582
|
const allEntries = [];
|
|
48253
48583
|
for (const filePath of filesToScan) {
|
|
48254
48584
|
try {
|
|
48255
|
-
const fileStat =
|
|
48585
|
+
const fileStat = fs25.statSync(filePath);
|
|
48256
48586
|
if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
|
|
48257
48587
|
continue;
|
|
48258
48588
|
}
|
|
48259
|
-
const content =
|
|
48589
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
48260
48590
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
48261
48591
|
allEntries.push(...entries);
|
|
48262
48592
|
} catch {}
|
|
@@ -48457,6 +48787,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
48457
48787
|
lint,
|
|
48458
48788
|
diff,
|
|
48459
48789
|
pkg_audit,
|
|
48790
|
+
phase_complete,
|
|
48460
48791
|
pre_check_batch,
|
|
48461
48792
|
retrieve_summary,
|
|
48462
48793
|
schema_drift,
|
|
@@ -48474,7 +48805,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
48474
48805
|
opencodeConfig.command = {
|
|
48475
48806
|
...opencodeConfig.command || {},
|
|
48476
48807
|
swarm: {
|
|
48477
|
-
template: "
|
|
48808
|
+
template: "/swarm $ARGUMENTS",
|
|
48478
48809
|
description: "Swarm management commands"
|
|
48479
48810
|
}
|
|
48480
48811
|
};
|
package/dist/state.d.ts
CHANGED
|
@@ -75,6 +75,12 @@ export interface AgentSessionState {
|
|
|
75
75
|
selfFixAttempted: boolean;
|
|
76
76
|
/** Phases that have already received a catastrophic zero-reviewer warning */
|
|
77
77
|
catastrophicPhaseWarnings: Set<number>;
|
|
78
|
+
/** Timestamp of most recent phase completion */
|
|
79
|
+
lastPhaseCompleteTimestamp: number;
|
|
80
|
+
/** Phase number of most recent phase completion */
|
|
81
|
+
lastPhaseCompletePhase: number;
|
|
82
|
+
/** Set of agents dispatched in current phase (normalized names) */
|
|
83
|
+
phaseAgentsDispatched: Set<string>;
|
|
78
84
|
}
|
|
79
85
|
/**
|
|
80
86
|
* Represents a single agent invocation window with isolated guardrail budgets.
|
|
@@ -190,3 +196,10 @@ export declare function getActiveWindow(sessionId: string): InvocationWindow | u
|
|
|
190
196
|
* @param maxWindows - Maximum number of windows to keep (default 50)
|
|
191
197
|
*/
|
|
192
198
|
export declare function pruneOldWindows(sessionId: string, maxAgeMs?: number, maxWindows?: number): void;
|
|
199
|
+
/**
|
|
200
|
+
* Record an agent dispatch for phase completion tracking.
|
|
201
|
+
* Normalizes the agent name via stripKnownSwarmPrefix before adding to phaseAgentsDispatched.
|
|
202
|
+
* @param sessionId - Session identifier
|
|
203
|
+
* @param agentName - Agent name to record (will be normalized)
|
|
204
|
+
*/
|
|
205
|
+
export declare function recordPhaseAgentDispatch(sessionId: string, agentName: string): void;
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { extract_code_blocks } from './file-extractor';
|
|
|
8
8
|
export { fetchGitingest, type GitingestArgs, gitingest } from './gitingest';
|
|
9
9
|
export { imports } from './imports';
|
|
10
10
|
export { lint } from './lint';
|
|
11
|
+
export { phase_complete } from './phase-complete';
|
|
11
12
|
export { pkg_audit } from './pkg-audit';
|
|
12
13
|
export { type PlaceholderFinding, type PlaceholderScanInput, type PlaceholderScanResult, placeholderScan, } from './placeholder-scan';
|
|
13
14
|
export { type PreCheckBatchInput, type PreCheckBatchResult, pre_check_batch, runPreCheckBatch, type ToolResult, } from './pre-check-batch';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase completion tool for tracking and validating phase completion.
|
|
3
|
+
* Core implementation - gathers data, enforces policy, writes event, resets state.
|
|
4
|
+
*/
|
|
5
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
6
|
+
/**
|
|
7
|
+
* Tool definition for phase_complete
|
|
8
|
+
*/
|
|
9
|
+
export declare const phase_complete: ToolDefinition;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Used for constants and agent setup references.
|
|
4
4
|
*/
|
|
5
5
|
/** Union type of all valid tool names */
|
|
6
|
-
export type ToolName = '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' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks';
|
|
6
|
+
export type ToolName = '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' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete';
|
|
7
7
|
/** Readonly array of all tool names */
|
|
8
8
|
export declare const TOOL_NAMES: readonly ToolName[];
|
|
9
9
|
/** Set for O(1) tool name validation */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.13.
|
|
3
|
+
"version": "6.13.2",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
11
|
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/zaxbysauce/opencode-swarm.git"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
12
20
|
"keywords": [
|
|
13
21
|
"opencode",
|
|
14
22
|
"opencode-plugin",
|