opencode-swarm 6.83.0 → 6.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- 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/index.d.ts +2 -0
- package/dist/cli/index.js +119 -8
- 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 +1581 -393
- 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
|
});
|
|
@@ -170,6 +172,8 @@ var init_constants = __esm(() => {
|
|
|
170
172
|
"critic_hallucination_verifier",
|
|
171
173
|
"curator_init",
|
|
172
174
|
"curator_phase",
|
|
175
|
+
"council_member",
|
|
176
|
+
"council_moderator",
|
|
173
177
|
...QA_AGENTS,
|
|
174
178
|
...PIPELINE_AGENTS
|
|
175
179
|
];
|
|
@@ -241,7 +245,8 @@ var init_constants = __esm(() => {
|
|
|
241
245
|
"suggest_patch",
|
|
242
246
|
"repo_map",
|
|
243
247
|
"get_qa_gate_profile",
|
|
244
|
-
"set_qa_gates"
|
|
248
|
+
"set_qa_gates",
|
|
249
|
+
"convene_general_council"
|
|
245
250
|
],
|
|
246
251
|
explorer: [
|
|
247
252
|
"complexity_hotspots",
|
|
@@ -391,7 +396,9 @@ var init_constants = __esm(() => {
|
|
|
391
396
|
"knowledge_recall"
|
|
392
397
|
],
|
|
393
398
|
curator_init: ["knowledge_recall"],
|
|
394
|
-
curator_phase: ["knowledge_recall"]
|
|
399
|
+
curator_phase: ["knowledge_recall"],
|
|
400
|
+
council_member: ["web_search"],
|
|
401
|
+
council_moderator: []
|
|
395
402
|
};
|
|
396
403
|
WRITE_TOOL_NAMES = [
|
|
397
404
|
"write",
|
|
@@ -453,13 +460,15 @@ var init_constants = __esm(() => {
|
|
|
453
460
|
gitingest: "fetch a GitHub repository full content via gitingest.com",
|
|
454
461
|
retrieve_summary: "retrieve the full content of a stored tool output summary",
|
|
455
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.",
|
|
456
465
|
batch_symbols: "Batched symbol extraction across multiple files. Returns per-file symbol summaries with isolated error handling.",
|
|
457
466
|
suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch.",
|
|
458
467
|
lint_spec: "validate .swarm/spec.md format and required fields",
|
|
459
468
|
get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison",
|
|
460
469
|
repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
|
|
461
|
-
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.",
|
|
462
|
-
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.",
|
|
463
472
|
req_coverage: "query requirement coverage status for tracked functional requirements"
|
|
464
473
|
};
|
|
465
474
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
@@ -483,6 +492,8 @@ var init_constants = __esm(() => {
|
|
|
483
492
|
designer: "opencode/trinity-large-preview-free",
|
|
484
493
|
curator_init: "opencode/trinity-large-preview-free",
|
|
485
494
|
curator_phase: "opencode/trinity-large-preview-free",
|
|
495
|
+
council_member: "opencode/trinity-large-preview-free",
|
|
496
|
+
council_moderator: "opencode/trinity-large-preview-free",
|
|
486
497
|
default: "opencode/trinity-large-preview-free"
|
|
487
498
|
};
|
|
488
499
|
DEFAULT_SCORING_CONFIG = {
|
|
@@ -14690,7 +14701,7 @@ function resolveGuardrailsConfig(config2, agentName) {
|
|
|
14690
14701
|
};
|
|
14691
14702
|
return resolved;
|
|
14692
14703
|
}
|
|
14693
|
-
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;
|
|
14694
14705
|
var init_schema = __esm(() => {
|
|
14695
14706
|
init_zod();
|
|
14696
14707
|
init_constants();
|
|
@@ -15005,21 +15016,25 @@ var init_schema = __esm(() => {
|
|
|
15005
15016
|
coder: {
|
|
15006
15017
|
max_tool_calls: 400,
|
|
15007
15018
|
max_duration_minutes: 45,
|
|
15019
|
+
max_consecutive_errors: 8,
|
|
15008
15020
|
warning_threshold: 0.85
|
|
15009
15021
|
},
|
|
15010
15022
|
test_engineer: {
|
|
15011
15023
|
max_tool_calls: 400,
|
|
15012
15024
|
max_duration_minutes: 45,
|
|
15025
|
+
max_consecutive_errors: 8,
|
|
15013
15026
|
warning_threshold: 0.85
|
|
15014
15027
|
},
|
|
15015
15028
|
explorer: {
|
|
15016
15029
|
max_tool_calls: 150,
|
|
15017
15030
|
max_duration_minutes: 20,
|
|
15031
|
+
max_consecutive_errors: 8,
|
|
15018
15032
|
warning_threshold: 0.75
|
|
15019
15033
|
},
|
|
15020
15034
|
reviewer: {
|
|
15021
15035
|
max_tool_calls: 200,
|
|
15022
15036
|
max_duration_minutes: 30,
|
|
15037
|
+
max_consecutive_errors: 8,
|
|
15023
15038
|
warning_threshold: 0.65
|
|
15024
15039
|
},
|
|
15025
15040
|
critic: {
|
|
@@ -15207,13 +15222,37 @@ var init_schema = __esm(() => {
|
|
|
15207
15222
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
|
|
15208
15223
|
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
|
|
15209
15224
|
});
|
|
15225
|
+
GeneralCouncilMemberConfigSchema = exports_external.object({
|
|
15226
|
+
memberId: exports_external.string().min(1),
|
|
15227
|
+
model: exports_external.string().min(1),
|
|
15228
|
+
role: exports_external.enum([
|
|
15229
|
+
"generalist",
|
|
15230
|
+
"skeptic",
|
|
15231
|
+
"domain_expert",
|
|
15232
|
+
"devil_advocate",
|
|
15233
|
+
"synthesizer"
|
|
15234
|
+
]),
|
|
15235
|
+
persona: exports_external.string().optional()
|
|
15236
|
+
}).strict();
|
|
15237
|
+
GeneralCouncilConfigSchema = exports_external.object({
|
|
15238
|
+
enabled: exports_external.boolean().default(false),
|
|
15239
|
+
searchProvider: exports_external.enum(["tavily", "brave"]).default("tavily"),
|
|
15240
|
+
searchApiKey: exports_external.string().optional(),
|
|
15241
|
+
members: exports_external.array(GeneralCouncilMemberConfigSchema).default([]),
|
|
15242
|
+
presets: exports_external.record(exports_external.string(), exports_external.array(GeneralCouncilMemberConfigSchema)).default({}),
|
|
15243
|
+
deliberate: exports_external.boolean().default(true),
|
|
15244
|
+
moderator: exports_external.boolean().default(true),
|
|
15245
|
+
moderatorModel: exports_external.string().optional(),
|
|
15246
|
+
maxSourcesPerMember: exports_external.number().int().min(1).max(20).default(5)
|
|
15247
|
+
}).strict();
|
|
15210
15248
|
CouncilConfigSchema = exports_external.object({
|
|
15211
15249
|
enabled: exports_external.boolean().default(false),
|
|
15212
15250
|
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
15213
15251
|
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
15214
15252
|
vetoPriority: exports_external.boolean().default(true),
|
|
15215
15253
|
requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
|
|
15216
|
-
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.")
|
|
15254
|
+
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."),
|
|
15255
|
+
general: GeneralCouncilConfigSchema.optional()
|
|
15217
15256
|
}).strict();
|
|
15218
15257
|
ParallelizationConfigSchema = exports_external.object({
|
|
15219
15258
|
enabled: exports_external.boolean().default(false),
|
|
@@ -19707,7 +19746,8 @@ var init_qa_gate_profile = __esm(() => {
|
|
|
19707
19746
|
critic_pre_plan: true,
|
|
19708
19747
|
hallucination_guard: false,
|
|
19709
19748
|
sast_enabled: true,
|
|
19710
|
-
mutation_test: false
|
|
19749
|
+
mutation_test: false,
|
|
19750
|
+
council_general_review: false
|
|
19711
19751
|
};
|
|
19712
19752
|
});
|
|
19713
19753
|
|
|
@@ -23344,7 +23384,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
23344
23384
|
if (window2.consecutiveErrors >= agentConfig.max_consecutive_errors) {
|
|
23345
23385
|
window2.hardLimitHit = true;
|
|
23346
23386
|
telemetry.hardLimitHit(sessionID, window2.agentName, "consecutive_errors", window2.consecutiveErrors);
|
|
23347
|
-
throw new Error(`\uD83D\uDED1 LIMIT REACHED: ${window2.consecutiveErrors} consecutive tool errors detected. Return your progress summary with details of what went wrong.`);
|
|
23387
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: ${window2.consecutiveErrors} consecutive tool errors detected. Return your progress summary with details of what went wrong. Run /swarm reset-session to clear the circuit breaker without restarting your session.`);
|
|
23348
23388
|
}
|
|
23349
23389
|
const idleMinutes = (Date.now() - window2.lastSuccessTimeMs) / 60000;
|
|
23350
23390
|
if (idleMinutes >= agentConfig.idle_timeout_minutes) {
|
|
@@ -23885,31 +23925,32 @@ function createGuardrailsHooks(directory, directoryOrConfig, config2, authorityC
|
|
|
23885
23925
|
return;
|
|
23886
23926
|
const hasError = output.output === null || output.output === undefined;
|
|
23887
23927
|
if (hasError) {
|
|
23888
|
-
|
|
23889
|
-
|
|
23890
|
-
|
|
23891
|
-
|
|
23892
|
-
|
|
23893
|
-
|
|
23894
|
-
|
|
23895
|
-
|
|
23896
|
-
|
|
23897
|
-
|
|
23898
|
-
|
|
23899
|
-
|
|
23900
|
-
|
|
23901
|
-
|
|
23902
|
-
|
|
23903
|
-
|
|
23904
|
-
|
|
23905
|
-
session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Applied fallback model "${fallbackModel}" (attempt ${session.model_fallback_index}). ` + `Using /swarm handoff to reset to primary model.`);
|
|
23906
|
-
} else {
|
|
23907
|
-
session.pendingAdvisoryMessages ??= [];
|
|
23908
|
-
session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `No fallback models configured for this agent. Add "fallback_models": ["model-a", "model-b"] ` + `to the agent's config in opencode-swarm.json.`);
|
|
23928
|
+
const outputStr = typeof output.output === "string" ? output.output : "";
|
|
23929
|
+
const errorContent = output.error ?? outputStr;
|
|
23930
|
+
const isTransient = !!session && !session.modelFallbackExhausted && typeof errorContent === "string" && TRANSIENT_MODEL_ERROR_PATTERN.test(errorContent);
|
|
23931
|
+
if (!isTransient) {
|
|
23932
|
+
window2.consecutiveErrors++;
|
|
23933
|
+
}
|
|
23934
|
+
if (session && isTransient) {
|
|
23935
|
+
session.model_fallback_index++;
|
|
23936
|
+
const baseAgentName = session.agentName ? session.agentName.replace(/^[^_]+[_]/, "") : "";
|
|
23937
|
+
const swarmAgents = getSwarmAgents();
|
|
23938
|
+
const fallbackModels = swarmAgents?.[baseAgentName]?.fallback_models;
|
|
23939
|
+
session.modelFallbackExhausted = !fallbackModels || session.model_fallback_index > fallbackModels.length;
|
|
23940
|
+
const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, swarmAgents);
|
|
23941
|
+
const primaryModel = swarmAgents?.[baseAgentName]?.model ?? "default";
|
|
23942
|
+
if (fallbackModel) {
|
|
23943
|
+
if (swarmAgents?.[baseAgentName]) {
|
|
23944
|
+
swarmAgents[baseAgentName].model = fallbackModel;
|
|
23909
23945
|
}
|
|
23910
|
-
|
|
23911
|
-
|
|
23946
|
+
session.pendingAdvisoryMessages ??= [];
|
|
23947
|
+
session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Applied fallback model "${fallbackModel}" (attempt ${session.model_fallback_index}). ` + `Using /swarm handoff to reset to primary model.`);
|
|
23948
|
+
} else {
|
|
23949
|
+
session.pendingAdvisoryMessages ??= [];
|
|
23950
|
+
session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `No fallback models configured for this agent. Add "fallback_models": ["model-a", "model-b"] ` + `to the agent's config in opencode-swarm.json.`);
|
|
23912
23951
|
}
|
|
23952
|
+
telemetry.modelFallback(input.sessionID, session.agentName, primaryModel, fallbackModel ?? "none", "transient_model_error");
|
|
23953
|
+
swarmState.pendingEvents++;
|
|
23913
23954
|
}
|
|
23914
23955
|
} else {
|
|
23915
23956
|
window2.consecutiveErrors = 0;
|
|
@@ -41864,6 +41905,76 @@ var init_config2 = __esm(() => {
|
|
|
41864
41905
|
init_loader();
|
|
41865
41906
|
});
|
|
41866
41907
|
|
|
41908
|
+
// src/commands/council.ts
|
|
41909
|
+
function sanitizeQuestion(raw) {
|
|
41910
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
41911
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
41912
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
41913
|
+
if (normalized.length <= MAX_QUESTION_LEN)
|
|
41914
|
+
return normalized;
|
|
41915
|
+
return `${normalized.slice(0, MAX_QUESTION_LEN)}\u2026`;
|
|
41916
|
+
}
|
|
41917
|
+
function sanitizePresetName(raw) {
|
|
41918
|
+
const trimmed = raw.trim();
|
|
41919
|
+
if (!trimmed)
|
|
41920
|
+
return null;
|
|
41921
|
+
if (trimmed.length > 64)
|
|
41922
|
+
return null;
|
|
41923
|
+
if (!/^[A-Za-z0-9_-]+$/.test(trimmed))
|
|
41924
|
+
return null;
|
|
41925
|
+
return trimmed;
|
|
41926
|
+
}
|
|
41927
|
+
function parseArgs(args2) {
|
|
41928
|
+
const out2 = { specReview: false, rest: [] };
|
|
41929
|
+
for (let i2 = 0;i2 < args2.length; i2++) {
|
|
41930
|
+
const token = args2[i2];
|
|
41931
|
+
if (token === "--spec-review") {
|
|
41932
|
+
out2.specReview = true;
|
|
41933
|
+
continue;
|
|
41934
|
+
}
|
|
41935
|
+
if (token === "--preset") {
|
|
41936
|
+
const next = args2[i2 + 1];
|
|
41937
|
+
if (next !== undefined) {
|
|
41938
|
+
const sanitized = sanitizePresetName(next);
|
|
41939
|
+
if (sanitized)
|
|
41940
|
+
out2.preset = sanitized;
|
|
41941
|
+
i2++;
|
|
41942
|
+
}
|
|
41943
|
+
continue;
|
|
41944
|
+
}
|
|
41945
|
+
out2.rest.push(token);
|
|
41946
|
+
}
|
|
41947
|
+
return out2;
|
|
41948
|
+
}
|
|
41949
|
+
async function handleCouncilCommand(_directory, args2) {
|
|
41950
|
+
const parsed = parseArgs(args2);
|
|
41951
|
+
const question = sanitizeQuestion(parsed.rest.join(" "));
|
|
41952
|
+
if (!question) {
|
|
41953
|
+
return USAGE;
|
|
41954
|
+
}
|
|
41955
|
+
const tokens = ["MODE: COUNCIL"];
|
|
41956
|
+
if (parsed.preset) {
|
|
41957
|
+
tokens.push(`preset=${parsed.preset}`);
|
|
41958
|
+
}
|
|
41959
|
+
if (parsed.specReview) {
|
|
41960
|
+
tokens.push("spec_review");
|
|
41961
|
+
}
|
|
41962
|
+
return `[${tokens.join(" ")}] ${question}`;
|
|
41963
|
+
}
|
|
41964
|
+
var MAX_QUESTION_LEN = 2000, USAGE;
|
|
41965
|
+
var init_council = __esm(() => {
|
|
41966
|
+
USAGE = [
|
|
41967
|
+
"Usage: /swarm council <question> [--preset <name>] [--spec-review]",
|
|
41968
|
+
"",
|
|
41969
|
+
" question The question to put to the council",
|
|
41970
|
+
" --preset <name> Use a named member preset from council.general.presets",
|
|
41971
|
+
" --spec-review Use spec_review mode (single advisory pass on a draft spec)",
|
|
41972
|
+
"",
|
|
41973
|
+
"Requires council.general.enabled: true and a configured search API key in opencode-swarm.json."
|
|
41974
|
+
].join(`
|
|
41975
|
+
`);
|
|
41976
|
+
});
|
|
41977
|
+
|
|
41867
41978
|
// src/agents/explorer.ts
|
|
41868
41979
|
function createExplorerAgent(model, customPrompt, customAppendPrompt) {
|
|
41869
41980
|
let prompt = EXPLORER_PROMPT;
|
|
@@ -52150,7 +52261,8 @@ var init_qa_gates = __esm(() => {
|
|
|
52150
52261
|
"critic_pre_plan",
|
|
52151
52262
|
"hallucination_guard",
|
|
52152
52263
|
"sast_enabled",
|
|
52153
|
-
"mutation_test"
|
|
52264
|
+
"mutation_test",
|
|
52265
|
+
"council_general_review"
|
|
52154
52266
|
];
|
|
52155
52267
|
});
|
|
52156
52268
|
|
|
@@ -52969,7 +53081,7 @@ async function handleResetSessionCommand(directory, _args) {
|
|
|
52969
53081
|
"",
|
|
52970
53082
|
"Session state cleared. Plan, evidence, and knowledge preserved.",
|
|
52971
53083
|
"",
|
|
52972
|
-
"**
|
|
53084
|
+
"**All circuit breakers and revision limits have been cleared.** You can continue in this session \u2014 fresh state will be initialized automatically on the next tool call."
|
|
52973
53085
|
].join(`
|
|
52974
53086
|
`);
|
|
52975
53087
|
}
|
|
@@ -53776,6 +53888,7 @@ var init_registry = __esm(() => {
|
|
|
53776
53888
|
init_checkpoint2();
|
|
53777
53889
|
init_close();
|
|
53778
53890
|
init_config2();
|
|
53891
|
+
init_council();
|
|
53779
53892
|
init_curate();
|
|
53780
53893
|
init_dark_matter();
|
|
53781
53894
|
init_diagnose();
|
|
@@ -53931,11 +54044,17 @@ var init_registry = __esm(() => {
|
|
|
53931
54044
|
args: "[topic-text]",
|
|
53932
54045
|
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."
|
|
53933
54046
|
},
|
|
54047
|
+
council: {
|
|
54048
|
+
handler: (ctx) => handleCouncilCommand(ctx.directory, ctx.args),
|
|
54049
|
+
description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--preset <name>] [--spec-review]",
|
|
54050
|
+
args: "<question> [--preset <name>] [--spec-review]",
|
|
54051
|
+
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."
|
|
54052
|
+
},
|
|
53934
54053
|
"qa-gates": {
|
|
53935
54054
|
handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
|
|
53936
54055
|
description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
|
|
53937
54056
|
args: "[show|enable|override] <gate>...",
|
|
53938
|
-
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."
|
|
54057
|
+
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."
|
|
53939
54058
|
},
|
|
53940
54059
|
promote: {
|
|
53941
54060
|
handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
|
|
@@ -54091,14 +54210,24 @@ from different members.`;
|
|
|
54091
54210
|
function buildYourToolsList(council) {
|
|
54092
54211
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54093
54212
|
const sorted = [...tools].sort();
|
|
54094
|
-
const
|
|
54213
|
+
const qaCouncilEnabled = council?.enabled === true;
|
|
54214
|
+
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54215
|
+
const filtered = sorted.filter((t) => {
|
|
54216
|
+
if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
|
|
54217
|
+
return false;
|
|
54218
|
+
}
|
|
54219
|
+
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
54220
|
+
return false;
|
|
54221
|
+
}
|
|
54222
|
+
return true;
|
|
54223
|
+
});
|
|
54095
54224
|
return `Task (delegation), ${filtered.join(", ")}.`;
|
|
54096
54225
|
}
|
|
54097
54226
|
function buildQaGateSelectionDialogue(modeLabel) {
|
|
54098
54227
|
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.";
|
|
54099
54228
|
return `${leadIn}
|
|
54100
54229
|
|
|
54101
|
-
Present the
|
|
54230
|
+
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:
|
|
54102
54231
|
- reviewer (default: ON) \u2014 code review of coder output
|
|
54103
54232
|
- test_engineer (default: ON) \u2014 test verification of coder output
|
|
54104
54233
|
- sme_enabled (default: ON) \u2014 SME consultation during planning/clarification
|
|
@@ -54107,13 +54236,24 @@ Present the eight gates with their defaults (DEFAULT_QA_GATES) as a single user-
|
|
|
54107
54236
|
- council_mode (default: OFF) \u2014 multi-member council gate (recommended for high-impact architecture, public APIs, schema/data mutation, security-sensitive code)
|
|
54108
54237
|
- 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)
|
|
54109
54238
|
- 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)
|
|
54239
|
+
- 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.
|
|
54110
54240
|
|
|
54111
54241
|
One question, one message, defaults pre-stated. Wait for the user's answer.`;
|
|
54112
54242
|
}
|
|
54113
54243
|
function buildAvailableToolsList(council) {
|
|
54114
54244
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54115
54245
|
const sorted = [...tools].sort();
|
|
54116
|
-
const
|
|
54246
|
+
const qaCouncilEnabled = council?.enabled === true;
|
|
54247
|
+
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54248
|
+
const filtered = sorted.filter((t) => {
|
|
54249
|
+
if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
|
|
54250
|
+
return false;
|
|
54251
|
+
}
|
|
54252
|
+
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
54253
|
+
return false;
|
|
54254
|
+
}
|
|
54255
|
+
return true;
|
|
54256
|
+
});
|
|
54117
54257
|
return filtered.map((t) => {
|
|
54118
54258
|
const desc = TOOL_DESCRIPTIONS[t];
|
|
54119
54259
|
return desc ? `${t} (${desc})` : t;
|
|
@@ -54154,7 +54294,8 @@ function buildSlashCommandsList() {
|
|
|
54154
54294
|
"analyze",
|
|
54155
54295
|
"plan",
|
|
54156
54296
|
"sync-plan",
|
|
54157
|
-
"acknowledge-spec-drift"
|
|
54297
|
+
"acknowledge-spec-drift",
|
|
54298
|
+
"council"
|
|
54158
54299
|
],
|
|
54159
54300
|
"Execution Modes": ["turbo", "full-auto"],
|
|
54160
54301
|
Observation: [
|
|
@@ -54799,6 +54940,29 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
|
|
|
54799
54940
|
**Phase 6: QA GATE SELECTION (architect, dialogue only).**
|
|
54800
54941
|
{{QA_GATE_DIALOGUE_BRAINSTORM}}
|
|
54801
54942
|
|
|
54943
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
54944
|
+
GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
|
|
54945
|
+
\u2717 "I'll use the defaults \u2014 they're probably fine"
|
|
54946
|
+
\u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
|
|
54947
|
+
\u2717 "The user didn't mention gates, so defaults are fine"
|
|
54948
|
+
\u2192 WRONG: silence is not consent. The gate dialogue is not optional.
|
|
54949
|
+
\u2717 "I'll handle it in MODE: PLAN after the spec is done"
|
|
54950
|
+
\u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
|
|
54951
|
+
save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
|
|
54952
|
+
\u2717 "This feature is simple \u2014 gates are obvious"
|
|
54953
|
+
\u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
|
|
54954
|
+
\u2717 "I already know which gates are right for this project"
|
|
54955
|
+
\u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
|
|
54956
|
+
\u2717 "council_general_review is off by default, I don't need to mention it"
|
|
54957
|
+
\u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
|
|
54958
|
+
|
|
54959
|
+
MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
|
|
54960
|
+
You are BLOCKED until ALL THREE of these conditions are met:
|
|
54961
|
+
(1) The gate selection question has been presented to the user in a single message
|
|
54962
|
+
(2) The user has responded (accept defaults OR customized list)
|
|
54963
|
+
(3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
|
|
54964
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
54965
|
+
|
|
54802
54966
|
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:
|
|
54803
54967
|
\`\`\`
|
|
54804
54968
|
## Pending QA Gate Selection
|
|
@@ -54810,6 +54974,7 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
|
|
|
54810
54974
|
- council_mode: <true|false>
|
|
54811
54975
|
- hallucination_guard: <true|false>
|
|
54812
54976
|
- mutation_test: <true|false>
|
|
54977
|
+
- council_general_review: <true|false>
|
|
54813
54978
|
- recorded_at: <ISO timestamp>
|
|
54814
54979
|
\`\`\`
|
|
54815
54980
|
MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
|
|
@@ -54852,6 +55017,29 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
|
|
|
54852
55017
|
5b. **QA GATE SELECTION (dialogue only).**
|
|
54853
55018
|
{{QA_GATE_DIALOGUE_SPECIFY}}
|
|
54854
55019
|
|
|
55020
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55021
|
+
GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
|
|
55022
|
+
\u2717 "I'll use the defaults \u2014 they're probably fine"
|
|
55023
|
+
\u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
|
|
55024
|
+
\u2717 "The user didn't mention gates, so defaults are fine"
|
|
55025
|
+
\u2192 WRONG: silence is not consent. The gate dialogue is not optional.
|
|
55026
|
+
\u2717 "I'll handle it in MODE: PLAN after the spec is done"
|
|
55027
|
+
\u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
|
|
55028
|
+
save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
|
|
55029
|
+
\u2717 "This feature is simple \u2014 gates are obvious"
|
|
55030
|
+
\u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
|
|
55031
|
+
\u2717 "I already know which gates are right for this project"
|
|
55032
|
+
\u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
|
|
55033
|
+
\u2717 "council_general_review is off by default, I don't need to mention it"
|
|
55034
|
+
\u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
|
|
55035
|
+
|
|
55036
|
+
MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
|
|
55037
|
+
You are BLOCKED until ALL THREE of these conditions are met:
|
|
55038
|
+
(1) The gate selection question has been presented to the user in a single message
|
|
55039
|
+
(2) The user has responded (accept defaults OR customized list)
|
|
55040
|
+
(3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
|
|
55041
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55042
|
+
|
|
54855
55043
|
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:
|
|
54856
55044
|
\`\`\`
|
|
54857
55045
|
## Pending QA Gate Selection
|
|
@@ -54863,9 +55051,37 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
|
|
|
54863
55051
|
- council_mode: <true|false>
|
|
54864
55052
|
- hallucination_guard: <true|false>
|
|
54865
55053
|
- mutation_test: <true|false>
|
|
55054
|
+
- council_general_review: <true|false>
|
|
54866
55055
|
- recorded_at: <ISO timestamp>
|
|
54867
55056
|
\`\`\`
|
|
54868
55057
|
MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
|
|
55058
|
+
|
|
55059
|
+
5c. **SPECIFY-COUNCIL-REVIEW (fires ONLY when council_general_review gate is true).**
|
|
55060
|
+
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.
|
|
55061
|
+
|
|
55062
|
+
If \`council_general_review\` is true:
|
|
55063
|
+
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.
|
|
55064
|
+
2. Determine the council members from \`council.general.members\` (or \`council.general.presets[<name>]\` if you were invoked via \`/swarm council --preset <name>\` originally).
|
|
55065
|
+
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.
|
|
55066
|
+
4. Collect all member JSON responses.
|
|
55067
|
+
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.
|
|
55068
|
+
6. Read \`consensusPoints\` \u2014 incorporate unambiguous consensus directly into the spec.
|
|
55069
|
+
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.
|
|
55070
|
+
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.
|
|
55071
|
+
9. Revise \`.swarm/spec.md\` to reflect the council input.
|
|
55072
|
+
|
|
55073
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55074
|
+
SPECIFY-COUNCIL-REVIEW RULES:
|
|
55075
|
+
\u2717 "council_general_review is off by default, I'll skip this"
|
|
55076
|
+
\u2192 CORRECT only when the gate is explicitly false or absent. Do NOT assume false. Read the actual gate value before deciding to skip.
|
|
55077
|
+
\u2717 "The spec is already good, no need to ask the council"
|
|
55078
|
+
\u2192 WRONG when gate is true: the user enabled this gate for a reason. Run it regardless.
|
|
55079
|
+
\u2717 "I'll include round2Responses for spec_review \u2014 more is better"
|
|
55080
|
+
\u2192 WRONG: spec review is a single advisory pass. Omit \`round2Responses\` for spec_review mode.
|
|
55081
|
+
\u2717 "I'll skip the moderator pass to save time"
|
|
55082
|
+
\u2192 WRONG when council.general.moderator is true: invoke \`{{AGENT_PREFIX}}council_moderator\` with the moderatorPrompt the tool returns.
|
|
55083
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55084
|
+
|
|
54869
55085
|
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\`.
|
|
54870
55086
|
|
|
54871
55087
|
SPEC CONTENT RULES \u2014 the spec MUST NOT contain:
|
|
@@ -55034,6 +55250,39 @@ This check fires automatically in:
|
|
|
55034
55250
|
|
|
55035
55251
|
GREENFIELD EXEMPTION: If the work is purely greenfield (new project, no existing codebase references), skip this check.
|
|
55036
55252
|
|
|
55253
|
+
### MODE: COUNCIL
|
|
55254
|
+
|
|
55255
|
+
Activates when: user invokes \`/swarm council <question>\` (optionally with \`--preset <name>\` or \`--spec-review\`).
|
|
55256
|
+
|
|
55257
|
+
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.
|
|
55258
|
+
|
|
55259
|
+
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\`).
|
|
55260
|
+
|
|
55261
|
+
#### Pre-flight (always run first)
|
|
55262
|
+
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.
|
|
55263
|
+
|
|
55264
|
+
#### Round 1 \u2014 Parallel Independent Search
|
|
55265
|
+
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.
|
|
55266
|
+
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.
|
|
55267
|
+
4. Collect all member JSON responses (each member returns a fenced JSON block per the council_member prompt).
|
|
55268
|
+
|
|
55269
|
+
#### Synthesis and Deliberation (when council.general.deliberate is true; default true)
|
|
55270
|
+
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\`.
|
|
55271
|
+
6. If \`disagreementsCount > 0\`:
|
|
55272
|
+
a. For each disagreement in the tool's response, identify the disputing members (the members listed in the disagreement's positions).
|
|
55273
|
+
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.
|
|
55274
|
+
c. Collect the Round 2 responses.
|
|
55275
|
+
d. Call \`convene_general_council\` AGAIN with both \`round1Responses\` AND \`round2Responses\` populated.
|
|
55276
|
+
|
|
55277
|
+
#### Moderator Pass (when council.general.moderator is true; default true)
|
|
55278
|
+
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.
|
|
55279
|
+
|
|
55280
|
+
#### Output
|
|
55281
|
+
8. Present the final answer to the user:
|
|
55282
|
+
- If the moderator pass ran: present the moderator's output verbatim, prefaced with the participating models (one line).
|
|
55283
|
+
- If no moderator: present the structural \`synthesis\` markdown from the tool's return.
|
|
55284
|
+
In either case, do NOT present the raw per-member JSON. Do NOT silently pick a winner among persisting disagreements \u2014 surface them honestly.
|
|
55285
|
+
|
|
55037
55286
|
### MODE: PLAN
|
|
55038
55287
|
|
|
55039
55288
|
SPEC GATE (soft \u2014 check before planning):
|
|
@@ -55112,7 +55361,18 @@ save_plan({
|
|
|
55112
55361
|
**POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
|
|
55113
55362
|
After \`save_plan\` succeeds, read \`.swarm/context.md\`:
|
|
55114
55363
|
- 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.
|
|
55115
|
-
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
55364
|
+
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
55365
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55366
|
+
INLINE GATE SELECTION \u2014 no pending section found in context.md. You MUST ask now.
|
|
55367
|
+
\u2717 "I'll call set_qa_gates with defaults and move on"
|
|
55368
|
+
\u2192 WRONG: set_qa_gates with assumed values is a gate violation. The user must answer first.
|
|
55369
|
+
\u2717 "The user provided a plan \u2014 they know what gates they want"
|
|
55370
|
+
\u2192 WRONG: providing a plan is not the same as configuring gates. Always ask.
|
|
55371
|
+
|
|
55372
|
+
MANDATORY PAUSE: Present the gate question. Wait for the user's answer.
|
|
55373
|
+
Do NOT call \`set_qa_gates\` until the user has responded.
|
|
55374
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55375
|
+
Then call \`set_qa_gates\` with the user's chosen flags.
|
|
55116
55376
|
Either path must yield a persisted QA gate profile before the first task dispatches.
|
|
55117
55377
|
|
|
55118
55378
|
\u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
|
|
@@ -55701,6 +55961,189 @@ META.SUMMARY CONVENTION \u2014 When reporting task completion, include:
|
|
|
55701
55961
|
|
|
55702
55962
|
`;
|
|
55703
55963
|
|
|
55964
|
+
// src/agents/council-member.ts
|
|
55965
|
+
function createCouncilMemberAgent(model, customPrompt, customAppendPrompt) {
|
|
55966
|
+
let prompt = COUNCIL_MEMBER_PROMPT;
|
|
55967
|
+
if (customPrompt) {
|
|
55968
|
+
prompt = customPrompt;
|
|
55969
|
+
} else if (customAppendPrompt) {
|
|
55970
|
+
prompt = `${COUNCIL_MEMBER_PROMPT}
|
|
55971
|
+
|
|
55972
|
+
${customAppendPrompt}`;
|
|
55973
|
+
}
|
|
55974
|
+
return {
|
|
55975
|
+
name: "council_member",
|
|
55976
|
+
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.",
|
|
55977
|
+
config: {
|
|
55978
|
+
model,
|
|
55979
|
+
temperature: 0.4,
|
|
55980
|
+
prompt,
|
|
55981
|
+
tools: {
|
|
55982
|
+
write: false,
|
|
55983
|
+
edit: false,
|
|
55984
|
+
patch: false
|
|
55985
|
+
}
|
|
55986
|
+
}
|
|
55987
|
+
};
|
|
55988
|
+
}
|
|
55989
|
+
var COUNCIL_MEMBER_PROMPT = `You are Council Member {{MEMBER_ID}} ({{ROLE}}) on a multi-model General Council.
|
|
55990
|
+
|
|
55991
|
+
{{PERSONA_BLOCK}}
|
|
55992
|
+
|
|
55993
|
+
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.
|
|
55994
|
+
|
|
55995
|
+
================================================================
|
|
55996
|
+
ROUND {{ROUND}} PROTOCOL
|
|
55997
|
+
================================================================
|
|
55998
|
+
|
|
55999
|
+
ROUND 1 \u2014 Independent Research and Answer
|
|
56000
|
+
- Issue 1\u20133 targeted web_search calls to gather evidence relevant to the question.
|
|
56001
|
+
- Cite EVERY factual claim with a source URL from your search results.
|
|
56002
|
+
- State your confidence (0.0\u20131.0) explicitly. Be honest \u2014 overconfident answers hurt the council.
|
|
56003
|
+
- Enumerate areas of uncertainty so the architect knows where you're guessing vs. where you're sure.
|
|
56004
|
+
- Do NOT coordinate with other members. You will not see their responses until Round 2.
|
|
56005
|
+
- Do NOT pad. Be concise. Substance over volume.
|
|
56006
|
+
|
|
56007
|
+
ROUND 2 \u2014 Targeted Deliberation (ONLY when this round is invoked for you)
|
|
56008
|
+
- {{DISAGREEMENT_BLOCK}}
|
|
56009
|
+
- Issue at most 1 additional web_search call.
|
|
56010
|
+
- Declare your stance explicitly using one of these keywords as the FIRST word of a paragraph:
|
|
56011
|
+
MAINTAIN \u2014 your Round 1 position holds; cite the new evidence supporting it
|
|
56012
|
+
CONCEDE \u2014 the opposing position is correct; state specifically what you got wrong
|
|
56013
|
+
NUANCE \u2014 both positions are partially right; state the boundary condition that distinguishes them
|
|
56014
|
+
- Never CONCEDE without evidence. Sycophantic capitulation degrades the council below an individual member's baseline (NSED arXiv:2601.16863).
|
|
56015
|
+
- Never MAINTAIN without engaging the opposing argument on its merits.
|
|
56016
|
+
|
|
56017
|
+
================================================================
|
|
56018
|
+
RESPONSE FORMAT (always \u2014 both rounds)
|
|
56019
|
+
================================================================
|
|
56020
|
+
|
|
56021
|
+
Reply with a single fenced JSON block. No prose outside the block.
|
|
56022
|
+
|
|
56023
|
+
\`\`\`json
|
|
56024
|
+
{
|
|
56025
|
+
"memberId": "{{MEMBER_ID}}",
|
|
56026
|
+
"role": "{{ROLE}}",
|
|
56027
|
+
"round": {{ROUND}},
|
|
56028
|
+
"response": "Your full answer (Round 1) or stance + reasoning (Round 2). Markdown OK inside the string.",
|
|
56029
|
+
"searchQueries": ["query 1", "query 2"],
|
|
56030
|
+
"sources": [
|
|
56031
|
+
{ "title": "...", "url": "...", "snippet": "...", "query": "..." }
|
|
56032
|
+
],
|
|
56033
|
+
"confidence": 0.85,
|
|
56034
|
+
"areasOfUncertainty": [
|
|
56035
|
+
"What I'm not sure about, in plain language."
|
|
56036
|
+
],
|
|
56037
|
+
"disagreementTopics": []
|
|
56038
|
+
}
|
|
56039
|
+
\`\`\`
|
|
56040
|
+
|
|
56041
|
+
For Round 1: leave \`disagreementTopics\` as []. For Round 2: list the specific disagreement topics this response addresses.
|
|
56042
|
+
|
|
56043
|
+
================================================================
|
|
56044
|
+
HARD RULES
|
|
56045
|
+
================================================================
|
|
56046
|
+
- web_search is your ONLY tool. You cannot read or write files, run commands, or delegate.
|
|
56047
|
+
- Never invent sources. If a search returns nothing useful, say so in \`areasOfUncertainty\`.
|
|
56048
|
+
- Never echo other members' responses verbatim. Paraphrase or quote with attribution.
|
|
56049
|
+
- Stay within your role and persona. The architect chose you for a specific perspective.
|
|
56050
|
+
`;
|
|
56051
|
+
|
|
56052
|
+
// src/agents/council-moderator.ts
|
|
56053
|
+
function createCouncilModeratorAgent(model, customPrompt, customAppendPrompt) {
|
|
56054
|
+
let prompt = COUNCIL_MODERATOR_PROMPT;
|
|
56055
|
+
if (customPrompt) {
|
|
56056
|
+
prompt = customPrompt;
|
|
56057
|
+
} else if (customAppendPrompt) {
|
|
56058
|
+
prompt = `${COUNCIL_MODERATOR_PROMPT}
|
|
56059
|
+
|
|
56060
|
+
${customAppendPrompt}`;
|
|
56061
|
+
}
|
|
56062
|
+
return {
|
|
56063
|
+
name: "council_moderator",
|
|
56064
|
+
description: "General Council moderator. Synthesizes a coherent final answer from member " + "responses; no web search (works on already-gathered content).",
|
|
56065
|
+
config: {
|
|
56066
|
+
model,
|
|
56067
|
+
temperature: 0.3,
|
|
56068
|
+
prompt,
|
|
56069
|
+
tools: {
|
|
56070
|
+
write: false,
|
|
56071
|
+
edit: false,
|
|
56072
|
+
patch: false
|
|
56073
|
+
}
|
|
56074
|
+
}
|
|
56075
|
+
};
|
|
56076
|
+
}
|
|
56077
|
+
var COUNCIL_MODERATOR_PROMPT = `You are the General Council Moderator.
|
|
56078
|
+
|
|
56079
|
+
You are receiving the structural synthesis from a multi-model council deliberation:
|
|
56080
|
+
- Question (and mode: general or spec_review)
|
|
56081
|
+
- All member Round 1 responses with sources
|
|
56082
|
+
- Detected disagreements
|
|
56083
|
+
- Round 2 deliberation responses (if any)
|
|
56084
|
+
- Confidence-weighted consensus claims
|
|
56085
|
+
- Persisting disagreements after deliberation
|
|
56086
|
+
|
|
56087
|
+
Your job: produce a coherent, well-structured final answer for the user.
|
|
56088
|
+
|
|
56089
|
+
================================================================
|
|
56090
|
+
RULES
|
|
56091
|
+
================================================================
|
|
56092
|
+
|
|
56093
|
+
1. LEAD WITH CONSENSUS \u2014 open with the strongest consensus position. Use the
|
|
56094
|
+
confidence-weighted ordering (Quadratic Voting): higher-confidence claims
|
|
56095
|
+
from multiple members rank higher, but evidence quality outranks raw
|
|
56096
|
+
confidence. Never elevate a single confident voice over a well-evidenced
|
|
56097
|
+
contrary majority.
|
|
56098
|
+
|
|
56099
|
+
2. ACKNOWLEDGE DISAGREEMENT HONESTLY \u2014 for each persisting disagreement, write
|
|
56100
|
+
"experts disagree on X because\u2026" and present the strongest version of each
|
|
56101
|
+
side. Do NOT pretend disagreements are resolved when they are not. Do NOT
|
|
56102
|
+
silently pick a winner.
|
|
56103
|
+
|
|
56104
|
+
3. CITE THE STRONGEST SOURCES \u2014 link key claims with [title](url) format from
|
|
56105
|
+
the deduplicated source list. Pick the most reputable source for each claim;
|
|
56106
|
+
do not cite duplicates.
|
|
56107
|
+
|
|
56108
|
+
4. BE CONCISE \u2014 the user wants an answer, not a committee report. Default
|
|
56109
|
+
length: a few short paragraphs plus a bulleted summary. Expand only when
|
|
56110
|
+
the question genuinely requires it.
|
|
56111
|
+
|
|
56112
|
+
================================================================
|
|
56113
|
+
HARD CONSTRAINTS
|
|
56114
|
+
================================================================
|
|
56115
|
+
|
|
56116
|
+
- You MUST NOT invent claims that are not present in the council's responses.
|
|
56117
|
+
- You MUST NOT add new web research. If something was missed, say so.
|
|
56118
|
+
- You MUST NOT favor a position based on member confidence alone \u2014 evidence
|
|
56119
|
+
quality is the tie-breaker.
|
|
56120
|
+
- You have NO tools. You write the final synthesis from the input given.
|
|
56121
|
+
|
|
56122
|
+
================================================================
|
|
56123
|
+
OUTPUT FORMAT
|
|
56124
|
+
================================================================
|
|
56125
|
+
|
|
56126
|
+
Plain markdown. No code fences. No JSON. Suggested structure:
|
|
56127
|
+
|
|
56128
|
+
# Answer
|
|
56129
|
+
|
|
56130
|
+
<lead consensus position with citation(s)>
|
|
56131
|
+
|
|
56132
|
+
<remaining consensus / context paragraphs as needed>
|
|
56133
|
+
|
|
56134
|
+
## Where Experts Disagree
|
|
56135
|
+
|
|
56136
|
+
- <topic 1>: <position A> vs <position B>, with sources for each
|
|
56137
|
+
- <topic 2>: ...
|
|
56138
|
+
|
|
56139
|
+
## Sources
|
|
56140
|
+
|
|
56141
|
+
- [title](url)
|
|
56142
|
+
- ...
|
|
56143
|
+
|
|
56144
|
+
(Omit any section that is empty.)
|
|
56145
|
+
`;
|
|
56146
|
+
|
|
55704
56147
|
// src/agents/critic.ts
|
|
55705
56148
|
function parseSoundingBoardResponse(raw) {
|
|
55706
56149
|
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
@@ -57345,6 +57788,19 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
|
|
|
57345
57788
|
testEngineer.name = prefixName("test_engineer");
|
|
57346
57789
|
agents.push(applyOverrides(testEngineer, swarmAgents, swarmPrefix));
|
|
57347
57790
|
}
|
|
57791
|
+
if (pluginConfig?.council?.general?.enabled === true && !isAgentDisabled("council_member", swarmAgents, swarmPrefix)) {
|
|
57792
|
+
const councilMemberPrompts = getPrompts("council_member");
|
|
57793
|
+
const councilMember = createCouncilMemberAgent(getModel("council_member"), councilMemberPrompts.prompt, councilMemberPrompts.appendPrompt);
|
|
57794
|
+
councilMember.name = prefixName("council_member");
|
|
57795
|
+
agents.push(applyOverrides(councilMember, swarmAgents, swarmPrefix));
|
|
57796
|
+
}
|
|
57797
|
+
if (pluginConfig?.council?.general?.enabled === true && pluginConfig?.council?.general?.moderator === true && !isAgentDisabled("council_moderator", swarmAgents, swarmPrefix)) {
|
|
57798
|
+
const moderatorPrompts = getPrompts("council_moderator");
|
|
57799
|
+
const moderatorModel = pluginConfig?.council?.general?.moderatorModel ?? getModel("council_moderator");
|
|
57800
|
+
const councilModerator = createCouncilModeratorAgent(moderatorModel, moderatorPrompts.prompt, moderatorPrompts.appendPrompt);
|
|
57801
|
+
councilModerator.name = prefixName("council_moderator");
|
|
57802
|
+
agents.push(applyOverrides(councilModerator, swarmAgents, swarmPrefix));
|
|
57803
|
+
}
|
|
57348
57804
|
if (!isAgentDisabled("docs", swarmAgents, swarmPrefix)) {
|
|
57349
57805
|
const docsPrompts = getPrompts("docs");
|
|
57350
57806
|
const docs = createDocsAgent(getModel("docs"), docsPrompts.prompt, docsPrompts.appendPrompt);
|
|
@@ -62357,7 +62813,7 @@ var init_curator_drift = __esm(() => {
|
|
|
62357
62813
|
|
|
62358
62814
|
// src/index.ts
|
|
62359
62815
|
init_agents();
|
|
62360
|
-
import * as
|
|
62816
|
+
import * as path103 from "path";
|
|
62361
62817
|
|
|
62362
62818
|
// src/background/index.ts
|
|
62363
62819
|
init_event_bus();
|
|
@@ -62656,6 +63112,7 @@ init_benchmark();
|
|
|
62656
63112
|
init_checkpoint2();
|
|
62657
63113
|
init_close();
|
|
62658
63114
|
init_config2();
|
|
63115
|
+
init_council();
|
|
62659
63116
|
init_curate();
|
|
62660
63117
|
init_dark_matter();
|
|
62661
63118
|
init_diagnose();
|
|
@@ -74014,6 +74471,496 @@ var convene_council = createSwarmTool({
|
|
|
74014
74471
|
}, null, 2);
|
|
74015
74472
|
}
|
|
74016
74473
|
});
|
|
74474
|
+
// src/tools/convene-general-council.ts
|
|
74475
|
+
init_dist();
|
|
74476
|
+
init_zod();
|
|
74477
|
+
init_loader();
|
|
74478
|
+
import * as fs59 from "fs";
|
|
74479
|
+
import * as path73 from "path";
|
|
74480
|
+
|
|
74481
|
+
// src/council/general-council-advisory.ts
|
|
74482
|
+
var ADVISORY_HEADER = "[general_council] (advisory; not blocking)";
|
|
74483
|
+
function pushGeneralCouncilAdvisory(session, result) {
|
|
74484
|
+
if (!session)
|
|
74485
|
+
return;
|
|
74486
|
+
const body2 = renderAdvisoryBody(result);
|
|
74487
|
+
if (!body2)
|
|
74488
|
+
return;
|
|
74489
|
+
session.pendingAdvisoryMessages ??= [];
|
|
74490
|
+
session.pendingAdvisoryMessages.push(`${ADVISORY_HEADER}
|
|
74491
|
+
${body2}`);
|
|
74492
|
+
}
|
|
74493
|
+
function renderAdvisoryBody(result) {
|
|
74494
|
+
const parts2 = [result.synthesis];
|
|
74495
|
+
if (result.moderatorOutput && result.moderatorOutput.trim().length > 0) {
|
|
74496
|
+
parts2.push("", "### Moderator Output", result.moderatorOutput);
|
|
74497
|
+
}
|
|
74498
|
+
return parts2.join(`
|
|
74499
|
+
`).trim();
|
|
74500
|
+
}
|
|
74501
|
+
|
|
74502
|
+
// src/council/disagreement-detector.ts
|
|
74503
|
+
var MAX_DISAGREEMENTS = 10;
|
|
74504
|
+
var EXPLICIT_DISAGREEMENT_MARKERS = [
|
|
74505
|
+
"i disagree with",
|
|
74506
|
+
"i would push back on",
|
|
74507
|
+
"contrary to",
|
|
74508
|
+
"this contradicts",
|
|
74509
|
+
"unlike "
|
|
74510
|
+
];
|
|
74511
|
+
var STRONG_RECOMMENDATION_MARKERS = [
|
|
74512
|
+
"recommend",
|
|
74513
|
+
"best approach",
|
|
74514
|
+
"should use",
|
|
74515
|
+
"i suggest",
|
|
74516
|
+
"the answer is",
|
|
74517
|
+
"the right choice is"
|
|
74518
|
+
];
|
|
74519
|
+
function tokenize(text) {
|
|
74520
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
|
|
74521
|
+
}
|
|
74522
|
+
function termOverlap(a, b) {
|
|
74523
|
+
const tokensA = new Set(tokenize(a));
|
|
74524
|
+
const tokensB = new Set(tokenize(b));
|
|
74525
|
+
if (tokensA.size === 0 || tokensB.size === 0)
|
|
74526
|
+
return 0;
|
|
74527
|
+
let intersection3 = 0;
|
|
74528
|
+
for (const t of tokensA) {
|
|
74529
|
+
if (tokensB.has(t))
|
|
74530
|
+
intersection3++;
|
|
74531
|
+
}
|
|
74532
|
+
const union3 = tokensA.size + tokensB.size - intersection3;
|
|
74533
|
+
return union3 === 0 ? 0 : intersection3 / union3;
|
|
74534
|
+
}
|
|
74535
|
+
function extractMarkerSentence(response, markers) {
|
|
74536
|
+
const lower = response.toLowerCase();
|
|
74537
|
+
const sentences = response.split(/(?<=[.!?])\s+/);
|
|
74538
|
+
for (const sentence of sentences) {
|
|
74539
|
+
const sentLower = sentence.toLowerCase();
|
|
74540
|
+
if (markers.some((m) => sentLower.includes(m))) {
|
|
74541
|
+
return sentence.trim();
|
|
74542
|
+
}
|
|
74543
|
+
}
|
|
74544
|
+
for (const marker of markers) {
|
|
74545
|
+
const idx = lower.indexOf(marker);
|
|
74546
|
+
if (idx !== -1) {
|
|
74547
|
+
const slice = response.slice(idx, idx + 200);
|
|
74548
|
+
return slice.split(/\n/)[0]?.trim() ?? slice.trim();
|
|
74549
|
+
}
|
|
74550
|
+
}
|
|
74551
|
+
return null;
|
|
74552
|
+
}
|
|
74553
|
+
function dedupeByTopic(disagreements) {
|
|
74554
|
+
const result = [];
|
|
74555
|
+
for (const d of disagreements) {
|
|
74556
|
+
const topicLower = d.topic.toLowerCase();
|
|
74557
|
+
const existing = result.find((r) => r.topic.toLowerCase().includes(topicLower) || topicLower.includes(r.topic.toLowerCase()));
|
|
74558
|
+
if (existing) {
|
|
74559
|
+
for (const pos of d.positions) {
|
|
74560
|
+
if (!existing.positions.some((p) => p.memberId === pos.memberId)) {
|
|
74561
|
+
existing.positions.push(pos);
|
|
74562
|
+
}
|
|
74563
|
+
}
|
|
74564
|
+
} else {
|
|
74565
|
+
result.push(d);
|
|
74566
|
+
}
|
|
74567
|
+
}
|
|
74568
|
+
return result;
|
|
74569
|
+
}
|
|
74570
|
+
function detectExplicitMarkers(responses) {
|
|
74571
|
+
const out2 = [];
|
|
74572
|
+
for (const member of responses) {
|
|
74573
|
+
const markerSentence = extractMarkerSentence(member.response, EXPLICIT_DISAGREEMENT_MARKERS);
|
|
74574
|
+
if (!markerSentence)
|
|
74575
|
+
continue;
|
|
74576
|
+
const position = {
|
|
74577
|
+
memberId: member.memberId,
|
|
74578
|
+
claim: markerSentence,
|
|
74579
|
+
evidence: member.sources[0]?.url ?? "(no source cited in marker sentence)"
|
|
74580
|
+
};
|
|
74581
|
+
out2.push({
|
|
74582
|
+
topic: markerSentence.slice(0, 80),
|
|
74583
|
+
positions: [position]
|
|
74584
|
+
});
|
|
74585
|
+
}
|
|
74586
|
+
return out2;
|
|
74587
|
+
}
|
|
74588
|
+
function extractRecommendation(response) {
|
|
74589
|
+
return extractMarkerSentence(response, STRONG_RECOMMENDATION_MARKERS);
|
|
74590
|
+
}
|
|
74591
|
+
function detectClaimDivergence(responses) {
|
|
74592
|
+
const recommendations = [];
|
|
74593
|
+
for (const member of responses) {
|
|
74594
|
+
const rec = extractRecommendation(member.response);
|
|
74595
|
+
if (!rec)
|
|
74596
|
+
continue;
|
|
74597
|
+
recommendations.push({
|
|
74598
|
+
memberId: member.memberId,
|
|
74599
|
+
text: rec,
|
|
74600
|
+
evidence: member.sources[0]?.url ?? "(no source cited)"
|
|
74601
|
+
});
|
|
74602
|
+
}
|
|
74603
|
+
const out2 = [];
|
|
74604
|
+
for (let i2 = 0;i2 < recommendations.length; i2++) {
|
|
74605
|
+
for (let j = i2 + 1;j < recommendations.length; j++) {
|
|
74606
|
+
const a = recommendations[i2];
|
|
74607
|
+
const b = recommendations[j];
|
|
74608
|
+
if (!a || !b)
|
|
74609
|
+
continue;
|
|
74610
|
+
const topicOverlap = termOverlap(a.text, b.text);
|
|
74611
|
+
if (topicOverlap > 0.4)
|
|
74612
|
+
continue;
|
|
74613
|
+
if (topicOverlap > 0 && topicOverlap < 0.3) {
|
|
74614
|
+
const topic = `${a.text.slice(0, 50)} vs ${b.text.slice(0, 50)}`;
|
|
74615
|
+
out2.push({
|
|
74616
|
+
topic,
|
|
74617
|
+
positions: [
|
|
74618
|
+
{ memberId: a.memberId, claim: a.text, evidence: a.evidence },
|
|
74619
|
+
{ memberId: b.memberId, claim: b.text, evidence: b.evidence }
|
|
74620
|
+
]
|
|
74621
|
+
});
|
|
74622
|
+
}
|
|
74623
|
+
}
|
|
74624
|
+
}
|
|
74625
|
+
return out2;
|
|
74626
|
+
}
|
|
74627
|
+
function detectDisagreements(responses) {
|
|
74628
|
+
if (!Array.isArray(responses) || responses.length < 2)
|
|
74629
|
+
return [];
|
|
74630
|
+
const safeResponses = responses.filter((r) => typeof r?.memberId === "string" && typeof r?.response === "string");
|
|
74631
|
+
const explicit = detectExplicitMarkers(safeResponses);
|
|
74632
|
+
const divergent = detectClaimDivergence(safeResponses);
|
|
74633
|
+
const combined = [...explicit, ...divergent];
|
|
74634
|
+
const deduped = dedupeByTopic(combined);
|
|
74635
|
+
return deduped.slice(0, MAX_DISAGREEMENTS);
|
|
74636
|
+
}
|
|
74637
|
+
|
|
74638
|
+
// src/council/general-council-service.ts
|
|
74639
|
+
var CONSENSUS_WEIGHT_THRESHOLD = 0.6;
|
|
74640
|
+
function tokenize2(text) {
|
|
74641
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4);
|
|
74642
|
+
}
|
|
74643
|
+
function similarity(a, b) {
|
|
74644
|
+
const tokensA = new Set(tokenize2(a));
|
|
74645
|
+
const tokensB = new Set(tokenize2(b));
|
|
74646
|
+
if (tokensA.size === 0 || tokensB.size === 0)
|
|
74647
|
+
return 0;
|
|
74648
|
+
let intersection3 = 0;
|
|
74649
|
+
for (const t of tokensA)
|
|
74650
|
+
if (tokensB.has(t))
|
|
74651
|
+
intersection3++;
|
|
74652
|
+
const union3 = tokensA.size + tokensB.size - intersection3;
|
|
74653
|
+
return union3 === 0 ? 0 : intersection3 / union3;
|
|
74654
|
+
}
|
|
74655
|
+
function extractClaims(response) {
|
|
74656
|
+
return response.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length >= 30 && s.length <= 400);
|
|
74657
|
+
}
|
|
74658
|
+
function buildConsensusClusters(responses) {
|
|
74659
|
+
if (responses.length < 2)
|
|
74660
|
+
return [];
|
|
74661
|
+
const totalMembers = responses.length;
|
|
74662
|
+
const clusters = [];
|
|
74663
|
+
for (const member of responses) {
|
|
74664
|
+
const confidence = clamp01(member.confidence ?? 0.5);
|
|
74665
|
+
const claims = extractClaims(member.response ?? "");
|
|
74666
|
+
for (const claim of claims) {
|
|
74667
|
+
let assigned = false;
|
|
74668
|
+
for (const cluster of clusters) {
|
|
74669
|
+
if (similarity(cluster.representative, claim) >= 0.5) {
|
|
74670
|
+
if (!cluster.memberIds.has(member.memberId)) {
|
|
74671
|
+
cluster.weightedAgreement += confidence;
|
|
74672
|
+
cluster.memberIds.add(member.memberId);
|
|
74673
|
+
}
|
|
74674
|
+
if (claim.length > cluster.representative.length) {
|
|
74675
|
+
cluster.representative = claim;
|
|
74676
|
+
}
|
|
74677
|
+
assigned = true;
|
|
74678
|
+
break;
|
|
74679
|
+
}
|
|
74680
|
+
}
|
|
74681
|
+
if (!assigned) {
|
|
74682
|
+
clusters.push({
|
|
74683
|
+
representative: claim,
|
|
74684
|
+
weightedAgreement: confidence,
|
|
74685
|
+
memberIds: new Set([member.memberId])
|
|
74686
|
+
});
|
|
74687
|
+
}
|
|
74688
|
+
}
|
|
74689
|
+
}
|
|
74690
|
+
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);
|
|
74691
|
+
}
|
|
74692
|
+
function clamp01(n) {
|
|
74693
|
+
if (typeof n !== "number" || Number.isNaN(n))
|
|
74694
|
+
return 0;
|
|
74695
|
+
if (n < 0)
|
|
74696
|
+
return 0;
|
|
74697
|
+
if (n > 1)
|
|
74698
|
+
return 1;
|
|
74699
|
+
return n;
|
|
74700
|
+
}
|
|
74701
|
+
function computePersistingDisagreements(disagreements, round2) {
|
|
74702
|
+
if (disagreements.length === 0)
|
|
74703
|
+
return [];
|
|
74704
|
+
if (round2.length === 0)
|
|
74705
|
+
return disagreements;
|
|
74706
|
+
return disagreements.filter((d) => {
|
|
74707
|
+
const disputants = new Set(d.positions.map((p) => p.memberId));
|
|
74708
|
+
const conceded = round2.some((r) => {
|
|
74709
|
+
if (!disputants.has(r.memberId))
|
|
74710
|
+
return false;
|
|
74711
|
+
if (!r.disagreementTopics?.includes(d.topic))
|
|
74712
|
+
return false;
|
|
74713
|
+
return /\bconcede\b/i.test(r.response ?? "");
|
|
74714
|
+
});
|
|
74715
|
+
return !conceded;
|
|
74716
|
+
});
|
|
74717
|
+
}
|
|
74718
|
+
function dedupeSources(round1, round2) {
|
|
74719
|
+
const seen = new Set;
|
|
74720
|
+
const out2 = [];
|
|
74721
|
+
const allSources = [...round1, ...round2].flatMap((r) => r.sources ?? []);
|
|
74722
|
+
for (const src of allSources) {
|
|
74723
|
+
if (!src?.url)
|
|
74724
|
+
continue;
|
|
74725
|
+
if (seen.has(src.url))
|
|
74726
|
+
continue;
|
|
74727
|
+
seen.add(src.url);
|
|
74728
|
+
out2.push(src);
|
|
74729
|
+
}
|
|
74730
|
+
return out2;
|
|
74731
|
+
}
|
|
74732
|
+
function renderSynthesisMarkdown(question, mode, roundsCompleted, members, consensusPoints, persistingDisagreements, allSources) {
|
|
74733
|
+
const memberLines = members.map((m) => `- ${m.memberId} (${m.model}, ${m.role})`).join(`
|
|
74734
|
+
`);
|
|
74735
|
+
const consensusBlock = consensusPoints.length > 0 ? consensusPoints.map((c) => `- ${c}`).join(`
|
|
74736
|
+
`) : "_No consensus claims reached the weighted-agreement threshold._";
|
|
74737
|
+
const disagreementsBlock = persistingDisagreements.length > 0 ? persistingDisagreements.map((d) => `- **${d.topic}**
|
|
74738
|
+
` + d.positions.map((p) => ` - ${p.memberId}: ${p.claim}`).join(`
|
|
74739
|
+
`)).join(`
|
|
74740
|
+
`) : "_No persisting disagreements after deliberation._";
|
|
74741
|
+
const sourcesBlock = allSources.length > 0 ? allSources.map((s) => `- [${s.title || s.url}](${s.url})`).join(`
|
|
74742
|
+
`) : "_No sources cited._";
|
|
74743
|
+
return [
|
|
74744
|
+
"## General Council Synthesis",
|
|
74745
|
+
"",
|
|
74746
|
+
`**Question:** ${question}`,
|
|
74747
|
+
`**Mode:** ${mode}`,
|
|
74748
|
+
`**Members:**
|
|
74749
|
+
${memberLines}`,
|
|
74750
|
+
`**Rounds:** ${roundsCompleted}`,
|
|
74751
|
+
"",
|
|
74752
|
+
"### Consensus",
|
|
74753
|
+
consensusBlock,
|
|
74754
|
+
"",
|
|
74755
|
+
"### Persistent Disagreements",
|
|
74756
|
+
disagreementsBlock,
|
|
74757
|
+
"",
|
|
74758
|
+
"### Sources",
|
|
74759
|
+
sourcesBlock
|
|
74760
|
+
].join(`
|
|
74761
|
+
`);
|
|
74762
|
+
}
|
|
74763
|
+
function synthesizeGeneralCouncil(question, mode, round1Responses, round2Responses) {
|
|
74764
|
+
const safeRound1 = Array.isArray(round1Responses) ? round1Responses : [];
|
|
74765
|
+
const safeRound2 = Array.isArray(round2Responses) ? round2Responses : [];
|
|
74766
|
+
const disagreements = detectDisagreements(safeRound1);
|
|
74767
|
+
const consensusPoints = buildConsensusClusters(safeRound1);
|
|
74768
|
+
const persistingDisagreements = computePersistingDisagreements(disagreements, safeRound2);
|
|
74769
|
+
const allSources = dedupeSources(safeRound1, safeRound2);
|
|
74770
|
+
const roundsCompleted = safeRound2.length > 0 ? 2 : 1;
|
|
74771
|
+
const synthesis = renderSynthesisMarkdown(question, mode, roundsCompleted, safeRound1, consensusPoints, persistingDisagreements, allSources);
|
|
74772
|
+
return {
|
|
74773
|
+
question,
|
|
74774
|
+
mode,
|
|
74775
|
+
round1Responses: safeRound1,
|
|
74776
|
+
disagreements,
|
|
74777
|
+
round2Responses: safeRound2,
|
|
74778
|
+
synthesis,
|
|
74779
|
+
consensusPoints,
|
|
74780
|
+
persistingDisagreements: persistingDisagreements.map((d) => d.topic),
|
|
74781
|
+
allSources,
|
|
74782
|
+
timestamp: new Date().toISOString()
|
|
74783
|
+
};
|
|
74784
|
+
}
|
|
74785
|
+
|
|
74786
|
+
// src/tools/convene-general-council.ts
|
|
74787
|
+
init_state();
|
|
74788
|
+
init_create_tool();
|
|
74789
|
+
init_resolve_working_directory();
|
|
74790
|
+
var WebSearchResultSchema = exports_external.object({
|
|
74791
|
+
title: exports_external.string(),
|
|
74792
|
+
url: exports_external.string(),
|
|
74793
|
+
snippet: exports_external.string(),
|
|
74794
|
+
query: exports_external.string()
|
|
74795
|
+
});
|
|
74796
|
+
var MemberRoleEnum = exports_external.enum([
|
|
74797
|
+
"generalist",
|
|
74798
|
+
"skeptic",
|
|
74799
|
+
"domain_expert",
|
|
74800
|
+
"devil_advocate",
|
|
74801
|
+
"synthesizer"
|
|
74802
|
+
]);
|
|
74803
|
+
var Round1ResponseSchema = exports_external.object({
|
|
74804
|
+
memberId: exports_external.string().min(1),
|
|
74805
|
+
model: exports_external.string().min(1),
|
|
74806
|
+
role: MemberRoleEnum,
|
|
74807
|
+
response: exports_external.string(),
|
|
74808
|
+
sources: exports_external.array(WebSearchResultSchema).default([]),
|
|
74809
|
+
searchQueries: exports_external.array(exports_external.string()).default([]),
|
|
74810
|
+
confidence: exports_external.number().min(0).max(1),
|
|
74811
|
+
areasOfUncertainty: exports_external.array(exports_external.string()).default([]),
|
|
74812
|
+
durationMs: exports_external.number().nonnegative().default(0)
|
|
74813
|
+
});
|
|
74814
|
+
var Round2ResponseSchema = Round1ResponseSchema.extend({
|
|
74815
|
+
disagreementTopics: exports_external.array(exports_external.string()).default([])
|
|
74816
|
+
});
|
|
74817
|
+
var ArgsSchema2 = exports_external.object({
|
|
74818
|
+
question: exports_external.string().min(1).max(8000),
|
|
74819
|
+
mode: exports_external.enum(["general", "spec_review"]).default("general"),
|
|
74820
|
+
members: exports_external.array(exports_external.string()).default([]),
|
|
74821
|
+
round1Responses: exports_external.array(Round1ResponseSchema).min(1),
|
|
74822
|
+
round2Responses: exports_external.array(Round2ResponseSchema).optional(),
|
|
74823
|
+
working_directory: exports_external.string().optional()
|
|
74824
|
+
});
|
|
74825
|
+
function buildModeratorPrompt(question, synthesis) {
|
|
74826
|
+
return [
|
|
74827
|
+
"A multi-model council has deliberated on the following question. Your job is to synthesize",
|
|
74828
|
+
"the council output into a single coherent answer for the user, following the rules in your",
|
|
74829
|
+
"system prompt (lead with consensus, acknowledge persisting disagreement honestly, cite the",
|
|
74830
|
+
"strongest sources, be concise, do not invent claims, do not run new searches).",
|
|
74831
|
+
"",
|
|
74832
|
+
`QUESTION:
|
|
74833
|
+
${question}`,
|
|
74834
|
+
"",
|
|
74835
|
+
"COUNCIL OUTPUT:",
|
|
74836
|
+
synthesis
|
|
74837
|
+
].join(`
|
|
74838
|
+
`);
|
|
74839
|
+
}
|
|
74840
|
+
var convene_general_council = createSwarmTool({
|
|
74841
|
+
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.",
|
|
74842
|
+
args: {
|
|
74843
|
+
question: tool.schema.string().min(1).max(8000).describe("The question put to the council, or the spec text to review."),
|
|
74844
|
+
mode: tool.schema.enum(["general", "spec_review"]).optional().describe('"general" for /swarm council; "spec_review" for SPECIFY-COUNCIL-REVIEW gate.'),
|
|
74845
|
+
members: tool.schema.array(tool.schema.string()).optional().describe("Optional list of member IDs convened (for evidence/audit)."),
|
|
74846
|
+
round1Responses: tool.schema.array(tool.schema.object({
|
|
74847
|
+
memberId: tool.schema.string().min(1),
|
|
74848
|
+
model: tool.schema.string().min(1),
|
|
74849
|
+
role: tool.schema.enum([
|
|
74850
|
+
"generalist",
|
|
74851
|
+
"skeptic",
|
|
74852
|
+
"domain_expert",
|
|
74853
|
+
"devil_advocate",
|
|
74854
|
+
"synthesizer"
|
|
74855
|
+
]),
|
|
74856
|
+
response: tool.schema.string(),
|
|
74857
|
+
sources: tool.schema.array(tool.schema.object({
|
|
74858
|
+
title: tool.schema.string(),
|
|
74859
|
+
url: tool.schema.string(),
|
|
74860
|
+
snippet: tool.schema.string(),
|
|
74861
|
+
query: tool.schema.string()
|
|
74862
|
+
})).optional(),
|
|
74863
|
+
searchQueries: tool.schema.array(tool.schema.string()).optional(),
|
|
74864
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
74865
|
+
areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
|
|
74866
|
+
durationMs: tool.schema.number().nonnegative().optional()
|
|
74867
|
+
})).describe("Round 1 member responses (one per council member)."),
|
|
74868
|
+
round2Responses: tool.schema.array(tool.schema.object({
|
|
74869
|
+
memberId: tool.schema.string().min(1),
|
|
74870
|
+
model: tool.schema.string().min(1),
|
|
74871
|
+
role: tool.schema.enum([
|
|
74872
|
+
"generalist",
|
|
74873
|
+
"skeptic",
|
|
74874
|
+
"domain_expert",
|
|
74875
|
+
"devil_advocate",
|
|
74876
|
+
"synthesizer"
|
|
74877
|
+
]),
|
|
74878
|
+
response: tool.schema.string(),
|
|
74879
|
+
sources: tool.schema.array(tool.schema.object({
|
|
74880
|
+
title: tool.schema.string(),
|
|
74881
|
+
url: tool.schema.string(),
|
|
74882
|
+
snippet: tool.schema.string(),
|
|
74883
|
+
query: tool.schema.string()
|
|
74884
|
+
})).optional(),
|
|
74885
|
+
searchQueries: tool.schema.array(tool.schema.string()).optional(),
|
|
74886
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
74887
|
+
areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
|
|
74888
|
+
durationMs: tool.schema.number().nonnegative().optional(),
|
|
74889
|
+
disagreementTopics: tool.schema.array(tool.schema.string()).optional()
|
|
74890
|
+
})).optional().describe("Round 2 deliberation responses (omit when no deliberation has occurred)."),
|
|
74891
|
+
working_directory: tool.schema.string().optional().describe("Project root for config + evidence path resolution.")
|
|
74892
|
+
},
|
|
74893
|
+
execute: async (args2, directory, ctx) => {
|
|
74894
|
+
const parsed = ArgsSchema2.safeParse(args2);
|
|
74895
|
+
if (!parsed.success) {
|
|
74896
|
+
const fail = {
|
|
74897
|
+
success: false,
|
|
74898
|
+
reason: "invalid_args",
|
|
74899
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
74900
|
+
};
|
|
74901
|
+
return JSON.stringify(fail, null, 2);
|
|
74902
|
+
}
|
|
74903
|
+
const input = parsed.data;
|
|
74904
|
+
const dirResult = resolveWorkingDirectory(input.working_directory, directory);
|
|
74905
|
+
if (!dirResult.success) {
|
|
74906
|
+
const fail = {
|
|
74907
|
+
success: false,
|
|
74908
|
+
reason: "invalid_working_directory",
|
|
74909
|
+
message: dirResult.message
|
|
74910
|
+
};
|
|
74911
|
+
return JSON.stringify(fail, null, 2);
|
|
74912
|
+
}
|
|
74913
|
+
const workingDir = dirResult.directory;
|
|
74914
|
+
const config3 = loadPluginConfig(workingDir);
|
|
74915
|
+
const generalConfig = config3.council?.general;
|
|
74916
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
74917
|
+
const fail = {
|
|
74918
|
+
success: false,
|
|
74919
|
+
reason: "council_general_disabled",
|
|
74920
|
+
message: "convene_general_council requires council.general.enabled: true in opencode-swarm.json."
|
|
74921
|
+
};
|
|
74922
|
+
return JSON.stringify(fail, null, 2);
|
|
74923
|
+
}
|
|
74924
|
+
const round1 = input.round1Responses;
|
|
74925
|
+
const round2 = input.round2Responses ?? [];
|
|
74926
|
+
const result = synthesizeGeneralCouncil(input.question, input.mode, round1, round2);
|
|
74927
|
+
const evidenceDir = path73.join(workingDir, ".swarm", "council", "general");
|
|
74928
|
+
const safeTimestamp = result.timestamp.replace(/[:.]/g, "-");
|
|
74929
|
+
const evidenceFile = `${safeTimestamp}-${input.mode}.json`;
|
|
74930
|
+
const evidencePath = path73.join(evidenceDir, evidenceFile);
|
|
74931
|
+
try {
|
|
74932
|
+
await fs59.promises.mkdir(evidenceDir, { recursive: true });
|
|
74933
|
+
await fs59.promises.writeFile(evidencePath, JSON.stringify(result, null, 2));
|
|
74934
|
+
} catch (err2) {
|
|
74935
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
74936
|
+
console.warn(`[convene_general_council] Failed to write evidence to ${evidencePath}: ${message}`);
|
|
74937
|
+
}
|
|
74938
|
+
try {
|
|
74939
|
+
const sessionID = ctx?.sessionID;
|
|
74940
|
+
if (sessionID) {
|
|
74941
|
+
const session = getAgentSession(sessionID);
|
|
74942
|
+
if (session) {
|
|
74943
|
+
pushGeneralCouncilAdvisory(session, result);
|
|
74944
|
+
}
|
|
74945
|
+
}
|
|
74946
|
+
} catch {}
|
|
74947
|
+
const moderatorPrompt = generalConfig.moderator === true ? buildModeratorPrompt(input.question, result.synthesis) : undefined;
|
|
74948
|
+
const ok = {
|
|
74949
|
+
success: true,
|
|
74950
|
+
question: input.question,
|
|
74951
|
+
mode: input.mode,
|
|
74952
|
+
roundsCompleted: round2.length > 0 ? 2 : 1,
|
|
74953
|
+
consensusPoints: result.consensusPoints,
|
|
74954
|
+
disagreementsCount: result.disagreements.length,
|
|
74955
|
+
persistingDisagreements: result.persistingDisagreements,
|
|
74956
|
+
allSourcesCount: result.allSources.length,
|
|
74957
|
+
synthesis: result.synthesis,
|
|
74958
|
+
...moderatorPrompt !== undefined && { moderatorPrompt },
|
|
74959
|
+
evidencePath
|
|
74960
|
+
};
|
|
74961
|
+
return JSON.stringify(ok, null, 2);
|
|
74962
|
+
}
|
|
74963
|
+
});
|
|
74017
74964
|
// src/tools/curator-analyze.ts
|
|
74018
74965
|
init_dist();
|
|
74019
74966
|
init_config();
|
|
@@ -74143,7 +75090,7 @@ var CriteriaItemSchema = exports_external.object({
|
|
|
74143
75090
|
description: exports_external.string().min(10).max(500),
|
|
74144
75091
|
mandatory: exports_external.boolean()
|
|
74145
75092
|
});
|
|
74146
|
-
var
|
|
75093
|
+
var ArgsSchema3 = exports_external.object({
|
|
74147
75094
|
taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format"),
|
|
74148
75095
|
criteria: exports_external.array(CriteriaItemSchema).min(1).max(20),
|
|
74149
75096
|
working_directory: exports_external.string().optional()
|
|
@@ -74160,7 +75107,7 @@ var declare_council_criteria = createSwarmTool({
|
|
|
74160
75107
|
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.")
|
|
74161
75108
|
},
|
|
74162
75109
|
async execute(args2, directory) {
|
|
74163
|
-
const parsed =
|
|
75110
|
+
const parsed = ArgsSchema3.safeParse(args2);
|
|
74164
75111
|
if (!parsed.success) {
|
|
74165
75112
|
return JSON.stringify({
|
|
74166
75113
|
success: false,
|
|
@@ -74221,8 +75168,8 @@ init_scope_persistence();
|
|
|
74221
75168
|
init_state();
|
|
74222
75169
|
init_task_id();
|
|
74223
75170
|
init_create_tool();
|
|
74224
|
-
import * as
|
|
74225
|
-
import * as
|
|
75171
|
+
import * as fs60 from "fs";
|
|
75172
|
+
import * as path74 from "path";
|
|
74226
75173
|
function validateTaskIdFormat2(taskId) {
|
|
74227
75174
|
return validateTaskIdFormat(taskId);
|
|
74228
75175
|
}
|
|
@@ -74296,8 +75243,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74296
75243
|
};
|
|
74297
75244
|
}
|
|
74298
75245
|
}
|
|
74299
|
-
normalizedDir =
|
|
74300
|
-
const pathParts = normalizedDir.split(
|
|
75246
|
+
normalizedDir = path74.normalize(args2.working_directory);
|
|
75247
|
+
const pathParts = normalizedDir.split(path74.sep);
|
|
74301
75248
|
if (pathParts.includes("..")) {
|
|
74302
75249
|
return {
|
|
74303
75250
|
success: false,
|
|
@@ -74307,11 +75254,11 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74307
75254
|
]
|
|
74308
75255
|
};
|
|
74309
75256
|
}
|
|
74310
|
-
const resolvedDir =
|
|
75257
|
+
const resolvedDir = path74.resolve(normalizedDir);
|
|
74311
75258
|
try {
|
|
74312
|
-
const realPath =
|
|
74313
|
-
const planPath2 =
|
|
74314
|
-
if (!
|
|
75259
|
+
const realPath = fs60.realpathSync(resolvedDir);
|
|
75260
|
+
const planPath2 = path74.join(realPath, ".swarm", "plan.json");
|
|
75261
|
+
if (!fs60.existsSync(planPath2)) {
|
|
74315
75262
|
return {
|
|
74316
75263
|
success: false,
|
|
74317
75264
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -74334,8 +75281,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74334
75281
|
console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
|
|
74335
75282
|
}
|
|
74336
75283
|
const directory = normalizedDir || fallbackDir;
|
|
74337
|
-
const planPath =
|
|
74338
|
-
if (!
|
|
75284
|
+
const planPath = path74.resolve(directory, ".swarm", "plan.json");
|
|
75285
|
+
if (!fs60.existsSync(planPath)) {
|
|
74339
75286
|
return {
|
|
74340
75287
|
success: false,
|
|
74341
75288
|
message: "No plan found",
|
|
@@ -74344,7 +75291,7 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74344
75291
|
}
|
|
74345
75292
|
let planContent;
|
|
74346
75293
|
try {
|
|
74347
|
-
planContent = JSON.parse(
|
|
75294
|
+
planContent = JSON.parse(fs60.readFileSync(planPath, "utf-8"));
|
|
74348
75295
|
} catch {
|
|
74349
75296
|
return {
|
|
74350
75297
|
success: false,
|
|
@@ -74374,8 +75321,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74374
75321
|
const normalizeErrors = [];
|
|
74375
75322
|
const dir = normalizedDir || fallbackDir || process.cwd();
|
|
74376
75323
|
const mergedFiles = rawMergedFiles.map((file3) => {
|
|
74377
|
-
if (
|
|
74378
|
-
const relativePath =
|
|
75324
|
+
if (path74.isAbsolute(file3)) {
|
|
75325
|
+
const relativePath = path74.relative(dir, file3).replace(/\\/g, "/");
|
|
74379
75326
|
if (relativePath.startsWith("..")) {
|
|
74380
75327
|
normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
|
|
74381
75328
|
return file3;
|
|
@@ -74435,8 +75382,8 @@ var declare_scope = createSwarmTool({
|
|
|
74435
75382
|
// src/tools/diff.ts
|
|
74436
75383
|
init_dist();
|
|
74437
75384
|
import * as child_process7 from "child_process";
|
|
74438
|
-
import * as
|
|
74439
|
-
import * as
|
|
75385
|
+
import * as fs61 from "fs";
|
|
75386
|
+
import * as path75 from "path";
|
|
74440
75387
|
init_create_tool();
|
|
74441
75388
|
var MAX_DIFF_LINES = 500;
|
|
74442
75389
|
var DIFF_TIMEOUT_MS = 30000;
|
|
@@ -74465,20 +75412,20 @@ function validateBase(base) {
|
|
|
74465
75412
|
function validatePaths(paths) {
|
|
74466
75413
|
if (!paths)
|
|
74467
75414
|
return null;
|
|
74468
|
-
for (const
|
|
74469
|
-
if (!
|
|
75415
|
+
for (const path76 of paths) {
|
|
75416
|
+
if (!path76 || path76.length === 0) {
|
|
74470
75417
|
return "empty path not allowed";
|
|
74471
75418
|
}
|
|
74472
|
-
if (
|
|
75419
|
+
if (path76.length > MAX_PATH_LENGTH) {
|
|
74473
75420
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
74474
75421
|
}
|
|
74475
|
-
if (SHELL_METACHARACTERS2.test(
|
|
75422
|
+
if (SHELL_METACHARACTERS2.test(path76)) {
|
|
74476
75423
|
return "path contains shell metacharacters";
|
|
74477
75424
|
}
|
|
74478
|
-
if (
|
|
75425
|
+
if (path76.startsWith("-")) {
|
|
74479
75426
|
return 'path cannot start with "-" (option-like arguments not allowed)';
|
|
74480
75427
|
}
|
|
74481
|
-
if (CONTROL_CHAR_PATTERN2.test(
|
|
75428
|
+
if (CONTROL_CHAR_PATTERN2.test(path76)) {
|
|
74482
75429
|
return "path contains control characters";
|
|
74483
75430
|
}
|
|
74484
75431
|
}
|
|
@@ -74584,8 +75531,8 @@ var diff = createSwarmTool({
|
|
|
74584
75531
|
if (parts2.length >= 3) {
|
|
74585
75532
|
const additions = parseInt(parts2[0], 10) || 0;
|
|
74586
75533
|
const deletions = parseInt(parts2[1], 10) || 0;
|
|
74587
|
-
const
|
|
74588
|
-
files.push({ path:
|
|
75534
|
+
const path76 = parts2[2];
|
|
75535
|
+
files.push({ path: path76, additions, deletions });
|
|
74589
75536
|
}
|
|
74590
75537
|
}
|
|
74591
75538
|
const contractChanges = [];
|
|
@@ -74625,7 +75572,7 @@ var diff = createSwarmTool({
|
|
|
74625
75572
|
} else if (base === "unstaged") {
|
|
74626
75573
|
const oldRef = `:${file3.path}`;
|
|
74627
75574
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
74628
|
-
newContent =
|
|
75575
|
+
newContent = fs61.readFileSync(path75.join(directory, file3.path), "utf-8");
|
|
74629
75576
|
} else {
|
|
74630
75577
|
const oldRef = `${base}:${file3.path}`;
|
|
74631
75578
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
@@ -74699,8 +75646,8 @@ var diff = createSwarmTool({
|
|
|
74699
75646
|
// src/tools/diff-summary.ts
|
|
74700
75647
|
init_dist();
|
|
74701
75648
|
import * as child_process8 from "child_process";
|
|
74702
|
-
import * as
|
|
74703
|
-
import * as
|
|
75649
|
+
import * as fs62 from "fs";
|
|
75650
|
+
import * as path76 from "path";
|
|
74704
75651
|
init_create_tool();
|
|
74705
75652
|
var diff_summary = createSwarmTool({
|
|
74706
75653
|
description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
|
|
@@ -74748,7 +75695,7 @@ var diff_summary = createSwarmTool({
|
|
|
74748
75695
|
}
|
|
74749
75696
|
try {
|
|
74750
75697
|
let oldContent;
|
|
74751
|
-
const newContent =
|
|
75698
|
+
const newContent = fs62.readFileSync(path76.join(workingDir, filePath), "utf-8");
|
|
74752
75699
|
if (fileExistsInHead) {
|
|
74753
75700
|
oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
|
|
74754
75701
|
encoding: "utf-8",
|
|
@@ -74975,8 +75922,8 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
74975
75922
|
init_dist();
|
|
74976
75923
|
init_create_tool();
|
|
74977
75924
|
init_path_security();
|
|
74978
|
-
import * as
|
|
74979
|
-
import * as
|
|
75925
|
+
import * as fs63 from "fs";
|
|
75926
|
+
import * as path77 from "path";
|
|
74980
75927
|
var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
|
|
74981
75928
|
var MAX_EVIDENCE_FILES = 1000;
|
|
74982
75929
|
var EVIDENCE_DIR3 = ".swarm/evidence";
|
|
@@ -75003,9 +75950,9 @@ function validateRequiredTypes(input) {
|
|
|
75003
75950
|
return null;
|
|
75004
75951
|
}
|
|
75005
75952
|
function isPathWithinSwarm2(filePath, cwd) {
|
|
75006
|
-
const normalizedCwd =
|
|
75007
|
-
const swarmPath =
|
|
75008
|
-
const normalizedPath =
|
|
75953
|
+
const normalizedCwd = path77.resolve(cwd);
|
|
75954
|
+
const swarmPath = path77.join(normalizedCwd, ".swarm");
|
|
75955
|
+
const normalizedPath = path77.resolve(filePath);
|
|
75009
75956
|
return normalizedPath.startsWith(swarmPath);
|
|
75010
75957
|
}
|
|
75011
75958
|
function parseCompletedTasks(planContent) {
|
|
@@ -75021,12 +75968,12 @@ function parseCompletedTasks(planContent) {
|
|
|
75021
75968
|
}
|
|
75022
75969
|
function readEvidenceFiles(evidenceDir, _cwd) {
|
|
75023
75970
|
const evidence = [];
|
|
75024
|
-
if (!
|
|
75971
|
+
if (!fs63.existsSync(evidenceDir) || !fs63.statSync(evidenceDir).isDirectory()) {
|
|
75025
75972
|
return evidence;
|
|
75026
75973
|
}
|
|
75027
75974
|
let files;
|
|
75028
75975
|
try {
|
|
75029
|
-
files =
|
|
75976
|
+
files = fs63.readdirSync(evidenceDir);
|
|
75030
75977
|
} catch {
|
|
75031
75978
|
return evidence;
|
|
75032
75979
|
}
|
|
@@ -75035,14 +75982,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75035
75982
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
75036
75983
|
continue;
|
|
75037
75984
|
}
|
|
75038
|
-
const filePath =
|
|
75985
|
+
const filePath = path77.join(evidenceDir, filename);
|
|
75039
75986
|
try {
|
|
75040
|
-
const resolvedPath =
|
|
75041
|
-
const evidenceDirResolved =
|
|
75987
|
+
const resolvedPath = path77.resolve(filePath);
|
|
75988
|
+
const evidenceDirResolved = path77.resolve(evidenceDir);
|
|
75042
75989
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
75043
75990
|
continue;
|
|
75044
75991
|
}
|
|
75045
|
-
const stat4 =
|
|
75992
|
+
const stat4 = fs63.lstatSync(filePath);
|
|
75046
75993
|
if (!stat4.isFile()) {
|
|
75047
75994
|
continue;
|
|
75048
75995
|
}
|
|
@@ -75051,7 +75998,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75051
75998
|
}
|
|
75052
75999
|
let fileStat;
|
|
75053
76000
|
try {
|
|
75054
|
-
fileStat =
|
|
76001
|
+
fileStat = fs63.statSync(filePath);
|
|
75055
76002
|
if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
|
|
75056
76003
|
continue;
|
|
75057
76004
|
}
|
|
@@ -75060,7 +76007,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75060
76007
|
}
|
|
75061
76008
|
let content;
|
|
75062
76009
|
try {
|
|
75063
|
-
content =
|
|
76010
|
+
content = fs63.readFileSync(filePath, "utf-8");
|
|
75064
76011
|
} catch {
|
|
75065
76012
|
continue;
|
|
75066
76013
|
}
|
|
@@ -75156,7 +76103,7 @@ var evidence_check = createSwarmTool({
|
|
|
75156
76103
|
return JSON.stringify(errorResult, null, 2);
|
|
75157
76104
|
}
|
|
75158
76105
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
|
|
75159
|
-
const planPath =
|
|
76106
|
+
const planPath = path77.join(cwd, PLAN_FILE);
|
|
75160
76107
|
if (!isPathWithinSwarm2(planPath, cwd)) {
|
|
75161
76108
|
const errorResult = {
|
|
75162
76109
|
error: "plan file path validation failed",
|
|
@@ -75170,7 +76117,7 @@ var evidence_check = createSwarmTool({
|
|
|
75170
76117
|
}
|
|
75171
76118
|
let planContent;
|
|
75172
76119
|
try {
|
|
75173
|
-
planContent =
|
|
76120
|
+
planContent = fs63.readFileSync(planPath, "utf-8");
|
|
75174
76121
|
} catch {
|
|
75175
76122
|
const result2 = {
|
|
75176
76123
|
message: "No completed tasks found in plan.",
|
|
@@ -75188,7 +76135,7 @@ var evidence_check = createSwarmTool({
|
|
|
75188
76135
|
};
|
|
75189
76136
|
return JSON.stringify(result2, null, 2);
|
|
75190
76137
|
}
|
|
75191
|
-
const evidenceDir =
|
|
76138
|
+
const evidenceDir = path77.join(cwd, EVIDENCE_DIR3);
|
|
75192
76139
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
75193
76140
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
75194
76141
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -75205,8 +76152,8 @@ var evidence_check = createSwarmTool({
|
|
|
75205
76152
|
// src/tools/file-extractor.ts
|
|
75206
76153
|
init_tool();
|
|
75207
76154
|
init_create_tool();
|
|
75208
|
-
import * as
|
|
75209
|
-
import * as
|
|
76155
|
+
import * as fs64 from "fs";
|
|
76156
|
+
import * as path78 from "path";
|
|
75210
76157
|
var EXT_MAP = {
|
|
75211
76158
|
python: ".py",
|
|
75212
76159
|
py: ".py",
|
|
@@ -75268,8 +76215,8 @@ var extract_code_blocks = createSwarmTool({
|
|
|
75268
76215
|
execute: async (args2, directory) => {
|
|
75269
76216
|
const { content, output_dir, prefix } = args2;
|
|
75270
76217
|
const targetDir = output_dir || directory;
|
|
75271
|
-
if (!
|
|
75272
|
-
|
|
76218
|
+
if (!fs64.existsSync(targetDir)) {
|
|
76219
|
+
fs64.mkdirSync(targetDir, { recursive: true });
|
|
75273
76220
|
}
|
|
75274
76221
|
if (!content) {
|
|
75275
76222
|
return "Error: content is required";
|
|
@@ -75287,16 +76234,16 @@ var extract_code_blocks = createSwarmTool({
|
|
|
75287
76234
|
if (prefix) {
|
|
75288
76235
|
filename = `${prefix}_${filename}`;
|
|
75289
76236
|
}
|
|
75290
|
-
let filepath =
|
|
75291
|
-
const base =
|
|
75292
|
-
const ext =
|
|
76237
|
+
let filepath = path78.join(targetDir, filename);
|
|
76238
|
+
const base = path78.basename(filepath, path78.extname(filepath));
|
|
76239
|
+
const ext = path78.extname(filepath);
|
|
75293
76240
|
let counter = 1;
|
|
75294
|
-
while (
|
|
75295
|
-
filepath =
|
|
76241
|
+
while (fs64.existsSync(filepath)) {
|
|
76242
|
+
filepath = path78.join(targetDir, `${base}_${counter}${ext}`);
|
|
75296
76243
|
counter++;
|
|
75297
76244
|
}
|
|
75298
76245
|
try {
|
|
75299
|
-
|
|
76246
|
+
fs64.writeFileSync(filepath, code.trim(), "utf-8");
|
|
75300
76247
|
savedFiles.push(filepath);
|
|
75301
76248
|
} catch (error93) {
|
|
75302
76249
|
errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
|
|
@@ -75555,8 +76502,8 @@ var gitingest = createSwarmTool({
|
|
|
75555
76502
|
init_dist();
|
|
75556
76503
|
init_create_tool();
|
|
75557
76504
|
init_path_security();
|
|
75558
|
-
import * as
|
|
75559
|
-
import * as
|
|
76505
|
+
import * as fs65 from "fs";
|
|
76506
|
+
import * as path79 from "path";
|
|
75560
76507
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
75561
76508
|
var MAX_SYMBOL_LENGTH = 256;
|
|
75562
76509
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
@@ -75604,7 +76551,7 @@ function validateSymbolInput(symbol3) {
|
|
|
75604
76551
|
return null;
|
|
75605
76552
|
}
|
|
75606
76553
|
function isBinaryFile2(filePath, buffer) {
|
|
75607
|
-
const ext =
|
|
76554
|
+
const ext = path79.extname(filePath).toLowerCase();
|
|
75608
76555
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
75609
76556
|
return false;
|
|
75610
76557
|
}
|
|
@@ -75628,15 +76575,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
75628
76575
|
const imports = [];
|
|
75629
76576
|
let _resolvedTarget;
|
|
75630
76577
|
try {
|
|
75631
|
-
_resolvedTarget =
|
|
76578
|
+
_resolvedTarget = path79.resolve(targetFile);
|
|
75632
76579
|
} catch {
|
|
75633
76580
|
_resolvedTarget = targetFile;
|
|
75634
76581
|
}
|
|
75635
|
-
const targetBasename =
|
|
76582
|
+
const targetBasename = path79.basename(targetFile, path79.extname(targetFile));
|
|
75636
76583
|
const targetWithExt = targetFile;
|
|
75637
76584
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
75638
|
-
const normalizedTargetWithExt =
|
|
75639
|
-
const normalizedTargetWithoutExt =
|
|
76585
|
+
const normalizedTargetWithExt = path79.normalize(targetWithExt).replace(/\\/g, "/");
|
|
76586
|
+
const normalizedTargetWithoutExt = path79.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
75640
76587
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
75641
76588
|
for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
|
|
75642
76589
|
const modulePath = match[1] || match[2] || match[3];
|
|
@@ -75659,9 +76606,9 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
75659
76606
|
}
|
|
75660
76607
|
const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
75661
76608
|
let isMatch = false;
|
|
75662
|
-
const _targetDir =
|
|
75663
|
-
const targetExt =
|
|
75664
|
-
const targetBasenameNoExt =
|
|
76609
|
+
const _targetDir = path79.dirname(targetFile);
|
|
76610
|
+
const targetExt = path79.extname(targetFile);
|
|
76611
|
+
const targetBasenameNoExt = path79.basename(targetFile, targetExt);
|
|
75665
76612
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
75666
76613
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
75667
76614
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
@@ -75718,7 +76665,7 @@ var SKIP_DIRECTORIES4 = new Set([
|
|
|
75718
76665
|
function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
|
|
75719
76666
|
let entries;
|
|
75720
76667
|
try {
|
|
75721
|
-
entries =
|
|
76668
|
+
entries = fs65.readdirSync(dir);
|
|
75722
76669
|
} catch (e) {
|
|
75723
76670
|
stats.fileErrors.push({
|
|
75724
76671
|
path: dir,
|
|
@@ -75729,13 +76676,13 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
75729
76676
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
75730
76677
|
for (const entry of entries) {
|
|
75731
76678
|
if (SKIP_DIRECTORIES4.has(entry)) {
|
|
75732
|
-
stats.skippedDirs.push(
|
|
76679
|
+
stats.skippedDirs.push(path79.join(dir, entry));
|
|
75733
76680
|
continue;
|
|
75734
76681
|
}
|
|
75735
|
-
const fullPath =
|
|
76682
|
+
const fullPath = path79.join(dir, entry);
|
|
75736
76683
|
let stat4;
|
|
75737
76684
|
try {
|
|
75738
|
-
stat4 =
|
|
76685
|
+
stat4 = fs65.statSync(fullPath);
|
|
75739
76686
|
} catch (e) {
|
|
75740
76687
|
stats.fileErrors.push({
|
|
75741
76688
|
path: fullPath,
|
|
@@ -75746,7 +76693,7 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
75746
76693
|
if (stat4.isDirectory()) {
|
|
75747
76694
|
findSourceFiles3(fullPath, files, stats);
|
|
75748
76695
|
} else if (stat4.isFile()) {
|
|
75749
|
-
const ext =
|
|
76696
|
+
const ext = path79.extname(fullPath).toLowerCase();
|
|
75750
76697
|
if (SUPPORTED_EXTENSIONS3.includes(ext)) {
|
|
75751
76698
|
files.push(fullPath);
|
|
75752
76699
|
}
|
|
@@ -75803,8 +76750,8 @@ var imports = createSwarmTool({
|
|
|
75803
76750
|
return JSON.stringify(errorResult, null, 2);
|
|
75804
76751
|
}
|
|
75805
76752
|
try {
|
|
75806
|
-
const targetFile =
|
|
75807
|
-
if (!
|
|
76753
|
+
const targetFile = path79.resolve(file3);
|
|
76754
|
+
if (!fs65.existsSync(targetFile)) {
|
|
75808
76755
|
const errorResult = {
|
|
75809
76756
|
error: `target file not found: ${file3}`,
|
|
75810
76757
|
target: file3,
|
|
@@ -75814,7 +76761,7 @@ var imports = createSwarmTool({
|
|
|
75814
76761
|
};
|
|
75815
76762
|
return JSON.stringify(errorResult, null, 2);
|
|
75816
76763
|
}
|
|
75817
|
-
const targetStat =
|
|
76764
|
+
const targetStat = fs65.statSync(targetFile);
|
|
75818
76765
|
if (!targetStat.isFile()) {
|
|
75819
76766
|
const errorResult = {
|
|
75820
76767
|
error: "target must be a file, not a directory",
|
|
@@ -75825,7 +76772,7 @@ var imports = createSwarmTool({
|
|
|
75825
76772
|
};
|
|
75826
76773
|
return JSON.stringify(errorResult, null, 2);
|
|
75827
76774
|
}
|
|
75828
|
-
const baseDir =
|
|
76775
|
+
const baseDir = path79.dirname(targetFile);
|
|
75829
76776
|
const scanStats = {
|
|
75830
76777
|
skippedDirs: [],
|
|
75831
76778
|
skippedFiles: 0,
|
|
@@ -75840,12 +76787,12 @@ var imports = createSwarmTool({
|
|
|
75840
76787
|
if (consumers.length >= MAX_CONSUMERS)
|
|
75841
76788
|
break;
|
|
75842
76789
|
try {
|
|
75843
|
-
const stat4 =
|
|
76790
|
+
const stat4 = fs65.statSync(filePath);
|
|
75844
76791
|
if (stat4.size > MAX_FILE_SIZE_BYTES7) {
|
|
75845
76792
|
skippedFileCount++;
|
|
75846
76793
|
continue;
|
|
75847
76794
|
}
|
|
75848
|
-
const buffer =
|
|
76795
|
+
const buffer = fs65.readFileSync(filePath);
|
|
75849
76796
|
if (isBinaryFile2(filePath, buffer)) {
|
|
75850
76797
|
skippedFileCount++;
|
|
75851
76798
|
continue;
|
|
@@ -76357,8 +77304,8 @@ init_schema();
|
|
|
76357
77304
|
init_qa_gate_profile();
|
|
76358
77305
|
init_manager2();
|
|
76359
77306
|
init_curator();
|
|
76360
|
-
import * as
|
|
76361
|
-
import * as
|
|
77307
|
+
import * as fs66 from "fs";
|
|
77308
|
+
import * as path80 from "path";
|
|
76362
77309
|
init_knowledge_curator();
|
|
76363
77310
|
init_knowledge_reader();
|
|
76364
77311
|
init_knowledge_store();
|
|
@@ -76589,11 +77536,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76589
77536
|
safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
|
|
76590
77537
|
}
|
|
76591
77538
|
try {
|
|
76592
|
-
const driftEvidencePath =
|
|
77539
|
+
const driftEvidencePath = path80.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
|
|
76593
77540
|
let driftVerdictFound = false;
|
|
76594
77541
|
let driftVerdictApproved = false;
|
|
76595
77542
|
try {
|
|
76596
|
-
const driftEvidenceContent =
|
|
77543
|
+
const driftEvidenceContent = fs66.readFileSync(driftEvidencePath, "utf-8");
|
|
76597
77544
|
const driftEvidence = JSON.parse(driftEvidenceContent);
|
|
76598
77545
|
const entries = driftEvidence.entries ?? [];
|
|
76599
77546
|
for (const entry of entries) {
|
|
@@ -76623,14 +77570,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76623
77570
|
driftVerdictFound = false;
|
|
76624
77571
|
}
|
|
76625
77572
|
if (!driftVerdictFound) {
|
|
76626
|
-
const specPath =
|
|
76627
|
-
const specExists =
|
|
77573
|
+
const specPath = path80.join(dir, ".swarm", "spec.md");
|
|
77574
|
+
const specExists = fs66.existsSync(specPath);
|
|
76628
77575
|
if (!specExists) {
|
|
76629
77576
|
let incompleteTaskCount = 0;
|
|
76630
77577
|
let planPhaseFound = false;
|
|
76631
77578
|
try {
|
|
76632
77579
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76633
|
-
const planRaw =
|
|
77580
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76634
77581
|
const plan = JSON.parse(planRaw);
|
|
76635
77582
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76636
77583
|
if (targetPhase) {
|
|
@@ -76681,11 +77628,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76681
77628
|
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
76682
77629
|
const effective = getEffectiveGates(profile, overrides);
|
|
76683
77630
|
if (effective.hallucination_guard === true) {
|
|
76684
|
-
const hgPath =
|
|
77631
|
+
const hgPath = path80.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
|
|
76685
77632
|
let hgVerdictFound = false;
|
|
76686
77633
|
let hgVerdictApproved = false;
|
|
76687
77634
|
try {
|
|
76688
|
-
const hgContent =
|
|
77635
|
+
const hgContent = fs66.readFileSync(hgPath, "utf-8");
|
|
76689
77636
|
const hgBundle = JSON.parse(hgContent);
|
|
76690
77637
|
for (const entry of hgBundle.entries ?? []) {
|
|
76691
77638
|
if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
|
|
@@ -76753,11 +77700,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76753
77700
|
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
76754
77701
|
const effective = getEffectiveGates(profile, overrides);
|
|
76755
77702
|
if (effective.mutation_test === true) {
|
|
76756
|
-
const mgPath =
|
|
77703
|
+
const mgPath = path80.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
|
|
76757
77704
|
let mgVerdictFound = false;
|
|
76758
77705
|
let mgVerdict;
|
|
76759
77706
|
try {
|
|
76760
|
-
const mgContent =
|
|
77707
|
+
const mgContent = fs66.readFileSync(mgPath, "utf-8");
|
|
76761
77708
|
const mgBundle = JSON.parse(mgContent);
|
|
76762
77709
|
for (const entry of mgBundle.entries ?? []) {
|
|
76763
77710
|
if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
|
|
@@ -76825,7 +77772,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76825
77772
|
}
|
|
76826
77773
|
if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
|
|
76827
77774
|
try {
|
|
76828
|
-
const projectName =
|
|
77775
|
+
const projectName = path80.basename(dir);
|
|
76829
77776
|
const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
|
|
76830
77777
|
if (curationResult) {
|
|
76831
77778
|
const sessionState = swarmState.agentSessions.get(sessionID);
|
|
@@ -76905,7 +77852,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76905
77852
|
let phaseRequiredAgents;
|
|
76906
77853
|
try {
|
|
76907
77854
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76908
|
-
const planRaw =
|
|
77855
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76909
77856
|
const plan = JSON.parse(planRaw);
|
|
76910
77857
|
const phaseObj = plan.phases.find((p) => p.id === phase);
|
|
76911
77858
|
phaseRequiredAgents = phaseObj?.required_agents;
|
|
@@ -76920,7 +77867,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76920
77867
|
if (agentsMissing.length > 0) {
|
|
76921
77868
|
try {
|
|
76922
77869
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76923
|
-
const planRaw =
|
|
77870
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76924
77871
|
const plan = JSON.parse(planRaw);
|
|
76925
77872
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76926
77873
|
if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
|
|
@@ -76960,7 +77907,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76960
77907
|
if (phaseCompleteConfig.regression_sweep?.enforce) {
|
|
76961
77908
|
try {
|
|
76962
77909
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76963
|
-
const planRaw =
|
|
77910
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76964
77911
|
const plan = JSON.parse(planRaw);
|
|
76965
77912
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76966
77913
|
if (targetPhase) {
|
|
@@ -77014,7 +77961,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77014
77961
|
}
|
|
77015
77962
|
try {
|
|
77016
77963
|
const eventsPath = validateSwarmPath(dir, "events.jsonl");
|
|
77017
|
-
|
|
77964
|
+
fs66.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
77018
77965
|
`, "utf-8");
|
|
77019
77966
|
} catch (writeError) {
|
|
77020
77967
|
warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
@@ -77089,12 +78036,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77089
78036
|
warnings.push(`Warning: failed to update plan.json phase status`);
|
|
77090
78037
|
try {
|
|
77091
78038
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
77092
|
-
const planRaw =
|
|
78039
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
77093
78040
|
const plan2 = JSON.parse(planRaw);
|
|
77094
78041
|
const phaseObj = plan2.phases.find((p) => p.id === phase);
|
|
77095
78042
|
if (phaseObj) {
|
|
77096
78043
|
phaseObj.status = "complete";
|
|
77097
|
-
|
|
78044
|
+
fs66.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
|
|
77098
78045
|
}
|
|
77099
78046
|
} catch {}
|
|
77100
78047
|
} else if (plan) {
|
|
@@ -77131,12 +78078,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77131
78078
|
warnings.push(`Warning: failed to update plan.json phase status`);
|
|
77132
78079
|
try {
|
|
77133
78080
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
77134
|
-
const planRaw =
|
|
78081
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
77135
78082
|
const plan = JSON.parse(planRaw);
|
|
77136
78083
|
const phaseObj = plan.phases.find((p) => p.id === phase);
|
|
77137
78084
|
if (phaseObj) {
|
|
77138
78085
|
phaseObj.status = "complete";
|
|
77139
|
-
|
|
78086
|
+
fs66.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
77140
78087
|
}
|
|
77141
78088
|
} catch {}
|
|
77142
78089
|
}
|
|
@@ -77193,8 +78140,8 @@ init_dist();
|
|
|
77193
78140
|
init_discovery();
|
|
77194
78141
|
init_utils();
|
|
77195
78142
|
init_create_tool();
|
|
77196
|
-
import * as
|
|
77197
|
-
import * as
|
|
78143
|
+
import * as fs67 from "fs";
|
|
78144
|
+
import * as path81 from "path";
|
|
77198
78145
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
77199
78146
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
77200
78147
|
function isValidEcosystem(value) {
|
|
@@ -77222,31 +78169,31 @@ function validateArgs3(args2) {
|
|
|
77222
78169
|
function detectEcosystems(directory) {
|
|
77223
78170
|
const ecosystems = [];
|
|
77224
78171
|
const cwd = directory;
|
|
77225
|
-
if (
|
|
78172
|
+
if (fs67.existsSync(path81.join(cwd, "package.json"))) {
|
|
77226
78173
|
ecosystems.push("npm");
|
|
77227
78174
|
}
|
|
77228
|
-
if (
|
|
78175
|
+
if (fs67.existsSync(path81.join(cwd, "pyproject.toml")) || fs67.existsSync(path81.join(cwd, "requirements.txt"))) {
|
|
77229
78176
|
ecosystems.push("pip");
|
|
77230
78177
|
}
|
|
77231
|
-
if (
|
|
78178
|
+
if (fs67.existsSync(path81.join(cwd, "Cargo.toml"))) {
|
|
77232
78179
|
ecosystems.push("cargo");
|
|
77233
78180
|
}
|
|
77234
|
-
if (
|
|
78181
|
+
if (fs67.existsSync(path81.join(cwd, "go.mod"))) {
|
|
77235
78182
|
ecosystems.push("go");
|
|
77236
78183
|
}
|
|
77237
78184
|
try {
|
|
77238
|
-
const files =
|
|
78185
|
+
const files = fs67.readdirSync(cwd);
|
|
77239
78186
|
if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
|
|
77240
78187
|
ecosystems.push("dotnet");
|
|
77241
78188
|
}
|
|
77242
78189
|
} catch {}
|
|
77243
|
-
if (
|
|
78190
|
+
if (fs67.existsSync(path81.join(cwd, "Gemfile")) || fs67.existsSync(path81.join(cwd, "Gemfile.lock"))) {
|
|
77244
78191
|
ecosystems.push("ruby");
|
|
77245
78192
|
}
|
|
77246
|
-
if (
|
|
78193
|
+
if (fs67.existsSync(path81.join(cwd, "pubspec.yaml"))) {
|
|
77247
78194
|
ecosystems.push("dart");
|
|
77248
78195
|
}
|
|
77249
|
-
if (
|
|
78196
|
+
if (fs67.existsSync(path81.join(cwd, "composer.lock"))) {
|
|
77250
78197
|
ecosystems.push("composer");
|
|
77251
78198
|
}
|
|
77252
78199
|
return ecosystems;
|
|
@@ -78405,8 +79352,8 @@ var pkg_audit = createSwarmTool({
|
|
|
78405
79352
|
// src/tools/placeholder-scan.ts
|
|
78406
79353
|
init_dist();
|
|
78407
79354
|
init_manager2();
|
|
78408
|
-
import * as
|
|
78409
|
-
import * as
|
|
79355
|
+
import * as fs68 from "fs";
|
|
79356
|
+
import * as path82 from "path";
|
|
78410
79357
|
init_utils();
|
|
78411
79358
|
init_create_tool();
|
|
78412
79359
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
@@ -78529,7 +79476,7 @@ function isScaffoldFile(filePath) {
|
|
|
78529
79476
|
if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
|
|
78530
79477
|
return true;
|
|
78531
79478
|
}
|
|
78532
|
-
const filename =
|
|
79479
|
+
const filename = path82.basename(filePath);
|
|
78533
79480
|
if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
|
|
78534
79481
|
return true;
|
|
78535
79482
|
}
|
|
@@ -78546,7 +79493,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
|
|
|
78546
79493
|
if (regex.test(normalizedPath)) {
|
|
78547
79494
|
return true;
|
|
78548
79495
|
}
|
|
78549
|
-
const filename =
|
|
79496
|
+
const filename = path82.basename(filePath);
|
|
78550
79497
|
const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
78551
79498
|
if (filenameRegex.test(filename)) {
|
|
78552
79499
|
return true;
|
|
@@ -78555,7 +79502,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
|
|
|
78555
79502
|
return false;
|
|
78556
79503
|
}
|
|
78557
79504
|
function isParserSupported(filePath) {
|
|
78558
|
-
const ext =
|
|
79505
|
+
const ext = path82.extname(filePath).toLowerCase();
|
|
78559
79506
|
return SUPPORTED_PARSER_EXTENSIONS.has(ext);
|
|
78560
79507
|
}
|
|
78561
79508
|
function isPlanFile(filePath) {
|
|
@@ -78802,28 +79749,28 @@ async function placeholderScan(input, directory) {
|
|
|
78802
79749
|
let filesScanned = 0;
|
|
78803
79750
|
const filesWithFindings = new Set;
|
|
78804
79751
|
for (const filePath of changed_files) {
|
|
78805
|
-
const fullPath =
|
|
78806
|
-
const resolvedDirectory =
|
|
78807
|
-
if (!fullPath.startsWith(resolvedDirectory +
|
|
79752
|
+
const fullPath = path82.isAbsolute(filePath) ? filePath : path82.resolve(directory, filePath);
|
|
79753
|
+
const resolvedDirectory = path82.resolve(directory);
|
|
79754
|
+
if (!fullPath.startsWith(resolvedDirectory + path82.sep) && fullPath !== resolvedDirectory) {
|
|
78808
79755
|
continue;
|
|
78809
79756
|
}
|
|
78810
|
-
if (!
|
|
79757
|
+
if (!fs68.existsSync(fullPath)) {
|
|
78811
79758
|
continue;
|
|
78812
79759
|
}
|
|
78813
79760
|
if (isAllowedByGlobs(filePath, allow_globs)) {
|
|
78814
79761
|
continue;
|
|
78815
79762
|
}
|
|
78816
|
-
const relativeFilePath =
|
|
79763
|
+
const relativeFilePath = path82.relative(directory, fullPath).replace(/\\/g, "/");
|
|
78817
79764
|
if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
|
|
78818
79765
|
continue;
|
|
78819
79766
|
}
|
|
78820
79767
|
let content;
|
|
78821
79768
|
try {
|
|
78822
|
-
const stat4 =
|
|
79769
|
+
const stat4 = fs68.statSync(fullPath);
|
|
78823
79770
|
if (stat4.size > MAX_FILE_SIZE) {
|
|
78824
79771
|
continue;
|
|
78825
79772
|
}
|
|
78826
|
-
content =
|
|
79773
|
+
content = fs68.readFileSync(fullPath, "utf-8");
|
|
78827
79774
|
} catch {
|
|
78828
79775
|
continue;
|
|
78829
79776
|
}
|
|
@@ -78885,8 +79832,8 @@ var placeholder_scan = createSwarmTool({
|
|
|
78885
79832
|
});
|
|
78886
79833
|
// src/tools/pre-check-batch.ts
|
|
78887
79834
|
init_dist();
|
|
78888
|
-
import * as
|
|
78889
|
-
import * as
|
|
79835
|
+
import * as fs71 from "fs";
|
|
79836
|
+
import * as path85 from "path";
|
|
78890
79837
|
init_manager2();
|
|
78891
79838
|
init_utils();
|
|
78892
79839
|
init_create_tool();
|
|
@@ -79021,8 +79968,8 @@ var quality_budget = createSwarmTool({
|
|
|
79021
79968
|
init_dist();
|
|
79022
79969
|
init_manager2();
|
|
79023
79970
|
init_detector();
|
|
79024
|
-
import * as
|
|
79025
|
-
import * as
|
|
79971
|
+
import * as fs70 from "fs";
|
|
79972
|
+
import * as path84 from "path";
|
|
79026
79973
|
import { extname as extname18 } from "path";
|
|
79027
79974
|
|
|
79028
79975
|
// src/sast/rules/c.ts
|
|
@@ -79915,25 +80862,25 @@ init_create_tool();
|
|
|
79915
80862
|
// src/tools/sast-baseline.ts
|
|
79916
80863
|
init_utils2();
|
|
79917
80864
|
import * as crypto8 from "crypto";
|
|
79918
|
-
import * as
|
|
79919
|
-
import * as
|
|
80865
|
+
import * as fs69 from "fs";
|
|
80866
|
+
import * as path83 from "path";
|
|
79920
80867
|
var BASELINE_SCHEMA_VERSION = "1.0.0";
|
|
79921
80868
|
var MAX_BASELINE_FINDINGS = 2000;
|
|
79922
80869
|
var MAX_BASELINE_BYTES = 2 * 1048576;
|
|
79923
80870
|
var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
|
|
79924
80871
|
function normalizeFindingPath(directory, file3) {
|
|
79925
|
-
const resolved =
|
|
79926
|
-
const rel =
|
|
80872
|
+
const resolved = path83.isAbsolute(file3) ? file3 : path83.resolve(directory, file3);
|
|
80873
|
+
const rel = path83.relative(path83.resolve(directory), resolved);
|
|
79927
80874
|
return rel.replace(/\\/g, "/");
|
|
79928
80875
|
}
|
|
79929
80876
|
function baselineRelPath(phase) {
|
|
79930
|
-
return
|
|
80877
|
+
return path83.join("evidence", String(phase), "sast-baseline.json");
|
|
79931
80878
|
}
|
|
79932
80879
|
function tempRelPath(phase) {
|
|
79933
|
-
return
|
|
80880
|
+
return path83.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
|
|
79934
80881
|
}
|
|
79935
80882
|
function lockRelPath(phase) {
|
|
79936
|
-
return
|
|
80883
|
+
return path83.join("evidence", String(phase), "sast-baseline.json.lock");
|
|
79937
80884
|
}
|
|
79938
80885
|
function getLine(lines, idx) {
|
|
79939
80886
|
if (idx < 0 || idx >= lines.length)
|
|
@@ -79950,7 +80897,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
|
|
|
79950
80897
|
}
|
|
79951
80898
|
const lineNum = finding.location.line;
|
|
79952
80899
|
try {
|
|
79953
|
-
const content =
|
|
80900
|
+
const content = fs69.readFileSync(finding.location.file, "utf-8");
|
|
79954
80901
|
const lines = content.split(`
|
|
79955
80902
|
`);
|
|
79956
80903
|
const idx = lineNum - 1;
|
|
@@ -79981,7 +80928,7 @@ function assignOccurrenceIndices(findings, directory) {
|
|
|
79981
80928
|
try {
|
|
79982
80929
|
if (relFile.startsWith(".."))
|
|
79983
80930
|
throw new Error("escapes workspace");
|
|
79984
|
-
const content =
|
|
80931
|
+
const content = fs69.readFileSync(finding.location.file, "utf-8");
|
|
79985
80932
|
const lines = content.split(`
|
|
79986
80933
|
`);
|
|
79987
80934
|
const idx = lineNum - 1;
|
|
@@ -80010,11 +80957,11 @@ function assignOccurrenceIndices(findings, directory) {
|
|
|
80010
80957
|
async function acquireLock(lockPath) {
|
|
80011
80958
|
for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
|
|
80012
80959
|
try {
|
|
80013
|
-
const fd =
|
|
80014
|
-
|
|
80960
|
+
const fd = fs69.openSync(lockPath, "wx");
|
|
80961
|
+
fs69.closeSync(fd);
|
|
80015
80962
|
return () => {
|
|
80016
80963
|
try {
|
|
80017
|
-
|
|
80964
|
+
fs69.unlinkSync(lockPath);
|
|
80018
80965
|
} catch {}
|
|
80019
80966
|
};
|
|
80020
80967
|
} catch {
|
|
@@ -80054,12 +81001,12 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80054
81001
|
message: e instanceof Error ? e.message : "Path validation failed"
|
|
80055
81002
|
};
|
|
80056
81003
|
}
|
|
80057
|
-
|
|
81004
|
+
fs69.mkdirSync(path83.dirname(baselinePath), { recursive: true });
|
|
80058
81005
|
const releaseLock = await acquireLock(lockPath);
|
|
80059
81006
|
try {
|
|
80060
81007
|
let existing = null;
|
|
80061
81008
|
try {
|
|
80062
|
-
const raw =
|
|
81009
|
+
const raw = fs69.readFileSync(baselinePath, "utf-8");
|
|
80063
81010
|
const parsed = JSON.parse(raw);
|
|
80064
81011
|
if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
|
|
80065
81012
|
existing = parsed;
|
|
@@ -80119,8 +81066,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80119
81066
|
message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
|
|
80120
81067
|
};
|
|
80121
81068
|
}
|
|
80122
|
-
|
|
80123
|
-
|
|
81069
|
+
fs69.writeFileSync(tempPath, json4, "utf-8");
|
|
81070
|
+
fs69.renameSync(tempPath, baselinePath);
|
|
80124
81071
|
return {
|
|
80125
81072
|
status: "merged",
|
|
80126
81073
|
path: baselinePath,
|
|
@@ -80151,8 +81098,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80151
81098
|
message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
|
|
80152
81099
|
};
|
|
80153
81100
|
}
|
|
80154
|
-
|
|
80155
|
-
|
|
81101
|
+
fs69.writeFileSync(tempPath, json3, "utf-8");
|
|
81102
|
+
fs69.renameSync(tempPath, baselinePath);
|
|
80156
81103
|
return {
|
|
80157
81104
|
status: "written",
|
|
80158
81105
|
path: baselinePath,
|
|
@@ -80177,7 +81124,7 @@ function loadBaseline(directory, phase) {
|
|
|
80177
81124
|
};
|
|
80178
81125
|
}
|
|
80179
81126
|
try {
|
|
80180
|
-
const raw =
|
|
81127
|
+
const raw = fs69.readFileSync(baselinePath, "utf-8");
|
|
80181
81128
|
const parsed = JSON.parse(raw);
|
|
80182
81129
|
if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
|
|
80183
81130
|
return {
|
|
@@ -80219,17 +81166,17 @@ var SEVERITY_ORDER = {
|
|
|
80219
81166
|
};
|
|
80220
81167
|
function shouldSkipFile(filePath) {
|
|
80221
81168
|
try {
|
|
80222
|
-
const stats =
|
|
81169
|
+
const stats = fs70.statSync(filePath);
|
|
80223
81170
|
if (stats.size > MAX_FILE_SIZE_BYTES8) {
|
|
80224
81171
|
return { skip: true, reason: "file too large" };
|
|
80225
81172
|
}
|
|
80226
81173
|
if (stats.size === 0) {
|
|
80227
81174
|
return { skip: true, reason: "empty file" };
|
|
80228
81175
|
}
|
|
80229
|
-
const fd =
|
|
81176
|
+
const fd = fs70.openSync(filePath, "r");
|
|
80230
81177
|
const buffer = Buffer.alloc(8192);
|
|
80231
|
-
const bytesRead =
|
|
80232
|
-
|
|
81178
|
+
const bytesRead = fs70.readSync(fd, buffer, 0, 8192, 0);
|
|
81179
|
+
fs70.closeSync(fd);
|
|
80233
81180
|
if (bytesRead > 0) {
|
|
80234
81181
|
let nullCount = 0;
|
|
80235
81182
|
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
@@ -80268,7 +81215,7 @@ function countBySeverity(findings) {
|
|
|
80268
81215
|
}
|
|
80269
81216
|
function scanFileWithTierA(filePath, language) {
|
|
80270
81217
|
try {
|
|
80271
|
-
const content =
|
|
81218
|
+
const content = fs70.readFileSync(filePath, "utf-8");
|
|
80272
81219
|
const findings = executeRulesSync(filePath, content, language);
|
|
80273
81220
|
return findings.map((f) => ({
|
|
80274
81221
|
rule_id: f.rule_id,
|
|
@@ -80321,13 +81268,13 @@ async function sastScan(input, directory, config3) {
|
|
|
80321
81268
|
_filesSkipped++;
|
|
80322
81269
|
continue;
|
|
80323
81270
|
}
|
|
80324
|
-
const resolvedPath =
|
|
80325
|
-
const resolvedDirectory =
|
|
80326
|
-
if (!resolvedPath.startsWith(resolvedDirectory +
|
|
81271
|
+
const resolvedPath = path84.isAbsolute(filePath) ? filePath : path84.resolve(directory, filePath);
|
|
81272
|
+
const resolvedDirectory = path84.resolve(directory);
|
|
81273
|
+
if (!resolvedPath.startsWith(resolvedDirectory + path84.sep) && resolvedPath !== resolvedDirectory) {
|
|
80327
81274
|
_filesSkipped++;
|
|
80328
81275
|
continue;
|
|
80329
81276
|
}
|
|
80330
|
-
if (!
|
|
81277
|
+
if (!fs70.existsSync(resolvedPath)) {
|
|
80331
81278
|
_filesSkipped++;
|
|
80332
81279
|
continue;
|
|
80333
81280
|
}
|
|
@@ -80634,18 +81581,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
|
|
|
80634
81581
|
let resolved;
|
|
80635
81582
|
const isWinAbs = isWindowsAbsolutePath(inputPath);
|
|
80636
81583
|
if (isWinAbs) {
|
|
80637
|
-
resolved =
|
|
80638
|
-
} else if (
|
|
80639
|
-
resolved =
|
|
81584
|
+
resolved = path85.win32.resolve(inputPath);
|
|
81585
|
+
} else if (path85.isAbsolute(inputPath)) {
|
|
81586
|
+
resolved = path85.resolve(inputPath);
|
|
80640
81587
|
} else {
|
|
80641
|
-
resolved =
|
|
81588
|
+
resolved = path85.resolve(baseDir, inputPath);
|
|
80642
81589
|
}
|
|
80643
|
-
const workspaceResolved =
|
|
81590
|
+
const workspaceResolved = path85.resolve(workspaceDir);
|
|
80644
81591
|
let relative20;
|
|
80645
81592
|
if (isWinAbs) {
|
|
80646
|
-
relative20 =
|
|
81593
|
+
relative20 = path85.win32.relative(workspaceResolved, resolved);
|
|
80647
81594
|
} else {
|
|
80648
|
-
relative20 =
|
|
81595
|
+
relative20 = path85.relative(workspaceResolved, resolved);
|
|
80649
81596
|
}
|
|
80650
81597
|
if (relative20.startsWith("..")) {
|
|
80651
81598
|
return "path traversal detected";
|
|
@@ -80710,7 +81657,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
|
|
|
80710
81657
|
if (typeof file3 !== "string") {
|
|
80711
81658
|
continue;
|
|
80712
81659
|
}
|
|
80713
|
-
const resolvedPath =
|
|
81660
|
+
const resolvedPath = path85.resolve(file3);
|
|
80714
81661
|
const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
|
|
80715
81662
|
if (validationError) {
|
|
80716
81663
|
continue;
|
|
@@ -80867,7 +81814,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80867
81814
|
skippedFiles++;
|
|
80868
81815
|
continue;
|
|
80869
81816
|
}
|
|
80870
|
-
const resolvedPath =
|
|
81817
|
+
const resolvedPath = path85.resolve(file3);
|
|
80871
81818
|
const validationError = validatePath(resolvedPath, directory, directory);
|
|
80872
81819
|
if (validationError) {
|
|
80873
81820
|
skippedFiles++;
|
|
@@ -80885,14 +81832,14 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80885
81832
|
};
|
|
80886
81833
|
}
|
|
80887
81834
|
for (const file3 of validatedFiles) {
|
|
80888
|
-
const ext =
|
|
81835
|
+
const ext = path85.extname(file3).toLowerCase();
|
|
80889
81836
|
if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
|
|
80890
81837
|
skippedFiles++;
|
|
80891
81838
|
continue;
|
|
80892
81839
|
}
|
|
80893
81840
|
let stat4;
|
|
80894
81841
|
try {
|
|
80895
|
-
stat4 =
|
|
81842
|
+
stat4 = fs71.statSync(file3);
|
|
80896
81843
|
} catch {
|
|
80897
81844
|
skippedFiles++;
|
|
80898
81845
|
continue;
|
|
@@ -80903,7 +81850,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80903
81850
|
}
|
|
80904
81851
|
let content;
|
|
80905
81852
|
try {
|
|
80906
|
-
const buffer =
|
|
81853
|
+
const buffer = fs71.readFileSync(file3);
|
|
80907
81854
|
if (buffer.includes(0)) {
|
|
80908
81855
|
skippedFiles++;
|
|
80909
81856
|
continue;
|
|
@@ -81104,7 +82051,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
|
|
|
81104
82051
|
const preexistingFindings = [];
|
|
81105
82052
|
for (const finding of findings) {
|
|
81106
82053
|
const filePath = finding.location.file;
|
|
81107
|
-
const normalised =
|
|
82054
|
+
const normalised = path85.relative(directory, filePath).replace(/\\/g, "/");
|
|
81108
82055
|
const changedLines = changedLineRanges.get(normalised);
|
|
81109
82056
|
if (changedLines?.has(finding.location.line)) {
|
|
81110
82057
|
newFindings.push(finding);
|
|
@@ -81155,7 +82102,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
|
|
|
81155
82102
|
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
81156
82103
|
continue;
|
|
81157
82104
|
}
|
|
81158
|
-
changedFiles.push(
|
|
82105
|
+
changedFiles.push(path85.resolve(directory, file3));
|
|
81159
82106
|
}
|
|
81160
82107
|
if (changedFiles.length === 0) {
|
|
81161
82108
|
warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
|
|
@@ -81356,7 +82303,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
81356
82303
|
};
|
|
81357
82304
|
return JSON.stringify(errorResult, null, 2);
|
|
81358
82305
|
}
|
|
81359
|
-
const resolvedDirectory =
|
|
82306
|
+
const resolvedDirectory = path85.resolve(typedArgs.directory);
|
|
81360
82307
|
const workspaceAnchor = resolvedDirectory;
|
|
81361
82308
|
const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
|
|
81362
82309
|
if (dirError) {
|
|
@@ -81397,7 +82344,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
81397
82344
|
});
|
|
81398
82345
|
// src/tools/repo-map.ts
|
|
81399
82346
|
init_dist();
|
|
81400
|
-
import * as
|
|
82347
|
+
import * as path86 from "path";
|
|
81401
82348
|
init_path_security();
|
|
81402
82349
|
init_create_tool();
|
|
81403
82350
|
var VALID_ACTIONS = [
|
|
@@ -81422,7 +82369,7 @@ function validateFile(p) {
|
|
|
81422
82369
|
return "file contains control characters";
|
|
81423
82370
|
if (containsPathTraversal(p))
|
|
81424
82371
|
return "file contains path traversal";
|
|
81425
|
-
if (
|
|
82372
|
+
if (path86.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
|
|
81426
82373
|
return "file must be a workspace-relative path, not absolute";
|
|
81427
82374
|
}
|
|
81428
82375
|
return null;
|
|
@@ -81445,8 +82392,8 @@ function ok(action, payload) {
|
|
|
81445
82392
|
}
|
|
81446
82393
|
function toRelativeGraphPath(input, workspaceRoot) {
|
|
81447
82394
|
const normalized = input.replace(/\\/g, "/");
|
|
81448
|
-
if (
|
|
81449
|
-
const rel =
|
|
82395
|
+
if (path86.isAbsolute(normalized)) {
|
|
82396
|
+
const rel = path86.relative(workspaceRoot, normalized).replace(/\\/g, "/");
|
|
81450
82397
|
return normalizeGraphPath2(rel);
|
|
81451
82398
|
}
|
|
81452
82399
|
return normalizeGraphPath2(normalized);
|
|
@@ -81590,8 +82537,8 @@ var repo_map = createSwarmTool({
|
|
|
81590
82537
|
// src/tools/req-coverage.ts
|
|
81591
82538
|
init_dist();
|
|
81592
82539
|
init_create_tool();
|
|
81593
|
-
import * as
|
|
81594
|
-
import * as
|
|
82540
|
+
import * as fs72 from "fs";
|
|
82541
|
+
import * as path87 from "path";
|
|
81595
82542
|
var SPEC_FILE = ".swarm/spec.md";
|
|
81596
82543
|
var EVIDENCE_DIR4 = ".swarm/evidence";
|
|
81597
82544
|
var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
|
|
@@ -81650,19 +82597,19 @@ function extractObligationAndText(id, lineText) {
|
|
|
81650
82597
|
var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
|
|
81651
82598
|
function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
81652
82599
|
const touchedFiles = new Set;
|
|
81653
|
-
if (!
|
|
82600
|
+
if (!fs72.existsSync(evidenceDir) || !fs72.statSync(evidenceDir).isDirectory()) {
|
|
81654
82601
|
return [];
|
|
81655
82602
|
}
|
|
81656
82603
|
let entries;
|
|
81657
82604
|
try {
|
|
81658
|
-
entries =
|
|
82605
|
+
entries = fs72.readdirSync(evidenceDir);
|
|
81659
82606
|
} catch {
|
|
81660
82607
|
return [];
|
|
81661
82608
|
}
|
|
81662
82609
|
for (const entry of entries) {
|
|
81663
|
-
const entryPath =
|
|
82610
|
+
const entryPath = path87.join(evidenceDir, entry);
|
|
81664
82611
|
try {
|
|
81665
|
-
const stat4 =
|
|
82612
|
+
const stat4 = fs72.statSync(entryPath);
|
|
81666
82613
|
if (!stat4.isDirectory()) {
|
|
81667
82614
|
continue;
|
|
81668
82615
|
}
|
|
@@ -81676,14 +82623,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81676
82623
|
if (entryPhase !== String(phase)) {
|
|
81677
82624
|
continue;
|
|
81678
82625
|
}
|
|
81679
|
-
const evidenceFilePath =
|
|
82626
|
+
const evidenceFilePath = path87.join(entryPath, "evidence.json");
|
|
81680
82627
|
try {
|
|
81681
|
-
const resolvedPath =
|
|
81682
|
-
const evidenceDirResolved =
|
|
81683
|
-
if (!resolvedPath.startsWith(evidenceDirResolved +
|
|
82628
|
+
const resolvedPath = path87.resolve(evidenceFilePath);
|
|
82629
|
+
const evidenceDirResolved = path87.resolve(evidenceDir);
|
|
82630
|
+
if (!resolvedPath.startsWith(evidenceDirResolved + path87.sep)) {
|
|
81684
82631
|
continue;
|
|
81685
82632
|
}
|
|
81686
|
-
const stat4 =
|
|
82633
|
+
const stat4 = fs72.lstatSync(evidenceFilePath);
|
|
81687
82634
|
if (!stat4.isFile()) {
|
|
81688
82635
|
continue;
|
|
81689
82636
|
}
|
|
@@ -81695,7 +82642,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81695
82642
|
}
|
|
81696
82643
|
let content;
|
|
81697
82644
|
try {
|
|
81698
|
-
content =
|
|
82645
|
+
content = fs72.readFileSync(evidenceFilePath, "utf-8");
|
|
81699
82646
|
} catch {
|
|
81700
82647
|
continue;
|
|
81701
82648
|
}
|
|
@@ -81714,7 +82661,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81714
82661
|
if (Array.isArray(diffEntry.files_changed)) {
|
|
81715
82662
|
for (const file3 of diffEntry.files_changed) {
|
|
81716
82663
|
if (typeof file3 === "string") {
|
|
81717
|
-
touchedFiles.add(
|
|
82664
|
+
touchedFiles.add(path87.resolve(cwd, file3));
|
|
81718
82665
|
}
|
|
81719
82666
|
}
|
|
81720
82667
|
}
|
|
@@ -81727,12 +82674,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81727
82674
|
}
|
|
81728
82675
|
function searchFileForKeywords(filePath, keywords, cwd) {
|
|
81729
82676
|
try {
|
|
81730
|
-
const resolvedPath =
|
|
81731
|
-
const cwdResolved =
|
|
82677
|
+
const resolvedPath = path87.resolve(filePath);
|
|
82678
|
+
const cwdResolved = path87.resolve(cwd);
|
|
81732
82679
|
if (!resolvedPath.startsWith(cwdResolved)) {
|
|
81733
82680
|
return false;
|
|
81734
82681
|
}
|
|
81735
|
-
const content =
|
|
82682
|
+
const content = fs72.readFileSync(resolvedPath, "utf-8");
|
|
81736
82683
|
for (const keyword of keywords) {
|
|
81737
82684
|
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
81738
82685
|
if (regex.test(content)) {
|
|
@@ -81862,10 +82809,10 @@ var req_coverage = createSwarmTool({
|
|
|
81862
82809
|
}, null, 2);
|
|
81863
82810
|
}
|
|
81864
82811
|
const cwd = inputDirectory || directory;
|
|
81865
|
-
const specPath =
|
|
82812
|
+
const specPath = path87.join(cwd, SPEC_FILE);
|
|
81866
82813
|
let specContent;
|
|
81867
82814
|
try {
|
|
81868
|
-
specContent =
|
|
82815
|
+
specContent = fs72.readFileSync(specPath, "utf-8");
|
|
81869
82816
|
} catch (readError) {
|
|
81870
82817
|
return JSON.stringify({
|
|
81871
82818
|
success: false,
|
|
@@ -81889,7 +82836,7 @@ var req_coverage = createSwarmTool({
|
|
|
81889
82836
|
message: "No FR requirements found in spec.md"
|
|
81890
82837
|
}, null, 2);
|
|
81891
82838
|
}
|
|
81892
|
-
const evidenceDir =
|
|
82839
|
+
const evidenceDir = path87.join(cwd, EVIDENCE_DIR4);
|
|
81893
82840
|
const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
|
|
81894
82841
|
const analyzedRequirements = [];
|
|
81895
82842
|
let coveredCount = 0;
|
|
@@ -81915,12 +82862,12 @@ var req_coverage = createSwarmTool({
|
|
|
81915
82862
|
requirements: analyzedRequirements
|
|
81916
82863
|
};
|
|
81917
82864
|
const reportFilename = `req-coverage-phase-${phase}.json`;
|
|
81918
|
-
const reportPath =
|
|
82865
|
+
const reportPath = path87.join(evidenceDir, reportFilename);
|
|
81919
82866
|
try {
|
|
81920
|
-
if (!
|
|
81921
|
-
|
|
82867
|
+
if (!fs72.existsSync(evidenceDir)) {
|
|
82868
|
+
fs72.mkdirSync(evidenceDir, { recursive: true });
|
|
81922
82869
|
}
|
|
81923
|
-
|
|
82870
|
+
fs72.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
|
|
81924
82871
|
} catch (writeError) {
|
|
81925
82872
|
console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
81926
82873
|
}
|
|
@@ -81999,6 +82946,7 @@ ${paginatedContent}`;
|
|
|
81999
82946
|
// src/tools/save-plan.ts
|
|
82000
82947
|
init_tool();
|
|
82001
82948
|
init_plan_schema();
|
|
82949
|
+
init_qa_gate_profile();
|
|
82002
82950
|
init_file_locks();
|
|
82003
82951
|
init_checkpoint3();
|
|
82004
82952
|
init_ledger();
|
|
@@ -82006,8 +82954,8 @@ init_manager();
|
|
|
82006
82954
|
init_state();
|
|
82007
82955
|
init_create_tool();
|
|
82008
82956
|
import * as crypto9 from "crypto";
|
|
82009
|
-
import * as
|
|
82010
|
-
import * as
|
|
82957
|
+
import * as fs73 from "fs";
|
|
82958
|
+
import * as path88 from "path";
|
|
82011
82959
|
function detectPlaceholderContent(args2) {
|
|
82012
82960
|
const issues = [];
|
|
82013
82961
|
const placeholderPattern = /^\[\w[\w\s]*\]$/;
|
|
@@ -82081,17 +83029,17 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82081
83029
|
};
|
|
82082
83030
|
}
|
|
82083
83031
|
if (args2.working_directory && fallbackDir) {
|
|
82084
|
-
const resolvedTarget =
|
|
82085
|
-
const resolvedRoot =
|
|
83032
|
+
const resolvedTarget = path88.resolve(args2.working_directory);
|
|
83033
|
+
const resolvedRoot = path88.resolve(fallbackDir);
|
|
82086
83034
|
let fallbackExists = false;
|
|
82087
83035
|
try {
|
|
82088
|
-
|
|
83036
|
+
fs73.accessSync(resolvedRoot, fs73.constants.F_OK);
|
|
82089
83037
|
fallbackExists = true;
|
|
82090
83038
|
} catch {
|
|
82091
83039
|
fallbackExists = false;
|
|
82092
83040
|
}
|
|
82093
83041
|
if (fallbackExists) {
|
|
82094
|
-
const isSubdirectory = resolvedTarget.startsWith(resolvedRoot +
|
|
83042
|
+
const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path88.sep);
|
|
82095
83043
|
if (isSubdirectory) {
|
|
82096
83044
|
return {
|
|
82097
83045
|
success: false,
|
|
@@ -82107,11 +83055,11 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82107
83055
|
let specMtime;
|
|
82108
83056
|
let specHash;
|
|
82109
83057
|
if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
|
|
82110
|
-
const specPath =
|
|
83058
|
+
const specPath = path88.join(targetWorkspace, ".swarm", "spec.md");
|
|
82111
83059
|
try {
|
|
82112
|
-
const stat4 = await
|
|
83060
|
+
const stat4 = await fs73.promises.stat(specPath);
|
|
82113
83061
|
specMtime = stat4.mtime.toISOString();
|
|
82114
|
-
const content = await
|
|
83062
|
+
const content = await fs73.promises.readFile(specPath, "utf8");
|
|
82115
83063
|
specHash = crypto9.createHash("sha256").update(content).digest("hex");
|
|
82116
83064
|
} catch {
|
|
82117
83065
|
return {
|
|
@@ -82122,6 +83070,32 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82122
83070
|
};
|
|
82123
83071
|
}
|
|
82124
83072
|
}
|
|
83073
|
+
if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
|
|
83074
|
+
const contextPath = path88.join(targetWorkspace, ".swarm", "context.md");
|
|
83075
|
+
let contextContent = "";
|
|
83076
|
+
try {
|
|
83077
|
+
contextContent = await fs73.promises.readFile(contextPath, "utf8");
|
|
83078
|
+
} catch {}
|
|
83079
|
+
const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
|
|
83080
|
+
if (!hasPendingSection) {
|
|
83081
|
+
const candidatePlanId = `${args2.swarm_id}-${args2.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
83082
|
+
let existingProfile = null;
|
|
83083
|
+
try {
|
|
83084
|
+
existingProfile = getProfile(targetWorkspace, candidatePlanId);
|
|
83085
|
+
} catch {}
|
|
83086
|
+
if (!existingProfile) {
|
|
83087
|
+
return {
|
|
83088
|
+
success: false,
|
|
83089
|
+
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.',
|
|
83090
|
+
errors: [
|
|
83091
|
+
"Missing ## Pending QA Gate Selection in .swarm/context.md",
|
|
83092
|
+
"No existing QaGateProfile found for this plan"
|
|
83093
|
+
],
|
|
83094
|
+
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."
|
|
83095
|
+
};
|
|
83096
|
+
}
|
|
83097
|
+
}
|
|
83098
|
+
}
|
|
82125
83099
|
const dir = targetWorkspace;
|
|
82126
83100
|
const existingStatusMap = new Map;
|
|
82127
83101
|
let preservedExecutionProfile;
|
|
@@ -82247,14 +83221,14 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82247
83221
|
}
|
|
82248
83222
|
await writeCheckpoint(dir).catch(() => {});
|
|
82249
83223
|
try {
|
|
82250
|
-
const markerPath =
|
|
83224
|
+
const markerPath = path88.join(dir, ".swarm", ".plan-write-marker");
|
|
82251
83225
|
const marker = JSON.stringify({
|
|
82252
83226
|
source: "save_plan",
|
|
82253
83227
|
timestamp: new Date().toISOString(),
|
|
82254
83228
|
phases_count: plan.phases.length,
|
|
82255
83229
|
tasks_count: tasksCount
|
|
82256
83230
|
});
|
|
82257
|
-
await
|
|
83231
|
+
await fs73.promises.writeFile(markerPath, marker, "utf8");
|
|
82258
83232
|
} catch {}
|
|
82259
83233
|
const warnings = [];
|
|
82260
83234
|
let criticReviewFound = false;
|
|
@@ -82270,7 +83244,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82270
83244
|
return {
|
|
82271
83245
|
success: true,
|
|
82272
83246
|
message: "Plan saved successfully",
|
|
82273
|
-
plan_path:
|
|
83247
|
+
plan_path: path88.join(dir, ".swarm", "plan.json"),
|
|
82274
83248
|
phases_count: plan.phases.length,
|
|
82275
83249
|
tasks_count: tasksCount,
|
|
82276
83250
|
...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
|
|
@@ -82322,8 +83296,8 @@ var save_plan = createSwarmTool({
|
|
|
82322
83296
|
// src/tools/sbom-generate.ts
|
|
82323
83297
|
init_dist();
|
|
82324
83298
|
init_manager2();
|
|
82325
|
-
import * as
|
|
82326
|
-
import * as
|
|
83299
|
+
import * as fs74 from "fs";
|
|
83300
|
+
import * as path89 from "path";
|
|
82327
83301
|
|
|
82328
83302
|
// src/sbom/detectors/index.ts
|
|
82329
83303
|
init_utils();
|
|
@@ -83171,9 +84145,9 @@ function findManifestFiles(rootDir) {
|
|
|
83171
84145
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
83172
84146
|
function searchDir(dir) {
|
|
83173
84147
|
try {
|
|
83174
|
-
const entries =
|
|
84148
|
+
const entries = fs74.readdirSync(dir, { withFileTypes: true });
|
|
83175
84149
|
for (const entry of entries) {
|
|
83176
|
-
const fullPath =
|
|
84150
|
+
const fullPath = path89.join(dir, entry.name);
|
|
83177
84151
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
83178
84152
|
continue;
|
|
83179
84153
|
}
|
|
@@ -83182,7 +84156,7 @@ function findManifestFiles(rootDir) {
|
|
|
83182
84156
|
} else if (entry.isFile()) {
|
|
83183
84157
|
for (const pattern of patterns) {
|
|
83184
84158
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
83185
|
-
manifestFiles.push(
|
|
84159
|
+
manifestFiles.push(path89.relative(rootDir, fullPath));
|
|
83186
84160
|
break;
|
|
83187
84161
|
}
|
|
83188
84162
|
}
|
|
@@ -83198,13 +84172,13 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
83198
84172
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
83199
84173
|
for (const dir of directories) {
|
|
83200
84174
|
try {
|
|
83201
|
-
const entries =
|
|
84175
|
+
const entries = fs74.readdirSync(dir, { withFileTypes: true });
|
|
83202
84176
|
for (const entry of entries) {
|
|
83203
|
-
const fullPath =
|
|
84177
|
+
const fullPath = path89.join(dir, entry.name);
|
|
83204
84178
|
if (entry.isFile()) {
|
|
83205
84179
|
for (const pattern of patterns) {
|
|
83206
84180
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
83207
|
-
found.push(
|
|
84181
|
+
found.push(path89.relative(workingDir, fullPath));
|
|
83208
84182
|
break;
|
|
83209
84183
|
}
|
|
83210
84184
|
}
|
|
@@ -83217,11 +84191,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
83217
84191
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
83218
84192
|
const dirs = new Set;
|
|
83219
84193
|
for (const file3 of changedFiles) {
|
|
83220
|
-
let currentDir =
|
|
84194
|
+
let currentDir = path89.dirname(file3);
|
|
83221
84195
|
while (true) {
|
|
83222
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
83223
|
-
dirs.add(
|
|
83224
|
-
const parent =
|
|
84196
|
+
if (currentDir && currentDir !== "." && currentDir !== path89.sep) {
|
|
84197
|
+
dirs.add(path89.join(workingDir, currentDir));
|
|
84198
|
+
const parent = path89.dirname(currentDir);
|
|
83225
84199
|
if (parent === currentDir)
|
|
83226
84200
|
break;
|
|
83227
84201
|
currentDir = parent;
|
|
@@ -83235,7 +84209,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
83235
84209
|
}
|
|
83236
84210
|
function ensureOutputDir(outputDir) {
|
|
83237
84211
|
try {
|
|
83238
|
-
|
|
84212
|
+
fs74.mkdirSync(outputDir, { recursive: true });
|
|
83239
84213
|
} catch (error93) {
|
|
83240
84214
|
if (!error93 || error93.code !== "EEXIST") {
|
|
83241
84215
|
throw error93;
|
|
@@ -83305,7 +84279,7 @@ var sbom_generate = createSwarmTool({
|
|
|
83305
84279
|
const changedFiles = obj.changed_files;
|
|
83306
84280
|
const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
|
|
83307
84281
|
const workingDir = directory;
|
|
83308
|
-
const outputDir =
|
|
84282
|
+
const outputDir = path89.isAbsolute(relativeOutputDir) ? relativeOutputDir : path89.join(workingDir, relativeOutputDir);
|
|
83309
84283
|
let manifestFiles = [];
|
|
83310
84284
|
if (scope === "all") {
|
|
83311
84285
|
manifestFiles = findManifestFiles(workingDir);
|
|
@@ -83328,11 +84302,11 @@ var sbom_generate = createSwarmTool({
|
|
|
83328
84302
|
const processedFiles = [];
|
|
83329
84303
|
for (const manifestFile of manifestFiles) {
|
|
83330
84304
|
try {
|
|
83331
|
-
const fullPath =
|
|
83332
|
-
if (!
|
|
84305
|
+
const fullPath = path89.isAbsolute(manifestFile) ? manifestFile : path89.join(workingDir, manifestFile);
|
|
84306
|
+
if (!fs74.existsSync(fullPath)) {
|
|
83333
84307
|
continue;
|
|
83334
84308
|
}
|
|
83335
|
-
const content =
|
|
84309
|
+
const content = fs74.readFileSync(fullPath, "utf-8");
|
|
83336
84310
|
const components = detectComponents(manifestFile, content);
|
|
83337
84311
|
processedFiles.push(manifestFile);
|
|
83338
84312
|
if (components.length > 0) {
|
|
@@ -83345,8 +84319,8 @@ var sbom_generate = createSwarmTool({
|
|
|
83345
84319
|
const bom = generateCycloneDX(allComponents);
|
|
83346
84320
|
const bomJson = serializeCycloneDX(bom);
|
|
83347
84321
|
const filename = generateSbomFilename();
|
|
83348
|
-
const outputPath =
|
|
83349
|
-
|
|
84322
|
+
const outputPath = path89.join(outputDir, filename);
|
|
84323
|
+
fs74.writeFileSync(outputPath, bomJson, "utf-8");
|
|
83350
84324
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
83351
84325
|
try {
|
|
83352
84326
|
const timestamp = new Date().toISOString();
|
|
@@ -83388,8 +84362,8 @@ var sbom_generate = createSwarmTool({
|
|
|
83388
84362
|
// src/tools/schema-drift.ts
|
|
83389
84363
|
init_dist();
|
|
83390
84364
|
init_create_tool();
|
|
83391
|
-
import * as
|
|
83392
|
-
import * as
|
|
84365
|
+
import * as fs75 from "fs";
|
|
84366
|
+
import * as path90 from "path";
|
|
83393
84367
|
var SPEC_CANDIDATES = [
|
|
83394
84368
|
"openapi.json",
|
|
83395
84369
|
"openapi.yaml",
|
|
@@ -83421,28 +84395,28 @@ function normalizePath3(p) {
|
|
|
83421
84395
|
}
|
|
83422
84396
|
function discoverSpecFile(cwd, specFileArg) {
|
|
83423
84397
|
if (specFileArg) {
|
|
83424
|
-
const resolvedPath =
|
|
83425
|
-
const normalizedCwd = cwd.endsWith(
|
|
84398
|
+
const resolvedPath = path90.resolve(cwd, specFileArg);
|
|
84399
|
+
const normalizedCwd = cwd.endsWith(path90.sep) ? cwd : cwd + path90.sep;
|
|
83426
84400
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
83427
84401
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
83428
84402
|
}
|
|
83429
|
-
const ext =
|
|
84403
|
+
const ext = path90.extname(resolvedPath).toLowerCase();
|
|
83430
84404
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
83431
84405
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
83432
84406
|
}
|
|
83433
|
-
const stats =
|
|
84407
|
+
const stats = fs75.statSync(resolvedPath);
|
|
83434
84408
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
83435
84409
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
83436
84410
|
}
|
|
83437
|
-
if (!
|
|
84411
|
+
if (!fs75.existsSync(resolvedPath)) {
|
|
83438
84412
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
83439
84413
|
}
|
|
83440
84414
|
return resolvedPath;
|
|
83441
84415
|
}
|
|
83442
84416
|
for (const candidate of SPEC_CANDIDATES) {
|
|
83443
|
-
const candidatePath =
|
|
83444
|
-
if (
|
|
83445
|
-
const stats =
|
|
84417
|
+
const candidatePath = path90.resolve(cwd, candidate);
|
|
84418
|
+
if (fs75.existsSync(candidatePath)) {
|
|
84419
|
+
const stats = fs75.statSync(candidatePath);
|
|
83446
84420
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
83447
84421
|
return candidatePath;
|
|
83448
84422
|
}
|
|
@@ -83451,8 +84425,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
83451
84425
|
return null;
|
|
83452
84426
|
}
|
|
83453
84427
|
function parseSpec(specFile) {
|
|
83454
|
-
const content =
|
|
83455
|
-
const ext =
|
|
84428
|
+
const content = fs75.readFileSync(specFile, "utf-8");
|
|
84429
|
+
const ext = path90.extname(specFile).toLowerCase();
|
|
83456
84430
|
if (ext === ".json") {
|
|
83457
84431
|
return parseJsonSpec(content);
|
|
83458
84432
|
}
|
|
@@ -83523,12 +84497,12 @@ function extractRoutes(cwd) {
|
|
|
83523
84497
|
function walkDir(dir) {
|
|
83524
84498
|
let entries;
|
|
83525
84499
|
try {
|
|
83526
|
-
entries =
|
|
84500
|
+
entries = fs75.readdirSync(dir, { withFileTypes: true });
|
|
83527
84501
|
} catch {
|
|
83528
84502
|
return;
|
|
83529
84503
|
}
|
|
83530
84504
|
for (const entry of entries) {
|
|
83531
|
-
const fullPath =
|
|
84505
|
+
const fullPath = path90.join(dir, entry.name);
|
|
83532
84506
|
if (entry.isSymbolicLink()) {
|
|
83533
84507
|
continue;
|
|
83534
84508
|
}
|
|
@@ -83538,7 +84512,7 @@ function extractRoutes(cwd) {
|
|
|
83538
84512
|
}
|
|
83539
84513
|
walkDir(fullPath);
|
|
83540
84514
|
} else if (entry.isFile()) {
|
|
83541
|
-
const ext =
|
|
84515
|
+
const ext = path90.extname(entry.name).toLowerCase();
|
|
83542
84516
|
const baseName = entry.name.toLowerCase();
|
|
83543
84517
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
83544
84518
|
continue;
|
|
@@ -83556,7 +84530,7 @@ function extractRoutes(cwd) {
|
|
|
83556
84530
|
}
|
|
83557
84531
|
function extractRoutesFromFile(filePath) {
|
|
83558
84532
|
const routes = [];
|
|
83559
|
-
const content =
|
|
84533
|
+
const content = fs75.readFileSync(filePath, "utf-8");
|
|
83560
84534
|
const lines = content.split(/\r?\n/);
|
|
83561
84535
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
83562
84536
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -83704,8 +84678,8 @@ var schema_drift = createSwarmTool({
|
|
|
83704
84678
|
init_tool();
|
|
83705
84679
|
init_path_security();
|
|
83706
84680
|
init_create_tool();
|
|
83707
|
-
import * as
|
|
83708
|
-
import * as
|
|
84681
|
+
import * as fs76 from "fs";
|
|
84682
|
+
import * as path91 from "path";
|
|
83709
84683
|
var DEFAULT_MAX_RESULTS = 100;
|
|
83710
84684
|
var DEFAULT_MAX_LINES = 200;
|
|
83711
84685
|
var REGEX_TIMEOUT_MS = 5000;
|
|
@@ -83741,11 +84715,11 @@ function containsWindowsAttacks3(str) {
|
|
|
83741
84715
|
}
|
|
83742
84716
|
function isPathInWorkspace3(filePath, workspace) {
|
|
83743
84717
|
try {
|
|
83744
|
-
const resolvedPath =
|
|
83745
|
-
const realWorkspace =
|
|
83746
|
-
const realResolvedPath =
|
|
83747
|
-
const relativePath =
|
|
83748
|
-
if (relativePath.startsWith("..") ||
|
|
84718
|
+
const resolvedPath = path91.resolve(workspace, filePath);
|
|
84719
|
+
const realWorkspace = fs76.realpathSync(workspace);
|
|
84720
|
+
const realResolvedPath = fs76.realpathSync(resolvedPath);
|
|
84721
|
+
const relativePath = path91.relative(realWorkspace, realResolvedPath);
|
|
84722
|
+
if (relativePath.startsWith("..") || path91.isAbsolute(relativePath)) {
|
|
83749
84723
|
return false;
|
|
83750
84724
|
}
|
|
83751
84725
|
return true;
|
|
@@ -83758,12 +84732,12 @@ function validatePathForRead2(filePath, workspace) {
|
|
|
83758
84732
|
}
|
|
83759
84733
|
function findRgInEnvPath() {
|
|
83760
84734
|
const searchPath = process.env.PATH ?? "";
|
|
83761
|
-
for (const dir of searchPath.split(
|
|
84735
|
+
for (const dir of searchPath.split(path91.delimiter)) {
|
|
83762
84736
|
if (!dir)
|
|
83763
84737
|
continue;
|
|
83764
84738
|
const isWindows = process.platform === "win32";
|
|
83765
|
-
const candidate =
|
|
83766
|
-
if (
|
|
84739
|
+
const candidate = path91.join(dir, isWindows ? "rg.exe" : "rg");
|
|
84740
|
+
if (fs76.existsSync(candidate))
|
|
83767
84741
|
return candidate;
|
|
83768
84742
|
}
|
|
83769
84743
|
return null;
|
|
@@ -83890,10 +84864,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
|
|
|
83890
84864
|
return files;
|
|
83891
84865
|
}
|
|
83892
84866
|
try {
|
|
83893
|
-
const entries =
|
|
84867
|
+
const entries = fs76.readdirSync(dir, { withFileTypes: true });
|
|
83894
84868
|
for (const entry of entries) {
|
|
83895
|
-
const fullPath =
|
|
83896
|
-
const relativePath =
|
|
84869
|
+
const fullPath = path91.join(dir, entry.name);
|
|
84870
|
+
const relativePath = path91.relative(workspace, fullPath);
|
|
83897
84871
|
if (!validatePathForRead2(fullPath, workspace)) {
|
|
83898
84872
|
continue;
|
|
83899
84873
|
}
|
|
@@ -83934,13 +84908,13 @@ async function fallbackSearch(opts) {
|
|
|
83934
84908
|
const matches = [];
|
|
83935
84909
|
let total = 0;
|
|
83936
84910
|
for (const file3 of files) {
|
|
83937
|
-
const fullPath =
|
|
84911
|
+
const fullPath = path91.join(opts.workspace, file3);
|
|
83938
84912
|
if (!validatePathForRead2(fullPath, opts.workspace)) {
|
|
83939
84913
|
continue;
|
|
83940
84914
|
}
|
|
83941
84915
|
let stats;
|
|
83942
84916
|
try {
|
|
83943
|
-
stats =
|
|
84917
|
+
stats = fs76.statSync(fullPath);
|
|
83944
84918
|
if (stats.size > MAX_FILE_SIZE_BYTES10) {
|
|
83945
84919
|
continue;
|
|
83946
84920
|
}
|
|
@@ -83949,7 +84923,7 @@ async function fallbackSearch(opts) {
|
|
|
83949
84923
|
}
|
|
83950
84924
|
let content;
|
|
83951
84925
|
try {
|
|
83952
|
-
content =
|
|
84926
|
+
content = fs76.readFileSync(fullPath, "utf-8");
|
|
83953
84927
|
} catch {
|
|
83954
84928
|
continue;
|
|
83955
84929
|
}
|
|
@@ -84061,7 +85035,7 @@ var search = createSwarmTool({
|
|
|
84061
85035
|
message: "Exclude pattern contains invalid Windows-specific sequence"
|
|
84062
85036
|
}, null, 2);
|
|
84063
85037
|
}
|
|
84064
|
-
if (!
|
|
85038
|
+
if (!fs76.existsSync(directory)) {
|
|
84065
85039
|
return JSON.stringify({
|
|
84066
85040
|
error: true,
|
|
84067
85041
|
type: "unknown",
|
|
@@ -84129,7 +85103,8 @@ async function executeSetQaGates(args2, directory) {
|
|
|
84129
85103
|
"critic_pre_plan",
|
|
84130
85104
|
"hallucination_guard",
|
|
84131
85105
|
"sast_enabled",
|
|
84132
|
-
"mutation_test"
|
|
85106
|
+
"mutation_test",
|
|
85107
|
+
"council_general_review"
|
|
84133
85108
|
]) {
|
|
84134
85109
|
if (args2[key] !== undefined)
|
|
84135
85110
|
partial3[key] = args2[key];
|
|
@@ -84175,6 +85150,7 @@ var set_qa_gates = createSwarmTool({
|
|
|
84175
85150
|
hallucination_guard: tool.schema.boolean().optional().describe("Enable hallucination_guard checks on plan and implementation claims."),
|
|
84176
85151
|
sast_enabled: tool.schema.boolean().optional().describe("Enable SAST scanning as a required QA gate."),
|
|
84177
85152
|
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."),
|
|
85153
|
+
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."),
|
|
84178
85154
|
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.')
|
|
84179
85155
|
},
|
|
84180
85156
|
execute: async (args2, directory) => {
|
|
@@ -84186,8 +85162,8 @@ var set_qa_gates = createSwarmTool({
|
|
|
84186
85162
|
init_tool();
|
|
84187
85163
|
init_path_security();
|
|
84188
85164
|
init_create_tool();
|
|
84189
|
-
import * as
|
|
84190
|
-
import * as
|
|
85165
|
+
import * as fs77 from "fs";
|
|
85166
|
+
import * as path92 from "path";
|
|
84191
85167
|
var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
84192
85168
|
function containsWindowsAttacks4(str) {
|
|
84193
85169
|
if (/:[^\\/]/.test(str))
|
|
@@ -84201,14 +85177,14 @@ function containsWindowsAttacks4(str) {
|
|
|
84201
85177
|
}
|
|
84202
85178
|
function isPathInWorkspace4(filePath, workspace) {
|
|
84203
85179
|
try {
|
|
84204
|
-
const resolvedPath =
|
|
84205
|
-
if (!
|
|
85180
|
+
const resolvedPath = path92.resolve(workspace, filePath);
|
|
85181
|
+
if (!fs77.existsSync(resolvedPath)) {
|
|
84206
85182
|
return true;
|
|
84207
85183
|
}
|
|
84208
|
-
const realWorkspace =
|
|
84209
|
-
const realResolvedPath =
|
|
84210
|
-
const relativePath =
|
|
84211
|
-
if (relativePath.startsWith("..") ||
|
|
85184
|
+
const realWorkspace = fs77.realpathSync(workspace);
|
|
85185
|
+
const realResolvedPath = fs77.realpathSync(resolvedPath);
|
|
85186
|
+
const relativePath = path92.relative(realWorkspace, realResolvedPath);
|
|
85187
|
+
if (relativePath.startsWith("..") || path92.isAbsolute(relativePath)) {
|
|
84212
85188
|
return false;
|
|
84213
85189
|
}
|
|
84214
85190
|
return true;
|
|
@@ -84380,7 +85356,7 @@ var suggestPatch = createSwarmTool({
|
|
|
84380
85356
|
message: "changes cannot be empty"
|
|
84381
85357
|
}, null, 2);
|
|
84382
85358
|
}
|
|
84383
|
-
if (!
|
|
85359
|
+
if (!fs77.existsSync(directory)) {
|
|
84384
85360
|
return JSON.stringify({
|
|
84385
85361
|
success: false,
|
|
84386
85362
|
error: true,
|
|
@@ -84416,8 +85392,8 @@ var suggestPatch = createSwarmTool({
|
|
|
84416
85392
|
});
|
|
84417
85393
|
continue;
|
|
84418
85394
|
}
|
|
84419
|
-
const fullPath =
|
|
84420
|
-
if (!
|
|
85395
|
+
const fullPath = path92.resolve(directory, change.file);
|
|
85396
|
+
if (!fs77.existsSync(fullPath)) {
|
|
84421
85397
|
errors5.push({
|
|
84422
85398
|
success: false,
|
|
84423
85399
|
error: true,
|
|
@@ -84431,7 +85407,7 @@ var suggestPatch = createSwarmTool({
|
|
|
84431
85407
|
}
|
|
84432
85408
|
let content;
|
|
84433
85409
|
try {
|
|
84434
|
-
content =
|
|
85410
|
+
content = fs77.readFileSync(fullPath, "utf-8");
|
|
84435
85411
|
} catch (err3) {
|
|
84436
85412
|
errors5.push({
|
|
84437
85413
|
success: false,
|
|
@@ -84665,8 +85641,8 @@ var generate_mutants = createSwarmTool({
|
|
|
84665
85641
|
// src/tools/lint-spec.ts
|
|
84666
85642
|
init_spec_schema();
|
|
84667
85643
|
init_create_tool();
|
|
84668
|
-
import * as
|
|
84669
|
-
import * as
|
|
85644
|
+
import * as fs78 from "fs";
|
|
85645
|
+
import * as path93 from "path";
|
|
84670
85646
|
var SPEC_FILE_NAME = "spec.md";
|
|
84671
85647
|
var SWARM_DIR2 = ".swarm";
|
|
84672
85648
|
var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
|
|
@@ -84719,8 +85695,8 @@ var lint_spec = createSwarmTool({
|
|
|
84719
85695
|
async execute(_args, directory) {
|
|
84720
85696
|
const errors5 = [];
|
|
84721
85697
|
const warnings = [];
|
|
84722
|
-
const specPath =
|
|
84723
|
-
if (!
|
|
85698
|
+
const specPath = path93.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
|
|
85699
|
+
if (!fs78.existsSync(specPath)) {
|
|
84724
85700
|
const result2 = {
|
|
84725
85701
|
valid: false,
|
|
84726
85702
|
specMtime: null,
|
|
@@ -84739,12 +85715,12 @@ var lint_spec = createSwarmTool({
|
|
|
84739
85715
|
}
|
|
84740
85716
|
let specMtime = null;
|
|
84741
85717
|
try {
|
|
84742
|
-
const stats =
|
|
85718
|
+
const stats = fs78.statSync(specPath);
|
|
84743
85719
|
specMtime = stats.mtime.toISOString();
|
|
84744
85720
|
} catch {}
|
|
84745
85721
|
let content;
|
|
84746
85722
|
try {
|
|
84747
|
-
content =
|
|
85723
|
+
content = fs78.readFileSync(specPath, "utf-8");
|
|
84748
85724
|
} catch (e) {
|
|
84749
85725
|
const result2 = {
|
|
84750
85726
|
valid: false,
|
|
@@ -84789,13 +85765,13 @@ var lint_spec = createSwarmTool({
|
|
|
84789
85765
|
});
|
|
84790
85766
|
// src/tools/mutation-test.ts
|
|
84791
85767
|
init_dist();
|
|
84792
|
-
import * as
|
|
84793
|
-
import * as
|
|
85768
|
+
import * as fs79 from "fs";
|
|
85769
|
+
import * as path95 from "path";
|
|
84794
85770
|
|
|
84795
85771
|
// src/mutation/engine.ts
|
|
84796
85772
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
84797
85773
|
import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
|
|
84798
|
-
import * as
|
|
85774
|
+
import * as path94 from "path";
|
|
84799
85775
|
|
|
84800
85776
|
// src/mutation/equivalence.ts
|
|
84801
85777
|
function isStaticallyEquivalent(originalCode, mutatedCode) {
|
|
@@ -84930,7 +85906,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
|
|
|
84930
85906
|
let patchFile;
|
|
84931
85907
|
try {
|
|
84932
85908
|
const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
84933
|
-
patchFile =
|
|
85909
|
+
patchFile = path94.join(workingDir, `.mutation_patch_${safeId2}.diff`);
|
|
84934
85910
|
try {
|
|
84935
85911
|
writeFileSync18(patchFile, patch.patch);
|
|
84936
85912
|
} catch (writeErr) {
|
|
@@ -85324,8 +86300,8 @@ var mutation_test = createSwarmTool({
|
|
|
85324
86300
|
];
|
|
85325
86301
|
for (const filePath of uniquePaths) {
|
|
85326
86302
|
try {
|
|
85327
|
-
const resolvedPath =
|
|
85328
|
-
sourceFiles.set(filePath,
|
|
86303
|
+
const resolvedPath = path95.resolve(cwd, filePath);
|
|
86304
|
+
sourceFiles.set(filePath, fs79.readFileSync(resolvedPath, "utf-8"));
|
|
85329
86305
|
} catch {}
|
|
85330
86306
|
}
|
|
85331
86307
|
const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
|
|
@@ -85343,8 +86319,8 @@ var mutation_test = createSwarmTool({
|
|
|
85343
86319
|
init_dist();
|
|
85344
86320
|
init_manager2();
|
|
85345
86321
|
init_detector();
|
|
85346
|
-
import * as
|
|
85347
|
-
import * as
|
|
86322
|
+
import * as fs80 from "fs";
|
|
86323
|
+
import * as path96 from "path";
|
|
85348
86324
|
init_create_tool();
|
|
85349
86325
|
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
85350
86326
|
var BINARY_CHECK_BYTES = 8192;
|
|
@@ -85410,7 +86386,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85410
86386
|
if (languages?.length) {
|
|
85411
86387
|
const lowerLangs = languages.map((l) => l.toLowerCase());
|
|
85412
86388
|
filesToCheck = filesToCheck.filter((file3) => {
|
|
85413
|
-
const ext =
|
|
86389
|
+
const ext = path96.extname(file3.path).toLowerCase();
|
|
85414
86390
|
const langDef = getLanguageForExtension(ext);
|
|
85415
86391
|
const fileProfile = getProfileForFile(file3.path);
|
|
85416
86392
|
const langId = fileProfile?.id || langDef?.id;
|
|
@@ -85423,7 +86399,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85423
86399
|
let skippedCount = 0;
|
|
85424
86400
|
for (const fileInfo of filesToCheck) {
|
|
85425
86401
|
const { path: filePath } = fileInfo;
|
|
85426
|
-
const fullPath =
|
|
86402
|
+
const fullPath = path96.isAbsolute(filePath) ? filePath : path96.join(directory, filePath);
|
|
85427
86403
|
const result = {
|
|
85428
86404
|
path: filePath,
|
|
85429
86405
|
language: "",
|
|
@@ -85453,7 +86429,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85453
86429
|
}
|
|
85454
86430
|
let content;
|
|
85455
86431
|
try {
|
|
85456
|
-
content =
|
|
86432
|
+
content = fs80.readFileSync(fullPath, "utf8");
|
|
85457
86433
|
} catch {
|
|
85458
86434
|
result.skipped_reason = "file_read_error";
|
|
85459
86435
|
skippedCount++;
|
|
@@ -85472,7 +86448,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85472
86448
|
results.push(result);
|
|
85473
86449
|
continue;
|
|
85474
86450
|
}
|
|
85475
|
-
const ext =
|
|
86451
|
+
const ext = path96.extname(filePath).toLowerCase();
|
|
85476
86452
|
const langDef = getLanguageForExtension(ext);
|
|
85477
86453
|
result.language = profile?.id || langDef?.id || "unknown";
|
|
85478
86454
|
const errors5 = extractSyntaxErrors(parser, content);
|
|
@@ -85564,8 +86540,8 @@ init_dist();
|
|
|
85564
86540
|
init_utils();
|
|
85565
86541
|
init_create_tool();
|
|
85566
86542
|
init_path_security();
|
|
85567
|
-
import * as
|
|
85568
|
-
import * as
|
|
86543
|
+
import * as fs81 from "fs";
|
|
86544
|
+
import * as path97 from "path";
|
|
85569
86545
|
var MAX_TEXT_LENGTH = 200;
|
|
85570
86546
|
var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
|
|
85571
86547
|
var SUPPORTED_EXTENSIONS4 = new Set([
|
|
@@ -85631,9 +86607,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
85631
86607
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
85632
86608
|
}
|
|
85633
86609
|
try {
|
|
85634
|
-
const resolvedPath =
|
|
85635
|
-
const normalizedCwd =
|
|
85636
|
-
const normalizedResolved =
|
|
86610
|
+
const resolvedPath = path97.resolve(paths);
|
|
86611
|
+
const normalizedCwd = path97.resolve(cwd);
|
|
86612
|
+
const normalizedResolved = path97.resolve(resolvedPath);
|
|
85637
86613
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
85638
86614
|
return {
|
|
85639
86615
|
error: "paths must be within the current working directory",
|
|
@@ -85649,13 +86625,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
85649
86625
|
}
|
|
85650
86626
|
}
|
|
85651
86627
|
function isSupportedExtension(filePath) {
|
|
85652
|
-
const ext =
|
|
86628
|
+
const ext = path97.extname(filePath).toLowerCase();
|
|
85653
86629
|
return SUPPORTED_EXTENSIONS4.has(ext);
|
|
85654
86630
|
}
|
|
85655
86631
|
function findSourceFiles4(dir, files = []) {
|
|
85656
86632
|
let entries;
|
|
85657
86633
|
try {
|
|
85658
|
-
entries =
|
|
86634
|
+
entries = fs81.readdirSync(dir);
|
|
85659
86635
|
} catch {
|
|
85660
86636
|
return files;
|
|
85661
86637
|
}
|
|
@@ -85664,10 +86640,10 @@ function findSourceFiles4(dir, files = []) {
|
|
|
85664
86640
|
if (SKIP_DIRECTORIES5.has(entry)) {
|
|
85665
86641
|
continue;
|
|
85666
86642
|
}
|
|
85667
|
-
const fullPath =
|
|
86643
|
+
const fullPath = path97.join(dir, entry);
|
|
85668
86644
|
let stat4;
|
|
85669
86645
|
try {
|
|
85670
|
-
stat4 =
|
|
86646
|
+
stat4 = fs81.statSync(fullPath);
|
|
85671
86647
|
} catch {
|
|
85672
86648
|
continue;
|
|
85673
86649
|
}
|
|
@@ -85760,7 +86736,7 @@ var todo_extract = createSwarmTool({
|
|
|
85760
86736
|
return JSON.stringify(errorResult, null, 2);
|
|
85761
86737
|
}
|
|
85762
86738
|
const scanPath = resolvedPath;
|
|
85763
|
-
if (!
|
|
86739
|
+
if (!fs81.existsSync(scanPath)) {
|
|
85764
86740
|
const errorResult = {
|
|
85765
86741
|
error: `path not found: ${pathsInput}`,
|
|
85766
86742
|
total: 0,
|
|
@@ -85770,13 +86746,13 @@ var todo_extract = createSwarmTool({
|
|
|
85770
86746
|
return JSON.stringify(errorResult, null, 2);
|
|
85771
86747
|
}
|
|
85772
86748
|
const filesToScan = [];
|
|
85773
|
-
const stat4 =
|
|
86749
|
+
const stat4 = fs81.statSync(scanPath);
|
|
85774
86750
|
if (stat4.isFile()) {
|
|
85775
86751
|
if (isSupportedExtension(scanPath)) {
|
|
85776
86752
|
filesToScan.push(scanPath);
|
|
85777
86753
|
} else {
|
|
85778
86754
|
const errorResult = {
|
|
85779
|
-
error: `unsupported file extension: ${
|
|
86755
|
+
error: `unsupported file extension: ${path97.extname(scanPath)}`,
|
|
85780
86756
|
total: 0,
|
|
85781
86757
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
85782
86758
|
entries: []
|
|
@@ -85789,11 +86765,11 @@ var todo_extract = createSwarmTool({
|
|
|
85789
86765
|
const allEntries = [];
|
|
85790
86766
|
for (const filePath of filesToScan) {
|
|
85791
86767
|
try {
|
|
85792
|
-
const fileStat =
|
|
86768
|
+
const fileStat = fs81.statSync(filePath);
|
|
85793
86769
|
if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
|
|
85794
86770
|
continue;
|
|
85795
86771
|
}
|
|
85796
|
-
const content =
|
|
86772
|
+
const content = fs81.readFileSync(filePath, "utf-8");
|
|
85797
86773
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
85798
86774
|
allEntries.push(...entries);
|
|
85799
86775
|
} catch {}
|
|
@@ -85823,18 +86799,18 @@ init_tool();
|
|
|
85823
86799
|
init_loader();
|
|
85824
86800
|
init_schema();
|
|
85825
86801
|
init_gate_evidence();
|
|
85826
|
-
import * as
|
|
85827
|
-
import * as
|
|
86802
|
+
import * as fs83 from "fs";
|
|
86803
|
+
import * as path99 from "path";
|
|
85828
86804
|
|
|
85829
86805
|
// src/hooks/diff-scope.ts
|
|
85830
|
-
import * as
|
|
85831
|
-
import * as
|
|
86806
|
+
import * as fs82 from "fs";
|
|
86807
|
+
import * as path98 from "path";
|
|
85832
86808
|
function getDeclaredScope(taskId, directory) {
|
|
85833
86809
|
try {
|
|
85834
|
-
const planPath =
|
|
85835
|
-
if (!
|
|
86810
|
+
const planPath = path98.join(directory, ".swarm", "plan.json");
|
|
86811
|
+
if (!fs82.existsSync(planPath))
|
|
85836
86812
|
return null;
|
|
85837
|
-
const raw =
|
|
86813
|
+
const raw = fs82.readFileSync(planPath, "utf-8");
|
|
85838
86814
|
const plan = JSON.parse(raw);
|
|
85839
86815
|
for (const phase of plan.phases ?? []) {
|
|
85840
86816
|
for (const task of phase.tasks ?? []) {
|
|
@@ -85950,7 +86926,7 @@ var TIER_3_PATTERNS = [
|
|
|
85950
86926
|
];
|
|
85951
86927
|
function matchesTier3Pattern(files) {
|
|
85952
86928
|
for (const file3 of files) {
|
|
85953
|
-
const fileName =
|
|
86929
|
+
const fileName = path99.basename(file3);
|
|
85954
86930
|
for (const pattern of TIER_3_PATTERNS) {
|
|
85955
86931
|
if (pattern.test(fileName)) {
|
|
85956
86932
|
return true;
|
|
@@ -85964,8 +86940,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
85964
86940
|
if (hasActiveTurboMode()) {
|
|
85965
86941
|
const resolvedDir2 = workingDirectory;
|
|
85966
86942
|
try {
|
|
85967
|
-
const planPath =
|
|
85968
|
-
const planRaw =
|
|
86943
|
+
const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
|
|
86944
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
85969
86945
|
const plan = JSON.parse(planRaw);
|
|
85970
86946
|
for (const planPhase of plan.phases ?? []) {
|
|
85971
86947
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -86034,8 +87010,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
86034
87010
|
}
|
|
86035
87011
|
try {
|
|
86036
87012
|
const resolvedDir2 = workingDirectory;
|
|
86037
|
-
const planPath =
|
|
86038
|
-
const planRaw =
|
|
87013
|
+
const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
|
|
87014
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
86039
87015
|
const plan = JSON.parse(planRaw);
|
|
86040
87016
|
for (const planPhase of plan.phases ?? []) {
|
|
86041
87017
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -86259,8 +87235,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86259
87235
|
};
|
|
86260
87236
|
}
|
|
86261
87237
|
}
|
|
86262
|
-
normalizedDir =
|
|
86263
|
-
const pathParts = normalizedDir.split(
|
|
87238
|
+
normalizedDir = path99.normalize(args2.working_directory);
|
|
87239
|
+
const pathParts = normalizedDir.split(path99.sep);
|
|
86264
87240
|
if (pathParts.includes("..")) {
|
|
86265
87241
|
return {
|
|
86266
87242
|
success: false,
|
|
@@ -86270,11 +87246,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86270
87246
|
]
|
|
86271
87247
|
};
|
|
86272
87248
|
}
|
|
86273
|
-
const resolvedDir =
|
|
87249
|
+
const resolvedDir = path99.resolve(normalizedDir);
|
|
86274
87250
|
try {
|
|
86275
|
-
const realPath =
|
|
86276
|
-
const planPath =
|
|
86277
|
-
if (!
|
|
87251
|
+
const realPath = fs83.realpathSync(resolvedDir);
|
|
87252
|
+
const planPath = path99.join(realPath, ".swarm", "plan.json");
|
|
87253
|
+
if (!fs83.existsSync(planPath)) {
|
|
86278
87254
|
return {
|
|
86279
87255
|
success: false,
|
|
86280
87256
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -86305,22 +87281,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86305
87281
|
}
|
|
86306
87282
|
if (args2.status === "in_progress") {
|
|
86307
87283
|
try {
|
|
86308
|
-
const evidencePath =
|
|
86309
|
-
|
|
86310
|
-
const fd =
|
|
87284
|
+
const evidencePath = path99.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
|
|
87285
|
+
fs83.mkdirSync(path99.dirname(evidencePath), { recursive: true });
|
|
87286
|
+
const fd = fs83.openSync(evidencePath, "wx");
|
|
86311
87287
|
let writeOk = false;
|
|
86312
87288
|
try {
|
|
86313
|
-
|
|
87289
|
+
fs83.writeSync(fd, JSON.stringify({
|
|
86314
87290
|
taskId: args2.task_id,
|
|
86315
87291
|
required_gates: ["reviewer", "test_engineer"],
|
|
86316
87292
|
gates: {}
|
|
86317
87293
|
}, null, 2));
|
|
86318
87294
|
writeOk = true;
|
|
86319
87295
|
} finally {
|
|
86320
|
-
|
|
87296
|
+
fs83.closeSync(fd);
|
|
86321
87297
|
if (!writeOk) {
|
|
86322
87298
|
try {
|
|
86323
|
-
|
|
87299
|
+
fs83.unlinkSync(evidencePath);
|
|
86324
87300
|
} catch {}
|
|
86325
87301
|
}
|
|
86326
87302
|
}
|
|
@@ -86330,8 +87306,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86330
87306
|
recoverTaskStateFromDelegations(args2.task_id);
|
|
86331
87307
|
let phaseRequiresReviewer = true;
|
|
86332
87308
|
try {
|
|
86333
|
-
const planPath =
|
|
86334
|
-
const planRaw =
|
|
87309
|
+
const planPath = path99.join(directory, ".swarm", "plan.json");
|
|
87310
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
86335
87311
|
const plan = JSON.parse(planRaw);
|
|
86336
87312
|
const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
|
|
86337
87313
|
if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
|
|
@@ -86433,6 +87409,216 @@ var update_task_status = createSwarmTool({
|
|
|
86433
87409
|
return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
|
|
86434
87410
|
}
|
|
86435
87411
|
});
|
|
87412
|
+
// src/tools/web-search.ts
|
|
87413
|
+
init_dist();
|
|
87414
|
+
init_zod();
|
|
87415
|
+
init_loader();
|
|
87416
|
+
|
|
87417
|
+
// src/council/web-search-provider.ts
|
|
87418
|
+
class WebSearchError extends Error {
|
|
87419
|
+
cause;
|
|
87420
|
+
constructor(message, cause) {
|
|
87421
|
+
super(message);
|
|
87422
|
+
this.cause = cause;
|
|
87423
|
+
this.name = "WebSearchError";
|
|
87424
|
+
}
|
|
87425
|
+
}
|
|
87426
|
+
|
|
87427
|
+
class WebSearchConfigError extends Error {
|
|
87428
|
+
constructor(message) {
|
|
87429
|
+
super(message);
|
|
87430
|
+
this.name = "WebSearchConfigError";
|
|
87431
|
+
}
|
|
87432
|
+
}
|
|
87433
|
+
|
|
87434
|
+
class TavilyProvider {
|
|
87435
|
+
apiKey;
|
|
87436
|
+
constructor(apiKey) {
|
|
87437
|
+
this.apiKey = apiKey;
|
|
87438
|
+
}
|
|
87439
|
+
async search(query, maxResults) {
|
|
87440
|
+
let response;
|
|
87441
|
+
try {
|
|
87442
|
+
response = await fetch("https://api.tavily.com/search", {
|
|
87443
|
+
method: "POST",
|
|
87444
|
+
headers: { "Content-Type": "application/json" },
|
|
87445
|
+
body: JSON.stringify({
|
|
87446
|
+
api_key: this.apiKey,
|
|
87447
|
+
query,
|
|
87448
|
+
max_results: maxResults,
|
|
87449
|
+
search_depth: "advanced"
|
|
87450
|
+
})
|
|
87451
|
+
});
|
|
87452
|
+
} catch (err3) {
|
|
87453
|
+
throw new WebSearchError(`Tavily network error for query "${query}"`, err3);
|
|
87454
|
+
}
|
|
87455
|
+
if (!response.ok) {
|
|
87456
|
+
throw new WebSearchError(`Tavily HTTP ${response.status} for query "${query}"`);
|
|
87457
|
+
}
|
|
87458
|
+
let body2;
|
|
87459
|
+
try {
|
|
87460
|
+
body2 = await response.json();
|
|
87461
|
+
} catch (err3) {
|
|
87462
|
+
throw new WebSearchError("Tavily returned non-JSON response", err3);
|
|
87463
|
+
}
|
|
87464
|
+
const results = body2?.results;
|
|
87465
|
+
if (!Array.isArray(results)) {
|
|
87466
|
+
return [];
|
|
87467
|
+
}
|
|
87468
|
+
return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.content === "string").map((r) => ({
|
|
87469
|
+
title: r.title,
|
|
87470
|
+
url: r.url,
|
|
87471
|
+
snippet: r.content,
|
|
87472
|
+
query
|
|
87473
|
+
}));
|
|
87474
|
+
}
|
|
87475
|
+
}
|
|
87476
|
+
|
|
87477
|
+
class BraveProvider {
|
|
87478
|
+
apiKey;
|
|
87479
|
+
constructor(apiKey) {
|
|
87480
|
+
this.apiKey = apiKey;
|
|
87481
|
+
}
|
|
87482
|
+
async search(query, maxResults) {
|
|
87483
|
+
const url3 = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
87484
|
+
url3.searchParams.set("q", query);
|
|
87485
|
+
url3.searchParams.set("count", String(maxResults));
|
|
87486
|
+
let response;
|
|
87487
|
+
try {
|
|
87488
|
+
response = await fetch(url3.toString(), {
|
|
87489
|
+
method: "GET",
|
|
87490
|
+
headers: {
|
|
87491
|
+
"X-Subscription-Token": this.apiKey,
|
|
87492
|
+
Accept: "application/json"
|
|
87493
|
+
}
|
|
87494
|
+
});
|
|
87495
|
+
} catch (err3) {
|
|
87496
|
+
throw new WebSearchError(`Brave network error for query "${query}"`, err3);
|
|
87497
|
+
}
|
|
87498
|
+
if (!response.ok) {
|
|
87499
|
+
throw new WebSearchError(`Brave HTTP ${response.status} for query "${query}"`);
|
|
87500
|
+
}
|
|
87501
|
+
let body2;
|
|
87502
|
+
try {
|
|
87503
|
+
body2 = await response.json();
|
|
87504
|
+
} catch (err3) {
|
|
87505
|
+
throw new WebSearchError("Brave returned non-JSON response", err3);
|
|
87506
|
+
}
|
|
87507
|
+
const results = body2?.web?.results;
|
|
87508
|
+
if (!Array.isArray(results)) {
|
|
87509
|
+
return [];
|
|
87510
|
+
}
|
|
87511
|
+
return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.description === "string").map((r) => ({
|
|
87512
|
+
title: r.title,
|
|
87513
|
+
url: r.url,
|
|
87514
|
+
snippet: r.description,
|
|
87515
|
+
query
|
|
87516
|
+
}));
|
|
87517
|
+
}
|
|
87518
|
+
}
|
|
87519
|
+
function resolveApiKey(provider, configKey) {
|
|
87520
|
+
if (configKey && configKey.length > 0) {
|
|
87521
|
+
return configKey;
|
|
87522
|
+
}
|
|
87523
|
+
const envName = provider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
|
|
87524
|
+
const fromEnv = process.env[envName];
|
|
87525
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : undefined;
|
|
87526
|
+
}
|
|
87527
|
+
function createWebSearchProvider(config3) {
|
|
87528
|
+
const apiKey = resolveApiKey(config3.searchProvider, config3.searchApiKey);
|
|
87529
|
+
if (!apiKey) {
|
|
87530
|
+
const envName = config3.searchProvider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
|
|
87531
|
+
throw new WebSearchConfigError(`No API key for search provider "${config3.searchProvider}". Set ` + `council.general.searchApiKey in opencode-swarm.json or export ${envName}.`);
|
|
87532
|
+
}
|
|
87533
|
+
switch (config3.searchProvider) {
|
|
87534
|
+
case "tavily":
|
|
87535
|
+
return new TavilyProvider(apiKey);
|
|
87536
|
+
case "brave":
|
|
87537
|
+
return new BraveProvider(apiKey);
|
|
87538
|
+
}
|
|
87539
|
+
}
|
|
87540
|
+
|
|
87541
|
+
// src/tools/web-search.ts
|
|
87542
|
+
init_create_tool();
|
|
87543
|
+
init_resolve_working_directory();
|
|
87544
|
+
var MAX_RESULTS_HARD_CAP = 10;
|
|
87545
|
+
var ArgsSchema4 = exports_external.object({
|
|
87546
|
+
query: exports_external.string().min(1).max(500),
|
|
87547
|
+
max_results: exports_external.number().int().min(1).max(20).optional(),
|
|
87548
|
+
working_directory: exports_external.string().optional()
|
|
87549
|
+
});
|
|
87550
|
+
var web_search = createSwarmTool({
|
|
87551
|
+
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.",
|
|
87552
|
+
args: {
|
|
87553
|
+
query: tool.schema.string().min(1).max(500).describe("Search query string (1\u2013500 characters)."),
|
|
87554
|
+
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.`),
|
|
87555
|
+
working_directory: tool.schema.string().optional().describe("Project root for config resolution. Optional.")
|
|
87556
|
+
},
|
|
87557
|
+
execute: async (args2, directory) => {
|
|
87558
|
+
const parsed = ArgsSchema4.safeParse(args2);
|
|
87559
|
+
if (!parsed.success) {
|
|
87560
|
+
const fail = {
|
|
87561
|
+
success: false,
|
|
87562
|
+
reason: "invalid_args",
|
|
87563
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
87564
|
+
};
|
|
87565
|
+
return JSON.stringify(fail, null, 2);
|
|
87566
|
+
}
|
|
87567
|
+
const dirResult = resolveWorkingDirectory(parsed.data.working_directory, directory);
|
|
87568
|
+
if (!dirResult.success) {
|
|
87569
|
+
const fail = {
|
|
87570
|
+
success: false,
|
|
87571
|
+
reason: "invalid_working_directory",
|
|
87572
|
+
message: dirResult.message
|
|
87573
|
+
};
|
|
87574
|
+
return JSON.stringify(fail, null, 2);
|
|
87575
|
+
}
|
|
87576
|
+
const config3 = loadPluginConfig(dirResult.directory);
|
|
87577
|
+
const generalConfig = config3.council?.general;
|
|
87578
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
87579
|
+
const fail = {
|
|
87580
|
+
success: false,
|
|
87581
|
+
reason: "council_general_disabled",
|
|
87582
|
+
message: "web_search is disabled \u2014 set council.general.enabled: true in opencode-swarm.json."
|
|
87583
|
+
};
|
|
87584
|
+
return JSON.stringify(fail, null, 2);
|
|
87585
|
+
}
|
|
87586
|
+
const requested = parsed.data.max_results ?? generalConfig.maxSourcesPerMember;
|
|
87587
|
+
const maxResults = Math.min(requested, MAX_RESULTS_HARD_CAP);
|
|
87588
|
+
let provider;
|
|
87589
|
+
try {
|
|
87590
|
+
provider = createWebSearchProvider(generalConfig);
|
|
87591
|
+
} catch (err3) {
|
|
87592
|
+
const fail = {
|
|
87593
|
+
success: false,
|
|
87594
|
+
reason: err3 instanceof WebSearchConfigError ? "missing_api_key" : "provider_init_failed",
|
|
87595
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
87596
|
+
};
|
|
87597
|
+
return JSON.stringify(fail, null, 2);
|
|
87598
|
+
}
|
|
87599
|
+
try {
|
|
87600
|
+
const results = await provider.search(parsed.data.query, maxResults);
|
|
87601
|
+
const ok2 = {
|
|
87602
|
+
success: true,
|
|
87603
|
+
query: parsed.data.query,
|
|
87604
|
+
totalResults: results.length,
|
|
87605
|
+
results: results.map(({ title, url: url3, snippet }) => ({
|
|
87606
|
+
title,
|
|
87607
|
+
url: url3,
|
|
87608
|
+
snippet
|
|
87609
|
+
}))
|
|
87610
|
+
};
|
|
87611
|
+
return JSON.stringify(ok2, null, 2);
|
|
87612
|
+
} catch (err3) {
|
|
87613
|
+
const fail = {
|
|
87614
|
+
success: false,
|
|
87615
|
+
reason: err3 instanceof WebSearchError ? "search_failed" : "unknown",
|
|
87616
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
87617
|
+
};
|
|
87618
|
+
return JSON.stringify(fail, null, 2);
|
|
87619
|
+
}
|
|
87620
|
+
}
|
|
87621
|
+
});
|
|
86436
87622
|
// src/tools/write-drift-evidence.ts
|
|
86437
87623
|
init_tool();
|
|
86438
87624
|
init_qa_gate_profile();
|
|
@@ -86440,8 +87626,8 @@ init_utils2();
|
|
|
86440
87626
|
init_ledger();
|
|
86441
87627
|
init_manager();
|
|
86442
87628
|
init_create_tool();
|
|
86443
|
-
import
|
|
86444
|
-
import
|
|
87629
|
+
import fs84 from "fs";
|
|
87630
|
+
import path100 from "path";
|
|
86445
87631
|
function derivePlanId5(plan) {
|
|
86446
87632
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
86447
87633
|
}
|
|
@@ -86492,7 +87678,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
86492
87678
|
entries: [evidenceEntry]
|
|
86493
87679
|
};
|
|
86494
87680
|
const filename = "drift-verifier.json";
|
|
86495
|
-
const relativePath =
|
|
87681
|
+
const relativePath = path100.join("evidence", String(phase), filename);
|
|
86496
87682
|
let validatedPath;
|
|
86497
87683
|
try {
|
|
86498
87684
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86503,12 +87689,12 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
86503
87689
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86504
87690
|
}, null, 2);
|
|
86505
87691
|
}
|
|
86506
|
-
const evidenceDir =
|
|
87692
|
+
const evidenceDir = path100.dirname(validatedPath);
|
|
86507
87693
|
try {
|
|
86508
|
-
await
|
|
86509
|
-
const tempPath =
|
|
86510
|
-
await
|
|
86511
|
-
await
|
|
87694
|
+
await fs84.promises.mkdir(evidenceDir, { recursive: true });
|
|
87695
|
+
const tempPath = path100.join(evidenceDir, `.${filename}.tmp`);
|
|
87696
|
+
await fs84.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87697
|
+
await fs84.promises.rename(tempPath, validatedPath);
|
|
86512
87698
|
let snapshotInfo;
|
|
86513
87699
|
let snapshotError;
|
|
86514
87700
|
let qaProfileLocked;
|
|
@@ -86602,8 +87788,8 @@ var write_drift_evidence = createSwarmTool({
|
|
|
86602
87788
|
init_tool();
|
|
86603
87789
|
init_utils2();
|
|
86604
87790
|
init_create_tool();
|
|
86605
|
-
import
|
|
86606
|
-
import
|
|
87791
|
+
import fs85 from "fs";
|
|
87792
|
+
import path101 from "path";
|
|
86607
87793
|
function normalizeVerdict2(verdict) {
|
|
86608
87794
|
switch (verdict) {
|
|
86609
87795
|
case "APPROVED":
|
|
@@ -86651,7 +87837,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
86651
87837
|
entries: [evidenceEntry]
|
|
86652
87838
|
};
|
|
86653
87839
|
const filename = "hallucination-guard.json";
|
|
86654
|
-
const relativePath =
|
|
87840
|
+
const relativePath = path101.join("evidence", String(phase), filename);
|
|
86655
87841
|
let validatedPath;
|
|
86656
87842
|
try {
|
|
86657
87843
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86662,12 +87848,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
86662
87848
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86663
87849
|
}, null, 2);
|
|
86664
87850
|
}
|
|
86665
|
-
const evidenceDir =
|
|
87851
|
+
const evidenceDir = path101.dirname(validatedPath);
|
|
86666
87852
|
try {
|
|
86667
|
-
await
|
|
86668
|
-
const tempPath =
|
|
86669
|
-
await
|
|
86670
|
-
await
|
|
87853
|
+
await fs85.promises.mkdir(evidenceDir, { recursive: true });
|
|
87854
|
+
const tempPath = path101.join(evidenceDir, `.${filename}.tmp`);
|
|
87855
|
+
await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87856
|
+
await fs85.promises.rename(tempPath, validatedPath);
|
|
86671
87857
|
return JSON.stringify({
|
|
86672
87858
|
success: true,
|
|
86673
87859
|
phase,
|
|
@@ -86713,8 +87899,8 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
86713
87899
|
init_tool();
|
|
86714
87900
|
init_utils2();
|
|
86715
87901
|
init_create_tool();
|
|
86716
|
-
import
|
|
86717
|
-
import
|
|
87902
|
+
import fs86 from "fs";
|
|
87903
|
+
import path102 from "path";
|
|
86718
87904
|
function normalizeVerdict3(verdict) {
|
|
86719
87905
|
switch (verdict) {
|
|
86720
87906
|
case "PASS":
|
|
@@ -86788,7 +87974,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
86788
87974
|
entries: [evidenceEntry]
|
|
86789
87975
|
};
|
|
86790
87976
|
const filename = "mutation-gate.json";
|
|
86791
|
-
const relativePath =
|
|
87977
|
+
const relativePath = path102.join("evidence", String(phase), filename);
|
|
86792
87978
|
let validatedPath;
|
|
86793
87979
|
try {
|
|
86794
87980
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86799,12 +87985,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
86799
87985
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86800
87986
|
}, null, 2);
|
|
86801
87987
|
}
|
|
86802
|
-
const evidenceDir =
|
|
87988
|
+
const evidenceDir = path102.dirname(validatedPath);
|
|
86803
87989
|
try {
|
|
86804
|
-
await
|
|
86805
|
-
const tempPath =
|
|
86806
|
-
await
|
|
86807
|
-
await
|
|
87990
|
+
await fs86.promises.mkdir(evidenceDir, { recursive: true });
|
|
87991
|
+
const tempPath = path102.join(evidenceDir, `.${filename}.tmp`);
|
|
87992
|
+
await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87993
|
+
await fs86.promises.rename(tempPath, validatedPath);
|
|
86808
87994
|
return JSON.stringify({
|
|
86809
87995
|
success: true,
|
|
86810
87996
|
phase,
|
|
@@ -87022,7 +88208,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87022
88208
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
87023
88209
|
preflightTriggerManager = new PTM(automationConfig);
|
|
87024
88210
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
87025
|
-
const swarmDir =
|
|
88211
|
+
const swarmDir = path103.resolve(ctx.directory, ".swarm");
|
|
87026
88212
|
statusArtifact = new ASA(swarmDir);
|
|
87027
88213
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
87028
88214
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -87128,6 +88314,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87128
88314
|
completion_verify,
|
|
87129
88315
|
complexity_hotspots,
|
|
87130
88316
|
convene_council,
|
|
88317
|
+
convene_general_council,
|
|
87131
88318
|
curator_analyze,
|
|
87132
88319
|
declare_council_criteria,
|
|
87133
88320
|
knowledge_add,
|
|
@@ -87174,6 +88361,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87174
88361
|
build_check,
|
|
87175
88362
|
suggest_patch: suggestPatch,
|
|
87176
88363
|
update_task_status,
|
|
88364
|
+
web_search,
|
|
87177
88365
|
write_retro,
|
|
87178
88366
|
write_drift_evidence,
|
|
87179
88367
|
write_hallucination_evidence,
|