opencode-swarm 6.82.2 → 6.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/convene-general-council.test.d.ts +10 -0
- package/dist/__tests__/disagreement-detector.test.d.ts +7 -0
- package/dist/__tests__/general-council-service.test.d.ts +7 -0
- package/dist/__tests__/qa-gate-hardening.test.d.ts +12 -0
- package/dist/__tests__/web-search-provider.test.d.ts +6 -0
- package/dist/agents/architect.d.ts +9 -1
- package/dist/agents/council-member.d.ts +30 -0
- package/dist/agents/council-member.test.d.ts +8 -0
- package/dist/agents/council-moderator.d.ts +20 -0
- package/dist/agents/critic.d.ts +1 -1
- package/dist/agents/index.d.ts +2 -0
- package/dist/cli/index.js +115 -7
- package/dist/commands/council.d.ts +17 -0
- package/dist/commands/council.test.d.ts +4 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/registry.d.ts +7 -1
- package/dist/config/constants.d.ts +3 -3
- package/dist/config/schema.d.ts +109 -0
- package/dist/council/disagreement-detector.d.ts +24 -0
- package/dist/council/general-council-advisory.d.ts +29 -0
- package/dist/council/general-council-service.d.ts +22 -0
- package/dist/council/general-council-types.d.ts +98 -0
- package/dist/council/web-search-provider.d.ts +35 -0
- package/dist/db/qa-gate-profile.d.ts +5 -1
- package/dist/index.js +1567 -373
- package/dist/tools/convene-general-council.d.ts +25 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/set-qa-gates.d.ts +1 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/tools/web-search.d.ts +13 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -87,7 +87,9 @@ var init_tool_names = __esm(() => {
|
|
|
87
87
|
"get_approved_plan",
|
|
88
88
|
"repo_map",
|
|
89
89
|
"get_qa_gate_profile",
|
|
90
|
-
"set_qa_gates"
|
|
90
|
+
"set_qa_gates",
|
|
91
|
+
"web_search",
|
|
92
|
+
"convene_general_council"
|
|
91
93
|
];
|
|
92
94
|
TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
93
95
|
});
|
|
@@ -126,9 +128,7 @@ function isSubagent(name2) {
|
|
|
126
128
|
return ALL_SUBAGENT_NAMES.includes(name2);
|
|
127
129
|
}
|
|
128
130
|
function isLowCapabilityModel(modelId) {
|
|
129
|
-
|
|
130
|
-
return false;
|
|
131
|
-
const lower = modelId.toLowerCase();
|
|
131
|
+
const lower = (modelId || "").toLowerCase();
|
|
132
132
|
return LOW_CAPABILITY_MODELS.some((substr) => lower.includes(substr));
|
|
133
133
|
}
|
|
134
134
|
var QA_AGENTS, PIPELINE_AGENTS, ORCHESTRATOR_NAME = "architect", ALL_SUBAGENT_NAMES, ALL_AGENT_NAMES, OPENCODE_NATIVE_AGENTS, AGENT_TOOL_MAP, WRITE_TOOL_NAMES, TOOL_DESCRIPTIONS, DEFAULT_MODELS, DEFAULT_SCORING_CONFIG, LOW_CAPABILITY_MODELS, TURBO_MODE_BANNER = `## \uD83D\uDE80 TURBO MODE ACTIVE
|
|
@@ -172,6 +172,8 @@ var init_constants = __esm(() => {
|
|
|
172
172
|
"critic_hallucination_verifier",
|
|
173
173
|
"curator_init",
|
|
174
174
|
"curator_phase",
|
|
175
|
+
"council_member",
|
|
176
|
+
"council_moderator",
|
|
175
177
|
...QA_AGENTS,
|
|
176
178
|
...PIPELINE_AGENTS
|
|
177
179
|
];
|
|
@@ -243,7 +245,8 @@ var init_constants = __esm(() => {
|
|
|
243
245
|
"suggest_patch",
|
|
244
246
|
"repo_map",
|
|
245
247
|
"get_qa_gate_profile",
|
|
246
|
-
"set_qa_gates"
|
|
248
|
+
"set_qa_gates",
|
|
249
|
+
"convene_general_council"
|
|
247
250
|
],
|
|
248
251
|
explorer: [
|
|
249
252
|
"complexity_hotspots",
|
|
@@ -330,6 +333,7 @@ var init_constants = __esm(() => {
|
|
|
330
333
|
"symbols",
|
|
331
334
|
"knowledge_recall",
|
|
332
335
|
"req_coverage",
|
|
336
|
+
"get_approved_plan",
|
|
333
337
|
"repo_map"
|
|
334
338
|
],
|
|
335
339
|
critic_sounding_board: [
|
|
@@ -392,7 +396,9 @@ var init_constants = __esm(() => {
|
|
|
392
396
|
"knowledge_recall"
|
|
393
397
|
],
|
|
394
398
|
curator_init: ["knowledge_recall"],
|
|
395
|
-
curator_phase: ["knowledge_recall"]
|
|
399
|
+
curator_phase: ["knowledge_recall"],
|
|
400
|
+
council_member: ["web_search"],
|
|
401
|
+
council_moderator: []
|
|
396
402
|
};
|
|
397
403
|
WRITE_TOOL_NAMES = [
|
|
398
404
|
"write",
|
|
@@ -412,7 +418,7 @@ var init_constants = __esm(() => {
|
|
|
412
418
|
diff_summary: "filter classified AST changes by category, risk level, or file for reviewer drill-down",
|
|
413
419
|
imports: "dependency audit",
|
|
414
420
|
lint: "code quality",
|
|
415
|
-
placeholder_scan: "
|
|
421
|
+
placeholder_scan: "todo and FIXME comment detection",
|
|
416
422
|
secretscan: "secret detection",
|
|
417
423
|
sast_scan: "static analysis security scan",
|
|
418
424
|
syntax_check: "syntax validation",
|
|
@@ -454,13 +460,15 @@ var init_constants = __esm(() => {
|
|
|
454
460
|
gitingest: "fetch a GitHub repository full content via gitingest.com",
|
|
455
461
|
retrieve_summary: "retrieve the full content of a stored tool output summary",
|
|
456
462
|
search: "Workspace-scoped ripgrep-style text search with structured JSON output. Supports literal and regex modes, glob filtering, and result limits. NOTE: This is text search, not structural AST search \u2014 use symbols and imports tools for structural queries.",
|
|
463
|
+
web_search: "External web search (Tavily or Brave) for General Council member agents. Returns titled results with snippets and URLs. Restricted to council_member agents via AGENT_TOOL_MAP. Config-gated on council.general.enabled; requires a search API key.",
|
|
464
|
+
convene_general_council: "Synthesize responses from a multi-model General Council. Accepts parallel member responses (Round 1, optionally Round 2), detects disagreements, and returns consensus points, persisting disagreements, a structured synthesis, and an optional moderator prompt. Architect-only. Config-gated on council.general.enabled.",
|
|
457
465
|
batch_symbols: "Batched symbol extraction across multiple files. Returns per-file symbol summaries with isolated error handling.",
|
|
458
466
|
suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch.",
|
|
459
467
|
lint_spec: "validate .swarm/spec.md format and required fields",
|
|
460
468
|
get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison",
|
|
461
469
|
repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
|
|
462
|
-
get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates (reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test), lock state, and profile hash. Read-only.",
|
|
463
|
-
set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only \u2014 rejected once the profile is locked after critic approval. Supports: reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test.",
|
|
470
|
+
get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates (reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review), lock state, and profile hash. Read-only.",
|
|
471
|
+
set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only \u2014 rejected once the profile is locked after critic approval. Supports: reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review.",
|
|
464
472
|
req_coverage: "query requirement coverage status for tracked functional requirements"
|
|
465
473
|
};
|
|
466
474
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
@@ -484,6 +492,8 @@ var init_constants = __esm(() => {
|
|
|
484
492
|
designer: "opencode/trinity-large-preview-free",
|
|
485
493
|
curator_init: "opencode/trinity-large-preview-free",
|
|
486
494
|
curator_phase: "opencode/trinity-large-preview-free",
|
|
495
|
+
council_member: "opencode/trinity-large-preview-free",
|
|
496
|
+
council_moderator: "opencode/trinity-large-preview-free",
|
|
487
497
|
default: "opencode/trinity-large-preview-free"
|
|
488
498
|
};
|
|
489
499
|
DEFAULT_SCORING_CONFIG = {
|
|
@@ -14691,7 +14701,7 @@ function resolveGuardrailsConfig(config2, agentName) {
|
|
|
14691
14701
|
};
|
|
14692
14702
|
return resolved;
|
|
14693
14703
|
}
|
|
14694
|
-
var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, CouncilConfigSchema, ParallelizationConfigSchema, PluginConfigSchema;
|
|
14704
|
+
var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, GeneralCouncilMemberConfigSchema, GeneralCouncilConfigSchema, CouncilConfigSchema, ParallelizationConfigSchema, PluginConfigSchema;
|
|
14695
14705
|
var init_schema = __esm(() => {
|
|
14696
14706
|
init_zod();
|
|
14697
14707
|
init_constants();
|
|
@@ -15208,13 +15218,37 @@ var init_schema = __esm(() => {
|
|
|
15208
15218
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
|
|
15209
15219
|
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
|
|
15210
15220
|
});
|
|
15221
|
+
GeneralCouncilMemberConfigSchema = exports_external.object({
|
|
15222
|
+
memberId: exports_external.string().min(1),
|
|
15223
|
+
model: exports_external.string().min(1),
|
|
15224
|
+
role: exports_external.enum([
|
|
15225
|
+
"generalist",
|
|
15226
|
+
"skeptic",
|
|
15227
|
+
"domain_expert",
|
|
15228
|
+
"devil_advocate",
|
|
15229
|
+
"synthesizer"
|
|
15230
|
+
]),
|
|
15231
|
+
persona: exports_external.string().optional()
|
|
15232
|
+
}).strict();
|
|
15233
|
+
GeneralCouncilConfigSchema = exports_external.object({
|
|
15234
|
+
enabled: exports_external.boolean().default(false),
|
|
15235
|
+
searchProvider: exports_external.enum(["tavily", "brave"]).default("tavily"),
|
|
15236
|
+
searchApiKey: exports_external.string().optional(),
|
|
15237
|
+
members: exports_external.array(GeneralCouncilMemberConfigSchema).default([]),
|
|
15238
|
+
presets: exports_external.record(exports_external.string(), exports_external.array(GeneralCouncilMemberConfigSchema)).default({}),
|
|
15239
|
+
deliberate: exports_external.boolean().default(true),
|
|
15240
|
+
moderator: exports_external.boolean().default(true),
|
|
15241
|
+
moderatorModel: exports_external.string().optional(),
|
|
15242
|
+
maxSourcesPerMember: exports_external.number().int().min(1).max(20).default(5)
|
|
15243
|
+
}).strict();
|
|
15211
15244
|
CouncilConfigSchema = exports_external.object({
|
|
15212
15245
|
enabled: exports_external.boolean().default(false),
|
|
15213
15246
|
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
15214
15247
|
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
15215
15248
|
vetoPriority: exports_external.boolean().default(true),
|
|
15216
15249
|
requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
|
|
15217
|
-
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet.")
|
|
15250
|
+
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet."),
|
|
15251
|
+
general: GeneralCouncilConfigSchema.optional()
|
|
15218
15252
|
}).strict();
|
|
15219
15253
|
ParallelizationConfigSchema = exports_external.object({
|
|
15220
15254
|
enabled: exports_external.boolean().default(false),
|
|
@@ -19708,7 +19742,8 @@ var init_qa_gate_profile = __esm(() => {
|
|
|
19708
19742
|
critic_pre_plan: true,
|
|
19709
19743
|
hallucination_guard: false,
|
|
19710
19744
|
sast_enabled: true,
|
|
19711
|
-
mutation_test: false
|
|
19745
|
+
mutation_test: false,
|
|
19746
|
+
council_general_review: false
|
|
19712
19747
|
};
|
|
19713
19748
|
});
|
|
19714
19749
|
|
|
@@ -41865,6 +41900,76 @@ var init_config2 = __esm(() => {
|
|
|
41865
41900
|
init_loader();
|
|
41866
41901
|
});
|
|
41867
41902
|
|
|
41903
|
+
// src/commands/council.ts
|
|
41904
|
+
function sanitizeQuestion(raw) {
|
|
41905
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
41906
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
41907
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
41908
|
+
if (normalized.length <= MAX_QUESTION_LEN)
|
|
41909
|
+
return normalized;
|
|
41910
|
+
return `${normalized.slice(0, MAX_QUESTION_LEN)}\u2026`;
|
|
41911
|
+
}
|
|
41912
|
+
function sanitizePresetName(raw) {
|
|
41913
|
+
const trimmed = raw.trim();
|
|
41914
|
+
if (!trimmed)
|
|
41915
|
+
return null;
|
|
41916
|
+
if (trimmed.length > 64)
|
|
41917
|
+
return null;
|
|
41918
|
+
if (!/^[A-Za-z0-9_-]+$/.test(trimmed))
|
|
41919
|
+
return null;
|
|
41920
|
+
return trimmed;
|
|
41921
|
+
}
|
|
41922
|
+
function parseArgs(args2) {
|
|
41923
|
+
const out2 = { specReview: false, rest: [] };
|
|
41924
|
+
for (let i2 = 0;i2 < args2.length; i2++) {
|
|
41925
|
+
const token = args2[i2];
|
|
41926
|
+
if (token === "--spec-review") {
|
|
41927
|
+
out2.specReview = true;
|
|
41928
|
+
continue;
|
|
41929
|
+
}
|
|
41930
|
+
if (token === "--preset") {
|
|
41931
|
+
const next = args2[i2 + 1];
|
|
41932
|
+
if (next !== undefined) {
|
|
41933
|
+
const sanitized = sanitizePresetName(next);
|
|
41934
|
+
if (sanitized)
|
|
41935
|
+
out2.preset = sanitized;
|
|
41936
|
+
i2++;
|
|
41937
|
+
}
|
|
41938
|
+
continue;
|
|
41939
|
+
}
|
|
41940
|
+
out2.rest.push(token);
|
|
41941
|
+
}
|
|
41942
|
+
return out2;
|
|
41943
|
+
}
|
|
41944
|
+
async function handleCouncilCommand(_directory, args2) {
|
|
41945
|
+
const parsed = parseArgs(args2);
|
|
41946
|
+
const question = sanitizeQuestion(parsed.rest.join(" "));
|
|
41947
|
+
if (!question) {
|
|
41948
|
+
return USAGE;
|
|
41949
|
+
}
|
|
41950
|
+
const tokens = ["MODE: COUNCIL"];
|
|
41951
|
+
if (parsed.preset) {
|
|
41952
|
+
tokens.push(`preset=${parsed.preset}`);
|
|
41953
|
+
}
|
|
41954
|
+
if (parsed.specReview) {
|
|
41955
|
+
tokens.push("spec_review");
|
|
41956
|
+
}
|
|
41957
|
+
return `[${tokens.join(" ")}] ${question}`;
|
|
41958
|
+
}
|
|
41959
|
+
var MAX_QUESTION_LEN = 2000, USAGE;
|
|
41960
|
+
var init_council = __esm(() => {
|
|
41961
|
+
USAGE = [
|
|
41962
|
+
"Usage: /swarm council <question> [--preset <name>] [--spec-review]",
|
|
41963
|
+
"",
|
|
41964
|
+
" question The question to put to the council",
|
|
41965
|
+
" --preset <name> Use a named member preset from council.general.presets",
|
|
41966
|
+
" --spec-review Use spec_review mode (single advisory pass on a draft spec)",
|
|
41967
|
+
"",
|
|
41968
|
+
"Requires council.general.enabled: true and a configured search API key in opencode-swarm.json."
|
|
41969
|
+
].join(`
|
|
41970
|
+
`);
|
|
41971
|
+
});
|
|
41972
|
+
|
|
41868
41973
|
// src/agents/explorer.ts
|
|
41869
41974
|
function createExplorerAgent(model, customPrompt, customAppendPrompt) {
|
|
41870
41975
|
let prompt = EXPLORER_PROMPT;
|
|
@@ -52151,7 +52256,8 @@ var init_qa_gates = __esm(() => {
|
|
|
52151
52256
|
"critic_pre_plan",
|
|
52152
52257
|
"hallucination_guard",
|
|
52153
52258
|
"sast_enabled",
|
|
52154
|
-
"mutation_test"
|
|
52259
|
+
"mutation_test",
|
|
52260
|
+
"council_general_review"
|
|
52155
52261
|
];
|
|
52156
52262
|
});
|
|
52157
52263
|
|
|
@@ -53777,6 +53883,7 @@ var init_registry = __esm(() => {
|
|
|
53777
53883
|
init_checkpoint2();
|
|
53778
53884
|
init_close();
|
|
53779
53885
|
init_config2();
|
|
53886
|
+
init_council();
|
|
53780
53887
|
init_curate();
|
|
53781
53888
|
init_dark_matter();
|
|
53782
53889
|
init_diagnose();
|
|
@@ -53932,11 +54039,17 @@ var init_registry = __esm(() => {
|
|
|
53932
54039
|
args: "[topic-text]",
|
|
53933
54040
|
details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md."
|
|
53934
54041
|
},
|
|
54042
|
+
council: {
|
|
54043
|
+
handler: (ctx) => handleCouncilCommand(ctx.directory, ctx.args),
|
|
54044
|
+
description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--preset <name>] [--spec-review]",
|
|
54045
|
+
args: "<question> [--preset <name>] [--spec-review]",
|
|
54046
|
+
details: "Triggers the architect to convene a configurable General Council: each member independently web-searches, answers, and engages in one structured deliberation round on disagreements; an optional moderator pass synthesizes the final answer. --preset <name> selects a member group from council.general.presets. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in opencode-swarm.json."
|
|
54047
|
+
},
|
|
53935
54048
|
"qa-gates": {
|
|
53936
54049
|
handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
|
|
53937
54050
|
description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
|
|
53938
54051
|
args: "[show|enable|override] <gate>...",
|
|
53939
|
-
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test."
|
|
54052
|
+
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test, council_general_review."
|
|
53940
54053
|
},
|
|
53941
54054
|
promote: {
|
|
53942
54055
|
handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
|
|
@@ -54046,7 +54159,7 @@ follow the pattern \`C1\`, \`C2\`, etc. The criteria are persisted to
|
|
|
54046
54159
|
### Phase 1 \u2014 Parallel dispatch (when the coder signals the task is complete)
|
|
54047
54160
|
Dispatch all FIVE council members IN PARALLEL \u2014 do not run them sequentially.
|
|
54048
54161
|
Each receives ONLY their role-relevant context, not the full conversation:
|
|
54049
|
-
- \`critic\` \u2014 original task spec + acceptance criteria + code diff + test results
|
|
54162
|
+
- \`critic\` \u2014 original task spec + acceptance criteria + code diff + test results + approved-plan baseline comparison (via \`get_approved_plan\`) and spec-intent drift analysis against the approved baseline
|
|
54050
54163
|
- \`reviewer\` \u2014 semantic diff summary + blast radius (files importing changed files) + style guide
|
|
54051
54164
|
- \`sme\` \u2014 task domain context + relevant knowledge base entries
|
|
54052
54165
|
- \`test_engineer\` \u2014 changed test files + coverage delta + known mutation gaps
|
|
@@ -54092,14 +54205,24 @@ from different members.`;
|
|
|
54092
54205
|
function buildYourToolsList(council) {
|
|
54093
54206
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54094
54207
|
const sorted = [...tools].sort();
|
|
54095
|
-
const
|
|
54208
|
+
const qaCouncilEnabled = council?.enabled === true;
|
|
54209
|
+
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54210
|
+
const filtered = sorted.filter((t) => {
|
|
54211
|
+
if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
|
|
54212
|
+
return false;
|
|
54213
|
+
}
|
|
54214
|
+
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
54215
|
+
return false;
|
|
54216
|
+
}
|
|
54217
|
+
return true;
|
|
54218
|
+
});
|
|
54096
54219
|
return `Task (delegation), ${filtered.join(", ")}.`;
|
|
54097
54220
|
}
|
|
54098
54221
|
function buildQaGateSelectionDialogue(modeLabel) {
|
|
54099
54222
|
const leadIn = modeLabel === "BRAINSTORM" ? "Now ask the user which QA gates to enable for this plan \u2014 do not select on their behalf." : modeLabel === "SPECIFY" ? "Ask the user which QA gates to enable for this plan before suggesting the next step." : "No pending gate selection found in `.swarm/context.md`. Ask the user inline now.";
|
|
54100
54223
|
return `${leadIn}
|
|
54101
54224
|
|
|
54102
|
-
Present the
|
|
54225
|
+
Present the nine gates with their defaults (DEFAULT_QA_GATES) as a single user-facing question. Offer the user a one-shot choice: accept defaults, or customize. The nine gates are:
|
|
54103
54226
|
- reviewer (default: ON) \u2014 code review of coder output
|
|
54104
54227
|
- test_engineer (default: ON) \u2014 test verification of coder output
|
|
54105
54228
|
- sme_enabled (default: ON) \u2014 SME consultation during planning/clarification
|
|
@@ -54108,13 +54231,24 @@ Present the eight gates with their defaults (DEFAULT_QA_GATES) as a single user-
|
|
|
54108
54231
|
- council_mode (default: OFF) \u2014 multi-member council gate (recommended for high-impact architecture, public APIs, schema/data mutation, security-sensitive code)
|
|
54109
54232
|
- hallucination_guard (default: OFF) \u2014 when enabled, mandatory per-phase API/signature/claim/citation verification via critic_hallucination_verifier at PHASE-WRAP; phase_complete will REJECT phase completion unless .swarm/evidence/{phase}/hallucination-guard.json exists with an APPROVED verdict (recommended for claim-heavy or research-heavy work)
|
|
54110
54233
|
- mutation_test (default: OFF) \u2014 when enabled, runs mutation testing on source files touched this phase via generate_mutants + mutation_test + write_mutation_evidence at PHASE-WRAP; FAIL verdict blocks phase_complete; WARN is non-blocking (recommended for projects with coverage gaps or safety-critical code)
|
|
54234
|
+
- council_general_review (default: OFF) \u2014 when enabled, MODE: SPECIFY runs convene_general_council on the draft spec before the critic-gate; multiple models each independently search the web, deliberate on disagreements, and a moderator synthesizes a final answer that the architect folds into the spec (recommended for novel architecture, unclear best practices, or high-risk design decisions). Requires council.general.enabled: true and a configured search API key.
|
|
54111
54235
|
|
|
54112
54236
|
One question, one message, defaults pre-stated. Wait for the user's answer.`;
|
|
54113
54237
|
}
|
|
54114
54238
|
function buildAvailableToolsList(council) {
|
|
54115
54239
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54116
54240
|
const sorted = [...tools].sort();
|
|
54117
|
-
const
|
|
54241
|
+
const qaCouncilEnabled = council?.enabled === true;
|
|
54242
|
+
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54243
|
+
const filtered = sorted.filter((t) => {
|
|
54244
|
+
if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
|
|
54245
|
+
return false;
|
|
54246
|
+
}
|
|
54247
|
+
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
54248
|
+
return false;
|
|
54249
|
+
}
|
|
54250
|
+
return true;
|
|
54251
|
+
});
|
|
54118
54252
|
return filtered.map((t) => {
|
|
54119
54253
|
const desc = TOOL_DESCRIPTIONS[t];
|
|
54120
54254
|
return desc ? `${t} (${desc})` : t;
|
|
@@ -54155,7 +54289,8 @@ function buildSlashCommandsList() {
|
|
|
54155
54289
|
"analyze",
|
|
54156
54290
|
"plan",
|
|
54157
54291
|
"sync-plan",
|
|
54158
|
-
"acknowledge-spec-drift"
|
|
54292
|
+
"acknowledge-spec-drift",
|
|
54293
|
+
"council"
|
|
54159
54294
|
],
|
|
54160
54295
|
"Execution Modes": ["turbo", "full-auto"],
|
|
54161
54296
|
Observation: [
|
|
@@ -54800,6 +54935,29 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
|
|
|
54800
54935
|
**Phase 6: QA GATE SELECTION (architect, dialogue only).**
|
|
54801
54936
|
{{QA_GATE_DIALOGUE_BRAINSTORM}}
|
|
54802
54937
|
|
|
54938
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
54939
|
+
GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
|
|
54940
|
+
\u2717 "I'll use the defaults \u2014 they're probably fine"
|
|
54941
|
+
\u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
|
|
54942
|
+
\u2717 "The user didn't mention gates, so defaults are fine"
|
|
54943
|
+
\u2192 WRONG: silence is not consent. The gate dialogue is not optional.
|
|
54944
|
+
\u2717 "I'll handle it in MODE: PLAN after the spec is done"
|
|
54945
|
+
\u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
|
|
54946
|
+
save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
|
|
54947
|
+
\u2717 "This feature is simple \u2014 gates are obvious"
|
|
54948
|
+
\u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
|
|
54949
|
+
\u2717 "I already know which gates are right for this project"
|
|
54950
|
+
\u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
|
|
54951
|
+
\u2717 "council_general_review is off by default, I don't need to mention it"
|
|
54952
|
+
\u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
|
|
54953
|
+
|
|
54954
|
+
MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
|
|
54955
|
+
You are BLOCKED until ALL THREE of these conditions are met:
|
|
54956
|
+
(1) The gate selection question has been presented to the user in a single message
|
|
54957
|
+
(2) The user has responded (accept defaults OR customized list)
|
|
54958
|
+
(3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
|
|
54959
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
54960
|
+
|
|
54803
54961
|
Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
|
|
54804
54962
|
\`\`\`
|
|
54805
54963
|
## Pending QA Gate Selection
|
|
@@ -54811,6 +54969,7 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
|
|
|
54811
54969
|
- council_mode: <true|false>
|
|
54812
54970
|
- hallucination_guard: <true|false>
|
|
54813
54971
|
- mutation_test: <true|false>
|
|
54972
|
+
- council_general_review: <true|false>
|
|
54814
54973
|
- recorded_at: <ISO timestamp>
|
|
54815
54974
|
\`\`\`
|
|
54816
54975
|
MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
|
|
@@ -54853,6 +55012,29 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
|
|
|
54853
55012
|
5b. **QA GATE SELECTION (dialogue only).**
|
|
54854
55013
|
{{QA_GATE_DIALOGUE_SPECIFY}}
|
|
54855
55014
|
|
|
55015
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55016
|
+
GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
|
|
55017
|
+
\u2717 "I'll use the defaults \u2014 they're probably fine"
|
|
55018
|
+
\u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
|
|
55019
|
+
\u2717 "The user didn't mention gates, so defaults are fine"
|
|
55020
|
+
\u2192 WRONG: silence is not consent. The gate dialogue is not optional.
|
|
55021
|
+
\u2717 "I'll handle it in MODE: PLAN after the spec is done"
|
|
55022
|
+
\u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
|
|
55023
|
+
save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
|
|
55024
|
+
\u2717 "This feature is simple \u2014 gates are obvious"
|
|
55025
|
+
\u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
|
|
55026
|
+
\u2717 "I already know which gates are right for this project"
|
|
55027
|
+
\u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
|
|
55028
|
+
\u2717 "council_general_review is off by default, I don't need to mention it"
|
|
55029
|
+
\u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
|
|
55030
|
+
|
|
55031
|
+
MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
|
|
55032
|
+
You are BLOCKED until ALL THREE of these conditions are met:
|
|
55033
|
+
(1) The gate selection question has been presented to the user in a single message
|
|
55034
|
+
(2) The user has responded (accept defaults OR customized list)
|
|
55035
|
+
(3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
|
|
55036
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55037
|
+
|
|
54856
55038
|
Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
|
|
54857
55039
|
\`\`\`
|
|
54858
55040
|
## Pending QA Gate Selection
|
|
@@ -54864,9 +55046,37 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
|
|
|
54864
55046
|
- council_mode: <true|false>
|
|
54865
55047
|
- hallucination_guard: <true|false>
|
|
54866
55048
|
- mutation_test: <true|false>
|
|
55049
|
+
- council_general_review: <true|false>
|
|
54867
55050
|
- recorded_at: <ISO timestamp>
|
|
54868
55051
|
\`\`\`
|
|
54869
55052
|
MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
|
|
55053
|
+
|
|
55054
|
+
5c. **SPECIFY-COUNCIL-REVIEW (fires ONLY when council_general_review gate is true).**
|
|
55055
|
+
Read the elected QA gates (parse the \`## Pending QA Gate Selection\` section from \`.swarm/context.md\` you just wrote, OR call \`get_qa_gate_profile\` if a profile already exists). If \`council_general_review\` is false or absent, skip directly to step 7.
|
|
55056
|
+
|
|
55057
|
+
If \`council_general_review\` is true:
|
|
55058
|
+
1. Read \`council.general\` config. If \`council.general.enabled\` is not true OR no search API key is configured, surface to the user: "council_general_review gate is enabled but the General Council is not configured. Set council.general.enabled: true and configure a search API key in opencode-swarm.json, or unset council_general_review and re-run." Then stop.
|
|
55059
|
+
2. Determine the council members from \`council.general.members\` (or \`council.general.presets[<name>]\` if you were invoked via \`/swarm council --preset <name>\` originally).
|
|
55060
|
+
3. Delegate to each council member in PARALLEL \u2014 one message per member, then STOP and wait. Pass: the spec text as the question, the member's role/persona, round number 1. Do NOT share other members' perspectives at this stage.
|
|
55061
|
+
4. Collect all member JSON responses.
|
|
55062
|
+
5. Call \`convene_general_council\` with mode: 'spec_review', the spec as question, and the collected \`round1Responses\`. Omit \`round2Responses\` \u2014 spec review is a single-pass advisory, not a full deliberation.
|
|
55063
|
+
6. Read \`consensusPoints\` \u2014 incorporate unambiguous consensus directly into the spec.
|
|
55064
|
+
7. Read \`disagreements\` \u2014 for each: (a) accept one position with rationale, (b) mark as \`[NEEDS CLARIFICATION]\` in the spec, or (c) schedule an SME consultation.
|
|
55065
|
+
8. If \`council.general.moderator\` is true, the tool returned a \`moderatorPrompt\` field. Delegate this prompt to \`{{AGENT_PREFIX}}council_moderator\`. Use the moderator's output to refine the spec further.
|
|
55066
|
+
9. Revise \`.swarm/spec.md\` to reflect the council input.
|
|
55067
|
+
|
|
55068
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55069
|
+
SPECIFY-COUNCIL-REVIEW RULES:
|
|
55070
|
+
\u2717 "council_general_review is off by default, I'll skip this"
|
|
55071
|
+
\u2192 CORRECT only when the gate is explicitly false or absent. Do NOT assume false. Read the actual gate value before deciding to skip.
|
|
55072
|
+
\u2717 "The spec is already good, no need to ask the council"
|
|
55073
|
+
\u2192 WRONG when gate is true: the user enabled this gate for a reason. Run it regardless.
|
|
55074
|
+
\u2717 "I'll include round2Responses for spec_review \u2014 more is better"
|
|
55075
|
+
\u2192 WRONG: spec review is a single advisory pass. Omit \`round2Responses\` for spec_review mode.
|
|
55076
|
+
\u2717 "I'll skip the moderator pass to save time"
|
|
55077
|
+
\u2192 WRONG when council.general.moderator is true: invoke \`{{AGENT_PREFIX}}council_moderator\` with the moderatorPrompt the tool returns.
|
|
55078
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55079
|
+
|
|
54870
55080
|
7. Report a summary to the user (MUST count, SHALL count, scenario count, clarification markers, elected QA gates) and suggest the next step: \`CLARIFY-SPEC\` (if markers exist) or \`PLAN\`.
|
|
54871
55081
|
|
|
54872
55082
|
SPEC CONTENT RULES \u2014 the spec MUST NOT contain:
|
|
@@ -55035,6 +55245,39 @@ This check fires automatically in:
|
|
|
55035
55245
|
|
|
55036
55246
|
GREENFIELD EXEMPTION: If the work is purely greenfield (new project, no existing codebase references), skip this check.
|
|
55037
55247
|
|
|
55248
|
+
### MODE: COUNCIL
|
|
55249
|
+
|
|
55250
|
+
Activates when: user invokes \`/swarm council <question>\` (optionally with \`--preset <name>\` or \`--spec-review\`).
|
|
55251
|
+
|
|
55252
|
+
Purpose: convene a configurable multi-model General Council for an advisory deliberation. Each member independently web-searches and answers; the architect routes any disagreements back for one targeted reconciliation round; an optional moderator pass synthesizes the final user-facing answer.
|
|
55253
|
+
|
|
55254
|
+
This mode is ADVISORY \u2014 it does NOT block any other workflow and does NOT modify code, plans, or specs. The output is for the user (general mode) or for the spec being drafted in MODE: SPECIFY (spec_review mode, gated by \`council_general_review\`).
|
|
55255
|
+
|
|
55256
|
+
#### Pre-flight (always run first)
|
|
55257
|
+
1. Read \`council.general\` config. If \`council.general.enabled\` is not true OR no search API key is configured (neither \`council.general.searchApiKey\` nor the corresponding env var \`TAVILY_API_KEY\` / \`BRAVE_SEARCH_API_KEY\`), surface to the user: "General Council is not enabled. Set council.general.enabled: true and configure a search API key in opencode-swarm.json." Then STOP.
|
|
55258
|
+
|
|
55259
|
+
#### Round 1 \u2014 Parallel Independent Search
|
|
55260
|
+
2. Determine council members. Default: \`council.general.members\`. If invoked with \`--preset <name>\`: \`council.general.presets[<name>]\`. If a named preset is missing, surface a clear error and stop.
|
|
55261
|
+
3. Delegate to each council member in PARALLEL \u2014 one message per member, then STOP and wait for all responses to come back. Pass: the question, the member's role/persona, round number 1. Do NOT share other members' responses at this stage.
|
|
55262
|
+
4. Collect all member JSON responses (each member returns a fenced JSON block per the council_member prompt).
|
|
55263
|
+
|
|
55264
|
+
#### Synthesis and Deliberation (when council.general.deliberate is true; default true)
|
|
55265
|
+
5. Call \`convene_general_council\` with mode set from the command (\`general\` or \`spec_review\`), \`question\`, and the collected \`round1Responses\` only (omit \`round2Responses\`). Inspect the returned \`disagreementsCount\`.
|
|
55266
|
+
6. If \`disagreementsCount > 0\`:
|
|
55267
|
+
a. For each disagreement in the tool's response, identify the disputing members (the members listed in the disagreement's positions).
|
|
55268
|
+
b. Re-delegate ONLY to the disputing members \u2014 one message per member \u2014 passing: their Round 1 response, the disagreement topic, the opposing position(s), round number 2.
|
|
55269
|
+
c. Collect the Round 2 responses.
|
|
55270
|
+
d. Call \`convene_general_council\` AGAIN with both \`round1Responses\` AND \`round2Responses\` populated.
|
|
55271
|
+
|
|
55272
|
+
#### Moderator Pass (when council.general.moderator is true; default true)
|
|
55273
|
+
7. The most recent \`convene_general_council\` call returned a \`moderatorPrompt\` field. Delegate this prompt to \`{{AGENT_PREFIX}}council_moderator\`. The moderator agent has no tools and no web access \u2014 it synthesizes a final user-facing answer from the council output you give it. Collect the moderator's markdown output.
|
|
55274
|
+
|
|
55275
|
+
#### Output
|
|
55276
|
+
8. Present the final answer to the user:
|
|
55277
|
+
- If the moderator pass ran: present the moderator's output verbatim, prefaced with the participating models (one line).
|
|
55278
|
+
- If no moderator: present the structural \`synthesis\` markdown from the tool's return.
|
|
55279
|
+
In either case, do NOT present the raw per-member JSON. Do NOT silently pick a winner among persisting disagreements \u2014 surface them honestly.
|
|
55280
|
+
|
|
55038
55281
|
### MODE: PLAN
|
|
55039
55282
|
|
|
55040
55283
|
SPEC GATE (soft \u2014 check before planning):
|
|
@@ -55113,7 +55356,18 @@ save_plan({
|
|
|
55113
55356
|
**POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
|
|
55114
55357
|
After \`save_plan\` succeeds, read \`.swarm/context.md\`:
|
|
55115
55358
|
- If a \`## Pending QA Gate Selection\` section exists: parse the gate values, call \`set_qa_gates\` with those flags, confirm with the user ("QA gates applied: <list>"), then remove the section from context.md.
|
|
55116
|
-
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
55359
|
+
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
55360
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55361
|
+
INLINE GATE SELECTION \u2014 no pending section found in context.md. You MUST ask now.
|
|
55362
|
+
\u2717 "I'll call set_qa_gates with defaults and move on"
|
|
55363
|
+
\u2192 WRONG: set_qa_gates with assumed values is a gate violation. The user must answer first.
|
|
55364
|
+
\u2717 "The user provided a plan \u2014 they know what gates they want"
|
|
55365
|
+
\u2192 WRONG: providing a plan is not the same as configuring gates. Always ask.
|
|
55366
|
+
|
|
55367
|
+
MANDATORY PAUSE: Present the gate question. Wait for the user's answer.
|
|
55368
|
+
Do NOT call \`set_qa_gates\` until the user has responded.
|
|
55369
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55370
|
+
Then call \`set_qa_gates\` with the user's chosen flags.
|
|
55117
55371
|
Either path must yield a persisted QA gate profile before the first task dispatches.
|
|
55118
55372
|
|
|
55119
55373
|
\u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
|
|
@@ -55702,6 +55956,189 @@ META.SUMMARY CONVENTION \u2014 When reporting task completion, include:
|
|
|
55702
55956
|
|
|
55703
55957
|
`;
|
|
55704
55958
|
|
|
55959
|
+
// src/agents/council-member.ts
|
|
55960
|
+
function createCouncilMemberAgent(model, customPrompt, customAppendPrompt) {
|
|
55961
|
+
let prompt = COUNCIL_MEMBER_PROMPT;
|
|
55962
|
+
if (customPrompt) {
|
|
55963
|
+
prompt = customPrompt;
|
|
55964
|
+
} else if (customAppendPrompt) {
|
|
55965
|
+
prompt = `${COUNCIL_MEMBER_PROMPT}
|
|
55966
|
+
|
|
55967
|
+
${customAppendPrompt}`;
|
|
55968
|
+
}
|
|
55969
|
+
return {
|
|
55970
|
+
name: "council_member",
|
|
55971
|
+
description: "General Council deliberation member. Independently web-searches and answers in Round 1; " + "targeted MAINTAIN/CONCEDE/NUANCE deliberation in Round 2. Tool-restricted to web_search only.",
|
|
55972
|
+
config: {
|
|
55973
|
+
model,
|
|
55974
|
+
temperature: 0.4,
|
|
55975
|
+
prompt,
|
|
55976
|
+
tools: {
|
|
55977
|
+
write: false,
|
|
55978
|
+
edit: false,
|
|
55979
|
+
patch: false
|
|
55980
|
+
}
|
|
55981
|
+
}
|
|
55982
|
+
};
|
|
55983
|
+
}
|
|
55984
|
+
var COUNCIL_MEMBER_PROMPT = `You are Council Member {{MEMBER_ID}} ({{ROLE}}) on a multi-model General Council.
|
|
55985
|
+
|
|
55986
|
+
{{PERSONA_BLOCK}}
|
|
55987
|
+
|
|
55988
|
+
You are participating in Round {{ROUND}} of a structured deliberation. Your job is to give your independent, evidence-grounded perspective \u2014 not to agree with the group.
|
|
55989
|
+
|
|
55990
|
+
================================================================
|
|
55991
|
+
ROUND {{ROUND}} PROTOCOL
|
|
55992
|
+
================================================================
|
|
55993
|
+
|
|
55994
|
+
ROUND 1 \u2014 Independent Research and Answer
|
|
55995
|
+
- Issue 1\u20133 targeted web_search calls to gather evidence relevant to the question.
|
|
55996
|
+
- Cite EVERY factual claim with a source URL from your search results.
|
|
55997
|
+
- State your confidence (0.0\u20131.0) explicitly. Be honest \u2014 overconfident answers hurt the council.
|
|
55998
|
+
- Enumerate areas of uncertainty so the architect knows where you're guessing vs. where you're sure.
|
|
55999
|
+
- Do NOT coordinate with other members. You will not see their responses until Round 2.
|
|
56000
|
+
- Do NOT pad. Be concise. Substance over volume.
|
|
56001
|
+
|
|
56002
|
+
ROUND 2 \u2014 Targeted Deliberation (ONLY when this round is invoked for you)
|
|
56003
|
+
- {{DISAGREEMENT_BLOCK}}
|
|
56004
|
+
- Issue at most 1 additional web_search call.
|
|
56005
|
+
- Declare your stance explicitly using one of these keywords as the FIRST word of a paragraph:
|
|
56006
|
+
MAINTAIN \u2014 your Round 1 position holds; cite the new evidence supporting it
|
|
56007
|
+
CONCEDE \u2014 the opposing position is correct; state specifically what you got wrong
|
|
56008
|
+
NUANCE \u2014 both positions are partially right; state the boundary condition that distinguishes them
|
|
56009
|
+
- Never CONCEDE without evidence. Sycophantic capitulation degrades the council below an individual member's baseline (NSED arXiv:2601.16863).
|
|
56010
|
+
- Never MAINTAIN without engaging the opposing argument on its merits.
|
|
56011
|
+
|
|
56012
|
+
================================================================
|
|
56013
|
+
RESPONSE FORMAT (always \u2014 both rounds)
|
|
56014
|
+
================================================================
|
|
56015
|
+
|
|
56016
|
+
Reply with a single fenced JSON block. No prose outside the block.
|
|
56017
|
+
|
|
56018
|
+
\`\`\`json
|
|
56019
|
+
{
|
|
56020
|
+
"memberId": "{{MEMBER_ID}}",
|
|
56021
|
+
"role": "{{ROLE}}",
|
|
56022
|
+
"round": {{ROUND}},
|
|
56023
|
+
"response": "Your full answer (Round 1) or stance + reasoning (Round 2). Markdown OK inside the string.",
|
|
56024
|
+
"searchQueries": ["query 1", "query 2"],
|
|
56025
|
+
"sources": [
|
|
56026
|
+
{ "title": "...", "url": "...", "snippet": "...", "query": "..." }
|
|
56027
|
+
],
|
|
56028
|
+
"confidence": 0.85,
|
|
56029
|
+
"areasOfUncertainty": [
|
|
56030
|
+
"What I'm not sure about, in plain language."
|
|
56031
|
+
],
|
|
56032
|
+
"disagreementTopics": []
|
|
56033
|
+
}
|
|
56034
|
+
\`\`\`
|
|
56035
|
+
|
|
56036
|
+
For Round 1: leave \`disagreementTopics\` as []. For Round 2: list the specific disagreement topics this response addresses.
|
|
56037
|
+
|
|
56038
|
+
================================================================
|
|
56039
|
+
HARD RULES
|
|
56040
|
+
================================================================
|
|
56041
|
+
- web_search is your ONLY tool. You cannot read or write files, run commands, or delegate.
|
|
56042
|
+
- Never invent sources. If a search returns nothing useful, say so in \`areasOfUncertainty\`.
|
|
56043
|
+
- Never echo other members' responses verbatim. Paraphrase or quote with attribution.
|
|
56044
|
+
- Stay within your role and persona. The architect chose you for a specific perspective.
|
|
56045
|
+
`;
|
|
56046
|
+
|
|
56047
|
+
// src/agents/council-moderator.ts
|
|
56048
|
+
function createCouncilModeratorAgent(model, customPrompt, customAppendPrompt) {
|
|
56049
|
+
let prompt = COUNCIL_MODERATOR_PROMPT;
|
|
56050
|
+
if (customPrompt) {
|
|
56051
|
+
prompt = customPrompt;
|
|
56052
|
+
} else if (customAppendPrompt) {
|
|
56053
|
+
prompt = `${COUNCIL_MODERATOR_PROMPT}
|
|
56054
|
+
|
|
56055
|
+
${customAppendPrompt}`;
|
|
56056
|
+
}
|
|
56057
|
+
return {
|
|
56058
|
+
name: "council_moderator",
|
|
56059
|
+
description: "General Council moderator. Synthesizes a coherent final answer from member " + "responses; no web search (works on already-gathered content).",
|
|
56060
|
+
config: {
|
|
56061
|
+
model,
|
|
56062
|
+
temperature: 0.3,
|
|
56063
|
+
prompt,
|
|
56064
|
+
tools: {
|
|
56065
|
+
write: false,
|
|
56066
|
+
edit: false,
|
|
56067
|
+
patch: false
|
|
56068
|
+
}
|
|
56069
|
+
}
|
|
56070
|
+
};
|
|
56071
|
+
}
|
|
56072
|
+
var COUNCIL_MODERATOR_PROMPT = `You are the General Council Moderator.
|
|
56073
|
+
|
|
56074
|
+
You are receiving the structural synthesis from a multi-model council deliberation:
|
|
56075
|
+
- Question (and mode: general or spec_review)
|
|
56076
|
+
- All member Round 1 responses with sources
|
|
56077
|
+
- Detected disagreements
|
|
56078
|
+
- Round 2 deliberation responses (if any)
|
|
56079
|
+
- Confidence-weighted consensus claims
|
|
56080
|
+
- Persisting disagreements after deliberation
|
|
56081
|
+
|
|
56082
|
+
Your job: produce a coherent, well-structured final answer for the user.
|
|
56083
|
+
|
|
56084
|
+
================================================================
|
|
56085
|
+
RULES
|
|
56086
|
+
================================================================
|
|
56087
|
+
|
|
56088
|
+
1. LEAD WITH CONSENSUS \u2014 open with the strongest consensus position. Use the
|
|
56089
|
+
confidence-weighted ordering (Quadratic Voting): higher-confidence claims
|
|
56090
|
+
from multiple members rank higher, but evidence quality outranks raw
|
|
56091
|
+
confidence. Never elevate a single confident voice over a well-evidenced
|
|
56092
|
+
contrary majority.
|
|
56093
|
+
|
|
56094
|
+
2. ACKNOWLEDGE DISAGREEMENT HONESTLY \u2014 for each persisting disagreement, write
|
|
56095
|
+
"experts disagree on X because\u2026" and present the strongest version of each
|
|
56096
|
+
side. Do NOT pretend disagreements are resolved when they are not. Do NOT
|
|
56097
|
+
silently pick a winner.
|
|
56098
|
+
|
|
56099
|
+
3. CITE THE STRONGEST SOURCES \u2014 link key claims with [title](url) format from
|
|
56100
|
+
the deduplicated source list. Pick the most reputable source for each claim;
|
|
56101
|
+
do not cite duplicates.
|
|
56102
|
+
|
|
56103
|
+
4. BE CONCISE \u2014 the user wants an answer, not a committee report. Default
|
|
56104
|
+
length: a few short paragraphs plus a bulleted summary. Expand only when
|
|
56105
|
+
the question genuinely requires it.
|
|
56106
|
+
|
|
56107
|
+
================================================================
|
|
56108
|
+
HARD CONSTRAINTS
|
|
56109
|
+
================================================================
|
|
56110
|
+
|
|
56111
|
+
- You MUST NOT invent claims that are not present in the council's responses.
|
|
56112
|
+
- You MUST NOT add new web research. If something was missed, say so.
|
|
56113
|
+
- You MUST NOT favor a position based on member confidence alone \u2014 evidence
|
|
56114
|
+
quality is the tie-breaker.
|
|
56115
|
+
- You have NO tools. You write the final synthesis from the input given.
|
|
56116
|
+
|
|
56117
|
+
================================================================
|
|
56118
|
+
OUTPUT FORMAT
|
|
56119
|
+
================================================================
|
|
56120
|
+
|
|
56121
|
+
Plain markdown. No code fences. No JSON. Suggested structure:
|
|
56122
|
+
|
|
56123
|
+
# Answer
|
|
56124
|
+
|
|
56125
|
+
<lead consensus position with citation(s)>
|
|
56126
|
+
|
|
56127
|
+
<remaining consensus / context paragraphs as needed>
|
|
56128
|
+
|
|
56129
|
+
## Where Experts Disagree
|
|
56130
|
+
|
|
56131
|
+
- <topic 1>: <position A> vs <position B>, with sources for each
|
|
56132
|
+
- <topic 2>: ...
|
|
56133
|
+
|
|
56134
|
+
## Sources
|
|
56135
|
+
|
|
56136
|
+
- [title](url)
|
|
56137
|
+
- ...
|
|
56138
|
+
|
|
56139
|
+
(Omit any section that is empty.)
|
|
56140
|
+
`;
|
|
56141
|
+
|
|
55705
56142
|
// src/agents/critic.ts
|
|
55706
56143
|
function parseSoundingBoardResponse(raw) {
|
|
55707
56144
|
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
@@ -55841,6 +56278,18 @@ EXECUTION PROFILE CHECK (when plan includes execution_profile):
|
|
|
55841
56278
|
- Task Atomicity: Does any single task touch 2+ files or mix unrelated concerns ("implement auth and add logging and refactor config")? Flag as MAJOR \u2014 oversized tasks blow coder's context and cause downstream gate failures. Suggested fix: Split into sequential single-file tasks grouped by concern, not per-file subtasks.
|
|
55842
56279
|
- Governance Compliance (conditional): If \`.swarm/context.md\` contains a \`## Project Governance\` section, read the MUST and SHOULD rules and validate the plan against them. MUST rule violations are CRITICAL severity. SHOULD rule violations are recommendation-level (note them but do not block approval). If no \`## Project Governance\` section exists in context.md, skip this check silently.
|
|
55843
56280
|
|
|
56281
|
+
## BASELINE COMPARISON (mandatory before plan review)
|
|
56282
|
+
|
|
56283
|
+
Before reviewing the plan, check whether it was silently mutated since last critic approval.
|
|
56284
|
+
|
|
56285
|
+
1. Call the \`get_approved_plan\` tool (no arguments required \u2014 it derives identity internally).
|
|
56286
|
+
2. Examine the response:
|
|
56287
|
+
- If \`success: false\` with \`reason: "no_approved_snapshot"\`: this is the first plan or no prior approval exists. Note this and proceed with plan review.
|
|
56288
|
+
- If \`drift_detected: false\`: baseline integrity confirmed \u2014 the plan has not been mutated since the last critic approval. Proceed with plan review.
|
|
56289
|
+
- If \`drift_detected: true\`: CRITICAL finding \u2014 plan mutated after approval. Compare \`approved_plan\` vs \`current_plan\` to identify what changed (phases added/removed, tasks modified, scope changes). Report findings in a \`## BASELINE DRIFT\` section before the rubric assessment.
|
|
56290
|
+
- If \`drift_detected: "unknown"\`: flag as warning and proceed with caution.
|
|
56291
|
+
3. Report spec-intent divergence: compare the approved baseline intent against what the current plan actually does, not just structural diff. Identify if the plan's purpose or scope has drifted from the original approved intent.
|
|
56292
|
+
|
|
55844
56293
|
## PLAN ASSESSMENT DIMENSIONS
|
|
55845
56294
|
Evaluate ALL seven dimensions. Report any that fail:
|
|
55846
56295
|
1. TASK ATOMICITY: Can each task be completed and QA'd independently?
|
|
@@ -57334,6 +57783,19 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
|
|
|
57334
57783
|
testEngineer.name = prefixName("test_engineer");
|
|
57335
57784
|
agents.push(applyOverrides(testEngineer, swarmAgents, swarmPrefix));
|
|
57336
57785
|
}
|
|
57786
|
+
if (pluginConfig?.council?.general?.enabled === true && !isAgentDisabled("council_member", swarmAgents, swarmPrefix)) {
|
|
57787
|
+
const councilMemberPrompts = getPrompts("council_member");
|
|
57788
|
+
const councilMember = createCouncilMemberAgent(getModel("council_member"), councilMemberPrompts.prompt, councilMemberPrompts.appendPrompt);
|
|
57789
|
+
councilMember.name = prefixName("council_member");
|
|
57790
|
+
agents.push(applyOverrides(councilMember, swarmAgents, swarmPrefix));
|
|
57791
|
+
}
|
|
57792
|
+
if (pluginConfig?.council?.general?.enabled === true && pluginConfig?.council?.general?.moderator === true && !isAgentDisabled("council_moderator", swarmAgents, swarmPrefix)) {
|
|
57793
|
+
const moderatorPrompts = getPrompts("council_moderator");
|
|
57794
|
+
const moderatorModel = pluginConfig?.council?.general?.moderatorModel ?? getModel("council_moderator");
|
|
57795
|
+
const councilModerator = createCouncilModeratorAgent(moderatorModel, moderatorPrompts.prompt, moderatorPrompts.appendPrompt);
|
|
57796
|
+
councilModerator.name = prefixName("council_moderator");
|
|
57797
|
+
agents.push(applyOverrides(councilModerator, swarmAgents, swarmPrefix));
|
|
57798
|
+
}
|
|
57337
57799
|
if (!isAgentDisabled("docs", swarmAgents, swarmPrefix)) {
|
|
57338
57800
|
const docsPrompts = getPrompts("docs");
|
|
57339
57801
|
const docs = createDocsAgent(getModel("docs"), docsPrompts.prompt, docsPrompts.appendPrompt);
|
|
@@ -62346,7 +62808,7 @@ var init_curator_drift = __esm(() => {
|
|
|
62346
62808
|
|
|
62347
62809
|
// src/index.ts
|
|
62348
62810
|
init_agents();
|
|
62349
|
-
import * as
|
|
62811
|
+
import * as path103 from "path";
|
|
62350
62812
|
|
|
62351
62813
|
// src/background/index.ts
|
|
62352
62814
|
init_event_bus();
|
|
@@ -62645,6 +63107,7 @@ init_benchmark();
|
|
|
62645
63107
|
init_checkpoint2();
|
|
62646
63108
|
init_close();
|
|
62647
63109
|
init_config2();
|
|
63110
|
+
init_council();
|
|
62648
63111
|
init_curate();
|
|
62649
63112
|
init_dark_matter();
|
|
62650
63113
|
init_diagnose();
|
|
@@ -74003,6 +74466,496 @@ var convene_council = createSwarmTool({
|
|
|
74003
74466
|
}, null, 2);
|
|
74004
74467
|
}
|
|
74005
74468
|
});
|
|
74469
|
+
// src/tools/convene-general-council.ts
|
|
74470
|
+
init_dist();
|
|
74471
|
+
init_zod();
|
|
74472
|
+
init_loader();
|
|
74473
|
+
import * as fs59 from "fs";
|
|
74474
|
+
import * as path73 from "path";
|
|
74475
|
+
|
|
74476
|
+
// src/council/general-council-advisory.ts
|
|
74477
|
+
var ADVISORY_HEADER = "[general_council] (advisory; not blocking)";
|
|
74478
|
+
function pushGeneralCouncilAdvisory(session, result) {
|
|
74479
|
+
if (!session)
|
|
74480
|
+
return;
|
|
74481
|
+
const body2 = renderAdvisoryBody(result);
|
|
74482
|
+
if (!body2)
|
|
74483
|
+
return;
|
|
74484
|
+
session.pendingAdvisoryMessages ??= [];
|
|
74485
|
+
session.pendingAdvisoryMessages.push(`${ADVISORY_HEADER}
|
|
74486
|
+
${body2}`);
|
|
74487
|
+
}
|
|
74488
|
+
function renderAdvisoryBody(result) {
|
|
74489
|
+
const parts2 = [result.synthesis];
|
|
74490
|
+
if (result.moderatorOutput && result.moderatorOutput.trim().length > 0) {
|
|
74491
|
+
parts2.push("", "### Moderator Output", result.moderatorOutput);
|
|
74492
|
+
}
|
|
74493
|
+
return parts2.join(`
|
|
74494
|
+
`).trim();
|
|
74495
|
+
}
|
|
74496
|
+
|
|
74497
|
+
// src/council/disagreement-detector.ts
|
|
74498
|
+
var MAX_DISAGREEMENTS = 10;
|
|
74499
|
+
var EXPLICIT_DISAGREEMENT_MARKERS = [
|
|
74500
|
+
"i disagree with",
|
|
74501
|
+
"i would push back on",
|
|
74502
|
+
"contrary to",
|
|
74503
|
+
"this contradicts",
|
|
74504
|
+
"unlike "
|
|
74505
|
+
];
|
|
74506
|
+
var STRONG_RECOMMENDATION_MARKERS = [
|
|
74507
|
+
"recommend",
|
|
74508
|
+
"best approach",
|
|
74509
|
+
"should use",
|
|
74510
|
+
"i suggest",
|
|
74511
|
+
"the answer is",
|
|
74512
|
+
"the right choice is"
|
|
74513
|
+
];
|
|
74514
|
+
function tokenize(text) {
|
|
74515
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
|
|
74516
|
+
}
|
|
74517
|
+
function termOverlap(a, b) {
|
|
74518
|
+
const tokensA = new Set(tokenize(a));
|
|
74519
|
+
const tokensB = new Set(tokenize(b));
|
|
74520
|
+
if (tokensA.size === 0 || tokensB.size === 0)
|
|
74521
|
+
return 0;
|
|
74522
|
+
let intersection3 = 0;
|
|
74523
|
+
for (const t of tokensA) {
|
|
74524
|
+
if (tokensB.has(t))
|
|
74525
|
+
intersection3++;
|
|
74526
|
+
}
|
|
74527
|
+
const union3 = tokensA.size + tokensB.size - intersection3;
|
|
74528
|
+
return union3 === 0 ? 0 : intersection3 / union3;
|
|
74529
|
+
}
|
|
74530
|
+
function extractMarkerSentence(response, markers) {
|
|
74531
|
+
const lower = response.toLowerCase();
|
|
74532
|
+
const sentences = response.split(/(?<=[.!?])\s+/);
|
|
74533
|
+
for (const sentence of sentences) {
|
|
74534
|
+
const sentLower = sentence.toLowerCase();
|
|
74535
|
+
if (markers.some((m) => sentLower.includes(m))) {
|
|
74536
|
+
return sentence.trim();
|
|
74537
|
+
}
|
|
74538
|
+
}
|
|
74539
|
+
for (const marker of markers) {
|
|
74540
|
+
const idx = lower.indexOf(marker);
|
|
74541
|
+
if (idx !== -1) {
|
|
74542
|
+
const slice = response.slice(idx, idx + 200);
|
|
74543
|
+
return slice.split(/\n/)[0]?.trim() ?? slice.trim();
|
|
74544
|
+
}
|
|
74545
|
+
}
|
|
74546
|
+
return null;
|
|
74547
|
+
}
|
|
74548
|
+
function dedupeByTopic(disagreements) {
|
|
74549
|
+
const result = [];
|
|
74550
|
+
for (const d of disagreements) {
|
|
74551
|
+
const topicLower = d.topic.toLowerCase();
|
|
74552
|
+
const existing = result.find((r) => r.topic.toLowerCase().includes(topicLower) || topicLower.includes(r.topic.toLowerCase()));
|
|
74553
|
+
if (existing) {
|
|
74554
|
+
for (const pos of d.positions) {
|
|
74555
|
+
if (!existing.positions.some((p) => p.memberId === pos.memberId)) {
|
|
74556
|
+
existing.positions.push(pos);
|
|
74557
|
+
}
|
|
74558
|
+
}
|
|
74559
|
+
} else {
|
|
74560
|
+
result.push(d);
|
|
74561
|
+
}
|
|
74562
|
+
}
|
|
74563
|
+
return result;
|
|
74564
|
+
}
|
|
74565
|
+
function detectExplicitMarkers(responses) {
|
|
74566
|
+
const out2 = [];
|
|
74567
|
+
for (const member of responses) {
|
|
74568
|
+
const markerSentence = extractMarkerSentence(member.response, EXPLICIT_DISAGREEMENT_MARKERS);
|
|
74569
|
+
if (!markerSentence)
|
|
74570
|
+
continue;
|
|
74571
|
+
const position = {
|
|
74572
|
+
memberId: member.memberId,
|
|
74573
|
+
claim: markerSentence,
|
|
74574
|
+
evidence: member.sources[0]?.url ?? "(no source cited in marker sentence)"
|
|
74575
|
+
};
|
|
74576
|
+
out2.push({
|
|
74577
|
+
topic: markerSentence.slice(0, 80),
|
|
74578
|
+
positions: [position]
|
|
74579
|
+
});
|
|
74580
|
+
}
|
|
74581
|
+
return out2;
|
|
74582
|
+
}
|
|
74583
|
+
function extractRecommendation(response) {
|
|
74584
|
+
return extractMarkerSentence(response, STRONG_RECOMMENDATION_MARKERS);
|
|
74585
|
+
}
|
|
74586
|
+
function detectClaimDivergence(responses) {
|
|
74587
|
+
const recommendations = [];
|
|
74588
|
+
for (const member of responses) {
|
|
74589
|
+
const rec = extractRecommendation(member.response);
|
|
74590
|
+
if (!rec)
|
|
74591
|
+
continue;
|
|
74592
|
+
recommendations.push({
|
|
74593
|
+
memberId: member.memberId,
|
|
74594
|
+
text: rec,
|
|
74595
|
+
evidence: member.sources[0]?.url ?? "(no source cited)"
|
|
74596
|
+
});
|
|
74597
|
+
}
|
|
74598
|
+
const out2 = [];
|
|
74599
|
+
for (let i2 = 0;i2 < recommendations.length; i2++) {
|
|
74600
|
+
for (let j = i2 + 1;j < recommendations.length; j++) {
|
|
74601
|
+
const a = recommendations[i2];
|
|
74602
|
+
const b = recommendations[j];
|
|
74603
|
+
if (!a || !b)
|
|
74604
|
+
continue;
|
|
74605
|
+
const topicOverlap = termOverlap(a.text, b.text);
|
|
74606
|
+
if (topicOverlap > 0.4)
|
|
74607
|
+
continue;
|
|
74608
|
+
if (topicOverlap > 0 && topicOverlap < 0.3) {
|
|
74609
|
+
const topic = `${a.text.slice(0, 50)} vs ${b.text.slice(0, 50)}`;
|
|
74610
|
+
out2.push({
|
|
74611
|
+
topic,
|
|
74612
|
+
positions: [
|
|
74613
|
+
{ memberId: a.memberId, claim: a.text, evidence: a.evidence },
|
|
74614
|
+
{ memberId: b.memberId, claim: b.text, evidence: b.evidence }
|
|
74615
|
+
]
|
|
74616
|
+
});
|
|
74617
|
+
}
|
|
74618
|
+
}
|
|
74619
|
+
}
|
|
74620
|
+
return out2;
|
|
74621
|
+
}
|
|
74622
|
+
function detectDisagreements(responses) {
|
|
74623
|
+
if (!Array.isArray(responses) || responses.length < 2)
|
|
74624
|
+
return [];
|
|
74625
|
+
const safeResponses = responses.filter((r) => typeof r?.memberId === "string" && typeof r?.response === "string");
|
|
74626
|
+
const explicit = detectExplicitMarkers(safeResponses);
|
|
74627
|
+
const divergent = detectClaimDivergence(safeResponses);
|
|
74628
|
+
const combined = [...explicit, ...divergent];
|
|
74629
|
+
const deduped = dedupeByTopic(combined);
|
|
74630
|
+
return deduped.slice(0, MAX_DISAGREEMENTS);
|
|
74631
|
+
}
|
|
74632
|
+
|
|
74633
|
+
// src/council/general-council-service.ts
|
|
74634
|
+
var CONSENSUS_WEIGHT_THRESHOLD = 0.6;
|
|
74635
|
+
function tokenize2(text) {
|
|
74636
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4);
|
|
74637
|
+
}
|
|
74638
|
+
function similarity(a, b) {
|
|
74639
|
+
const tokensA = new Set(tokenize2(a));
|
|
74640
|
+
const tokensB = new Set(tokenize2(b));
|
|
74641
|
+
if (tokensA.size === 0 || tokensB.size === 0)
|
|
74642
|
+
return 0;
|
|
74643
|
+
let intersection3 = 0;
|
|
74644
|
+
for (const t of tokensA)
|
|
74645
|
+
if (tokensB.has(t))
|
|
74646
|
+
intersection3++;
|
|
74647
|
+
const union3 = tokensA.size + tokensB.size - intersection3;
|
|
74648
|
+
return union3 === 0 ? 0 : intersection3 / union3;
|
|
74649
|
+
}
|
|
74650
|
+
function extractClaims(response) {
|
|
74651
|
+
return response.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length >= 30 && s.length <= 400);
|
|
74652
|
+
}
|
|
74653
|
+
function buildConsensusClusters(responses) {
|
|
74654
|
+
if (responses.length < 2)
|
|
74655
|
+
return [];
|
|
74656
|
+
const totalMembers = responses.length;
|
|
74657
|
+
const clusters = [];
|
|
74658
|
+
for (const member of responses) {
|
|
74659
|
+
const confidence = clamp01(member.confidence ?? 0.5);
|
|
74660
|
+
const claims = extractClaims(member.response ?? "");
|
|
74661
|
+
for (const claim of claims) {
|
|
74662
|
+
let assigned = false;
|
|
74663
|
+
for (const cluster of clusters) {
|
|
74664
|
+
if (similarity(cluster.representative, claim) >= 0.5) {
|
|
74665
|
+
if (!cluster.memberIds.has(member.memberId)) {
|
|
74666
|
+
cluster.weightedAgreement += confidence;
|
|
74667
|
+
cluster.memberIds.add(member.memberId);
|
|
74668
|
+
}
|
|
74669
|
+
if (claim.length > cluster.representative.length) {
|
|
74670
|
+
cluster.representative = claim;
|
|
74671
|
+
}
|
|
74672
|
+
assigned = true;
|
|
74673
|
+
break;
|
|
74674
|
+
}
|
|
74675
|
+
}
|
|
74676
|
+
if (!assigned) {
|
|
74677
|
+
clusters.push({
|
|
74678
|
+
representative: claim,
|
|
74679
|
+
weightedAgreement: confidence,
|
|
74680
|
+
memberIds: new Set([member.memberId])
|
|
74681
|
+
});
|
|
74682
|
+
}
|
|
74683
|
+
}
|
|
74684
|
+
}
|
|
74685
|
+
return clusters.filter((c) => c.memberIds.size >= 2 && c.weightedAgreement / totalMembers >= CONSENSUS_WEIGHT_THRESHOLD).sort((a, b) => b.weightedAgreement - a.weightedAgreement || b.memberIds.size - a.memberIds.size).map((c) => c.representative);
|
|
74686
|
+
}
|
|
74687
|
+
function clamp01(n) {
|
|
74688
|
+
if (typeof n !== "number" || Number.isNaN(n))
|
|
74689
|
+
return 0;
|
|
74690
|
+
if (n < 0)
|
|
74691
|
+
return 0;
|
|
74692
|
+
if (n > 1)
|
|
74693
|
+
return 1;
|
|
74694
|
+
return n;
|
|
74695
|
+
}
|
|
74696
|
+
function computePersistingDisagreements(disagreements, round2) {
|
|
74697
|
+
if (disagreements.length === 0)
|
|
74698
|
+
return [];
|
|
74699
|
+
if (round2.length === 0)
|
|
74700
|
+
return disagreements;
|
|
74701
|
+
return disagreements.filter((d) => {
|
|
74702
|
+
const disputants = new Set(d.positions.map((p) => p.memberId));
|
|
74703
|
+
const conceded = round2.some((r) => {
|
|
74704
|
+
if (!disputants.has(r.memberId))
|
|
74705
|
+
return false;
|
|
74706
|
+
if (!r.disagreementTopics?.includes(d.topic))
|
|
74707
|
+
return false;
|
|
74708
|
+
return /\bconcede\b/i.test(r.response ?? "");
|
|
74709
|
+
});
|
|
74710
|
+
return !conceded;
|
|
74711
|
+
});
|
|
74712
|
+
}
|
|
74713
|
+
function dedupeSources(round1, round2) {
|
|
74714
|
+
const seen = new Set;
|
|
74715
|
+
const out2 = [];
|
|
74716
|
+
const allSources = [...round1, ...round2].flatMap((r) => r.sources ?? []);
|
|
74717
|
+
for (const src of allSources) {
|
|
74718
|
+
if (!src?.url)
|
|
74719
|
+
continue;
|
|
74720
|
+
if (seen.has(src.url))
|
|
74721
|
+
continue;
|
|
74722
|
+
seen.add(src.url);
|
|
74723
|
+
out2.push(src);
|
|
74724
|
+
}
|
|
74725
|
+
return out2;
|
|
74726
|
+
}
|
|
74727
|
+
function renderSynthesisMarkdown(question, mode, roundsCompleted, members, consensusPoints, persistingDisagreements, allSources) {
|
|
74728
|
+
const memberLines = members.map((m) => `- ${m.memberId} (${m.model}, ${m.role})`).join(`
|
|
74729
|
+
`);
|
|
74730
|
+
const consensusBlock = consensusPoints.length > 0 ? consensusPoints.map((c) => `- ${c}`).join(`
|
|
74731
|
+
`) : "_No consensus claims reached the weighted-agreement threshold._";
|
|
74732
|
+
const disagreementsBlock = persistingDisagreements.length > 0 ? persistingDisagreements.map((d) => `- **${d.topic}**
|
|
74733
|
+
` + d.positions.map((p) => ` - ${p.memberId}: ${p.claim}`).join(`
|
|
74734
|
+
`)).join(`
|
|
74735
|
+
`) : "_No persisting disagreements after deliberation._";
|
|
74736
|
+
const sourcesBlock = allSources.length > 0 ? allSources.map((s) => `- [${s.title || s.url}](${s.url})`).join(`
|
|
74737
|
+
`) : "_No sources cited._";
|
|
74738
|
+
return [
|
|
74739
|
+
"## General Council Synthesis",
|
|
74740
|
+
"",
|
|
74741
|
+
`**Question:** ${question}`,
|
|
74742
|
+
`**Mode:** ${mode}`,
|
|
74743
|
+
`**Members:**
|
|
74744
|
+
${memberLines}`,
|
|
74745
|
+
`**Rounds:** ${roundsCompleted}`,
|
|
74746
|
+
"",
|
|
74747
|
+
"### Consensus",
|
|
74748
|
+
consensusBlock,
|
|
74749
|
+
"",
|
|
74750
|
+
"### Persistent Disagreements",
|
|
74751
|
+
disagreementsBlock,
|
|
74752
|
+
"",
|
|
74753
|
+
"### Sources",
|
|
74754
|
+
sourcesBlock
|
|
74755
|
+
].join(`
|
|
74756
|
+
`);
|
|
74757
|
+
}
|
|
74758
|
+
function synthesizeGeneralCouncil(question, mode, round1Responses, round2Responses) {
|
|
74759
|
+
const safeRound1 = Array.isArray(round1Responses) ? round1Responses : [];
|
|
74760
|
+
const safeRound2 = Array.isArray(round2Responses) ? round2Responses : [];
|
|
74761
|
+
const disagreements = detectDisagreements(safeRound1);
|
|
74762
|
+
const consensusPoints = buildConsensusClusters(safeRound1);
|
|
74763
|
+
const persistingDisagreements = computePersistingDisagreements(disagreements, safeRound2);
|
|
74764
|
+
const allSources = dedupeSources(safeRound1, safeRound2);
|
|
74765
|
+
const roundsCompleted = safeRound2.length > 0 ? 2 : 1;
|
|
74766
|
+
const synthesis = renderSynthesisMarkdown(question, mode, roundsCompleted, safeRound1, consensusPoints, persistingDisagreements, allSources);
|
|
74767
|
+
return {
|
|
74768
|
+
question,
|
|
74769
|
+
mode,
|
|
74770
|
+
round1Responses: safeRound1,
|
|
74771
|
+
disagreements,
|
|
74772
|
+
round2Responses: safeRound2,
|
|
74773
|
+
synthesis,
|
|
74774
|
+
consensusPoints,
|
|
74775
|
+
persistingDisagreements: persistingDisagreements.map((d) => d.topic),
|
|
74776
|
+
allSources,
|
|
74777
|
+
timestamp: new Date().toISOString()
|
|
74778
|
+
};
|
|
74779
|
+
}
|
|
74780
|
+
|
|
74781
|
+
// src/tools/convene-general-council.ts
|
|
74782
|
+
init_state();
|
|
74783
|
+
init_create_tool();
|
|
74784
|
+
init_resolve_working_directory();
|
|
74785
|
+
var WebSearchResultSchema = exports_external.object({
|
|
74786
|
+
title: exports_external.string(),
|
|
74787
|
+
url: exports_external.string(),
|
|
74788
|
+
snippet: exports_external.string(),
|
|
74789
|
+
query: exports_external.string()
|
|
74790
|
+
});
|
|
74791
|
+
var MemberRoleEnum = exports_external.enum([
|
|
74792
|
+
"generalist",
|
|
74793
|
+
"skeptic",
|
|
74794
|
+
"domain_expert",
|
|
74795
|
+
"devil_advocate",
|
|
74796
|
+
"synthesizer"
|
|
74797
|
+
]);
|
|
74798
|
+
var Round1ResponseSchema = exports_external.object({
|
|
74799
|
+
memberId: exports_external.string().min(1),
|
|
74800
|
+
model: exports_external.string().min(1),
|
|
74801
|
+
role: MemberRoleEnum,
|
|
74802
|
+
response: exports_external.string(),
|
|
74803
|
+
sources: exports_external.array(WebSearchResultSchema).default([]),
|
|
74804
|
+
searchQueries: exports_external.array(exports_external.string()).default([]),
|
|
74805
|
+
confidence: exports_external.number().min(0).max(1),
|
|
74806
|
+
areasOfUncertainty: exports_external.array(exports_external.string()).default([]),
|
|
74807
|
+
durationMs: exports_external.number().nonnegative().default(0)
|
|
74808
|
+
});
|
|
74809
|
+
var Round2ResponseSchema = Round1ResponseSchema.extend({
|
|
74810
|
+
disagreementTopics: exports_external.array(exports_external.string()).default([])
|
|
74811
|
+
});
|
|
74812
|
+
var ArgsSchema2 = exports_external.object({
|
|
74813
|
+
question: exports_external.string().min(1).max(8000),
|
|
74814
|
+
mode: exports_external.enum(["general", "spec_review"]).default("general"),
|
|
74815
|
+
members: exports_external.array(exports_external.string()).default([]),
|
|
74816
|
+
round1Responses: exports_external.array(Round1ResponseSchema).min(1),
|
|
74817
|
+
round2Responses: exports_external.array(Round2ResponseSchema).optional(),
|
|
74818
|
+
working_directory: exports_external.string().optional()
|
|
74819
|
+
});
|
|
74820
|
+
function buildModeratorPrompt(question, synthesis) {
|
|
74821
|
+
return [
|
|
74822
|
+
"A multi-model council has deliberated on the following question. Your job is to synthesize",
|
|
74823
|
+
"the council output into a single coherent answer for the user, following the rules in your",
|
|
74824
|
+
"system prompt (lead with consensus, acknowledge persisting disagreement honestly, cite the",
|
|
74825
|
+
"strongest sources, be concise, do not invent claims, do not run new searches).",
|
|
74826
|
+
"",
|
|
74827
|
+
`QUESTION:
|
|
74828
|
+
${question}`,
|
|
74829
|
+
"",
|
|
74830
|
+
"COUNCIL OUTPUT:",
|
|
74831
|
+
synthesis
|
|
74832
|
+
].join(`
|
|
74833
|
+
`);
|
|
74834
|
+
}
|
|
74835
|
+
var convene_general_council = createSwarmTool({
|
|
74836
|
+
description: "Synthesize responses from a multi-model General Council. Accepts parallel member " + "responses (Round 1, optionally Round 2), detects disagreements, and returns " + "consensus points, persisting disagreements, a structured synthesis, and an optional " + "moderator prompt. Architect-only. Config-gated on council.general.enabled.",
|
|
74837
|
+
args: {
|
|
74838
|
+
question: tool.schema.string().min(1).max(8000).describe("The question put to the council, or the spec text to review."),
|
|
74839
|
+
mode: tool.schema.enum(["general", "spec_review"]).optional().describe('"general" for /swarm council; "spec_review" for SPECIFY-COUNCIL-REVIEW gate.'),
|
|
74840
|
+
members: tool.schema.array(tool.schema.string()).optional().describe("Optional list of member IDs convened (for evidence/audit)."),
|
|
74841
|
+
round1Responses: tool.schema.array(tool.schema.object({
|
|
74842
|
+
memberId: tool.schema.string().min(1),
|
|
74843
|
+
model: tool.schema.string().min(1),
|
|
74844
|
+
role: tool.schema.enum([
|
|
74845
|
+
"generalist",
|
|
74846
|
+
"skeptic",
|
|
74847
|
+
"domain_expert",
|
|
74848
|
+
"devil_advocate",
|
|
74849
|
+
"synthesizer"
|
|
74850
|
+
]),
|
|
74851
|
+
response: tool.schema.string(),
|
|
74852
|
+
sources: tool.schema.array(tool.schema.object({
|
|
74853
|
+
title: tool.schema.string(),
|
|
74854
|
+
url: tool.schema.string(),
|
|
74855
|
+
snippet: tool.schema.string(),
|
|
74856
|
+
query: tool.schema.string()
|
|
74857
|
+
})).optional(),
|
|
74858
|
+
searchQueries: tool.schema.array(tool.schema.string()).optional(),
|
|
74859
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
74860
|
+
areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
|
|
74861
|
+
durationMs: tool.schema.number().nonnegative().optional()
|
|
74862
|
+
})).describe("Round 1 member responses (one per council member)."),
|
|
74863
|
+
round2Responses: tool.schema.array(tool.schema.object({
|
|
74864
|
+
memberId: tool.schema.string().min(1),
|
|
74865
|
+
model: tool.schema.string().min(1),
|
|
74866
|
+
role: tool.schema.enum([
|
|
74867
|
+
"generalist",
|
|
74868
|
+
"skeptic",
|
|
74869
|
+
"domain_expert",
|
|
74870
|
+
"devil_advocate",
|
|
74871
|
+
"synthesizer"
|
|
74872
|
+
]),
|
|
74873
|
+
response: tool.schema.string(),
|
|
74874
|
+
sources: tool.schema.array(tool.schema.object({
|
|
74875
|
+
title: tool.schema.string(),
|
|
74876
|
+
url: tool.schema.string(),
|
|
74877
|
+
snippet: tool.schema.string(),
|
|
74878
|
+
query: tool.schema.string()
|
|
74879
|
+
})).optional(),
|
|
74880
|
+
searchQueries: tool.schema.array(tool.schema.string()).optional(),
|
|
74881
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
74882
|
+
areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
|
|
74883
|
+
durationMs: tool.schema.number().nonnegative().optional(),
|
|
74884
|
+
disagreementTopics: tool.schema.array(tool.schema.string()).optional()
|
|
74885
|
+
})).optional().describe("Round 2 deliberation responses (omit when no deliberation has occurred)."),
|
|
74886
|
+
working_directory: tool.schema.string().optional().describe("Project root for config + evidence path resolution.")
|
|
74887
|
+
},
|
|
74888
|
+
execute: async (args2, directory, ctx) => {
|
|
74889
|
+
const parsed = ArgsSchema2.safeParse(args2);
|
|
74890
|
+
if (!parsed.success) {
|
|
74891
|
+
const fail = {
|
|
74892
|
+
success: false,
|
|
74893
|
+
reason: "invalid_args",
|
|
74894
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
74895
|
+
};
|
|
74896
|
+
return JSON.stringify(fail, null, 2);
|
|
74897
|
+
}
|
|
74898
|
+
const input = parsed.data;
|
|
74899
|
+
const dirResult = resolveWorkingDirectory(input.working_directory, directory);
|
|
74900
|
+
if (!dirResult.success) {
|
|
74901
|
+
const fail = {
|
|
74902
|
+
success: false,
|
|
74903
|
+
reason: "invalid_working_directory",
|
|
74904
|
+
message: dirResult.message
|
|
74905
|
+
};
|
|
74906
|
+
return JSON.stringify(fail, null, 2);
|
|
74907
|
+
}
|
|
74908
|
+
const workingDir = dirResult.directory;
|
|
74909
|
+
const config3 = loadPluginConfig(workingDir);
|
|
74910
|
+
const generalConfig = config3.council?.general;
|
|
74911
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
74912
|
+
const fail = {
|
|
74913
|
+
success: false,
|
|
74914
|
+
reason: "council_general_disabled",
|
|
74915
|
+
message: "convene_general_council requires council.general.enabled: true in opencode-swarm.json."
|
|
74916
|
+
};
|
|
74917
|
+
return JSON.stringify(fail, null, 2);
|
|
74918
|
+
}
|
|
74919
|
+
const round1 = input.round1Responses;
|
|
74920
|
+
const round2 = input.round2Responses ?? [];
|
|
74921
|
+
const result = synthesizeGeneralCouncil(input.question, input.mode, round1, round2);
|
|
74922
|
+
const evidenceDir = path73.join(workingDir, ".swarm", "council", "general");
|
|
74923
|
+
const safeTimestamp = result.timestamp.replace(/[:.]/g, "-");
|
|
74924
|
+
const evidenceFile = `${safeTimestamp}-${input.mode}.json`;
|
|
74925
|
+
const evidencePath = path73.join(evidenceDir, evidenceFile);
|
|
74926
|
+
try {
|
|
74927
|
+
await fs59.promises.mkdir(evidenceDir, { recursive: true });
|
|
74928
|
+
await fs59.promises.writeFile(evidencePath, JSON.stringify(result, null, 2));
|
|
74929
|
+
} catch (err2) {
|
|
74930
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
74931
|
+
console.warn(`[convene_general_council] Failed to write evidence to ${evidencePath}: ${message}`);
|
|
74932
|
+
}
|
|
74933
|
+
try {
|
|
74934
|
+
const sessionID = ctx?.sessionID;
|
|
74935
|
+
if (sessionID) {
|
|
74936
|
+
const session = getAgentSession(sessionID);
|
|
74937
|
+
if (session) {
|
|
74938
|
+
pushGeneralCouncilAdvisory(session, result);
|
|
74939
|
+
}
|
|
74940
|
+
}
|
|
74941
|
+
} catch {}
|
|
74942
|
+
const moderatorPrompt = generalConfig.moderator === true ? buildModeratorPrompt(input.question, result.synthesis) : undefined;
|
|
74943
|
+
const ok = {
|
|
74944
|
+
success: true,
|
|
74945
|
+
question: input.question,
|
|
74946
|
+
mode: input.mode,
|
|
74947
|
+
roundsCompleted: round2.length > 0 ? 2 : 1,
|
|
74948
|
+
consensusPoints: result.consensusPoints,
|
|
74949
|
+
disagreementsCount: result.disagreements.length,
|
|
74950
|
+
persistingDisagreements: result.persistingDisagreements,
|
|
74951
|
+
allSourcesCount: result.allSources.length,
|
|
74952
|
+
synthesis: result.synthesis,
|
|
74953
|
+
...moderatorPrompt !== undefined && { moderatorPrompt },
|
|
74954
|
+
evidencePath
|
|
74955
|
+
};
|
|
74956
|
+
return JSON.stringify(ok, null, 2);
|
|
74957
|
+
}
|
|
74958
|
+
});
|
|
74006
74959
|
// src/tools/curator-analyze.ts
|
|
74007
74960
|
init_dist();
|
|
74008
74961
|
init_config();
|
|
@@ -74132,7 +75085,7 @@ var CriteriaItemSchema = exports_external.object({
|
|
|
74132
75085
|
description: exports_external.string().min(10).max(500),
|
|
74133
75086
|
mandatory: exports_external.boolean()
|
|
74134
75087
|
});
|
|
74135
|
-
var
|
|
75088
|
+
var ArgsSchema3 = exports_external.object({
|
|
74136
75089
|
taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format"),
|
|
74137
75090
|
criteria: exports_external.array(CriteriaItemSchema).min(1).max(20),
|
|
74138
75091
|
working_directory: exports_external.string().optional()
|
|
@@ -74149,7 +75102,7 @@ var declare_council_criteria = createSwarmTool({
|
|
|
74149
75102
|
working_directory: tool.schema.string().optional().describe("Explicit project root directory. When provided, .swarm/council/ is resolved relative to this path instead of the plugin context directory.")
|
|
74150
75103
|
},
|
|
74151
75104
|
async execute(args2, directory) {
|
|
74152
|
-
const parsed =
|
|
75105
|
+
const parsed = ArgsSchema3.safeParse(args2);
|
|
74153
75106
|
if (!parsed.success) {
|
|
74154
75107
|
return JSON.stringify({
|
|
74155
75108
|
success: false,
|
|
@@ -74210,8 +75163,8 @@ init_scope_persistence();
|
|
|
74210
75163
|
init_state();
|
|
74211
75164
|
init_task_id();
|
|
74212
75165
|
init_create_tool();
|
|
74213
|
-
import * as
|
|
74214
|
-
import * as
|
|
75166
|
+
import * as fs60 from "fs";
|
|
75167
|
+
import * as path74 from "path";
|
|
74215
75168
|
function validateTaskIdFormat2(taskId) {
|
|
74216
75169
|
return validateTaskIdFormat(taskId);
|
|
74217
75170
|
}
|
|
@@ -74285,8 +75238,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74285
75238
|
};
|
|
74286
75239
|
}
|
|
74287
75240
|
}
|
|
74288
|
-
normalizedDir =
|
|
74289
|
-
const pathParts = normalizedDir.split(
|
|
75241
|
+
normalizedDir = path74.normalize(args2.working_directory);
|
|
75242
|
+
const pathParts = normalizedDir.split(path74.sep);
|
|
74290
75243
|
if (pathParts.includes("..")) {
|
|
74291
75244
|
return {
|
|
74292
75245
|
success: false,
|
|
@@ -74296,11 +75249,11 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74296
75249
|
]
|
|
74297
75250
|
};
|
|
74298
75251
|
}
|
|
74299
|
-
const resolvedDir =
|
|
75252
|
+
const resolvedDir = path74.resolve(normalizedDir);
|
|
74300
75253
|
try {
|
|
74301
|
-
const realPath =
|
|
74302
|
-
const planPath2 =
|
|
74303
|
-
if (!
|
|
75254
|
+
const realPath = fs60.realpathSync(resolvedDir);
|
|
75255
|
+
const planPath2 = path74.join(realPath, ".swarm", "plan.json");
|
|
75256
|
+
if (!fs60.existsSync(planPath2)) {
|
|
74304
75257
|
return {
|
|
74305
75258
|
success: false,
|
|
74306
75259
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -74323,8 +75276,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74323
75276
|
console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
|
|
74324
75277
|
}
|
|
74325
75278
|
const directory = normalizedDir || fallbackDir;
|
|
74326
|
-
const planPath =
|
|
74327
|
-
if (!
|
|
75279
|
+
const planPath = path74.resolve(directory, ".swarm", "plan.json");
|
|
75280
|
+
if (!fs60.existsSync(planPath)) {
|
|
74328
75281
|
return {
|
|
74329
75282
|
success: false,
|
|
74330
75283
|
message: "No plan found",
|
|
@@ -74333,7 +75286,7 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74333
75286
|
}
|
|
74334
75287
|
let planContent;
|
|
74335
75288
|
try {
|
|
74336
|
-
planContent = JSON.parse(
|
|
75289
|
+
planContent = JSON.parse(fs60.readFileSync(planPath, "utf-8"));
|
|
74337
75290
|
} catch {
|
|
74338
75291
|
return {
|
|
74339
75292
|
success: false,
|
|
@@ -74363,8 +75316,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74363
75316
|
const normalizeErrors = [];
|
|
74364
75317
|
const dir = normalizedDir || fallbackDir || process.cwd();
|
|
74365
75318
|
const mergedFiles = rawMergedFiles.map((file3) => {
|
|
74366
|
-
if (
|
|
74367
|
-
const relativePath =
|
|
75319
|
+
if (path74.isAbsolute(file3)) {
|
|
75320
|
+
const relativePath = path74.relative(dir, file3).replace(/\\/g, "/");
|
|
74368
75321
|
if (relativePath.startsWith("..")) {
|
|
74369
75322
|
normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
|
|
74370
75323
|
return file3;
|
|
@@ -74424,8 +75377,8 @@ var declare_scope = createSwarmTool({
|
|
|
74424
75377
|
// src/tools/diff.ts
|
|
74425
75378
|
init_dist();
|
|
74426
75379
|
import * as child_process7 from "child_process";
|
|
74427
|
-
import * as
|
|
74428
|
-
import * as
|
|
75380
|
+
import * as fs61 from "fs";
|
|
75381
|
+
import * as path75 from "path";
|
|
74429
75382
|
init_create_tool();
|
|
74430
75383
|
var MAX_DIFF_LINES = 500;
|
|
74431
75384
|
var DIFF_TIMEOUT_MS = 30000;
|
|
@@ -74454,20 +75407,20 @@ function validateBase(base) {
|
|
|
74454
75407
|
function validatePaths(paths) {
|
|
74455
75408
|
if (!paths)
|
|
74456
75409
|
return null;
|
|
74457
|
-
for (const
|
|
74458
|
-
if (!
|
|
75410
|
+
for (const path76 of paths) {
|
|
75411
|
+
if (!path76 || path76.length === 0) {
|
|
74459
75412
|
return "empty path not allowed";
|
|
74460
75413
|
}
|
|
74461
|
-
if (
|
|
75414
|
+
if (path76.length > MAX_PATH_LENGTH) {
|
|
74462
75415
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
74463
75416
|
}
|
|
74464
|
-
if (SHELL_METACHARACTERS2.test(
|
|
75417
|
+
if (SHELL_METACHARACTERS2.test(path76)) {
|
|
74465
75418
|
return "path contains shell metacharacters";
|
|
74466
75419
|
}
|
|
74467
|
-
if (
|
|
75420
|
+
if (path76.startsWith("-")) {
|
|
74468
75421
|
return 'path cannot start with "-" (option-like arguments not allowed)';
|
|
74469
75422
|
}
|
|
74470
|
-
if (CONTROL_CHAR_PATTERN2.test(
|
|
75423
|
+
if (CONTROL_CHAR_PATTERN2.test(path76)) {
|
|
74471
75424
|
return "path contains control characters";
|
|
74472
75425
|
}
|
|
74473
75426
|
}
|
|
@@ -74573,8 +75526,8 @@ var diff = createSwarmTool({
|
|
|
74573
75526
|
if (parts2.length >= 3) {
|
|
74574
75527
|
const additions = parseInt(parts2[0], 10) || 0;
|
|
74575
75528
|
const deletions = parseInt(parts2[1], 10) || 0;
|
|
74576
|
-
const
|
|
74577
|
-
files.push({ path:
|
|
75529
|
+
const path76 = parts2[2];
|
|
75530
|
+
files.push({ path: path76, additions, deletions });
|
|
74578
75531
|
}
|
|
74579
75532
|
}
|
|
74580
75533
|
const contractChanges = [];
|
|
@@ -74614,7 +75567,7 @@ var diff = createSwarmTool({
|
|
|
74614
75567
|
} else if (base === "unstaged") {
|
|
74615
75568
|
const oldRef = `:${file3.path}`;
|
|
74616
75569
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
74617
|
-
newContent =
|
|
75570
|
+
newContent = fs61.readFileSync(path75.join(directory, file3.path), "utf-8");
|
|
74618
75571
|
} else {
|
|
74619
75572
|
const oldRef = `${base}:${file3.path}`;
|
|
74620
75573
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
@@ -74688,8 +75641,8 @@ var diff = createSwarmTool({
|
|
|
74688
75641
|
// src/tools/diff-summary.ts
|
|
74689
75642
|
init_dist();
|
|
74690
75643
|
import * as child_process8 from "child_process";
|
|
74691
|
-
import * as
|
|
74692
|
-
import * as
|
|
75644
|
+
import * as fs62 from "fs";
|
|
75645
|
+
import * as path76 from "path";
|
|
74693
75646
|
init_create_tool();
|
|
74694
75647
|
var diff_summary = createSwarmTool({
|
|
74695
75648
|
description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
|
|
@@ -74737,7 +75690,7 @@ var diff_summary = createSwarmTool({
|
|
|
74737
75690
|
}
|
|
74738
75691
|
try {
|
|
74739
75692
|
let oldContent;
|
|
74740
|
-
const newContent =
|
|
75693
|
+
const newContent = fs62.readFileSync(path76.join(workingDir, filePath), "utf-8");
|
|
74741
75694
|
if (fileExistsInHead) {
|
|
74742
75695
|
oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
|
|
74743
75696
|
encoding: "utf-8",
|
|
@@ -74964,8 +75917,8 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
74964
75917
|
init_dist();
|
|
74965
75918
|
init_create_tool();
|
|
74966
75919
|
init_path_security();
|
|
74967
|
-
import * as
|
|
74968
|
-
import * as
|
|
75920
|
+
import * as fs63 from "fs";
|
|
75921
|
+
import * as path77 from "path";
|
|
74969
75922
|
var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
|
|
74970
75923
|
var MAX_EVIDENCE_FILES = 1000;
|
|
74971
75924
|
var EVIDENCE_DIR3 = ".swarm/evidence";
|
|
@@ -74992,9 +75945,9 @@ function validateRequiredTypes(input) {
|
|
|
74992
75945
|
return null;
|
|
74993
75946
|
}
|
|
74994
75947
|
function isPathWithinSwarm2(filePath, cwd) {
|
|
74995
|
-
const normalizedCwd =
|
|
74996
|
-
const swarmPath =
|
|
74997
|
-
const normalizedPath =
|
|
75948
|
+
const normalizedCwd = path77.resolve(cwd);
|
|
75949
|
+
const swarmPath = path77.join(normalizedCwd, ".swarm");
|
|
75950
|
+
const normalizedPath = path77.resolve(filePath);
|
|
74998
75951
|
return normalizedPath.startsWith(swarmPath);
|
|
74999
75952
|
}
|
|
75000
75953
|
function parseCompletedTasks(planContent) {
|
|
@@ -75010,12 +75963,12 @@ function parseCompletedTasks(planContent) {
|
|
|
75010
75963
|
}
|
|
75011
75964
|
function readEvidenceFiles(evidenceDir, _cwd) {
|
|
75012
75965
|
const evidence = [];
|
|
75013
|
-
if (!
|
|
75966
|
+
if (!fs63.existsSync(evidenceDir) || !fs63.statSync(evidenceDir).isDirectory()) {
|
|
75014
75967
|
return evidence;
|
|
75015
75968
|
}
|
|
75016
75969
|
let files;
|
|
75017
75970
|
try {
|
|
75018
|
-
files =
|
|
75971
|
+
files = fs63.readdirSync(evidenceDir);
|
|
75019
75972
|
} catch {
|
|
75020
75973
|
return evidence;
|
|
75021
75974
|
}
|
|
@@ -75024,14 +75977,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75024
75977
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
75025
75978
|
continue;
|
|
75026
75979
|
}
|
|
75027
|
-
const filePath =
|
|
75980
|
+
const filePath = path77.join(evidenceDir, filename);
|
|
75028
75981
|
try {
|
|
75029
|
-
const resolvedPath =
|
|
75030
|
-
const evidenceDirResolved =
|
|
75982
|
+
const resolvedPath = path77.resolve(filePath);
|
|
75983
|
+
const evidenceDirResolved = path77.resolve(evidenceDir);
|
|
75031
75984
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
75032
75985
|
continue;
|
|
75033
75986
|
}
|
|
75034
|
-
const stat4 =
|
|
75987
|
+
const stat4 = fs63.lstatSync(filePath);
|
|
75035
75988
|
if (!stat4.isFile()) {
|
|
75036
75989
|
continue;
|
|
75037
75990
|
}
|
|
@@ -75040,7 +75993,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75040
75993
|
}
|
|
75041
75994
|
let fileStat;
|
|
75042
75995
|
try {
|
|
75043
|
-
fileStat =
|
|
75996
|
+
fileStat = fs63.statSync(filePath);
|
|
75044
75997
|
if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
|
|
75045
75998
|
continue;
|
|
75046
75999
|
}
|
|
@@ -75049,7 +76002,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75049
76002
|
}
|
|
75050
76003
|
let content;
|
|
75051
76004
|
try {
|
|
75052
|
-
content =
|
|
76005
|
+
content = fs63.readFileSync(filePath, "utf-8");
|
|
75053
76006
|
} catch {
|
|
75054
76007
|
continue;
|
|
75055
76008
|
}
|
|
@@ -75145,7 +76098,7 @@ var evidence_check = createSwarmTool({
|
|
|
75145
76098
|
return JSON.stringify(errorResult, null, 2);
|
|
75146
76099
|
}
|
|
75147
76100
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
|
|
75148
|
-
const planPath =
|
|
76101
|
+
const planPath = path77.join(cwd, PLAN_FILE);
|
|
75149
76102
|
if (!isPathWithinSwarm2(planPath, cwd)) {
|
|
75150
76103
|
const errorResult = {
|
|
75151
76104
|
error: "plan file path validation failed",
|
|
@@ -75159,7 +76112,7 @@ var evidence_check = createSwarmTool({
|
|
|
75159
76112
|
}
|
|
75160
76113
|
let planContent;
|
|
75161
76114
|
try {
|
|
75162
|
-
planContent =
|
|
76115
|
+
planContent = fs63.readFileSync(planPath, "utf-8");
|
|
75163
76116
|
} catch {
|
|
75164
76117
|
const result2 = {
|
|
75165
76118
|
message: "No completed tasks found in plan.",
|
|
@@ -75177,7 +76130,7 @@ var evidence_check = createSwarmTool({
|
|
|
75177
76130
|
};
|
|
75178
76131
|
return JSON.stringify(result2, null, 2);
|
|
75179
76132
|
}
|
|
75180
|
-
const evidenceDir =
|
|
76133
|
+
const evidenceDir = path77.join(cwd, EVIDENCE_DIR3);
|
|
75181
76134
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
75182
76135
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
75183
76136
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -75194,8 +76147,8 @@ var evidence_check = createSwarmTool({
|
|
|
75194
76147
|
// src/tools/file-extractor.ts
|
|
75195
76148
|
init_tool();
|
|
75196
76149
|
init_create_tool();
|
|
75197
|
-
import * as
|
|
75198
|
-
import * as
|
|
76150
|
+
import * as fs64 from "fs";
|
|
76151
|
+
import * as path78 from "path";
|
|
75199
76152
|
var EXT_MAP = {
|
|
75200
76153
|
python: ".py",
|
|
75201
76154
|
py: ".py",
|
|
@@ -75257,8 +76210,8 @@ var extract_code_blocks = createSwarmTool({
|
|
|
75257
76210
|
execute: async (args2, directory) => {
|
|
75258
76211
|
const { content, output_dir, prefix } = args2;
|
|
75259
76212
|
const targetDir = output_dir || directory;
|
|
75260
|
-
if (!
|
|
75261
|
-
|
|
76213
|
+
if (!fs64.existsSync(targetDir)) {
|
|
76214
|
+
fs64.mkdirSync(targetDir, { recursive: true });
|
|
75262
76215
|
}
|
|
75263
76216
|
if (!content) {
|
|
75264
76217
|
return "Error: content is required";
|
|
@@ -75276,16 +76229,16 @@ var extract_code_blocks = createSwarmTool({
|
|
|
75276
76229
|
if (prefix) {
|
|
75277
76230
|
filename = `${prefix}_${filename}`;
|
|
75278
76231
|
}
|
|
75279
|
-
let filepath =
|
|
75280
|
-
const base =
|
|
75281
|
-
const ext =
|
|
76232
|
+
let filepath = path78.join(targetDir, filename);
|
|
76233
|
+
const base = path78.basename(filepath, path78.extname(filepath));
|
|
76234
|
+
const ext = path78.extname(filepath);
|
|
75282
76235
|
let counter = 1;
|
|
75283
|
-
while (
|
|
75284
|
-
filepath =
|
|
76236
|
+
while (fs64.existsSync(filepath)) {
|
|
76237
|
+
filepath = path78.join(targetDir, `${base}_${counter}${ext}`);
|
|
75285
76238
|
counter++;
|
|
75286
76239
|
}
|
|
75287
76240
|
try {
|
|
75288
|
-
|
|
76241
|
+
fs64.writeFileSync(filepath, code.trim(), "utf-8");
|
|
75289
76242
|
savedFiles.push(filepath);
|
|
75290
76243
|
} catch (error93) {
|
|
75291
76244
|
errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
|
|
@@ -75544,8 +76497,8 @@ var gitingest = createSwarmTool({
|
|
|
75544
76497
|
init_dist();
|
|
75545
76498
|
init_create_tool();
|
|
75546
76499
|
init_path_security();
|
|
75547
|
-
import * as
|
|
75548
|
-
import * as
|
|
76500
|
+
import * as fs65 from "fs";
|
|
76501
|
+
import * as path79 from "path";
|
|
75549
76502
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
75550
76503
|
var MAX_SYMBOL_LENGTH = 256;
|
|
75551
76504
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
@@ -75593,7 +76546,7 @@ function validateSymbolInput(symbol3) {
|
|
|
75593
76546
|
return null;
|
|
75594
76547
|
}
|
|
75595
76548
|
function isBinaryFile2(filePath, buffer) {
|
|
75596
|
-
const ext =
|
|
76549
|
+
const ext = path79.extname(filePath).toLowerCase();
|
|
75597
76550
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
75598
76551
|
return false;
|
|
75599
76552
|
}
|
|
@@ -75617,15 +76570,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
75617
76570
|
const imports = [];
|
|
75618
76571
|
let _resolvedTarget;
|
|
75619
76572
|
try {
|
|
75620
|
-
_resolvedTarget =
|
|
76573
|
+
_resolvedTarget = path79.resolve(targetFile);
|
|
75621
76574
|
} catch {
|
|
75622
76575
|
_resolvedTarget = targetFile;
|
|
75623
76576
|
}
|
|
75624
|
-
const targetBasename =
|
|
76577
|
+
const targetBasename = path79.basename(targetFile, path79.extname(targetFile));
|
|
75625
76578
|
const targetWithExt = targetFile;
|
|
75626
76579
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
75627
|
-
const normalizedTargetWithExt =
|
|
75628
|
-
const normalizedTargetWithoutExt =
|
|
76580
|
+
const normalizedTargetWithExt = path79.normalize(targetWithExt).replace(/\\/g, "/");
|
|
76581
|
+
const normalizedTargetWithoutExt = path79.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
75629
76582
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
75630
76583
|
for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
|
|
75631
76584
|
const modulePath = match[1] || match[2] || match[3];
|
|
@@ -75648,9 +76601,9 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
75648
76601
|
}
|
|
75649
76602
|
const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
75650
76603
|
let isMatch = false;
|
|
75651
|
-
const _targetDir =
|
|
75652
|
-
const targetExt =
|
|
75653
|
-
const targetBasenameNoExt =
|
|
76604
|
+
const _targetDir = path79.dirname(targetFile);
|
|
76605
|
+
const targetExt = path79.extname(targetFile);
|
|
76606
|
+
const targetBasenameNoExt = path79.basename(targetFile, targetExt);
|
|
75654
76607
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
75655
76608
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
75656
76609
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
@@ -75707,7 +76660,7 @@ var SKIP_DIRECTORIES4 = new Set([
|
|
|
75707
76660
|
function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
|
|
75708
76661
|
let entries;
|
|
75709
76662
|
try {
|
|
75710
|
-
entries =
|
|
76663
|
+
entries = fs65.readdirSync(dir);
|
|
75711
76664
|
} catch (e) {
|
|
75712
76665
|
stats.fileErrors.push({
|
|
75713
76666
|
path: dir,
|
|
@@ -75718,13 +76671,13 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
75718
76671
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
75719
76672
|
for (const entry of entries) {
|
|
75720
76673
|
if (SKIP_DIRECTORIES4.has(entry)) {
|
|
75721
|
-
stats.skippedDirs.push(
|
|
76674
|
+
stats.skippedDirs.push(path79.join(dir, entry));
|
|
75722
76675
|
continue;
|
|
75723
76676
|
}
|
|
75724
|
-
const fullPath =
|
|
76677
|
+
const fullPath = path79.join(dir, entry);
|
|
75725
76678
|
let stat4;
|
|
75726
76679
|
try {
|
|
75727
|
-
stat4 =
|
|
76680
|
+
stat4 = fs65.statSync(fullPath);
|
|
75728
76681
|
} catch (e) {
|
|
75729
76682
|
stats.fileErrors.push({
|
|
75730
76683
|
path: fullPath,
|
|
@@ -75735,7 +76688,7 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
75735
76688
|
if (stat4.isDirectory()) {
|
|
75736
76689
|
findSourceFiles3(fullPath, files, stats);
|
|
75737
76690
|
} else if (stat4.isFile()) {
|
|
75738
|
-
const ext =
|
|
76691
|
+
const ext = path79.extname(fullPath).toLowerCase();
|
|
75739
76692
|
if (SUPPORTED_EXTENSIONS3.includes(ext)) {
|
|
75740
76693
|
files.push(fullPath);
|
|
75741
76694
|
}
|
|
@@ -75792,8 +76745,8 @@ var imports = createSwarmTool({
|
|
|
75792
76745
|
return JSON.stringify(errorResult, null, 2);
|
|
75793
76746
|
}
|
|
75794
76747
|
try {
|
|
75795
|
-
const targetFile =
|
|
75796
|
-
if (!
|
|
76748
|
+
const targetFile = path79.resolve(file3);
|
|
76749
|
+
if (!fs65.existsSync(targetFile)) {
|
|
75797
76750
|
const errorResult = {
|
|
75798
76751
|
error: `target file not found: ${file3}`,
|
|
75799
76752
|
target: file3,
|
|
@@ -75803,7 +76756,7 @@ var imports = createSwarmTool({
|
|
|
75803
76756
|
};
|
|
75804
76757
|
return JSON.stringify(errorResult, null, 2);
|
|
75805
76758
|
}
|
|
75806
|
-
const targetStat =
|
|
76759
|
+
const targetStat = fs65.statSync(targetFile);
|
|
75807
76760
|
if (!targetStat.isFile()) {
|
|
75808
76761
|
const errorResult = {
|
|
75809
76762
|
error: "target must be a file, not a directory",
|
|
@@ -75814,7 +76767,7 @@ var imports = createSwarmTool({
|
|
|
75814
76767
|
};
|
|
75815
76768
|
return JSON.stringify(errorResult, null, 2);
|
|
75816
76769
|
}
|
|
75817
|
-
const baseDir =
|
|
76770
|
+
const baseDir = path79.dirname(targetFile);
|
|
75818
76771
|
const scanStats = {
|
|
75819
76772
|
skippedDirs: [],
|
|
75820
76773
|
skippedFiles: 0,
|
|
@@ -75829,12 +76782,12 @@ var imports = createSwarmTool({
|
|
|
75829
76782
|
if (consumers.length >= MAX_CONSUMERS)
|
|
75830
76783
|
break;
|
|
75831
76784
|
try {
|
|
75832
|
-
const stat4 =
|
|
76785
|
+
const stat4 = fs65.statSync(filePath);
|
|
75833
76786
|
if (stat4.size > MAX_FILE_SIZE_BYTES7) {
|
|
75834
76787
|
skippedFileCount++;
|
|
75835
76788
|
continue;
|
|
75836
76789
|
}
|
|
75837
|
-
const buffer =
|
|
76790
|
+
const buffer = fs65.readFileSync(filePath);
|
|
75838
76791
|
if (isBinaryFile2(filePath, buffer)) {
|
|
75839
76792
|
skippedFileCount++;
|
|
75840
76793
|
continue;
|
|
@@ -76346,8 +77299,8 @@ init_schema();
|
|
|
76346
77299
|
init_qa_gate_profile();
|
|
76347
77300
|
init_manager2();
|
|
76348
77301
|
init_curator();
|
|
76349
|
-
import * as
|
|
76350
|
-
import * as
|
|
77302
|
+
import * as fs66 from "fs";
|
|
77303
|
+
import * as path80 from "path";
|
|
76351
77304
|
init_knowledge_curator();
|
|
76352
77305
|
init_knowledge_reader();
|
|
76353
77306
|
init_knowledge_store();
|
|
@@ -76578,11 +77531,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76578
77531
|
safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
|
|
76579
77532
|
}
|
|
76580
77533
|
try {
|
|
76581
|
-
const driftEvidencePath =
|
|
77534
|
+
const driftEvidencePath = path80.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
|
|
76582
77535
|
let driftVerdictFound = false;
|
|
76583
77536
|
let driftVerdictApproved = false;
|
|
76584
77537
|
try {
|
|
76585
|
-
const driftEvidenceContent =
|
|
77538
|
+
const driftEvidenceContent = fs66.readFileSync(driftEvidencePath, "utf-8");
|
|
76586
77539
|
const driftEvidence = JSON.parse(driftEvidenceContent);
|
|
76587
77540
|
const entries = driftEvidence.entries ?? [];
|
|
76588
77541
|
for (const entry of entries) {
|
|
@@ -76612,14 +77565,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76612
77565
|
driftVerdictFound = false;
|
|
76613
77566
|
}
|
|
76614
77567
|
if (!driftVerdictFound) {
|
|
76615
|
-
const specPath =
|
|
76616
|
-
const specExists =
|
|
77568
|
+
const specPath = path80.join(dir, ".swarm", "spec.md");
|
|
77569
|
+
const specExists = fs66.existsSync(specPath);
|
|
76617
77570
|
if (!specExists) {
|
|
76618
77571
|
let incompleteTaskCount = 0;
|
|
76619
77572
|
let planPhaseFound = false;
|
|
76620
77573
|
try {
|
|
76621
77574
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76622
|
-
const planRaw =
|
|
77575
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76623
77576
|
const plan = JSON.parse(planRaw);
|
|
76624
77577
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76625
77578
|
if (targetPhase) {
|
|
@@ -76670,11 +77623,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76670
77623
|
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
76671
77624
|
const effective = getEffectiveGates(profile, overrides);
|
|
76672
77625
|
if (effective.hallucination_guard === true) {
|
|
76673
|
-
const hgPath =
|
|
77626
|
+
const hgPath = path80.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
|
|
76674
77627
|
let hgVerdictFound = false;
|
|
76675
77628
|
let hgVerdictApproved = false;
|
|
76676
77629
|
try {
|
|
76677
|
-
const hgContent =
|
|
77630
|
+
const hgContent = fs66.readFileSync(hgPath, "utf-8");
|
|
76678
77631
|
const hgBundle = JSON.parse(hgContent);
|
|
76679
77632
|
for (const entry of hgBundle.entries ?? []) {
|
|
76680
77633
|
if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
|
|
@@ -76742,11 +77695,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76742
77695
|
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
76743
77696
|
const effective = getEffectiveGates(profile, overrides);
|
|
76744
77697
|
if (effective.mutation_test === true) {
|
|
76745
|
-
const mgPath =
|
|
77698
|
+
const mgPath = path80.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
|
|
76746
77699
|
let mgVerdictFound = false;
|
|
76747
77700
|
let mgVerdict;
|
|
76748
77701
|
try {
|
|
76749
|
-
const mgContent =
|
|
77702
|
+
const mgContent = fs66.readFileSync(mgPath, "utf-8");
|
|
76750
77703
|
const mgBundle = JSON.parse(mgContent);
|
|
76751
77704
|
for (const entry of mgBundle.entries ?? []) {
|
|
76752
77705
|
if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
|
|
@@ -76814,7 +77767,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76814
77767
|
}
|
|
76815
77768
|
if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
|
|
76816
77769
|
try {
|
|
76817
|
-
const projectName =
|
|
77770
|
+
const projectName = path80.basename(dir);
|
|
76818
77771
|
const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
|
|
76819
77772
|
if (curationResult) {
|
|
76820
77773
|
const sessionState = swarmState.agentSessions.get(sessionID);
|
|
@@ -76894,7 +77847,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76894
77847
|
let phaseRequiredAgents;
|
|
76895
77848
|
try {
|
|
76896
77849
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76897
|
-
const planRaw =
|
|
77850
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76898
77851
|
const plan = JSON.parse(planRaw);
|
|
76899
77852
|
const phaseObj = plan.phases.find((p) => p.id === phase);
|
|
76900
77853
|
phaseRequiredAgents = phaseObj?.required_agents;
|
|
@@ -76909,7 +77862,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76909
77862
|
if (agentsMissing.length > 0) {
|
|
76910
77863
|
try {
|
|
76911
77864
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76912
|
-
const planRaw =
|
|
77865
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76913
77866
|
const plan = JSON.parse(planRaw);
|
|
76914
77867
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76915
77868
|
if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
|
|
@@ -76949,7 +77902,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76949
77902
|
if (phaseCompleteConfig.regression_sweep?.enforce) {
|
|
76950
77903
|
try {
|
|
76951
77904
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76952
|
-
const planRaw =
|
|
77905
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76953
77906
|
const plan = JSON.parse(planRaw);
|
|
76954
77907
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76955
77908
|
if (targetPhase) {
|
|
@@ -77003,7 +77956,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77003
77956
|
}
|
|
77004
77957
|
try {
|
|
77005
77958
|
const eventsPath = validateSwarmPath(dir, "events.jsonl");
|
|
77006
|
-
|
|
77959
|
+
fs66.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
77007
77960
|
`, "utf-8");
|
|
77008
77961
|
} catch (writeError) {
|
|
77009
77962
|
warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
@@ -77078,12 +78031,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77078
78031
|
warnings.push(`Warning: failed to update plan.json phase status`);
|
|
77079
78032
|
try {
|
|
77080
78033
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
77081
|
-
const planRaw =
|
|
78034
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
77082
78035
|
const plan2 = JSON.parse(planRaw);
|
|
77083
78036
|
const phaseObj = plan2.phases.find((p) => p.id === phase);
|
|
77084
78037
|
if (phaseObj) {
|
|
77085
78038
|
phaseObj.status = "complete";
|
|
77086
|
-
|
|
78039
|
+
fs66.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
|
|
77087
78040
|
}
|
|
77088
78041
|
} catch {}
|
|
77089
78042
|
} else if (plan) {
|
|
@@ -77120,12 +78073,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77120
78073
|
warnings.push(`Warning: failed to update plan.json phase status`);
|
|
77121
78074
|
try {
|
|
77122
78075
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
77123
|
-
const planRaw =
|
|
78076
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
77124
78077
|
const plan = JSON.parse(planRaw);
|
|
77125
78078
|
const phaseObj = plan.phases.find((p) => p.id === phase);
|
|
77126
78079
|
if (phaseObj) {
|
|
77127
78080
|
phaseObj.status = "complete";
|
|
77128
|
-
|
|
78081
|
+
fs66.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
77129
78082
|
}
|
|
77130
78083
|
} catch {}
|
|
77131
78084
|
}
|
|
@@ -77182,8 +78135,8 @@ init_dist();
|
|
|
77182
78135
|
init_discovery();
|
|
77183
78136
|
init_utils();
|
|
77184
78137
|
init_create_tool();
|
|
77185
|
-
import * as
|
|
77186
|
-
import * as
|
|
78138
|
+
import * as fs67 from "fs";
|
|
78139
|
+
import * as path81 from "path";
|
|
77187
78140
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
77188
78141
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
77189
78142
|
function isValidEcosystem(value) {
|
|
@@ -77211,31 +78164,31 @@ function validateArgs3(args2) {
|
|
|
77211
78164
|
function detectEcosystems(directory) {
|
|
77212
78165
|
const ecosystems = [];
|
|
77213
78166
|
const cwd = directory;
|
|
77214
|
-
if (
|
|
78167
|
+
if (fs67.existsSync(path81.join(cwd, "package.json"))) {
|
|
77215
78168
|
ecosystems.push("npm");
|
|
77216
78169
|
}
|
|
77217
|
-
if (
|
|
78170
|
+
if (fs67.existsSync(path81.join(cwd, "pyproject.toml")) || fs67.existsSync(path81.join(cwd, "requirements.txt"))) {
|
|
77218
78171
|
ecosystems.push("pip");
|
|
77219
78172
|
}
|
|
77220
|
-
if (
|
|
78173
|
+
if (fs67.existsSync(path81.join(cwd, "Cargo.toml"))) {
|
|
77221
78174
|
ecosystems.push("cargo");
|
|
77222
78175
|
}
|
|
77223
|
-
if (
|
|
78176
|
+
if (fs67.existsSync(path81.join(cwd, "go.mod"))) {
|
|
77224
78177
|
ecosystems.push("go");
|
|
77225
78178
|
}
|
|
77226
78179
|
try {
|
|
77227
|
-
const files =
|
|
78180
|
+
const files = fs67.readdirSync(cwd);
|
|
77228
78181
|
if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
|
|
77229
78182
|
ecosystems.push("dotnet");
|
|
77230
78183
|
}
|
|
77231
78184
|
} catch {}
|
|
77232
|
-
if (
|
|
78185
|
+
if (fs67.existsSync(path81.join(cwd, "Gemfile")) || fs67.existsSync(path81.join(cwd, "Gemfile.lock"))) {
|
|
77233
78186
|
ecosystems.push("ruby");
|
|
77234
78187
|
}
|
|
77235
|
-
if (
|
|
78188
|
+
if (fs67.existsSync(path81.join(cwd, "pubspec.yaml"))) {
|
|
77236
78189
|
ecosystems.push("dart");
|
|
77237
78190
|
}
|
|
77238
|
-
if (
|
|
78191
|
+
if (fs67.existsSync(path81.join(cwd, "composer.lock"))) {
|
|
77239
78192
|
ecosystems.push("composer");
|
|
77240
78193
|
}
|
|
77241
78194
|
return ecosystems;
|
|
@@ -78394,8 +79347,8 @@ var pkg_audit = createSwarmTool({
|
|
|
78394
79347
|
// src/tools/placeholder-scan.ts
|
|
78395
79348
|
init_dist();
|
|
78396
79349
|
init_manager2();
|
|
78397
|
-
import * as
|
|
78398
|
-
import * as
|
|
79350
|
+
import * as fs68 from "fs";
|
|
79351
|
+
import * as path82 from "path";
|
|
78399
79352
|
init_utils();
|
|
78400
79353
|
init_create_tool();
|
|
78401
79354
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
@@ -78518,7 +79471,7 @@ function isScaffoldFile(filePath) {
|
|
|
78518
79471
|
if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
|
|
78519
79472
|
return true;
|
|
78520
79473
|
}
|
|
78521
|
-
const filename =
|
|
79474
|
+
const filename = path82.basename(filePath);
|
|
78522
79475
|
if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
|
|
78523
79476
|
return true;
|
|
78524
79477
|
}
|
|
@@ -78535,7 +79488,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
|
|
|
78535
79488
|
if (regex.test(normalizedPath)) {
|
|
78536
79489
|
return true;
|
|
78537
79490
|
}
|
|
78538
|
-
const filename =
|
|
79491
|
+
const filename = path82.basename(filePath);
|
|
78539
79492
|
const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
78540
79493
|
if (filenameRegex.test(filename)) {
|
|
78541
79494
|
return true;
|
|
@@ -78544,7 +79497,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
|
|
|
78544
79497
|
return false;
|
|
78545
79498
|
}
|
|
78546
79499
|
function isParserSupported(filePath) {
|
|
78547
|
-
const ext =
|
|
79500
|
+
const ext = path82.extname(filePath).toLowerCase();
|
|
78548
79501
|
return SUPPORTED_PARSER_EXTENSIONS.has(ext);
|
|
78549
79502
|
}
|
|
78550
79503
|
function isPlanFile(filePath) {
|
|
@@ -78791,28 +79744,28 @@ async function placeholderScan(input, directory) {
|
|
|
78791
79744
|
let filesScanned = 0;
|
|
78792
79745
|
const filesWithFindings = new Set;
|
|
78793
79746
|
for (const filePath of changed_files) {
|
|
78794
|
-
const fullPath =
|
|
78795
|
-
const resolvedDirectory =
|
|
78796
|
-
if (!fullPath.startsWith(resolvedDirectory +
|
|
79747
|
+
const fullPath = path82.isAbsolute(filePath) ? filePath : path82.resolve(directory, filePath);
|
|
79748
|
+
const resolvedDirectory = path82.resolve(directory);
|
|
79749
|
+
if (!fullPath.startsWith(resolvedDirectory + path82.sep) && fullPath !== resolvedDirectory) {
|
|
78797
79750
|
continue;
|
|
78798
79751
|
}
|
|
78799
|
-
if (!
|
|
79752
|
+
if (!fs68.existsSync(fullPath)) {
|
|
78800
79753
|
continue;
|
|
78801
79754
|
}
|
|
78802
79755
|
if (isAllowedByGlobs(filePath, allow_globs)) {
|
|
78803
79756
|
continue;
|
|
78804
79757
|
}
|
|
78805
|
-
const relativeFilePath =
|
|
79758
|
+
const relativeFilePath = path82.relative(directory, fullPath).replace(/\\/g, "/");
|
|
78806
79759
|
if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
|
|
78807
79760
|
continue;
|
|
78808
79761
|
}
|
|
78809
79762
|
let content;
|
|
78810
79763
|
try {
|
|
78811
|
-
const stat4 =
|
|
79764
|
+
const stat4 = fs68.statSync(fullPath);
|
|
78812
79765
|
if (stat4.size > MAX_FILE_SIZE) {
|
|
78813
79766
|
continue;
|
|
78814
79767
|
}
|
|
78815
|
-
content =
|
|
79768
|
+
content = fs68.readFileSync(fullPath, "utf-8");
|
|
78816
79769
|
} catch {
|
|
78817
79770
|
continue;
|
|
78818
79771
|
}
|
|
@@ -78874,8 +79827,8 @@ var placeholder_scan = createSwarmTool({
|
|
|
78874
79827
|
});
|
|
78875
79828
|
// src/tools/pre-check-batch.ts
|
|
78876
79829
|
init_dist();
|
|
78877
|
-
import * as
|
|
78878
|
-
import * as
|
|
79830
|
+
import * as fs71 from "fs";
|
|
79831
|
+
import * as path85 from "path";
|
|
78879
79832
|
init_manager2();
|
|
78880
79833
|
init_utils();
|
|
78881
79834
|
init_create_tool();
|
|
@@ -79010,8 +79963,8 @@ var quality_budget = createSwarmTool({
|
|
|
79010
79963
|
init_dist();
|
|
79011
79964
|
init_manager2();
|
|
79012
79965
|
init_detector();
|
|
79013
|
-
import * as
|
|
79014
|
-
import * as
|
|
79966
|
+
import * as fs70 from "fs";
|
|
79967
|
+
import * as path84 from "path";
|
|
79015
79968
|
import { extname as extname18 } from "path";
|
|
79016
79969
|
|
|
79017
79970
|
// src/sast/rules/c.ts
|
|
@@ -79904,25 +80857,25 @@ init_create_tool();
|
|
|
79904
80857
|
// src/tools/sast-baseline.ts
|
|
79905
80858
|
init_utils2();
|
|
79906
80859
|
import * as crypto8 from "crypto";
|
|
79907
|
-
import * as
|
|
79908
|
-
import * as
|
|
80860
|
+
import * as fs69 from "fs";
|
|
80861
|
+
import * as path83 from "path";
|
|
79909
80862
|
var BASELINE_SCHEMA_VERSION = "1.0.0";
|
|
79910
80863
|
var MAX_BASELINE_FINDINGS = 2000;
|
|
79911
80864
|
var MAX_BASELINE_BYTES = 2 * 1048576;
|
|
79912
80865
|
var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
|
|
79913
80866
|
function normalizeFindingPath(directory, file3) {
|
|
79914
|
-
const resolved =
|
|
79915
|
-
const rel =
|
|
80867
|
+
const resolved = path83.isAbsolute(file3) ? file3 : path83.resolve(directory, file3);
|
|
80868
|
+
const rel = path83.relative(path83.resolve(directory), resolved);
|
|
79916
80869
|
return rel.replace(/\\/g, "/");
|
|
79917
80870
|
}
|
|
79918
80871
|
function baselineRelPath(phase) {
|
|
79919
|
-
return
|
|
80872
|
+
return path83.join("evidence", String(phase), "sast-baseline.json");
|
|
79920
80873
|
}
|
|
79921
80874
|
function tempRelPath(phase) {
|
|
79922
|
-
return
|
|
80875
|
+
return path83.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
|
|
79923
80876
|
}
|
|
79924
80877
|
function lockRelPath(phase) {
|
|
79925
|
-
return
|
|
80878
|
+
return path83.join("evidence", String(phase), "sast-baseline.json.lock");
|
|
79926
80879
|
}
|
|
79927
80880
|
function getLine(lines, idx) {
|
|
79928
80881
|
if (idx < 0 || idx >= lines.length)
|
|
@@ -79939,7 +80892,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
|
|
|
79939
80892
|
}
|
|
79940
80893
|
const lineNum = finding.location.line;
|
|
79941
80894
|
try {
|
|
79942
|
-
const content =
|
|
80895
|
+
const content = fs69.readFileSync(finding.location.file, "utf-8");
|
|
79943
80896
|
const lines = content.split(`
|
|
79944
80897
|
`);
|
|
79945
80898
|
const idx = lineNum - 1;
|
|
@@ -79970,7 +80923,7 @@ function assignOccurrenceIndices(findings, directory) {
|
|
|
79970
80923
|
try {
|
|
79971
80924
|
if (relFile.startsWith(".."))
|
|
79972
80925
|
throw new Error("escapes workspace");
|
|
79973
|
-
const content =
|
|
80926
|
+
const content = fs69.readFileSync(finding.location.file, "utf-8");
|
|
79974
80927
|
const lines = content.split(`
|
|
79975
80928
|
`);
|
|
79976
80929
|
const idx = lineNum - 1;
|
|
@@ -79999,11 +80952,11 @@ function assignOccurrenceIndices(findings, directory) {
|
|
|
79999
80952
|
async function acquireLock(lockPath) {
|
|
80000
80953
|
for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
|
|
80001
80954
|
try {
|
|
80002
|
-
const fd =
|
|
80003
|
-
|
|
80955
|
+
const fd = fs69.openSync(lockPath, "wx");
|
|
80956
|
+
fs69.closeSync(fd);
|
|
80004
80957
|
return () => {
|
|
80005
80958
|
try {
|
|
80006
|
-
|
|
80959
|
+
fs69.unlinkSync(lockPath);
|
|
80007
80960
|
} catch {}
|
|
80008
80961
|
};
|
|
80009
80962
|
} catch {
|
|
@@ -80043,12 +80996,12 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80043
80996
|
message: e instanceof Error ? e.message : "Path validation failed"
|
|
80044
80997
|
};
|
|
80045
80998
|
}
|
|
80046
|
-
|
|
80999
|
+
fs69.mkdirSync(path83.dirname(baselinePath), { recursive: true });
|
|
80047
81000
|
const releaseLock = await acquireLock(lockPath);
|
|
80048
81001
|
try {
|
|
80049
81002
|
let existing = null;
|
|
80050
81003
|
try {
|
|
80051
|
-
const raw =
|
|
81004
|
+
const raw = fs69.readFileSync(baselinePath, "utf-8");
|
|
80052
81005
|
const parsed = JSON.parse(raw);
|
|
80053
81006
|
if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
|
|
80054
81007
|
existing = parsed;
|
|
@@ -80108,8 +81061,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80108
81061
|
message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
|
|
80109
81062
|
};
|
|
80110
81063
|
}
|
|
80111
|
-
|
|
80112
|
-
|
|
81064
|
+
fs69.writeFileSync(tempPath, json4, "utf-8");
|
|
81065
|
+
fs69.renameSync(tempPath, baselinePath);
|
|
80113
81066
|
return {
|
|
80114
81067
|
status: "merged",
|
|
80115
81068
|
path: baselinePath,
|
|
@@ -80140,8 +81093,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80140
81093
|
message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
|
|
80141
81094
|
};
|
|
80142
81095
|
}
|
|
80143
|
-
|
|
80144
|
-
|
|
81096
|
+
fs69.writeFileSync(tempPath, json3, "utf-8");
|
|
81097
|
+
fs69.renameSync(tempPath, baselinePath);
|
|
80145
81098
|
return {
|
|
80146
81099
|
status: "written",
|
|
80147
81100
|
path: baselinePath,
|
|
@@ -80166,7 +81119,7 @@ function loadBaseline(directory, phase) {
|
|
|
80166
81119
|
};
|
|
80167
81120
|
}
|
|
80168
81121
|
try {
|
|
80169
|
-
const raw =
|
|
81122
|
+
const raw = fs69.readFileSync(baselinePath, "utf-8");
|
|
80170
81123
|
const parsed = JSON.parse(raw);
|
|
80171
81124
|
if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
|
|
80172
81125
|
return {
|
|
@@ -80208,17 +81161,17 @@ var SEVERITY_ORDER = {
|
|
|
80208
81161
|
};
|
|
80209
81162
|
function shouldSkipFile(filePath) {
|
|
80210
81163
|
try {
|
|
80211
|
-
const stats =
|
|
81164
|
+
const stats = fs70.statSync(filePath);
|
|
80212
81165
|
if (stats.size > MAX_FILE_SIZE_BYTES8) {
|
|
80213
81166
|
return { skip: true, reason: "file too large" };
|
|
80214
81167
|
}
|
|
80215
81168
|
if (stats.size === 0) {
|
|
80216
81169
|
return { skip: true, reason: "empty file" };
|
|
80217
81170
|
}
|
|
80218
|
-
const fd =
|
|
81171
|
+
const fd = fs70.openSync(filePath, "r");
|
|
80219
81172
|
const buffer = Buffer.alloc(8192);
|
|
80220
|
-
const bytesRead =
|
|
80221
|
-
|
|
81173
|
+
const bytesRead = fs70.readSync(fd, buffer, 0, 8192, 0);
|
|
81174
|
+
fs70.closeSync(fd);
|
|
80222
81175
|
if (bytesRead > 0) {
|
|
80223
81176
|
let nullCount = 0;
|
|
80224
81177
|
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
@@ -80257,7 +81210,7 @@ function countBySeverity(findings) {
|
|
|
80257
81210
|
}
|
|
80258
81211
|
function scanFileWithTierA(filePath, language) {
|
|
80259
81212
|
try {
|
|
80260
|
-
const content =
|
|
81213
|
+
const content = fs70.readFileSync(filePath, "utf-8");
|
|
80261
81214
|
const findings = executeRulesSync(filePath, content, language);
|
|
80262
81215
|
return findings.map((f) => ({
|
|
80263
81216
|
rule_id: f.rule_id,
|
|
@@ -80310,13 +81263,13 @@ async function sastScan(input, directory, config3) {
|
|
|
80310
81263
|
_filesSkipped++;
|
|
80311
81264
|
continue;
|
|
80312
81265
|
}
|
|
80313
|
-
const resolvedPath =
|
|
80314
|
-
const resolvedDirectory =
|
|
80315
|
-
if (!resolvedPath.startsWith(resolvedDirectory +
|
|
81266
|
+
const resolvedPath = path84.isAbsolute(filePath) ? filePath : path84.resolve(directory, filePath);
|
|
81267
|
+
const resolvedDirectory = path84.resolve(directory);
|
|
81268
|
+
if (!resolvedPath.startsWith(resolvedDirectory + path84.sep) && resolvedPath !== resolvedDirectory) {
|
|
80316
81269
|
_filesSkipped++;
|
|
80317
81270
|
continue;
|
|
80318
81271
|
}
|
|
80319
|
-
if (!
|
|
81272
|
+
if (!fs70.existsSync(resolvedPath)) {
|
|
80320
81273
|
_filesSkipped++;
|
|
80321
81274
|
continue;
|
|
80322
81275
|
}
|
|
@@ -80623,18 +81576,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
|
|
|
80623
81576
|
let resolved;
|
|
80624
81577
|
const isWinAbs = isWindowsAbsolutePath(inputPath);
|
|
80625
81578
|
if (isWinAbs) {
|
|
80626
|
-
resolved =
|
|
80627
|
-
} else if (
|
|
80628
|
-
resolved =
|
|
81579
|
+
resolved = path85.win32.resolve(inputPath);
|
|
81580
|
+
} else if (path85.isAbsolute(inputPath)) {
|
|
81581
|
+
resolved = path85.resolve(inputPath);
|
|
80629
81582
|
} else {
|
|
80630
|
-
resolved =
|
|
81583
|
+
resolved = path85.resolve(baseDir, inputPath);
|
|
80631
81584
|
}
|
|
80632
|
-
const workspaceResolved =
|
|
81585
|
+
const workspaceResolved = path85.resolve(workspaceDir);
|
|
80633
81586
|
let relative20;
|
|
80634
81587
|
if (isWinAbs) {
|
|
80635
|
-
relative20 =
|
|
81588
|
+
relative20 = path85.win32.relative(workspaceResolved, resolved);
|
|
80636
81589
|
} else {
|
|
80637
|
-
relative20 =
|
|
81590
|
+
relative20 = path85.relative(workspaceResolved, resolved);
|
|
80638
81591
|
}
|
|
80639
81592
|
if (relative20.startsWith("..")) {
|
|
80640
81593
|
return "path traversal detected";
|
|
@@ -80699,7 +81652,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
|
|
|
80699
81652
|
if (typeof file3 !== "string") {
|
|
80700
81653
|
continue;
|
|
80701
81654
|
}
|
|
80702
|
-
const resolvedPath =
|
|
81655
|
+
const resolvedPath = path85.resolve(file3);
|
|
80703
81656
|
const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
|
|
80704
81657
|
if (validationError) {
|
|
80705
81658
|
continue;
|
|
@@ -80856,7 +81809,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80856
81809
|
skippedFiles++;
|
|
80857
81810
|
continue;
|
|
80858
81811
|
}
|
|
80859
|
-
const resolvedPath =
|
|
81812
|
+
const resolvedPath = path85.resolve(file3);
|
|
80860
81813
|
const validationError = validatePath(resolvedPath, directory, directory);
|
|
80861
81814
|
if (validationError) {
|
|
80862
81815
|
skippedFiles++;
|
|
@@ -80874,14 +81827,14 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80874
81827
|
};
|
|
80875
81828
|
}
|
|
80876
81829
|
for (const file3 of validatedFiles) {
|
|
80877
|
-
const ext =
|
|
81830
|
+
const ext = path85.extname(file3).toLowerCase();
|
|
80878
81831
|
if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
|
|
80879
81832
|
skippedFiles++;
|
|
80880
81833
|
continue;
|
|
80881
81834
|
}
|
|
80882
81835
|
let stat4;
|
|
80883
81836
|
try {
|
|
80884
|
-
stat4 =
|
|
81837
|
+
stat4 = fs71.statSync(file3);
|
|
80885
81838
|
} catch {
|
|
80886
81839
|
skippedFiles++;
|
|
80887
81840
|
continue;
|
|
@@ -80892,7 +81845,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80892
81845
|
}
|
|
80893
81846
|
let content;
|
|
80894
81847
|
try {
|
|
80895
|
-
const buffer =
|
|
81848
|
+
const buffer = fs71.readFileSync(file3);
|
|
80896
81849
|
if (buffer.includes(0)) {
|
|
80897
81850
|
skippedFiles++;
|
|
80898
81851
|
continue;
|
|
@@ -81093,7 +82046,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
|
|
|
81093
82046
|
const preexistingFindings = [];
|
|
81094
82047
|
for (const finding of findings) {
|
|
81095
82048
|
const filePath = finding.location.file;
|
|
81096
|
-
const normalised =
|
|
82049
|
+
const normalised = path85.relative(directory, filePath).replace(/\\/g, "/");
|
|
81097
82050
|
const changedLines = changedLineRanges.get(normalised);
|
|
81098
82051
|
if (changedLines?.has(finding.location.line)) {
|
|
81099
82052
|
newFindings.push(finding);
|
|
@@ -81144,7 +82097,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
|
|
|
81144
82097
|
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
81145
82098
|
continue;
|
|
81146
82099
|
}
|
|
81147
|
-
changedFiles.push(
|
|
82100
|
+
changedFiles.push(path85.resolve(directory, file3));
|
|
81148
82101
|
}
|
|
81149
82102
|
if (changedFiles.length === 0) {
|
|
81150
82103
|
warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
|
|
@@ -81345,7 +82298,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
81345
82298
|
};
|
|
81346
82299
|
return JSON.stringify(errorResult, null, 2);
|
|
81347
82300
|
}
|
|
81348
|
-
const resolvedDirectory =
|
|
82301
|
+
const resolvedDirectory = path85.resolve(typedArgs.directory);
|
|
81349
82302
|
const workspaceAnchor = resolvedDirectory;
|
|
81350
82303
|
const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
|
|
81351
82304
|
if (dirError) {
|
|
@@ -81386,7 +82339,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
81386
82339
|
});
|
|
81387
82340
|
// src/tools/repo-map.ts
|
|
81388
82341
|
init_dist();
|
|
81389
|
-
import * as
|
|
82342
|
+
import * as path86 from "path";
|
|
81390
82343
|
init_path_security();
|
|
81391
82344
|
init_create_tool();
|
|
81392
82345
|
var VALID_ACTIONS = [
|
|
@@ -81411,7 +82364,7 @@ function validateFile(p) {
|
|
|
81411
82364
|
return "file contains control characters";
|
|
81412
82365
|
if (containsPathTraversal(p))
|
|
81413
82366
|
return "file contains path traversal";
|
|
81414
|
-
if (
|
|
82367
|
+
if (path86.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
|
|
81415
82368
|
return "file must be a workspace-relative path, not absolute";
|
|
81416
82369
|
}
|
|
81417
82370
|
return null;
|
|
@@ -81434,8 +82387,8 @@ function ok(action, payload) {
|
|
|
81434
82387
|
}
|
|
81435
82388
|
function toRelativeGraphPath(input, workspaceRoot) {
|
|
81436
82389
|
const normalized = input.replace(/\\/g, "/");
|
|
81437
|
-
if (
|
|
81438
|
-
const rel =
|
|
82390
|
+
if (path86.isAbsolute(normalized)) {
|
|
82391
|
+
const rel = path86.relative(workspaceRoot, normalized).replace(/\\/g, "/");
|
|
81439
82392
|
return normalizeGraphPath2(rel);
|
|
81440
82393
|
}
|
|
81441
82394
|
return normalizeGraphPath2(normalized);
|
|
@@ -81579,8 +82532,8 @@ var repo_map = createSwarmTool({
|
|
|
81579
82532
|
// src/tools/req-coverage.ts
|
|
81580
82533
|
init_dist();
|
|
81581
82534
|
init_create_tool();
|
|
81582
|
-
import * as
|
|
81583
|
-
import * as
|
|
82535
|
+
import * as fs72 from "fs";
|
|
82536
|
+
import * as path87 from "path";
|
|
81584
82537
|
var SPEC_FILE = ".swarm/spec.md";
|
|
81585
82538
|
var EVIDENCE_DIR4 = ".swarm/evidence";
|
|
81586
82539
|
var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
|
|
@@ -81639,19 +82592,19 @@ function extractObligationAndText(id, lineText) {
|
|
|
81639
82592
|
var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
|
|
81640
82593
|
function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
81641
82594
|
const touchedFiles = new Set;
|
|
81642
|
-
if (!
|
|
82595
|
+
if (!fs72.existsSync(evidenceDir) || !fs72.statSync(evidenceDir).isDirectory()) {
|
|
81643
82596
|
return [];
|
|
81644
82597
|
}
|
|
81645
82598
|
let entries;
|
|
81646
82599
|
try {
|
|
81647
|
-
entries =
|
|
82600
|
+
entries = fs72.readdirSync(evidenceDir);
|
|
81648
82601
|
} catch {
|
|
81649
82602
|
return [];
|
|
81650
82603
|
}
|
|
81651
82604
|
for (const entry of entries) {
|
|
81652
|
-
const entryPath =
|
|
82605
|
+
const entryPath = path87.join(evidenceDir, entry);
|
|
81653
82606
|
try {
|
|
81654
|
-
const stat4 =
|
|
82607
|
+
const stat4 = fs72.statSync(entryPath);
|
|
81655
82608
|
if (!stat4.isDirectory()) {
|
|
81656
82609
|
continue;
|
|
81657
82610
|
}
|
|
@@ -81665,14 +82618,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81665
82618
|
if (entryPhase !== String(phase)) {
|
|
81666
82619
|
continue;
|
|
81667
82620
|
}
|
|
81668
|
-
const evidenceFilePath =
|
|
82621
|
+
const evidenceFilePath = path87.join(entryPath, "evidence.json");
|
|
81669
82622
|
try {
|
|
81670
|
-
const resolvedPath =
|
|
81671
|
-
const evidenceDirResolved =
|
|
81672
|
-
if (!resolvedPath.startsWith(evidenceDirResolved +
|
|
82623
|
+
const resolvedPath = path87.resolve(evidenceFilePath);
|
|
82624
|
+
const evidenceDirResolved = path87.resolve(evidenceDir);
|
|
82625
|
+
if (!resolvedPath.startsWith(evidenceDirResolved + path87.sep)) {
|
|
81673
82626
|
continue;
|
|
81674
82627
|
}
|
|
81675
|
-
const stat4 =
|
|
82628
|
+
const stat4 = fs72.lstatSync(evidenceFilePath);
|
|
81676
82629
|
if (!stat4.isFile()) {
|
|
81677
82630
|
continue;
|
|
81678
82631
|
}
|
|
@@ -81684,7 +82637,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81684
82637
|
}
|
|
81685
82638
|
let content;
|
|
81686
82639
|
try {
|
|
81687
|
-
content =
|
|
82640
|
+
content = fs72.readFileSync(evidenceFilePath, "utf-8");
|
|
81688
82641
|
} catch {
|
|
81689
82642
|
continue;
|
|
81690
82643
|
}
|
|
@@ -81703,7 +82656,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81703
82656
|
if (Array.isArray(diffEntry.files_changed)) {
|
|
81704
82657
|
for (const file3 of diffEntry.files_changed) {
|
|
81705
82658
|
if (typeof file3 === "string") {
|
|
81706
|
-
touchedFiles.add(
|
|
82659
|
+
touchedFiles.add(path87.resolve(cwd, file3));
|
|
81707
82660
|
}
|
|
81708
82661
|
}
|
|
81709
82662
|
}
|
|
@@ -81716,12 +82669,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81716
82669
|
}
|
|
81717
82670
|
function searchFileForKeywords(filePath, keywords, cwd) {
|
|
81718
82671
|
try {
|
|
81719
|
-
const resolvedPath =
|
|
81720
|
-
const cwdResolved =
|
|
82672
|
+
const resolvedPath = path87.resolve(filePath);
|
|
82673
|
+
const cwdResolved = path87.resolve(cwd);
|
|
81721
82674
|
if (!resolvedPath.startsWith(cwdResolved)) {
|
|
81722
82675
|
return false;
|
|
81723
82676
|
}
|
|
81724
|
-
const content =
|
|
82677
|
+
const content = fs72.readFileSync(resolvedPath, "utf-8");
|
|
81725
82678
|
for (const keyword of keywords) {
|
|
81726
82679
|
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
81727
82680
|
if (regex.test(content)) {
|
|
@@ -81851,10 +82804,10 @@ var req_coverage = createSwarmTool({
|
|
|
81851
82804
|
}, null, 2);
|
|
81852
82805
|
}
|
|
81853
82806
|
const cwd = inputDirectory || directory;
|
|
81854
|
-
const specPath =
|
|
82807
|
+
const specPath = path87.join(cwd, SPEC_FILE);
|
|
81855
82808
|
let specContent;
|
|
81856
82809
|
try {
|
|
81857
|
-
specContent =
|
|
82810
|
+
specContent = fs72.readFileSync(specPath, "utf-8");
|
|
81858
82811
|
} catch (readError) {
|
|
81859
82812
|
return JSON.stringify({
|
|
81860
82813
|
success: false,
|
|
@@ -81878,7 +82831,7 @@ var req_coverage = createSwarmTool({
|
|
|
81878
82831
|
message: "No FR requirements found in spec.md"
|
|
81879
82832
|
}, null, 2);
|
|
81880
82833
|
}
|
|
81881
|
-
const evidenceDir =
|
|
82834
|
+
const evidenceDir = path87.join(cwd, EVIDENCE_DIR4);
|
|
81882
82835
|
const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
|
|
81883
82836
|
const analyzedRequirements = [];
|
|
81884
82837
|
let coveredCount = 0;
|
|
@@ -81904,12 +82857,12 @@ var req_coverage = createSwarmTool({
|
|
|
81904
82857
|
requirements: analyzedRequirements
|
|
81905
82858
|
};
|
|
81906
82859
|
const reportFilename = `req-coverage-phase-${phase}.json`;
|
|
81907
|
-
const reportPath =
|
|
82860
|
+
const reportPath = path87.join(evidenceDir, reportFilename);
|
|
81908
82861
|
try {
|
|
81909
|
-
if (!
|
|
81910
|
-
|
|
82862
|
+
if (!fs72.existsSync(evidenceDir)) {
|
|
82863
|
+
fs72.mkdirSync(evidenceDir, { recursive: true });
|
|
81911
82864
|
}
|
|
81912
|
-
|
|
82865
|
+
fs72.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
|
|
81913
82866
|
} catch (writeError) {
|
|
81914
82867
|
console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
81915
82868
|
}
|
|
@@ -81988,6 +82941,7 @@ ${paginatedContent}`;
|
|
|
81988
82941
|
// src/tools/save-plan.ts
|
|
81989
82942
|
init_tool();
|
|
81990
82943
|
init_plan_schema();
|
|
82944
|
+
init_qa_gate_profile();
|
|
81991
82945
|
init_file_locks();
|
|
81992
82946
|
init_checkpoint3();
|
|
81993
82947
|
init_ledger();
|
|
@@ -81995,8 +82949,8 @@ init_manager();
|
|
|
81995
82949
|
init_state();
|
|
81996
82950
|
init_create_tool();
|
|
81997
82951
|
import * as crypto9 from "crypto";
|
|
81998
|
-
import * as
|
|
81999
|
-
import * as
|
|
82952
|
+
import * as fs73 from "fs";
|
|
82953
|
+
import * as path88 from "path";
|
|
82000
82954
|
function detectPlaceholderContent(args2) {
|
|
82001
82955
|
const issues = [];
|
|
82002
82956
|
const placeholderPattern = /^\[\w[\w\s]*\]$/;
|
|
@@ -82070,17 +83024,17 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82070
83024
|
};
|
|
82071
83025
|
}
|
|
82072
83026
|
if (args2.working_directory && fallbackDir) {
|
|
82073
|
-
const resolvedTarget =
|
|
82074
|
-
const resolvedRoot =
|
|
83027
|
+
const resolvedTarget = path88.resolve(args2.working_directory);
|
|
83028
|
+
const resolvedRoot = path88.resolve(fallbackDir);
|
|
82075
83029
|
let fallbackExists = false;
|
|
82076
83030
|
try {
|
|
82077
|
-
|
|
83031
|
+
fs73.accessSync(resolvedRoot, fs73.constants.F_OK);
|
|
82078
83032
|
fallbackExists = true;
|
|
82079
83033
|
} catch {
|
|
82080
83034
|
fallbackExists = false;
|
|
82081
83035
|
}
|
|
82082
83036
|
if (fallbackExists) {
|
|
82083
|
-
const isSubdirectory = resolvedTarget.startsWith(resolvedRoot +
|
|
83037
|
+
const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path88.sep);
|
|
82084
83038
|
if (isSubdirectory) {
|
|
82085
83039
|
return {
|
|
82086
83040
|
success: false,
|
|
@@ -82096,11 +83050,11 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82096
83050
|
let specMtime;
|
|
82097
83051
|
let specHash;
|
|
82098
83052
|
if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
|
|
82099
|
-
const specPath =
|
|
83053
|
+
const specPath = path88.join(targetWorkspace, ".swarm", "spec.md");
|
|
82100
83054
|
try {
|
|
82101
|
-
const stat4 = await
|
|
83055
|
+
const stat4 = await fs73.promises.stat(specPath);
|
|
82102
83056
|
specMtime = stat4.mtime.toISOString();
|
|
82103
|
-
const content = await
|
|
83057
|
+
const content = await fs73.promises.readFile(specPath, "utf8");
|
|
82104
83058
|
specHash = crypto9.createHash("sha256").update(content).digest("hex");
|
|
82105
83059
|
} catch {
|
|
82106
83060
|
return {
|
|
@@ -82111,6 +83065,32 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82111
83065
|
};
|
|
82112
83066
|
}
|
|
82113
83067
|
}
|
|
83068
|
+
if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
|
|
83069
|
+
const contextPath = path88.join(targetWorkspace, ".swarm", "context.md");
|
|
83070
|
+
let contextContent = "";
|
|
83071
|
+
try {
|
|
83072
|
+
contextContent = await fs73.promises.readFile(contextPath, "utf8");
|
|
83073
|
+
} catch {}
|
|
83074
|
+
const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
|
|
83075
|
+
if (!hasPendingSection) {
|
|
83076
|
+
const candidatePlanId = `${args2.swarm_id}-${args2.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
83077
|
+
let existingProfile = null;
|
|
83078
|
+
try {
|
|
83079
|
+
existingProfile = getProfile(targetWorkspace, candidatePlanId);
|
|
83080
|
+
} catch {}
|
|
83081
|
+
if (!existingProfile) {
|
|
83082
|
+
return {
|
|
83083
|
+
success: false,
|
|
83084
|
+
message: "QA_GATE_SELECTION_REQUIRED: QA gate selection has not been completed. " + "Present the gate selection question to the user (step 5b of MODE: SPECIFY " + "or Phase 6 of MODE: BRAINSTORM), write their response to .swarm/context.md " + 'under "## Pending QA Gate Selection", then retry save_plan.',
|
|
83085
|
+
errors: [
|
|
83086
|
+
"Missing ## Pending QA Gate Selection in .swarm/context.md",
|
|
83087
|
+
"No existing QaGateProfile found for this plan"
|
|
83088
|
+
],
|
|
83089
|
+
recovery_guidance: "Do not call set_qa_gates with assumed values. Present the gate dialogue, " + "wait for the user's answer, write it to context.md, then retry save_plan."
|
|
83090
|
+
};
|
|
83091
|
+
}
|
|
83092
|
+
}
|
|
83093
|
+
}
|
|
82114
83094
|
const dir = targetWorkspace;
|
|
82115
83095
|
const existingStatusMap = new Map;
|
|
82116
83096
|
let preservedExecutionProfile;
|
|
@@ -82236,14 +83216,14 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82236
83216
|
}
|
|
82237
83217
|
await writeCheckpoint(dir).catch(() => {});
|
|
82238
83218
|
try {
|
|
82239
|
-
const markerPath =
|
|
83219
|
+
const markerPath = path88.join(dir, ".swarm", ".plan-write-marker");
|
|
82240
83220
|
const marker = JSON.stringify({
|
|
82241
83221
|
source: "save_plan",
|
|
82242
83222
|
timestamp: new Date().toISOString(),
|
|
82243
83223
|
phases_count: plan.phases.length,
|
|
82244
83224
|
tasks_count: tasksCount
|
|
82245
83225
|
});
|
|
82246
|
-
await
|
|
83226
|
+
await fs73.promises.writeFile(markerPath, marker, "utf8");
|
|
82247
83227
|
} catch {}
|
|
82248
83228
|
const warnings = [];
|
|
82249
83229
|
let criticReviewFound = false;
|
|
@@ -82259,7 +83239,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82259
83239
|
return {
|
|
82260
83240
|
success: true,
|
|
82261
83241
|
message: "Plan saved successfully",
|
|
82262
|
-
plan_path:
|
|
83242
|
+
plan_path: path88.join(dir, ".swarm", "plan.json"),
|
|
82263
83243
|
phases_count: plan.phases.length,
|
|
82264
83244
|
tasks_count: tasksCount,
|
|
82265
83245
|
...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
|
|
@@ -82311,8 +83291,8 @@ var save_plan = createSwarmTool({
|
|
|
82311
83291
|
// src/tools/sbom-generate.ts
|
|
82312
83292
|
init_dist();
|
|
82313
83293
|
init_manager2();
|
|
82314
|
-
import * as
|
|
82315
|
-
import * as
|
|
83294
|
+
import * as fs74 from "fs";
|
|
83295
|
+
import * as path89 from "path";
|
|
82316
83296
|
|
|
82317
83297
|
// src/sbom/detectors/index.ts
|
|
82318
83298
|
init_utils();
|
|
@@ -83160,9 +84140,9 @@ function findManifestFiles(rootDir) {
|
|
|
83160
84140
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
83161
84141
|
function searchDir(dir) {
|
|
83162
84142
|
try {
|
|
83163
|
-
const entries =
|
|
84143
|
+
const entries = fs74.readdirSync(dir, { withFileTypes: true });
|
|
83164
84144
|
for (const entry of entries) {
|
|
83165
|
-
const fullPath =
|
|
84145
|
+
const fullPath = path89.join(dir, entry.name);
|
|
83166
84146
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
83167
84147
|
continue;
|
|
83168
84148
|
}
|
|
@@ -83171,7 +84151,7 @@ function findManifestFiles(rootDir) {
|
|
|
83171
84151
|
} else if (entry.isFile()) {
|
|
83172
84152
|
for (const pattern of patterns) {
|
|
83173
84153
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
83174
|
-
manifestFiles.push(
|
|
84154
|
+
manifestFiles.push(path89.relative(rootDir, fullPath));
|
|
83175
84155
|
break;
|
|
83176
84156
|
}
|
|
83177
84157
|
}
|
|
@@ -83187,13 +84167,13 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
83187
84167
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
83188
84168
|
for (const dir of directories) {
|
|
83189
84169
|
try {
|
|
83190
|
-
const entries =
|
|
84170
|
+
const entries = fs74.readdirSync(dir, { withFileTypes: true });
|
|
83191
84171
|
for (const entry of entries) {
|
|
83192
|
-
const fullPath =
|
|
84172
|
+
const fullPath = path89.join(dir, entry.name);
|
|
83193
84173
|
if (entry.isFile()) {
|
|
83194
84174
|
for (const pattern of patterns) {
|
|
83195
84175
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
83196
|
-
found.push(
|
|
84176
|
+
found.push(path89.relative(workingDir, fullPath));
|
|
83197
84177
|
break;
|
|
83198
84178
|
}
|
|
83199
84179
|
}
|
|
@@ -83206,11 +84186,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
83206
84186
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
83207
84187
|
const dirs = new Set;
|
|
83208
84188
|
for (const file3 of changedFiles) {
|
|
83209
|
-
let currentDir =
|
|
84189
|
+
let currentDir = path89.dirname(file3);
|
|
83210
84190
|
while (true) {
|
|
83211
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
83212
|
-
dirs.add(
|
|
83213
|
-
const parent =
|
|
84191
|
+
if (currentDir && currentDir !== "." && currentDir !== path89.sep) {
|
|
84192
|
+
dirs.add(path89.join(workingDir, currentDir));
|
|
84193
|
+
const parent = path89.dirname(currentDir);
|
|
83214
84194
|
if (parent === currentDir)
|
|
83215
84195
|
break;
|
|
83216
84196
|
currentDir = parent;
|
|
@@ -83224,7 +84204,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
83224
84204
|
}
|
|
83225
84205
|
function ensureOutputDir(outputDir) {
|
|
83226
84206
|
try {
|
|
83227
|
-
|
|
84207
|
+
fs74.mkdirSync(outputDir, { recursive: true });
|
|
83228
84208
|
} catch (error93) {
|
|
83229
84209
|
if (!error93 || error93.code !== "EEXIST") {
|
|
83230
84210
|
throw error93;
|
|
@@ -83294,7 +84274,7 @@ var sbom_generate = createSwarmTool({
|
|
|
83294
84274
|
const changedFiles = obj.changed_files;
|
|
83295
84275
|
const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
|
|
83296
84276
|
const workingDir = directory;
|
|
83297
|
-
const outputDir =
|
|
84277
|
+
const outputDir = path89.isAbsolute(relativeOutputDir) ? relativeOutputDir : path89.join(workingDir, relativeOutputDir);
|
|
83298
84278
|
let manifestFiles = [];
|
|
83299
84279
|
if (scope === "all") {
|
|
83300
84280
|
manifestFiles = findManifestFiles(workingDir);
|
|
@@ -83317,11 +84297,11 @@ var sbom_generate = createSwarmTool({
|
|
|
83317
84297
|
const processedFiles = [];
|
|
83318
84298
|
for (const manifestFile of manifestFiles) {
|
|
83319
84299
|
try {
|
|
83320
|
-
const fullPath =
|
|
83321
|
-
if (!
|
|
84300
|
+
const fullPath = path89.isAbsolute(manifestFile) ? manifestFile : path89.join(workingDir, manifestFile);
|
|
84301
|
+
if (!fs74.existsSync(fullPath)) {
|
|
83322
84302
|
continue;
|
|
83323
84303
|
}
|
|
83324
|
-
const content =
|
|
84304
|
+
const content = fs74.readFileSync(fullPath, "utf-8");
|
|
83325
84305
|
const components = detectComponents(manifestFile, content);
|
|
83326
84306
|
processedFiles.push(manifestFile);
|
|
83327
84307
|
if (components.length > 0) {
|
|
@@ -83334,8 +84314,8 @@ var sbom_generate = createSwarmTool({
|
|
|
83334
84314
|
const bom = generateCycloneDX(allComponents);
|
|
83335
84315
|
const bomJson = serializeCycloneDX(bom);
|
|
83336
84316
|
const filename = generateSbomFilename();
|
|
83337
|
-
const outputPath =
|
|
83338
|
-
|
|
84317
|
+
const outputPath = path89.join(outputDir, filename);
|
|
84318
|
+
fs74.writeFileSync(outputPath, bomJson, "utf-8");
|
|
83339
84319
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
83340
84320
|
try {
|
|
83341
84321
|
const timestamp = new Date().toISOString();
|
|
@@ -83377,8 +84357,8 @@ var sbom_generate = createSwarmTool({
|
|
|
83377
84357
|
// src/tools/schema-drift.ts
|
|
83378
84358
|
init_dist();
|
|
83379
84359
|
init_create_tool();
|
|
83380
|
-
import * as
|
|
83381
|
-
import * as
|
|
84360
|
+
import * as fs75 from "fs";
|
|
84361
|
+
import * as path90 from "path";
|
|
83382
84362
|
var SPEC_CANDIDATES = [
|
|
83383
84363
|
"openapi.json",
|
|
83384
84364
|
"openapi.yaml",
|
|
@@ -83410,28 +84390,28 @@ function normalizePath3(p) {
|
|
|
83410
84390
|
}
|
|
83411
84391
|
function discoverSpecFile(cwd, specFileArg) {
|
|
83412
84392
|
if (specFileArg) {
|
|
83413
|
-
const resolvedPath =
|
|
83414
|
-
const normalizedCwd = cwd.endsWith(
|
|
84393
|
+
const resolvedPath = path90.resolve(cwd, specFileArg);
|
|
84394
|
+
const normalizedCwd = cwd.endsWith(path90.sep) ? cwd : cwd + path90.sep;
|
|
83415
84395
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
83416
84396
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
83417
84397
|
}
|
|
83418
|
-
const ext =
|
|
84398
|
+
const ext = path90.extname(resolvedPath).toLowerCase();
|
|
83419
84399
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
83420
84400
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
83421
84401
|
}
|
|
83422
|
-
const stats =
|
|
84402
|
+
const stats = fs75.statSync(resolvedPath);
|
|
83423
84403
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
83424
84404
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
83425
84405
|
}
|
|
83426
|
-
if (!
|
|
84406
|
+
if (!fs75.existsSync(resolvedPath)) {
|
|
83427
84407
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
83428
84408
|
}
|
|
83429
84409
|
return resolvedPath;
|
|
83430
84410
|
}
|
|
83431
84411
|
for (const candidate of SPEC_CANDIDATES) {
|
|
83432
|
-
const candidatePath =
|
|
83433
|
-
if (
|
|
83434
|
-
const stats =
|
|
84412
|
+
const candidatePath = path90.resolve(cwd, candidate);
|
|
84413
|
+
if (fs75.existsSync(candidatePath)) {
|
|
84414
|
+
const stats = fs75.statSync(candidatePath);
|
|
83435
84415
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
83436
84416
|
return candidatePath;
|
|
83437
84417
|
}
|
|
@@ -83440,8 +84420,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
83440
84420
|
return null;
|
|
83441
84421
|
}
|
|
83442
84422
|
function parseSpec(specFile) {
|
|
83443
|
-
const content =
|
|
83444
|
-
const ext =
|
|
84423
|
+
const content = fs75.readFileSync(specFile, "utf-8");
|
|
84424
|
+
const ext = path90.extname(specFile).toLowerCase();
|
|
83445
84425
|
if (ext === ".json") {
|
|
83446
84426
|
return parseJsonSpec(content);
|
|
83447
84427
|
}
|
|
@@ -83512,12 +84492,12 @@ function extractRoutes(cwd) {
|
|
|
83512
84492
|
function walkDir(dir) {
|
|
83513
84493
|
let entries;
|
|
83514
84494
|
try {
|
|
83515
|
-
entries =
|
|
84495
|
+
entries = fs75.readdirSync(dir, { withFileTypes: true });
|
|
83516
84496
|
} catch {
|
|
83517
84497
|
return;
|
|
83518
84498
|
}
|
|
83519
84499
|
for (const entry of entries) {
|
|
83520
|
-
const fullPath =
|
|
84500
|
+
const fullPath = path90.join(dir, entry.name);
|
|
83521
84501
|
if (entry.isSymbolicLink()) {
|
|
83522
84502
|
continue;
|
|
83523
84503
|
}
|
|
@@ -83527,7 +84507,7 @@ function extractRoutes(cwd) {
|
|
|
83527
84507
|
}
|
|
83528
84508
|
walkDir(fullPath);
|
|
83529
84509
|
} else if (entry.isFile()) {
|
|
83530
|
-
const ext =
|
|
84510
|
+
const ext = path90.extname(entry.name).toLowerCase();
|
|
83531
84511
|
const baseName = entry.name.toLowerCase();
|
|
83532
84512
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
83533
84513
|
continue;
|
|
@@ -83545,7 +84525,7 @@ function extractRoutes(cwd) {
|
|
|
83545
84525
|
}
|
|
83546
84526
|
function extractRoutesFromFile(filePath) {
|
|
83547
84527
|
const routes = [];
|
|
83548
|
-
const content =
|
|
84528
|
+
const content = fs75.readFileSync(filePath, "utf-8");
|
|
83549
84529
|
const lines = content.split(/\r?\n/);
|
|
83550
84530
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
83551
84531
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -83693,8 +84673,8 @@ var schema_drift = createSwarmTool({
|
|
|
83693
84673
|
init_tool();
|
|
83694
84674
|
init_path_security();
|
|
83695
84675
|
init_create_tool();
|
|
83696
|
-
import * as
|
|
83697
|
-
import * as
|
|
84676
|
+
import * as fs76 from "fs";
|
|
84677
|
+
import * as path91 from "path";
|
|
83698
84678
|
var DEFAULT_MAX_RESULTS = 100;
|
|
83699
84679
|
var DEFAULT_MAX_LINES = 200;
|
|
83700
84680
|
var REGEX_TIMEOUT_MS = 5000;
|
|
@@ -83730,11 +84710,11 @@ function containsWindowsAttacks3(str) {
|
|
|
83730
84710
|
}
|
|
83731
84711
|
function isPathInWorkspace3(filePath, workspace) {
|
|
83732
84712
|
try {
|
|
83733
|
-
const resolvedPath =
|
|
83734
|
-
const realWorkspace =
|
|
83735
|
-
const realResolvedPath =
|
|
83736
|
-
const relativePath =
|
|
83737
|
-
if (relativePath.startsWith("..") ||
|
|
84713
|
+
const resolvedPath = path91.resolve(workspace, filePath);
|
|
84714
|
+
const realWorkspace = fs76.realpathSync(workspace);
|
|
84715
|
+
const realResolvedPath = fs76.realpathSync(resolvedPath);
|
|
84716
|
+
const relativePath = path91.relative(realWorkspace, realResolvedPath);
|
|
84717
|
+
if (relativePath.startsWith("..") || path91.isAbsolute(relativePath)) {
|
|
83738
84718
|
return false;
|
|
83739
84719
|
}
|
|
83740
84720
|
return true;
|
|
@@ -83747,12 +84727,12 @@ function validatePathForRead2(filePath, workspace) {
|
|
|
83747
84727
|
}
|
|
83748
84728
|
function findRgInEnvPath() {
|
|
83749
84729
|
const searchPath = process.env.PATH ?? "";
|
|
83750
|
-
for (const dir of searchPath.split(
|
|
84730
|
+
for (const dir of searchPath.split(path91.delimiter)) {
|
|
83751
84731
|
if (!dir)
|
|
83752
84732
|
continue;
|
|
83753
84733
|
const isWindows = process.platform === "win32";
|
|
83754
|
-
const candidate =
|
|
83755
|
-
if (
|
|
84734
|
+
const candidate = path91.join(dir, isWindows ? "rg.exe" : "rg");
|
|
84735
|
+
if (fs76.existsSync(candidate))
|
|
83756
84736
|
return candidate;
|
|
83757
84737
|
}
|
|
83758
84738
|
return null;
|
|
@@ -83879,10 +84859,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
|
|
|
83879
84859
|
return files;
|
|
83880
84860
|
}
|
|
83881
84861
|
try {
|
|
83882
|
-
const entries =
|
|
84862
|
+
const entries = fs76.readdirSync(dir, { withFileTypes: true });
|
|
83883
84863
|
for (const entry of entries) {
|
|
83884
|
-
const fullPath =
|
|
83885
|
-
const relativePath =
|
|
84864
|
+
const fullPath = path91.join(dir, entry.name);
|
|
84865
|
+
const relativePath = path91.relative(workspace, fullPath);
|
|
83886
84866
|
if (!validatePathForRead2(fullPath, workspace)) {
|
|
83887
84867
|
continue;
|
|
83888
84868
|
}
|
|
@@ -83923,13 +84903,13 @@ async function fallbackSearch(opts) {
|
|
|
83923
84903
|
const matches = [];
|
|
83924
84904
|
let total = 0;
|
|
83925
84905
|
for (const file3 of files) {
|
|
83926
|
-
const fullPath =
|
|
84906
|
+
const fullPath = path91.join(opts.workspace, file3);
|
|
83927
84907
|
if (!validatePathForRead2(fullPath, opts.workspace)) {
|
|
83928
84908
|
continue;
|
|
83929
84909
|
}
|
|
83930
84910
|
let stats;
|
|
83931
84911
|
try {
|
|
83932
|
-
stats =
|
|
84912
|
+
stats = fs76.statSync(fullPath);
|
|
83933
84913
|
if (stats.size > MAX_FILE_SIZE_BYTES10) {
|
|
83934
84914
|
continue;
|
|
83935
84915
|
}
|
|
@@ -83938,7 +84918,7 @@ async function fallbackSearch(opts) {
|
|
|
83938
84918
|
}
|
|
83939
84919
|
let content;
|
|
83940
84920
|
try {
|
|
83941
|
-
content =
|
|
84921
|
+
content = fs76.readFileSync(fullPath, "utf-8");
|
|
83942
84922
|
} catch {
|
|
83943
84923
|
continue;
|
|
83944
84924
|
}
|
|
@@ -84050,7 +85030,7 @@ var search = createSwarmTool({
|
|
|
84050
85030
|
message: "Exclude pattern contains invalid Windows-specific sequence"
|
|
84051
85031
|
}, null, 2);
|
|
84052
85032
|
}
|
|
84053
|
-
if (!
|
|
85033
|
+
if (!fs76.existsSync(directory)) {
|
|
84054
85034
|
return JSON.stringify({
|
|
84055
85035
|
error: true,
|
|
84056
85036
|
type: "unknown",
|
|
@@ -84118,7 +85098,8 @@ async function executeSetQaGates(args2, directory) {
|
|
|
84118
85098
|
"critic_pre_plan",
|
|
84119
85099
|
"hallucination_guard",
|
|
84120
85100
|
"sast_enabled",
|
|
84121
|
-
"mutation_test"
|
|
85101
|
+
"mutation_test",
|
|
85102
|
+
"council_general_review"
|
|
84122
85103
|
]) {
|
|
84123
85104
|
if (args2[key] !== undefined)
|
|
84124
85105
|
partial3[key] = args2[key];
|
|
@@ -84164,6 +85145,7 @@ var set_qa_gates = createSwarmTool({
|
|
|
84164
85145
|
hallucination_guard: tool.schema.boolean().optional().describe("Enable hallucination_guard checks on plan and implementation claims."),
|
|
84165
85146
|
sast_enabled: tool.schema.boolean().optional().describe("Enable SAST scanning as a required QA gate."),
|
|
84166
85147
|
mutation_test: tool.schema.boolean().optional().describe("Enable the mutation-testing gate (default: off). Requires mutation " + "tests to achieve a passing kill rate before phase completion; " + "WARN verdict allows advancement, FAIL blocks."),
|
|
85148
|
+
council_general_review: tool.schema.boolean().optional().describe("Enable the council_general_review gate (default: off). When on, " + "MODE: SPECIFY runs convene_general_council on the draft spec " + "before the critic-gate, folding multi-model deliberation into " + "the spec. Requires council.general.enabled and a search API key."),
|
|
84167
85149
|
project_type: tool.schema.string().optional().describe('Project type label (e.g. "ts", "python"). Only applied when the profile is being created for the first time.')
|
|
84168
85150
|
},
|
|
84169
85151
|
execute: async (args2, directory) => {
|
|
@@ -84175,8 +85157,8 @@ var set_qa_gates = createSwarmTool({
|
|
|
84175
85157
|
init_tool();
|
|
84176
85158
|
init_path_security();
|
|
84177
85159
|
init_create_tool();
|
|
84178
|
-
import * as
|
|
84179
|
-
import * as
|
|
85160
|
+
import * as fs77 from "fs";
|
|
85161
|
+
import * as path92 from "path";
|
|
84180
85162
|
var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
84181
85163
|
function containsWindowsAttacks4(str) {
|
|
84182
85164
|
if (/:[^\\/]/.test(str))
|
|
@@ -84190,14 +85172,14 @@ function containsWindowsAttacks4(str) {
|
|
|
84190
85172
|
}
|
|
84191
85173
|
function isPathInWorkspace4(filePath, workspace) {
|
|
84192
85174
|
try {
|
|
84193
|
-
const resolvedPath =
|
|
84194
|
-
if (!
|
|
85175
|
+
const resolvedPath = path92.resolve(workspace, filePath);
|
|
85176
|
+
if (!fs77.existsSync(resolvedPath)) {
|
|
84195
85177
|
return true;
|
|
84196
85178
|
}
|
|
84197
|
-
const realWorkspace =
|
|
84198
|
-
const realResolvedPath =
|
|
84199
|
-
const relativePath =
|
|
84200
|
-
if (relativePath.startsWith("..") ||
|
|
85179
|
+
const realWorkspace = fs77.realpathSync(workspace);
|
|
85180
|
+
const realResolvedPath = fs77.realpathSync(resolvedPath);
|
|
85181
|
+
const relativePath = path92.relative(realWorkspace, realResolvedPath);
|
|
85182
|
+
if (relativePath.startsWith("..") || path92.isAbsolute(relativePath)) {
|
|
84201
85183
|
return false;
|
|
84202
85184
|
}
|
|
84203
85185
|
return true;
|
|
@@ -84369,7 +85351,7 @@ var suggestPatch = createSwarmTool({
|
|
|
84369
85351
|
message: "changes cannot be empty"
|
|
84370
85352
|
}, null, 2);
|
|
84371
85353
|
}
|
|
84372
|
-
if (!
|
|
85354
|
+
if (!fs77.existsSync(directory)) {
|
|
84373
85355
|
return JSON.stringify({
|
|
84374
85356
|
success: false,
|
|
84375
85357
|
error: true,
|
|
@@ -84405,8 +85387,8 @@ var suggestPatch = createSwarmTool({
|
|
|
84405
85387
|
});
|
|
84406
85388
|
continue;
|
|
84407
85389
|
}
|
|
84408
|
-
const fullPath =
|
|
84409
|
-
if (!
|
|
85390
|
+
const fullPath = path92.resolve(directory, change.file);
|
|
85391
|
+
if (!fs77.existsSync(fullPath)) {
|
|
84410
85392
|
errors5.push({
|
|
84411
85393
|
success: false,
|
|
84412
85394
|
error: true,
|
|
@@ -84420,7 +85402,7 @@ var suggestPatch = createSwarmTool({
|
|
|
84420
85402
|
}
|
|
84421
85403
|
let content;
|
|
84422
85404
|
try {
|
|
84423
|
-
content =
|
|
85405
|
+
content = fs77.readFileSync(fullPath, "utf-8");
|
|
84424
85406
|
} catch (err3) {
|
|
84425
85407
|
errors5.push({
|
|
84426
85408
|
success: false,
|
|
@@ -84654,8 +85636,8 @@ var generate_mutants = createSwarmTool({
|
|
|
84654
85636
|
// src/tools/lint-spec.ts
|
|
84655
85637
|
init_spec_schema();
|
|
84656
85638
|
init_create_tool();
|
|
84657
|
-
import * as
|
|
84658
|
-
import * as
|
|
85639
|
+
import * as fs78 from "fs";
|
|
85640
|
+
import * as path93 from "path";
|
|
84659
85641
|
var SPEC_FILE_NAME = "spec.md";
|
|
84660
85642
|
var SWARM_DIR2 = ".swarm";
|
|
84661
85643
|
var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
|
|
@@ -84708,8 +85690,8 @@ var lint_spec = createSwarmTool({
|
|
|
84708
85690
|
async execute(_args, directory) {
|
|
84709
85691
|
const errors5 = [];
|
|
84710
85692
|
const warnings = [];
|
|
84711
|
-
const specPath =
|
|
84712
|
-
if (!
|
|
85693
|
+
const specPath = path93.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
|
|
85694
|
+
if (!fs78.existsSync(specPath)) {
|
|
84713
85695
|
const result2 = {
|
|
84714
85696
|
valid: false,
|
|
84715
85697
|
specMtime: null,
|
|
@@ -84728,12 +85710,12 @@ var lint_spec = createSwarmTool({
|
|
|
84728
85710
|
}
|
|
84729
85711
|
let specMtime = null;
|
|
84730
85712
|
try {
|
|
84731
|
-
const stats =
|
|
85713
|
+
const stats = fs78.statSync(specPath);
|
|
84732
85714
|
specMtime = stats.mtime.toISOString();
|
|
84733
85715
|
} catch {}
|
|
84734
85716
|
let content;
|
|
84735
85717
|
try {
|
|
84736
|
-
content =
|
|
85718
|
+
content = fs78.readFileSync(specPath, "utf-8");
|
|
84737
85719
|
} catch (e) {
|
|
84738
85720
|
const result2 = {
|
|
84739
85721
|
valid: false,
|
|
@@ -84778,13 +85760,13 @@ var lint_spec = createSwarmTool({
|
|
|
84778
85760
|
});
|
|
84779
85761
|
// src/tools/mutation-test.ts
|
|
84780
85762
|
init_dist();
|
|
84781
|
-
import * as
|
|
84782
|
-
import * as
|
|
85763
|
+
import * as fs79 from "fs";
|
|
85764
|
+
import * as path95 from "path";
|
|
84783
85765
|
|
|
84784
85766
|
// src/mutation/engine.ts
|
|
84785
85767
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
84786
85768
|
import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
|
|
84787
|
-
import * as
|
|
85769
|
+
import * as path94 from "path";
|
|
84788
85770
|
|
|
84789
85771
|
// src/mutation/equivalence.ts
|
|
84790
85772
|
function isStaticallyEquivalent(originalCode, mutatedCode) {
|
|
@@ -84919,7 +85901,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
|
|
|
84919
85901
|
let patchFile;
|
|
84920
85902
|
try {
|
|
84921
85903
|
const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
84922
|
-
patchFile =
|
|
85904
|
+
patchFile = path94.join(workingDir, `.mutation_patch_${safeId2}.diff`);
|
|
84923
85905
|
try {
|
|
84924
85906
|
writeFileSync18(patchFile, patch.patch);
|
|
84925
85907
|
} catch (writeErr) {
|
|
@@ -85313,8 +86295,8 @@ var mutation_test = createSwarmTool({
|
|
|
85313
86295
|
];
|
|
85314
86296
|
for (const filePath of uniquePaths) {
|
|
85315
86297
|
try {
|
|
85316
|
-
const resolvedPath =
|
|
85317
|
-
sourceFiles.set(filePath,
|
|
86298
|
+
const resolvedPath = path95.resolve(cwd, filePath);
|
|
86299
|
+
sourceFiles.set(filePath, fs79.readFileSync(resolvedPath, "utf-8"));
|
|
85318
86300
|
} catch {}
|
|
85319
86301
|
}
|
|
85320
86302
|
const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
|
|
@@ -85332,8 +86314,8 @@ var mutation_test = createSwarmTool({
|
|
|
85332
86314
|
init_dist();
|
|
85333
86315
|
init_manager2();
|
|
85334
86316
|
init_detector();
|
|
85335
|
-
import * as
|
|
85336
|
-
import * as
|
|
86317
|
+
import * as fs80 from "fs";
|
|
86318
|
+
import * as path96 from "path";
|
|
85337
86319
|
init_create_tool();
|
|
85338
86320
|
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
85339
86321
|
var BINARY_CHECK_BYTES = 8192;
|
|
@@ -85399,7 +86381,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85399
86381
|
if (languages?.length) {
|
|
85400
86382
|
const lowerLangs = languages.map((l) => l.toLowerCase());
|
|
85401
86383
|
filesToCheck = filesToCheck.filter((file3) => {
|
|
85402
|
-
const ext =
|
|
86384
|
+
const ext = path96.extname(file3.path).toLowerCase();
|
|
85403
86385
|
const langDef = getLanguageForExtension(ext);
|
|
85404
86386
|
const fileProfile = getProfileForFile(file3.path);
|
|
85405
86387
|
const langId = fileProfile?.id || langDef?.id;
|
|
@@ -85412,7 +86394,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85412
86394
|
let skippedCount = 0;
|
|
85413
86395
|
for (const fileInfo of filesToCheck) {
|
|
85414
86396
|
const { path: filePath } = fileInfo;
|
|
85415
|
-
const fullPath =
|
|
86397
|
+
const fullPath = path96.isAbsolute(filePath) ? filePath : path96.join(directory, filePath);
|
|
85416
86398
|
const result = {
|
|
85417
86399
|
path: filePath,
|
|
85418
86400
|
language: "",
|
|
@@ -85442,7 +86424,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85442
86424
|
}
|
|
85443
86425
|
let content;
|
|
85444
86426
|
try {
|
|
85445
|
-
content =
|
|
86427
|
+
content = fs80.readFileSync(fullPath, "utf8");
|
|
85446
86428
|
} catch {
|
|
85447
86429
|
result.skipped_reason = "file_read_error";
|
|
85448
86430
|
skippedCount++;
|
|
@@ -85461,7 +86443,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85461
86443
|
results.push(result);
|
|
85462
86444
|
continue;
|
|
85463
86445
|
}
|
|
85464
|
-
const ext =
|
|
86446
|
+
const ext = path96.extname(filePath).toLowerCase();
|
|
85465
86447
|
const langDef = getLanguageForExtension(ext);
|
|
85466
86448
|
result.language = profile?.id || langDef?.id || "unknown";
|
|
85467
86449
|
const errors5 = extractSyntaxErrors(parser, content);
|
|
@@ -85553,8 +86535,8 @@ init_dist();
|
|
|
85553
86535
|
init_utils();
|
|
85554
86536
|
init_create_tool();
|
|
85555
86537
|
init_path_security();
|
|
85556
|
-
import * as
|
|
85557
|
-
import * as
|
|
86538
|
+
import * as fs81 from "fs";
|
|
86539
|
+
import * as path97 from "path";
|
|
85558
86540
|
var MAX_TEXT_LENGTH = 200;
|
|
85559
86541
|
var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
|
|
85560
86542
|
var SUPPORTED_EXTENSIONS4 = new Set([
|
|
@@ -85620,9 +86602,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
85620
86602
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
85621
86603
|
}
|
|
85622
86604
|
try {
|
|
85623
|
-
const resolvedPath =
|
|
85624
|
-
const normalizedCwd =
|
|
85625
|
-
const normalizedResolved =
|
|
86605
|
+
const resolvedPath = path97.resolve(paths);
|
|
86606
|
+
const normalizedCwd = path97.resolve(cwd);
|
|
86607
|
+
const normalizedResolved = path97.resolve(resolvedPath);
|
|
85626
86608
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
85627
86609
|
return {
|
|
85628
86610
|
error: "paths must be within the current working directory",
|
|
@@ -85638,13 +86620,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
85638
86620
|
}
|
|
85639
86621
|
}
|
|
85640
86622
|
function isSupportedExtension(filePath) {
|
|
85641
|
-
const ext =
|
|
86623
|
+
const ext = path97.extname(filePath).toLowerCase();
|
|
85642
86624
|
return SUPPORTED_EXTENSIONS4.has(ext);
|
|
85643
86625
|
}
|
|
85644
86626
|
function findSourceFiles4(dir, files = []) {
|
|
85645
86627
|
let entries;
|
|
85646
86628
|
try {
|
|
85647
|
-
entries =
|
|
86629
|
+
entries = fs81.readdirSync(dir);
|
|
85648
86630
|
} catch {
|
|
85649
86631
|
return files;
|
|
85650
86632
|
}
|
|
@@ -85653,10 +86635,10 @@ function findSourceFiles4(dir, files = []) {
|
|
|
85653
86635
|
if (SKIP_DIRECTORIES5.has(entry)) {
|
|
85654
86636
|
continue;
|
|
85655
86637
|
}
|
|
85656
|
-
const fullPath =
|
|
86638
|
+
const fullPath = path97.join(dir, entry);
|
|
85657
86639
|
let stat4;
|
|
85658
86640
|
try {
|
|
85659
|
-
stat4 =
|
|
86641
|
+
stat4 = fs81.statSync(fullPath);
|
|
85660
86642
|
} catch {
|
|
85661
86643
|
continue;
|
|
85662
86644
|
}
|
|
@@ -85749,7 +86731,7 @@ var todo_extract = createSwarmTool({
|
|
|
85749
86731
|
return JSON.stringify(errorResult, null, 2);
|
|
85750
86732
|
}
|
|
85751
86733
|
const scanPath = resolvedPath;
|
|
85752
|
-
if (!
|
|
86734
|
+
if (!fs81.existsSync(scanPath)) {
|
|
85753
86735
|
const errorResult = {
|
|
85754
86736
|
error: `path not found: ${pathsInput}`,
|
|
85755
86737
|
total: 0,
|
|
@@ -85759,13 +86741,13 @@ var todo_extract = createSwarmTool({
|
|
|
85759
86741
|
return JSON.stringify(errorResult, null, 2);
|
|
85760
86742
|
}
|
|
85761
86743
|
const filesToScan = [];
|
|
85762
|
-
const stat4 =
|
|
86744
|
+
const stat4 = fs81.statSync(scanPath);
|
|
85763
86745
|
if (stat4.isFile()) {
|
|
85764
86746
|
if (isSupportedExtension(scanPath)) {
|
|
85765
86747
|
filesToScan.push(scanPath);
|
|
85766
86748
|
} else {
|
|
85767
86749
|
const errorResult = {
|
|
85768
|
-
error: `unsupported file extension: ${
|
|
86750
|
+
error: `unsupported file extension: ${path97.extname(scanPath)}`,
|
|
85769
86751
|
total: 0,
|
|
85770
86752
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
85771
86753
|
entries: []
|
|
@@ -85778,11 +86760,11 @@ var todo_extract = createSwarmTool({
|
|
|
85778
86760
|
const allEntries = [];
|
|
85779
86761
|
for (const filePath of filesToScan) {
|
|
85780
86762
|
try {
|
|
85781
|
-
const fileStat =
|
|
86763
|
+
const fileStat = fs81.statSync(filePath);
|
|
85782
86764
|
if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
|
|
85783
86765
|
continue;
|
|
85784
86766
|
}
|
|
85785
|
-
const content =
|
|
86767
|
+
const content = fs81.readFileSync(filePath, "utf-8");
|
|
85786
86768
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
85787
86769
|
allEntries.push(...entries);
|
|
85788
86770
|
} catch {}
|
|
@@ -85812,18 +86794,18 @@ init_tool();
|
|
|
85812
86794
|
init_loader();
|
|
85813
86795
|
init_schema();
|
|
85814
86796
|
init_gate_evidence();
|
|
85815
|
-
import * as
|
|
85816
|
-
import * as
|
|
86797
|
+
import * as fs83 from "fs";
|
|
86798
|
+
import * as path99 from "path";
|
|
85817
86799
|
|
|
85818
86800
|
// src/hooks/diff-scope.ts
|
|
85819
|
-
import * as
|
|
85820
|
-
import * as
|
|
86801
|
+
import * as fs82 from "fs";
|
|
86802
|
+
import * as path98 from "path";
|
|
85821
86803
|
function getDeclaredScope(taskId, directory) {
|
|
85822
86804
|
try {
|
|
85823
|
-
const planPath =
|
|
85824
|
-
if (!
|
|
86805
|
+
const planPath = path98.join(directory, ".swarm", "plan.json");
|
|
86806
|
+
if (!fs82.existsSync(planPath))
|
|
85825
86807
|
return null;
|
|
85826
|
-
const raw =
|
|
86808
|
+
const raw = fs82.readFileSync(planPath, "utf-8");
|
|
85827
86809
|
const plan = JSON.parse(raw);
|
|
85828
86810
|
for (const phase of plan.phases ?? []) {
|
|
85829
86811
|
for (const task of phase.tasks ?? []) {
|
|
@@ -85939,7 +86921,7 @@ var TIER_3_PATTERNS = [
|
|
|
85939
86921
|
];
|
|
85940
86922
|
function matchesTier3Pattern(files) {
|
|
85941
86923
|
for (const file3 of files) {
|
|
85942
|
-
const fileName =
|
|
86924
|
+
const fileName = path99.basename(file3);
|
|
85943
86925
|
for (const pattern of TIER_3_PATTERNS) {
|
|
85944
86926
|
if (pattern.test(fileName)) {
|
|
85945
86927
|
return true;
|
|
@@ -85953,8 +86935,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
85953
86935
|
if (hasActiveTurboMode()) {
|
|
85954
86936
|
const resolvedDir2 = workingDirectory;
|
|
85955
86937
|
try {
|
|
85956
|
-
const planPath =
|
|
85957
|
-
const planRaw =
|
|
86938
|
+
const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
|
|
86939
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
85958
86940
|
const plan = JSON.parse(planRaw);
|
|
85959
86941
|
for (const planPhase of plan.phases ?? []) {
|
|
85960
86942
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -86023,8 +87005,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
86023
87005
|
}
|
|
86024
87006
|
try {
|
|
86025
87007
|
const resolvedDir2 = workingDirectory;
|
|
86026
|
-
const planPath =
|
|
86027
|
-
const planRaw =
|
|
87008
|
+
const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
|
|
87009
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
86028
87010
|
const plan = JSON.parse(planRaw);
|
|
86029
87011
|
for (const planPhase of plan.phases ?? []) {
|
|
86030
87012
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -86248,8 +87230,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86248
87230
|
};
|
|
86249
87231
|
}
|
|
86250
87232
|
}
|
|
86251
|
-
normalizedDir =
|
|
86252
|
-
const pathParts = normalizedDir.split(
|
|
87233
|
+
normalizedDir = path99.normalize(args2.working_directory);
|
|
87234
|
+
const pathParts = normalizedDir.split(path99.sep);
|
|
86253
87235
|
if (pathParts.includes("..")) {
|
|
86254
87236
|
return {
|
|
86255
87237
|
success: false,
|
|
@@ -86259,11 +87241,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86259
87241
|
]
|
|
86260
87242
|
};
|
|
86261
87243
|
}
|
|
86262
|
-
const resolvedDir =
|
|
87244
|
+
const resolvedDir = path99.resolve(normalizedDir);
|
|
86263
87245
|
try {
|
|
86264
|
-
const realPath =
|
|
86265
|
-
const planPath =
|
|
86266
|
-
if (!
|
|
87246
|
+
const realPath = fs83.realpathSync(resolvedDir);
|
|
87247
|
+
const planPath = path99.join(realPath, ".swarm", "plan.json");
|
|
87248
|
+
if (!fs83.existsSync(planPath)) {
|
|
86267
87249
|
return {
|
|
86268
87250
|
success: false,
|
|
86269
87251
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -86294,22 +87276,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86294
87276
|
}
|
|
86295
87277
|
if (args2.status === "in_progress") {
|
|
86296
87278
|
try {
|
|
86297
|
-
const evidencePath =
|
|
86298
|
-
|
|
86299
|
-
const fd =
|
|
87279
|
+
const evidencePath = path99.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
|
|
87280
|
+
fs83.mkdirSync(path99.dirname(evidencePath), { recursive: true });
|
|
87281
|
+
const fd = fs83.openSync(evidencePath, "wx");
|
|
86300
87282
|
let writeOk = false;
|
|
86301
87283
|
try {
|
|
86302
|
-
|
|
87284
|
+
fs83.writeSync(fd, JSON.stringify({
|
|
86303
87285
|
taskId: args2.task_id,
|
|
86304
87286
|
required_gates: ["reviewer", "test_engineer"],
|
|
86305
87287
|
gates: {}
|
|
86306
87288
|
}, null, 2));
|
|
86307
87289
|
writeOk = true;
|
|
86308
87290
|
} finally {
|
|
86309
|
-
|
|
87291
|
+
fs83.closeSync(fd);
|
|
86310
87292
|
if (!writeOk) {
|
|
86311
87293
|
try {
|
|
86312
|
-
|
|
87294
|
+
fs83.unlinkSync(evidencePath);
|
|
86313
87295
|
} catch {}
|
|
86314
87296
|
}
|
|
86315
87297
|
}
|
|
@@ -86319,8 +87301,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86319
87301
|
recoverTaskStateFromDelegations(args2.task_id);
|
|
86320
87302
|
let phaseRequiresReviewer = true;
|
|
86321
87303
|
try {
|
|
86322
|
-
const planPath =
|
|
86323
|
-
const planRaw =
|
|
87304
|
+
const planPath = path99.join(directory, ".swarm", "plan.json");
|
|
87305
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
86324
87306
|
const plan = JSON.parse(planRaw);
|
|
86325
87307
|
const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
|
|
86326
87308
|
if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
|
|
@@ -86422,6 +87404,216 @@ var update_task_status = createSwarmTool({
|
|
|
86422
87404
|
return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
|
|
86423
87405
|
}
|
|
86424
87406
|
});
|
|
87407
|
+
// src/tools/web-search.ts
|
|
87408
|
+
init_dist();
|
|
87409
|
+
init_zod();
|
|
87410
|
+
init_loader();
|
|
87411
|
+
|
|
87412
|
+
// src/council/web-search-provider.ts
|
|
87413
|
+
class WebSearchError extends Error {
|
|
87414
|
+
cause;
|
|
87415
|
+
constructor(message, cause) {
|
|
87416
|
+
super(message);
|
|
87417
|
+
this.cause = cause;
|
|
87418
|
+
this.name = "WebSearchError";
|
|
87419
|
+
}
|
|
87420
|
+
}
|
|
87421
|
+
|
|
87422
|
+
class WebSearchConfigError extends Error {
|
|
87423
|
+
constructor(message) {
|
|
87424
|
+
super(message);
|
|
87425
|
+
this.name = "WebSearchConfigError";
|
|
87426
|
+
}
|
|
87427
|
+
}
|
|
87428
|
+
|
|
87429
|
+
class TavilyProvider {
|
|
87430
|
+
apiKey;
|
|
87431
|
+
constructor(apiKey) {
|
|
87432
|
+
this.apiKey = apiKey;
|
|
87433
|
+
}
|
|
87434
|
+
async search(query, maxResults) {
|
|
87435
|
+
let response;
|
|
87436
|
+
try {
|
|
87437
|
+
response = await fetch("https://api.tavily.com/search", {
|
|
87438
|
+
method: "POST",
|
|
87439
|
+
headers: { "Content-Type": "application/json" },
|
|
87440
|
+
body: JSON.stringify({
|
|
87441
|
+
api_key: this.apiKey,
|
|
87442
|
+
query,
|
|
87443
|
+
max_results: maxResults,
|
|
87444
|
+
search_depth: "advanced"
|
|
87445
|
+
})
|
|
87446
|
+
});
|
|
87447
|
+
} catch (err3) {
|
|
87448
|
+
throw new WebSearchError(`Tavily network error for query "${query}"`, err3);
|
|
87449
|
+
}
|
|
87450
|
+
if (!response.ok) {
|
|
87451
|
+
throw new WebSearchError(`Tavily HTTP ${response.status} for query "${query}"`);
|
|
87452
|
+
}
|
|
87453
|
+
let body2;
|
|
87454
|
+
try {
|
|
87455
|
+
body2 = await response.json();
|
|
87456
|
+
} catch (err3) {
|
|
87457
|
+
throw new WebSearchError("Tavily returned non-JSON response", err3);
|
|
87458
|
+
}
|
|
87459
|
+
const results = body2?.results;
|
|
87460
|
+
if (!Array.isArray(results)) {
|
|
87461
|
+
return [];
|
|
87462
|
+
}
|
|
87463
|
+
return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.content === "string").map((r) => ({
|
|
87464
|
+
title: r.title,
|
|
87465
|
+
url: r.url,
|
|
87466
|
+
snippet: r.content,
|
|
87467
|
+
query
|
|
87468
|
+
}));
|
|
87469
|
+
}
|
|
87470
|
+
}
|
|
87471
|
+
|
|
87472
|
+
class BraveProvider {
|
|
87473
|
+
apiKey;
|
|
87474
|
+
constructor(apiKey) {
|
|
87475
|
+
this.apiKey = apiKey;
|
|
87476
|
+
}
|
|
87477
|
+
async search(query, maxResults) {
|
|
87478
|
+
const url3 = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
87479
|
+
url3.searchParams.set("q", query);
|
|
87480
|
+
url3.searchParams.set("count", String(maxResults));
|
|
87481
|
+
let response;
|
|
87482
|
+
try {
|
|
87483
|
+
response = await fetch(url3.toString(), {
|
|
87484
|
+
method: "GET",
|
|
87485
|
+
headers: {
|
|
87486
|
+
"X-Subscription-Token": this.apiKey,
|
|
87487
|
+
Accept: "application/json"
|
|
87488
|
+
}
|
|
87489
|
+
});
|
|
87490
|
+
} catch (err3) {
|
|
87491
|
+
throw new WebSearchError(`Brave network error for query "${query}"`, err3);
|
|
87492
|
+
}
|
|
87493
|
+
if (!response.ok) {
|
|
87494
|
+
throw new WebSearchError(`Brave HTTP ${response.status} for query "${query}"`);
|
|
87495
|
+
}
|
|
87496
|
+
let body2;
|
|
87497
|
+
try {
|
|
87498
|
+
body2 = await response.json();
|
|
87499
|
+
} catch (err3) {
|
|
87500
|
+
throw new WebSearchError("Brave returned non-JSON response", err3);
|
|
87501
|
+
}
|
|
87502
|
+
const results = body2?.web?.results;
|
|
87503
|
+
if (!Array.isArray(results)) {
|
|
87504
|
+
return [];
|
|
87505
|
+
}
|
|
87506
|
+
return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.description === "string").map((r) => ({
|
|
87507
|
+
title: r.title,
|
|
87508
|
+
url: r.url,
|
|
87509
|
+
snippet: r.description,
|
|
87510
|
+
query
|
|
87511
|
+
}));
|
|
87512
|
+
}
|
|
87513
|
+
}
|
|
87514
|
+
function resolveApiKey(provider, configKey) {
|
|
87515
|
+
if (configKey && configKey.length > 0) {
|
|
87516
|
+
return configKey;
|
|
87517
|
+
}
|
|
87518
|
+
const envName = provider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
|
|
87519
|
+
const fromEnv = process.env[envName];
|
|
87520
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : undefined;
|
|
87521
|
+
}
|
|
87522
|
+
function createWebSearchProvider(config3) {
|
|
87523
|
+
const apiKey = resolveApiKey(config3.searchProvider, config3.searchApiKey);
|
|
87524
|
+
if (!apiKey) {
|
|
87525
|
+
const envName = config3.searchProvider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
|
|
87526
|
+
throw new WebSearchConfigError(`No API key for search provider "${config3.searchProvider}". Set ` + `council.general.searchApiKey in opencode-swarm.json or export ${envName}.`);
|
|
87527
|
+
}
|
|
87528
|
+
switch (config3.searchProvider) {
|
|
87529
|
+
case "tavily":
|
|
87530
|
+
return new TavilyProvider(apiKey);
|
|
87531
|
+
case "brave":
|
|
87532
|
+
return new BraveProvider(apiKey);
|
|
87533
|
+
}
|
|
87534
|
+
}
|
|
87535
|
+
|
|
87536
|
+
// src/tools/web-search.ts
|
|
87537
|
+
init_create_tool();
|
|
87538
|
+
init_resolve_working_directory();
|
|
87539
|
+
var MAX_RESULTS_HARD_CAP = 10;
|
|
87540
|
+
var ArgsSchema4 = exports_external.object({
|
|
87541
|
+
query: exports_external.string().min(1).max(500),
|
|
87542
|
+
max_results: exports_external.number().int().min(1).max(20).optional(),
|
|
87543
|
+
working_directory: exports_external.string().optional()
|
|
87544
|
+
});
|
|
87545
|
+
var web_search = createSwarmTool({
|
|
87546
|
+
description: "External web search for council member agents. Returns titled results with snippets and URLs. " + "Restricted to council_member agents via AGENT_TOOL_MAP. Requires council.general.enabled and a " + "configured search API key (Tavily or Brave). max_results is capped at 10 with default from council.general.maxSourcesPerMember.",
|
|
87547
|
+
args: {
|
|
87548
|
+
query: tool.schema.string().min(1).max(500).describe("Search query string (1\u2013500 characters)."),
|
|
87549
|
+
max_results: tool.schema.number().int().min(1).max(20).optional().describe(`Number of results to request (1\u201320). Hard-capped at ${MAX_RESULTS_HARD_CAP}. Defaults to council.general.maxSourcesPerMember.`),
|
|
87550
|
+
working_directory: tool.schema.string().optional().describe("Project root for config resolution. Optional.")
|
|
87551
|
+
},
|
|
87552
|
+
execute: async (args2, directory) => {
|
|
87553
|
+
const parsed = ArgsSchema4.safeParse(args2);
|
|
87554
|
+
if (!parsed.success) {
|
|
87555
|
+
const fail = {
|
|
87556
|
+
success: false,
|
|
87557
|
+
reason: "invalid_args",
|
|
87558
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
87559
|
+
};
|
|
87560
|
+
return JSON.stringify(fail, null, 2);
|
|
87561
|
+
}
|
|
87562
|
+
const dirResult = resolveWorkingDirectory(parsed.data.working_directory, directory);
|
|
87563
|
+
if (!dirResult.success) {
|
|
87564
|
+
const fail = {
|
|
87565
|
+
success: false,
|
|
87566
|
+
reason: "invalid_working_directory",
|
|
87567
|
+
message: dirResult.message
|
|
87568
|
+
};
|
|
87569
|
+
return JSON.stringify(fail, null, 2);
|
|
87570
|
+
}
|
|
87571
|
+
const config3 = loadPluginConfig(dirResult.directory);
|
|
87572
|
+
const generalConfig = config3.council?.general;
|
|
87573
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
87574
|
+
const fail = {
|
|
87575
|
+
success: false,
|
|
87576
|
+
reason: "council_general_disabled",
|
|
87577
|
+
message: "web_search is disabled \u2014 set council.general.enabled: true in opencode-swarm.json."
|
|
87578
|
+
};
|
|
87579
|
+
return JSON.stringify(fail, null, 2);
|
|
87580
|
+
}
|
|
87581
|
+
const requested = parsed.data.max_results ?? generalConfig.maxSourcesPerMember;
|
|
87582
|
+
const maxResults = Math.min(requested, MAX_RESULTS_HARD_CAP);
|
|
87583
|
+
let provider;
|
|
87584
|
+
try {
|
|
87585
|
+
provider = createWebSearchProvider(generalConfig);
|
|
87586
|
+
} catch (err3) {
|
|
87587
|
+
const fail = {
|
|
87588
|
+
success: false,
|
|
87589
|
+
reason: err3 instanceof WebSearchConfigError ? "missing_api_key" : "provider_init_failed",
|
|
87590
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
87591
|
+
};
|
|
87592
|
+
return JSON.stringify(fail, null, 2);
|
|
87593
|
+
}
|
|
87594
|
+
try {
|
|
87595
|
+
const results = await provider.search(parsed.data.query, maxResults);
|
|
87596
|
+
const ok2 = {
|
|
87597
|
+
success: true,
|
|
87598
|
+
query: parsed.data.query,
|
|
87599
|
+
totalResults: results.length,
|
|
87600
|
+
results: results.map(({ title, url: url3, snippet }) => ({
|
|
87601
|
+
title,
|
|
87602
|
+
url: url3,
|
|
87603
|
+
snippet
|
|
87604
|
+
}))
|
|
87605
|
+
};
|
|
87606
|
+
return JSON.stringify(ok2, null, 2);
|
|
87607
|
+
} catch (err3) {
|
|
87608
|
+
const fail = {
|
|
87609
|
+
success: false,
|
|
87610
|
+
reason: err3 instanceof WebSearchError ? "search_failed" : "unknown",
|
|
87611
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
87612
|
+
};
|
|
87613
|
+
return JSON.stringify(fail, null, 2);
|
|
87614
|
+
}
|
|
87615
|
+
}
|
|
87616
|
+
});
|
|
86425
87617
|
// src/tools/write-drift-evidence.ts
|
|
86426
87618
|
init_tool();
|
|
86427
87619
|
init_qa_gate_profile();
|
|
@@ -86429,8 +87621,8 @@ init_utils2();
|
|
|
86429
87621
|
init_ledger();
|
|
86430
87622
|
init_manager();
|
|
86431
87623
|
init_create_tool();
|
|
86432
|
-
import
|
|
86433
|
-
import
|
|
87624
|
+
import fs84 from "fs";
|
|
87625
|
+
import path100 from "path";
|
|
86434
87626
|
function derivePlanId5(plan) {
|
|
86435
87627
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
86436
87628
|
}
|
|
@@ -86481,7 +87673,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
86481
87673
|
entries: [evidenceEntry]
|
|
86482
87674
|
};
|
|
86483
87675
|
const filename = "drift-verifier.json";
|
|
86484
|
-
const relativePath =
|
|
87676
|
+
const relativePath = path100.join("evidence", String(phase), filename);
|
|
86485
87677
|
let validatedPath;
|
|
86486
87678
|
try {
|
|
86487
87679
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86492,12 +87684,12 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
86492
87684
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86493
87685
|
}, null, 2);
|
|
86494
87686
|
}
|
|
86495
|
-
const evidenceDir =
|
|
87687
|
+
const evidenceDir = path100.dirname(validatedPath);
|
|
86496
87688
|
try {
|
|
86497
|
-
await
|
|
86498
|
-
const tempPath =
|
|
86499
|
-
await
|
|
86500
|
-
await
|
|
87689
|
+
await fs84.promises.mkdir(evidenceDir, { recursive: true });
|
|
87690
|
+
const tempPath = path100.join(evidenceDir, `.${filename}.tmp`);
|
|
87691
|
+
await fs84.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87692
|
+
await fs84.promises.rename(tempPath, validatedPath);
|
|
86501
87693
|
let snapshotInfo;
|
|
86502
87694
|
let snapshotError;
|
|
86503
87695
|
let qaProfileLocked;
|
|
@@ -86591,8 +87783,8 @@ var write_drift_evidence = createSwarmTool({
|
|
|
86591
87783
|
init_tool();
|
|
86592
87784
|
init_utils2();
|
|
86593
87785
|
init_create_tool();
|
|
86594
|
-
import
|
|
86595
|
-
import
|
|
87786
|
+
import fs85 from "fs";
|
|
87787
|
+
import path101 from "path";
|
|
86596
87788
|
function normalizeVerdict2(verdict) {
|
|
86597
87789
|
switch (verdict) {
|
|
86598
87790
|
case "APPROVED":
|
|
@@ -86640,7 +87832,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
86640
87832
|
entries: [evidenceEntry]
|
|
86641
87833
|
};
|
|
86642
87834
|
const filename = "hallucination-guard.json";
|
|
86643
|
-
const relativePath =
|
|
87835
|
+
const relativePath = path101.join("evidence", String(phase), filename);
|
|
86644
87836
|
let validatedPath;
|
|
86645
87837
|
try {
|
|
86646
87838
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86651,12 +87843,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
86651
87843
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86652
87844
|
}, null, 2);
|
|
86653
87845
|
}
|
|
86654
|
-
const evidenceDir =
|
|
87846
|
+
const evidenceDir = path101.dirname(validatedPath);
|
|
86655
87847
|
try {
|
|
86656
|
-
await
|
|
86657
|
-
const tempPath =
|
|
86658
|
-
await
|
|
86659
|
-
await
|
|
87848
|
+
await fs85.promises.mkdir(evidenceDir, { recursive: true });
|
|
87849
|
+
const tempPath = path101.join(evidenceDir, `.${filename}.tmp`);
|
|
87850
|
+
await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87851
|
+
await fs85.promises.rename(tempPath, validatedPath);
|
|
86660
87852
|
return JSON.stringify({
|
|
86661
87853
|
success: true,
|
|
86662
87854
|
phase,
|
|
@@ -86702,8 +87894,8 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
86702
87894
|
init_tool();
|
|
86703
87895
|
init_utils2();
|
|
86704
87896
|
init_create_tool();
|
|
86705
|
-
import
|
|
86706
|
-
import
|
|
87897
|
+
import fs86 from "fs";
|
|
87898
|
+
import path102 from "path";
|
|
86707
87899
|
function normalizeVerdict3(verdict) {
|
|
86708
87900
|
switch (verdict) {
|
|
86709
87901
|
case "PASS":
|
|
@@ -86777,7 +87969,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
86777
87969
|
entries: [evidenceEntry]
|
|
86778
87970
|
};
|
|
86779
87971
|
const filename = "mutation-gate.json";
|
|
86780
|
-
const relativePath =
|
|
87972
|
+
const relativePath = path102.join("evidence", String(phase), filename);
|
|
86781
87973
|
let validatedPath;
|
|
86782
87974
|
try {
|
|
86783
87975
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86788,12 +87980,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
86788
87980
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86789
87981
|
}, null, 2);
|
|
86790
87982
|
}
|
|
86791
|
-
const evidenceDir =
|
|
87983
|
+
const evidenceDir = path102.dirname(validatedPath);
|
|
86792
87984
|
try {
|
|
86793
|
-
await
|
|
86794
|
-
const tempPath =
|
|
86795
|
-
await
|
|
86796
|
-
await
|
|
87985
|
+
await fs86.promises.mkdir(evidenceDir, { recursive: true });
|
|
87986
|
+
const tempPath = path102.join(evidenceDir, `.${filename}.tmp`);
|
|
87987
|
+
await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87988
|
+
await fs86.promises.rename(tempPath, validatedPath);
|
|
86797
87989
|
return JSON.stringify({
|
|
86798
87990
|
success: true,
|
|
86799
87991
|
phase,
|
|
@@ -87011,7 +88203,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87011
88203
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
87012
88204
|
preflightTriggerManager = new PTM(automationConfig);
|
|
87013
88205
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
87014
|
-
const swarmDir =
|
|
88206
|
+
const swarmDir = path103.resolve(ctx.directory, ".swarm");
|
|
87015
88207
|
statusArtifact = new ASA(swarmDir);
|
|
87016
88208
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
87017
88209
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -87117,6 +88309,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87117
88309
|
completion_verify,
|
|
87118
88310
|
complexity_hotspots,
|
|
87119
88311
|
convene_council,
|
|
88312
|
+
convene_general_council,
|
|
87120
88313
|
curator_analyze,
|
|
87121
88314
|
declare_council_criteria,
|
|
87122
88315
|
knowledge_add,
|
|
@@ -87163,6 +88356,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87163
88356
|
build_check,
|
|
87164
88357
|
suggest_patch: suggestPatch,
|
|
87165
88358
|
update_task_status,
|
|
88359
|
+
web_search,
|
|
87166
88360
|
write_retro,
|
|
87167
88361
|
write_drift_evidence,
|
|
87168
88362
|
write_hallucination_evidence,
|