opencode-swarm 6.83.0 → 6.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/convene-general-council.test.d.ts +10 -0
- package/dist/__tests__/disagreement-detector.test.d.ts +7 -0
- package/dist/__tests__/general-council-service.test.d.ts +7 -0
- package/dist/__tests__/qa-gate-hardening.test.d.ts +12 -0
- package/dist/__tests__/web-search-provider.test.d.ts +6 -0
- package/dist/agents/architect.d.ts +9 -1
- package/dist/agents/council-member.d.ts +30 -0
- package/dist/agents/council-member.test.d.ts +8 -0
- package/dist/agents/council-moderator.d.ts +20 -0
- package/dist/agents/index.d.ts +2 -0
- package/dist/cli/index.js +114 -7
- package/dist/commands/council.d.ts +17 -0
- package/dist/commands/council.test.d.ts +4 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/registry.d.ts +7 -1
- package/dist/config/constants.d.ts +3 -3
- package/dist/config/schema.d.ts +109 -0
- package/dist/council/disagreement-detector.d.ts +24 -0
- package/dist/council/general-council-advisory.d.ts +29 -0
- package/dist/council/general-council-service.d.ts +22 -0
- package/dist/council/general-council-types.d.ts +98 -0
- package/dist/council/web-search-provider.d.ts +35 -0
- package/dist/db/qa-gate-profile.d.ts +5 -1
- package/dist/index.js +1551 -368
- 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();
|
|
@@ -15207,13 +15218,37 @@ var init_schema = __esm(() => {
|
|
|
15207
15218
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
|
|
15208
15219
|
universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
|
|
15209
15220
|
});
|
|
15221
|
+
GeneralCouncilMemberConfigSchema = exports_external.object({
|
|
15222
|
+
memberId: exports_external.string().min(1),
|
|
15223
|
+
model: exports_external.string().min(1),
|
|
15224
|
+
role: exports_external.enum([
|
|
15225
|
+
"generalist",
|
|
15226
|
+
"skeptic",
|
|
15227
|
+
"domain_expert",
|
|
15228
|
+
"devil_advocate",
|
|
15229
|
+
"synthesizer"
|
|
15230
|
+
]),
|
|
15231
|
+
persona: exports_external.string().optional()
|
|
15232
|
+
}).strict();
|
|
15233
|
+
GeneralCouncilConfigSchema = exports_external.object({
|
|
15234
|
+
enabled: exports_external.boolean().default(false),
|
|
15235
|
+
searchProvider: exports_external.enum(["tavily", "brave"]).default("tavily"),
|
|
15236
|
+
searchApiKey: exports_external.string().optional(),
|
|
15237
|
+
members: exports_external.array(GeneralCouncilMemberConfigSchema).default([]),
|
|
15238
|
+
presets: exports_external.record(exports_external.string(), exports_external.array(GeneralCouncilMemberConfigSchema)).default({}),
|
|
15239
|
+
deliberate: exports_external.boolean().default(true),
|
|
15240
|
+
moderator: exports_external.boolean().default(true),
|
|
15241
|
+
moderatorModel: exports_external.string().optional(),
|
|
15242
|
+
maxSourcesPerMember: exports_external.number().int().min(1).max(20).default(5)
|
|
15243
|
+
}).strict();
|
|
15210
15244
|
CouncilConfigSchema = exports_external.object({
|
|
15211
15245
|
enabled: exports_external.boolean().default(false),
|
|
15212
15246
|
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
15213
15247
|
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
15214
15248
|
vetoPriority: exports_external.boolean().default(true),
|
|
15215
15249
|
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.")
|
|
15250
|
+
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet."),
|
|
15251
|
+
general: GeneralCouncilConfigSchema.optional()
|
|
15217
15252
|
}).strict();
|
|
15218
15253
|
ParallelizationConfigSchema = exports_external.object({
|
|
15219
15254
|
enabled: exports_external.boolean().default(false),
|
|
@@ -19707,7 +19742,8 @@ var init_qa_gate_profile = __esm(() => {
|
|
|
19707
19742
|
critic_pre_plan: true,
|
|
19708
19743
|
hallucination_guard: false,
|
|
19709
19744
|
sast_enabled: true,
|
|
19710
|
-
mutation_test: false
|
|
19745
|
+
mutation_test: false,
|
|
19746
|
+
council_general_review: false
|
|
19711
19747
|
};
|
|
19712
19748
|
});
|
|
19713
19749
|
|
|
@@ -41864,6 +41900,76 @@ var init_config2 = __esm(() => {
|
|
|
41864
41900
|
init_loader();
|
|
41865
41901
|
});
|
|
41866
41902
|
|
|
41903
|
+
// src/commands/council.ts
|
|
41904
|
+
function sanitizeQuestion(raw) {
|
|
41905
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
41906
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
41907
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
41908
|
+
if (normalized.length <= MAX_QUESTION_LEN)
|
|
41909
|
+
return normalized;
|
|
41910
|
+
return `${normalized.slice(0, MAX_QUESTION_LEN)}\u2026`;
|
|
41911
|
+
}
|
|
41912
|
+
function sanitizePresetName(raw) {
|
|
41913
|
+
const trimmed = raw.trim();
|
|
41914
|
+
if (!trimmed)
|
|
41915
|
+
return null;
|
|
41916
|
+
if (trimmed.length > 64)
|
|
41917
|
+
return null;
|
|
41918
|
+
if (!/^[A-Za-z0-9_-]+$/.test(trimmed))
|
|
41919
|
+
return null;
|
|
41920
|
+
return trimmed;
|
|
41921
|
+
}
|
|
41922
|
+
function parseArgs(args2) {
|
|
41923
|
+
const out2 = { specReview: false, rest: [] };
|
|
41924
|
+
for (let i2 = 0;i2 < args2.length; i2++) {
|
|
41925
|
+
const token = args2[i2];
|
|
41926
|
+
if (token === "--spec-review") {
|
|
41927
|
+
out2.specReview = true;
|
|
41928
|
+
continue;
|
|
41929
|
+
}
|
|
41930
|
+
if (token === "--preset") {
|
|
41931
|
+
const next = args2[i2 + 1];
|
|
41932
|
+
if (next !== undefined) {
|
|
41933
|
+
const sanitized = sanitizePresetName(next);
|
|
41934
|
+
if (sanitized)
|
|
41935
|
+
out2.preset = sanitized;
|
|
41936
|
+
i2++;
|
|
41937
|
+
}
|
|
41938
|
+
continue;
|
|
41939
|
+
}
|
|
41940
|
+
out2.rest.push(token);
|
|
41941
|
+
}
|
|
41942
|
+
return out2;
|
|
41943
|
+
}
|
|
41944
|
+
async function handleCouncilCommand(_directory, args2) {
|
|
41945
|
+
const parsed = parseArgs(args2);
|
|
41946
|
+
const question = sanitizeQuestion(parsed.rest.join(" "));
|
|
41947
|
+
if (!question) {
|
|
41948
|
+
return USAGE;
|
|
41949
|
+
}
|
|
41950
|
+
const tokens = ["MODE: COUNCIL"];
|
|
41951
|
+
if (parsed.preset) {
|
|
41952
|
+
tokens.push(`preset=${parsed.preset}`);
|
|
41953
|
+
}
|
|
41954
|
+
if (parsed.specReview) {
|
|
41955
|
+
tokens.push("spec_review");
|
|
41956
|
+
}
|
|
41957
|
+
return `[${tokens.join(" ")}] ${question}`;
|
|
41958
|
+
}
|
|
41959
|
+
var MAX_QUESTION_LEN = 2000, USAGE;
|
|
41960
|
+
var init_council = __esm(() => {
|
|
41961
|
+
USAGE = [
|
|
41962
|
+
"Usage: /swarm council <question> [--preset <name>] [--spec-review]",
|
|
41963
|
+
"",
|
|
41964
|
+
" question The question to put to the council",
|
|
41965
|
+
" --preset <name> Use a named member preset from council.general.presets",
|
|
41966
|
+
" --spec-review Use spec_review mode (single advisory pass on a draft spec)",
|
|
41967
|
+
"",
|
|
41968
|
+
"Requires council.general.enabled: true and a configured search API key in opencode-swarm.json."
|
|
41969
|
+
].join(`
|
|
41970
|
+
`);
|
|
41971
|
+
});
|
|
41972
|
+
|
|
41867
41973
|
// src/agents/explorer.ts
|
|
41868
41974
|
function createExplorerAgent(model, customPrompt, customAppendPrompt) {
|
|
41869
41975
|
let prompt = EXPLORER_PROMPT;
|
|
@@ -52150,7 +52256,8 @@ var init_qa_gates = __esm(() => {
|
|
|
52150
52256
|
"critic_pre_plan",
|
|
52151
52257
|
"hallucination_guard",
|
|
52152
52258
|
"sast_enabled",
|
|
52153
|
-
"mutation_test"
|
|
52259
|
+
"mutation_test",
|
|
52260
|
+
"council_general_review"
|
|
52154
52261
|
];
|
|
52155
52262
|
});
|
|
52156
52263
|
|
|
@@ -53776,6 +53883,7 @@ var init_registry = __esm(() => {
|
|
|
53776
53883
|
init_checkpoint2();
|
|
53777
53884
|
init_close();
|
|
53778
53885
|
init_config2();
|
|
53886
|
+
init_council();
|
|
53779
53887
|
init_curate();
|
|
53780
53888
|
init_dark_matter();
|
|
53781
53889
|
init_diagnose();
|
|
@@ -53931,11 +54039,17 @@ var init_registry = __esm(() => {
|
|
|
53931
54039
|
args: "[topic-text]",
|
|
53932
54040
|
details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md."
|
|
53933
54041
|
},
|
|
54042
|
+
council: {
|
|
54043
|
+
handler: (ctx) => handleCouncilCommand(ctx.directory, ctx.args),
|
|
54044
|
+
description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--preset <name>] [--spec-review]",
|
|
54045
|
+
args: "<question> [--preset <name>] [--spec-review]",
|
|
54046
|
+
details: "Triggers the architect to convene a configurable General Council: each member independently web-searches, answers, and engages in one structured deliberation round on disagreements; an optional moderator pass synthesizes the final answer. --preset <name> selects a member group from council.general.presets. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in opencode-swarm.json."
|
|
54047
|
+
},
|
|
53934
54048
|
"qa-gates": {
|
|
53935
54049
|
handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
|
|
53936
54050
|
description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
|
|
53937
54051
|
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."
|
|
54052
|
+
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test, council_general_review."
|
|
53939
54053
|
},
|
|
53940
54054
|
promote: {
|
|
53941
54055
|
handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
|
|
@@ -54091,14 +54205,24 @@ from different members.`;
|
|
|
54091
54205
|
function buildYourToolsList(council) {
|
|
54092
54206
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54093
54207
|
const sorted = [...tools].sort();
|
|
54094
|
-
const
|
|
54208
|
+
const qaCouncilEnabled = council?.enabled === true;
|
|
54209
|
+
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54210
|
+
const filtered = sorted.filter((t) => {
|
|
54211
|
+
if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
|
|
54212
|
+
return false;
|
|
54213
|
+
}
|
|
54214
|
+
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
54215
|
+
return false;
|
|
54216
|
+
}
|
|
54217
|
+
return true;
|
|
54218
|
+
});
|
|
54095
54219
|
return `Task (delegation), ${filtered.join(", ")}.`;
|
|
54096
54220
|
}
|
|
54097
54221
|
function buildQaGateSelectionDialogue(modeLabel) {
|
|
54098
54222
|
const leadIn = modeLabel === "BRAINSTORM" ? "Now ask the user which QA gates to enable for this plan \u2014 do not select on their behalf." : modeLabel === "SPECIFY" ? "Ask the user which QA gates to enable for this plan before suggesting the next step." : "No pending gate selection found in `.swarm/context.md`. Ask the user inline now.";
|
|
54099
54223
|
return `${leadIn}
|
|
54100
54224
|
|
|
54101
|
-
Present the
|
|
54225
|
+
Present the nine gates with their defaults (DEFAULT_QA_GATES) as a single user-facing question. Offer the user a one-shot choice: accept defaults, or customize. The nine gates are:
|
|
54102
54226
|
- reviewer (default: ON) \u2014 code review of coder output
|
|
54103
54227
|
- test_engineer (default: ON) \u2014 test verification of coder output
|
|
54104
54228
|
- sme_enabled (default: ON) \u2014 SME consultation during planning/clarification
|
|
@@ -54107,13 +54231,24 @@ Present the eight gates with their defaults (DEFAULT_QA_GATES) as a single user-
|
|
|
54107
54231
|
- council_mode (default: OFF) \u2014 multi-member council gate (recommended for high-impact architecture, public APIs, schema/data mutation, security-sensitive code)
|
|
54108
54232
|
- hallucination_guard (default: OFF) \u2014 when enabled, mandatory per-phase API/signature/claim/citation verification via critic_hallucination_verifier at PHASE-WRAP; phase_complete will REJECT phase completion unless .swarm/evidence/{phase}/hallucination-guard.json exists with an APPROVED verdict (recommended for claim-heavy or research-heavy work)
|
|
54109
54233
|
- mutation_test (default: OFF) \u2014 when enabled, runs mutation testing on source files touched this phase via generate_mutants + mutation_test + write_mutation_evidence at PHASE-WRAP; FAIL verdict blocks phase_complete; WARN is non-blocking (recommended for projects with coverage gaps or safety-critical code)
|
|
54234
|
+
- council_general_review (default: OFF) \u2014 when enabled, MODE: SPECIFY runs convene_general_council on the draft spec before the critic-gate; multiple models each independently search the web, deliberate on disagreements, and a moderator synthesizes a final answer that the architect folds into the spec (recommended for novel architecture, unclear best practices, or high-risk design decisions). Requires council.general.enabled: true and a configured search API key.
|
|
54110
54235
|
|
|
54111
54236
|
One question, one message, defaults pre-stated. Wait for the user's answer.`;
|
|
54112
54237
|
}
|
|
54113
54238
|
function buildAvailableToolsList(council) {
|
|
54114
54239
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54115
54240
|
const sorted = [...tools].sort();
|
|
54116
|
-
const
|
|
54241
|
+
const qaCouncilEnabled = council?.enabled === true;
|
|
54242
|
+
const generalCouncilEnabled = council?.general?.enabled === true;
|
|
54243
|
+
const filtered = sorted.filter((t) => {
|
|
54244
|
+
if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
|
|
54245
|
+
return false;
|
|
54246
|
+
}
|
|
54247
|
+
if (!generalCouncilEnabled && t === "convene_general_council") {
|
|
54248
|
+
return false;
|
|
54249
|
+
}
|
|
54250
|
+
return true;
|
|
54251
|
+
});
|
|
54117
54252
|
return filtered.map((t) => {
|
|
54118
54253
|
const desc = TOOL_DESCRIPTIONS[t];
|
|
54119
54254
|
return desc ? `${t} (${desc})` : t;
|
|
@@ -54154,7 +54289,8 @@ function buildSlashCommandsList() {
|
|
|
54154
54289
|
"analyze",
|
|
54155
54290
|
"plan",
|
|
54156
54291
|
"sync-plan",
|
|
54157
|
-
"acknowledge-spec-drift"
|
|
54292
|
+
"acknowledge-spec-drift",
|
|
54293
|
+
"council"
|
|
54158
54294
|
],
|
|
54159
54295
|
"Execution Modes": ["turbo", "full-auto"],
|
|
54160
54296
|
Observation: [
|
|
@@ -54799,6 +54935,29 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
|
|
|
54799
54935
|
**Phase 6: QA GATE SELECTION (architect, dialogue only).**
|
|
54800
54936
|
{{QA_GATE_DIALOGUE_BRAINSTORM}}
|
|
54801
54937
|
|
|
54938
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
54939
|
+
GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
|
|
54940
|
+
\u2717 "I'll use the defaults \u2014 they're probably fine"
|
|
54941
|
+
\u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
|
|
54942
|
+
\u2717 "The user didn't mention gates, so defaults are fine"
|
|
54943
|
+
\u2192 WRONG: silence is not consent. The gate dialogue is not optional.
|
|
54944
|
+
\u2717 "I'll handle it in MODE: PLAN after the spec is done"
|
|
54945
|
+
\u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
|
|
54946
|
+
save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
|
|
54947
|
+
\u2717 "This feature is simple \u2014 gates are obvious"
|
|
54948
|
+
\u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
|
|
54949
|
+
\u2717 "I already know which gates are right for this project"
|
|
54950
|
+
\u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
|
|
54951
|
+
\u2717 "council_general_review is off by default, I don't need to mention it"
|
|
54952
|
+
\u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
|
|
54953
|
+
|
|
54954
|
+
MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
|
|
54955
|
+
You are BLOCKED until ALL THREE of these conditions are met:
|
|
54956
|
+
(1) The gate selection question has been presented to the user in a single message
|
|
54957
|
+
(2) The user has responded (accept defaults OR customized list)
|
|
54958
|
+
(3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
|
|
54959
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
54960
|
+
|
|
54802
54961
|
Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
|
|
54803
54962
|
\`\`\`
|
|
54804
54963
|
## Pending QA Gate Selection
|
|
@@ -54810,6 +54969,7 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
|
|
|
54810
54969
|
- council_mode: <true|false>
|
|
54811
54970
|
- hallucination_guard: <true|false>
|
|
54812
54971
|
- mutation_test: <true|false>
|
|
54972
|
+
- council_general_review: <true|false>
|
|
54813
54973
|
- recorded_at: <ISO timestamp>
|
|
54814
54974
|
\`\`\`
|
|
54815
54975
|
MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
|
|
@@ -54852,6 +55012,29 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
|
|
|
54852
55012
|
5b. **QA GATE SELECTION (dialogue only).**
|
|
54853
55013
|
{{QA_GATE_DIALOGUE_SPECIFY}}
|
|
54854
55014
|
|
|
55015
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55016
|
+
GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
|
|
55017
|
+
\u2717 "I'll use the defaults \u2014 they're probably fine"
|
|
55018
|
+
\u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
|
|
55019
|
+
\u2717 "The user didn't mention gates, so defaults are fine"
|
|
55020
|
+
\u2192 WRONG: silence is not consent. The gate dialogue is not optional.
|
|
55021
|
+
\u2717 "I'll handle it in MODE: PLAN after the spec is done"
|
|
55022
|
+
\u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
|
|
55023
|
+
save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
|
|
55024
|
+
\u2717 "This feature is simple \u2014 gates are obvious"
|
|
55025
|
+
\u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
|
|
55026
|
+
\u2717 "I already know which gates are right for this project"
|
|
55027
|
+
\u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
|
|
55028
|
+
\u2717 "council_general_review is off by default, I don't need to mention it"
|
|
55029
|
+
\u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
|
|
55030
|
+
|
|
55031
|
+
MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
|
|
55032
|
+
You are BLOCKED until ALL THREE of these conditions are met:
|
|
55033
|
+
(1) The gate selection question has been presented to the user in a single message
|
|
55034
|
+
(2) The user has responded (accept defaults OR customized list)
|
|
55035
|
+
(3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
|
|
55036
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55037
|
+
|
|
54855
55038
|
Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
|
|
54856
55039
|
\`\`\`
|
|
54857
55040
|
## Pending QA Gate Selection
|
|
@@ -54863,9 +55046,37 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
|
|
|
54863
55046
|
- council_mode: <true|false>
|
|
54864
55047
|
- hallucination_guard: <true|false>
|
|
54865
55048
|
- mutation_test: <true|false>
|
|
55049
|
+
- council_general_review: <true|false>
|
|
54866
55050
|
- recorded_at: <ISO timestamp>
|
|
54867
55051
|
\`\`\`
|
|
54868
55052
|
MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
|
|
55053
|
+
|
|
55054
|
+
5c. **SPECIFY-COUNCIL-REVIEW (fires ONLY when council_general_review gate is true).**
|
|
55055
|
+
Read the elected QA gates (parse the \`## Pending QA Gate Selection\` section from \`.swarm/context.md\` you just wrote, OR call \`get_qa_gate_profile\` if a profile already exists). If \`council_general_review\` is false or absent, skip directly to step 7.
|
|
55056
|
+
|
|
55057
|
+
If \`council_general_review\` is true:
|
|
55058
|
+
1. Read \`council.general\` config. If \`council.general.enabled\` is not true OR no search API key is configured, surface to the user: "council_general_review gate is enabled but the General Council is not configured. Set council.general.enabled: true and configure a search API key in opencode-swarm.json, or unset council_general_review and re-run." Then stop.
|
|
55059
|
+
2. Determine the council members from \`council.general.members\` (or \`council.general.presets[<name>]\` if you were invoked via \`/swarm council --preset <name>\` originally).
|
|
55060
|
+
3. Delegate to each council member in PARALLEL \u2014 one message per member, then STOP and wait. Pass: the spec text as the question, the member's role/persona, round number 1. Do NOT share other members' perspectives at this stage.
|
|
55061
|
+
4. Collect all member JSON responses.
|
|
55062
|
+
5. Call \`convene_general_council\` with mode: 'spec_review', the spec as question, and the collected \`round1Responses\`. Omit \`round2Responses\` \u2014 spec review is a single-pass advisory, not a full deliberation.
|
|
55063
|
+
6. Read \`consensusPoints\` \u2014 incorporate unambiguous consensus directly into the spec.
|
|
55064
|
+
7. Read \`disagreements\` \u2014 for each: (a) accept one position with rationale, (b) mark as \`[NEEDS CLARIFICATION]\` in the spec, or (c) schedule an SME consultation.
|
|
55065
|
+
8. If \`council.general.moderator\` is true, the tool returned a \`moderatorPrompt\` field. Delegate this prompt to \`{{AGENT_PREFIX}}council_moderator\`. Use the moderator's output to refine the spec further.
|
|
55066
|
+
9. Revise \`.swarm/spec.md\` to reflect the council input.
|
|
55067
|
+
|
|
55068
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55069
|
+
SPECIFY-COUNCIL-REVIEW RULES:
|
|
55070
|
+
\u2717 "council_general_review is off by default, I'll skip this"
|
|
55071
|
+
\u2192 CORRECT only when the gate is explicitly false or absent. Do NOT assume false. Read the actual gate value before deciding to skip.
|
|
55072
|
+
\u2717 "The spec is already good, no need to ask the council"
|
|
55073
|
+
\u2192 WRONG when gate is true: the user enabled this gate for a reason. Run it regardless.
|
|
55074
|
+
\u2717 "I'll include round2Responses for spec_review \u2014 more is better"
|
|
55075
|
+
\u2192 WRONG: spec review is a single advisory pass. Omit \`round2Responses\` for spec_review mode.
|
|
55076
|
+
\u2717 "I'll skip the moderator pass to save time"
|
|
55077
|
+
\u2192 WRONG when council.general.moderator is true: invoke \`{{AGENT_PREFIX}}council_moderator\` with the moderatorPrompt the tool returns.
|
|
55078
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55079
|
+
|
|
54869
55080
|
7. Report a summary to the user (MUST count, SHALL count, scenario count, clarification markers, elected QA gates) and suggest the next step: \`CLARIFY-SPEC\` (if markers exist) or \`PLAN\`.
|
|
54870
55081
|
|
|
54871
55082
|
SPEC CONTENT RULES \u2014 the spec MUST NOT contain:
|
|
@@ -55034,6 +55245,39 @@ This check fires automatically in:
|
|
|
55034
55245
|
|
|
55035
55246
|
GREENFIELD EXEMPTION: If the work is purely greenfield (new project, no existing codebase references), skip this check.
|
|
55036
55247
|
|
|
55248
|
+
### MODE: COUNCIL
|
|
55249
|
+
|
|
55250
|
+
Activates when: user invokes \`/swarm council <question>\` (optionally with \`--preset <name>\` or \`--spec-review\`).
|
|
55251
|
+
|
|
55252
|
+
Purpose: convene a configurable multi-model General Council for an advisory deliberation. Each member independently web-searches and answers; the architect routes any disagreements back for one targeted reconciliation round; an optional moderator pass synthesizes the final user-facing answer.
|
|
55253
|
+
|
|
55254
|
+
This mode is ADVISORY \u2014 it does NOT block any other workflow and does NOT modify code, plans, or specs. The output is for the user (general mode) or for the spec being drafted in MODE: SPECIFY (spec_review mode, gated by \`council_general_review\`).
|
|
55255
|
+
|
|
55256
|
+
#### Pre-flight (always run first)
|
|
55257
|
+
1. Read \`council.general\` config. If \`council.general.enabled\` is not true OR no search API key is configured (neither \`council.general.searchApiKey\` nor the corresponding env var \`TAVILY_API_KEY\` / \`BRAVE_SEARCH_API_KEY\`), surface to the user: "General Council is not enabled. Set council.general.enabled: true and configure a search API key in opencode-swarm.json." Then STOP.
|
|
55258
|
+
|
|
55259
|
+
#### Round 1 \u2014 Parallel Independent Search
|
|
55260
|
+
2. Determine council members. Default: \`council.general.members\`. If invoked with \`--preset <name>\`: \`council.general.presets[<name>]\`. If a named preset is missing, surface a clear error and stop.
|
|
55261
|
+
3. Delegate to each council member in PARALLEL \u2014 one message per member, then STOP and wait for all responses to come back. Pass: the question, the member's role/persona, round number 1. Do NOT share other members' responses at this stage.
|
|
55262
|
+
4. Collect all member JSON responses (each member returns a fenced JSON block per the council_member prompt).
|
|
55263
|
+
|
|
55264
|
+
#### Synthesis and Deliberation (when council.general.deliberate is true; default true)
|
|
55265
|
+
5. Call \`convene_general_council\` with mode set from the command (\`general\` or \`spec_review\`), \`question\`, and the collected \`round1Responses\` only (omit \`round2Responses\`). Inspect the returned \`disagreementsCount\`.
|
|
55266
|
+
6. If \`disagreementsCount > 0\`:
|
|
55267
|
+
a. For each disagreement in the tool's response, identify the disputing members (the members listed in the disagreement's positions).
|
|
55268
|
+
b. Re-delegate ONLY to the disputing members \u2014 one message per member \u2014 passing: their Round 1 response, the disagreement topic, the opposing position(s), round number 2.
|
|
55269
|
+
c. Collect the Round 2 responses.
|
|
55270
|
+
d. Call \`convene_general_council\` AGAIN with both \`round1Responses\` AND \`round2Responses\` populated.
|
|
55271
|
+
|
|
55272
|
+
#### Moderator Pass (when council.general.moderator is true; default true)
|
|
55273
|
+
7. The most recent \`convene_general_council\` call returned a \`moderatorPrompt\` field. Delegate this prompt to \`{{AGENT_PREFIX}}council_moderator\`. The moderator agent has no tools and no web access \u2014 it synthesizes a final user-facing answer from the council output you give it. Collect the moderator's markdown output.
|
|
55274
|
+
|
|
55275
|
+
#### Output
|
|
55276
|
+
8. Present the final answer to the user:
|
|
55277
|
+
- If the moderator pass ran: present the moderator's output verbatim, prefaced with the participating models (one line).
|
|
55278
|
+
- If no moderator: present the structural \`synthesis\` markdown from the tool's return.
|
|
55279
|
+
In either case, do NOT present the raw per-member JSON. Do NOT silently pick a winner among persisting disagreements \u2014 surface them honestly.
|
|
55280
|
+
|
|
55037
55281
|
### MODE: PLAN
|
|
55038
55282
|
|
|
55039
55283
|
SPEC GATE (soft \u2014 check before planning):
|
|
@@ -55112,7 +55356,18 @@ save_plan({
|
|
|
55112
55356
|
**POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
|
|
55113
55357
|
After \`save_plan\` succeeds, read \`.swarm/context.md\`:
|
|
55114
55358
|
- If a \`## Pending QA Gate Selection\` section exists: parse the gate values, call \`set_qa_gates\` with those flags, confirm with the user ("QA gates applied: <list>"), then remove the section from context.md.
|
|
55115
|
-
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
55359
|
+
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
55360
|
+
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
55361
|
+
INLINE GATE SELECTION \u2014 no pending section found in context.md. You MUST ask now.
|
|
55362
|
+
\u2717 "I'll call set_qa_gates with defaults and move on"
|
|
55363
|
+
\u2192 WRONG: set_qa_gates with assumed values is a gate violation. The user must answer first.
|
|
55364
|
+
\u2717 "The user provided a plan \u2014 they know what gates they want"
|
|
55365
|
+
\u2192 WRONG: providing a plan is not the same as configuring gates. Always ask.
|
|
55366
|
+
|
|
55367
|
+
MANDATORY PAUSE: Present the gate question. Wait for the user's answer.
|
|
55368
|
+
Do NOT call \`set_qa_gates\` until the user has responded.
|
|
55369
|
+
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
55370
|
+
Then call \`set_qa_gates\` with the user's chosen flags.
|
|
55116
55371
|
Either path must yield a persisted QA gate profile before the first task dispatches.
|
|
55117
55372
|
|
|
55118
55373
|
\u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
|
|
@@ -55701,6 +55956,189 @@ META.SUMMARY CONVENTION \u2014 When reporting task completion, include:
|
|
|
55701
55956
|
|
|
55702
55957
|
`;
|
|
55703
55958
|
|
|
55959
|
+
// src/agents/council-member.ts
|
|
55960
|
+
function createCouncilMemberAgent(model, customPrompt, customAppendPrompt) {
|
|
55961
|
+
let prompt = COUNCIL_MEMBER_PROMPT;
|
|
55962
|
+
if (customPrompt) {
|
|
55963
|
+
prompt = customPrompt;
|
|
55964
|
+
} else if (customAppendPrompt) {
|
|
55965
|
+
prompt = `${COUNCIL_MEMBER_PROMPT}
|
|
55966
|
+
|
|
55967
|
+
${customAppendPrompt}`;
|
|
55968
|
+
}
|
|
55969
|
+
return {
|
|
55970
|
+
name: "council_member",
|
|
55971
|
+
description: "General Council deliberation member. Independently web-searches and answers in Round 1; " + "targeted MAINTAIN/CONCEDE/NUANCE deliberation in Round 2. Tool-restricted to web_search only.",
|
|
55972
|
+
config: {
|
|
55973
|
+
model,
|
|
55974
|
+
temperature: 0.4,
|
|
55975
|
+
prompt,
|
|
55976
|
+
tools: {
|
|
55977
|
+
write: false,
|
|
55978
|
+
edit: false,
|
|
55979
|
+
patch: false
|
|
55980
|
+
}
|
|
55981
|
+
}
|
|
55982
|
+
};
|
|
55983
|
+
}
|
|
55984
|
+
var COUNCIL_MEMBER_PROMPT = `You are Council Member {{MEMBER_ID}} ({{ROLE}}) on a multi-model General Council.
|
|
55985
|
+
|
|
55986
|
+
{{PERSONA_BLOCK}}
|
|
55987
|
+
|
|
55988
|
+
You are participating in Round {{ROUND}} of a structured deliberation. Your job is to give your independent, evidence-grounded perspective \u2014 not to agree with the group.
|
|
55989
|
+
|
|
55990
|
+
================================================================
|
|
55991
|
+
ROUND {{ROUND}} PROTOCOL
|
|
55992
|
+
================================================================
|
|
55993
|
+
|
|
55994
|
+
ROUND 1 \u2014 Independent Research and Answer
|
|
55995
|
+
- Issue 1\u20133 targeted web_search calls to gather evidence relevant to the question.
|
|
55996
|
+
- Cite EVERY factual claim with a source URL from your search results.
|
|
55997
|
+
- State your confidence (0.0\u20131.0) explicitly. Be honest \u2014 overconfident answers hurt the council.
|
|
55998
|
+
- Enumerate areas of uncertainty so the architect knows where you're guessing vs. where you're sure.
|
|
55999
|
+
- Do NOT coordinate with other members. You will not see their responses until Round 2.
|
|
56000
|
+
- Do NOT pad. Be concise. Substance over volume.
|
|
56001
|
+
|
|
56002
|
+
ROUND 2 \u2014 Targeted Deliberation (ONLY when this round is invoked for you)
|
|
56003
|
+
- {{DISAGREEMENT_BLOCK}}
|
|
56004
|
+
- Issue at most 1 additional web_search call.
|
|
56005
|
+
- Declare your stance explicitly using one of these keywords as the FIRST word of a paragraph:
|
|
56006
|
+
MAINTAIN \u2014 your Round 1 position holds; cite the new evidence supporting it
|
|
56007
|
+
CONCEDE \u2014 the opposing position is correct; state specifically what you got wrong
|
|
56008
|
+
NUANCE \u2014 both positions are partially right; state the boundary condition that distinguishes them
|
|
56009
|
+
- Never CONCEDE without evidence. Sycophantic capitulation degrades the council below an individual member's baseline (NSED arXiv:2601.16863).
|
|
56010
|
+
- Never MAINTAIN without engaging the opposing argument on its merits.
|
|
56011
|
+
|
|
56012
|
+
================================================================
|
|
56013
|
+
RESPONSE FORMAT (always \u2014 both rounds)
|
|
56014
|
+
================================================================
|
|
56015
|
+
|
|
56016
|
+
Reply with a single fenced JSON block. No prose outside the block.
|
|
56017
|
+
|
|
56018
|
+
\`\`\`json
|
|
56019
|
+
{
|
|
56020
|
+
"memberId": "{{MEMBER_ID}}",
|
|
56021
|
+
"role": "{{ROLE}}",
|
|
56022
|
+
"round": {{ROUND}},
|
|
56023
|
+
"response": "Your full answer (Round 1) or stance + reasoning (Round 2). Markdown OK inside the string.",
|
|
56024
|
+
"searchQueries": ["query 1", "query 2"],
|
|
56025
|
+
"sources": [
|
|
56026
|
+
{ "title": "...", "url": "...", "snippet": "...", "query": "..." }
|
|
56027
|
+
],
|
|
56028
|
+
"confidence": 0.85,
|
|
56029
|
+
"areasOfUncertainty": [
|
|
56030
|
+
"What I'm not sure about, in plain language."
|
|
56031
|
+
],
|
|
56032
|
+
"disagreementTopics": []
|
|
56033
|
+
}
|
|
56034
|
+
\`\`\`
|
|
56035
|
+
|
|
56036
|
+
For Round 1: leave \`disagreementTopics\` as []. For Round 2: list the specific disagreement topics this response addresses.
|
|
56037
|
+
|
|
56038
|
+
================================================================
|
|
56039
|
+
HARD RULES
|
|
56040
|
+
================================================================
|
|
56041
|
+
- web_search is your ONLY tool. You cannot read or write files, run commands, or delegate.
|
|
56042
|
+
- Never invent sources. If a search returns nothing useful, say so in \`areasOfUncertainty\`.
|
|
56043
|
+
- Never echo other members' responses verbatim. Paraphrase or quote with attribution.
|
|
56044
|
+
- Stay within your role and persona. The architect chose you for a specific perspective.
|
|
56045
|
+
`;
|
|
56046
|
+
|
|
56047
|
+
// src/agents/council-moderator.ts
|
|
56048
|
+
function createCouncilModeratorAgent(model, customPrompt, customAppendPrompt) {
|
|
56049
|
+
let prompt = COUNCIL_MODERATOR_PROMPT;
|
|
56050
|
+
if (customPrompt) {
|
|
56051
|
+
prompt = customPrompt;
|
|
56052
|
+
} else if (customAppendPrompt) {
|
|
56053
|
+
prompt = `${COUNCIL_MODERATOR_PROMPT}
|
|
56054
|
+
|
|
56055
|
+
${customAppendPrompt}`;
|
|
56056
|
+
}
|
|
56057
|
+
return {
|
|
56058
|
+
name: "council_moderator",
|
|
56059
|
+
description: "General Council moderator. Synthesizes a coherent final answer from member " + "responses; no web search (works on already-gathered content).",
|
|
56060
|
+
config: {
|
|
56061
|
+
model,
|
|
56062
|
+
temperature: 0.3,
|
|
56063
|
+
prompt,
|
|
56064
|
+
tools: {
|
|
56065
|
+
write: false,
|
|
56066
|
+
edit: false,
|
|
56067
|
+
patch: false
|
|
56068
|
+
}
|
|
56069
|
+
}
|
|
56070
|
+
};
|
|
56071
|
+
}
|
|
56072
|
+
var COUNCIL_MODERATOR_PROMPT = `You are the General Council Moderator.
|
|
56073
|
+
|
|
56074
|
+
You are receiving the structural synthesis from a multi-model council deliberation:
|
|
56075
|
+
- Question (and mode: general or spec_review)
|
|
56076
|
+
- All member Round 1 responses with sources
|
|
56077
|
+
- Detected disagreements
|
|
56078
|
+
- Round 2 deliberation responses (if any)
|
|
56079
|
+
- Confidence-weighted consensus claims
|
|
56080
|
+
- Persisting disagreements after deliberation
|
|
56081
|
+
|
|
56082
|
+
Your job: produce a coherent, well-structured final answer for the user.
|
|
56083
|
+
|
|
56084
|
+
================================================================
|
|
56085
|
+
RULES
|
|
56086
|
+
================================================================
|
|
56087
|
+
|
|
56088
|
+
1. LEAD WITH CONSENSUS \u2014 open with the strongest consensus position. Use the
|
|
56089
|
+
confidence-weighted ordering (Quadratic Voting): higher-confidence claims
|
|
56090
|
+
from multiple members rank higher, but evidence quality outranks raw
|
|
56091
|
+
confidence. Never elevate a single confident voice over a well-evidenced
|
|
56092
|
+
contrary majority.
|
|
56093
|
+
|
|
56094
|
+
2. ACKNOWLEDGE DISAGREEMENT HONESTLY \u2014 for each persisting disagreement, write
|
|
56095
|
+
"experts disagree on X because\u2026" and present the strongest version of each
|
|
56096
|
+
side. Do NOT pretend disagreements are resolved when they are not. Do NOT
|
|
56097
|
+
silently pick a winner.
|
|
56098
|
+
|
|
56099
|
+
3. CITE THE STRONGEST SOURCES \u2014 link key claims with [title](url) format from
|
|
56100
|
+
the deduplicated source list. Pick the most reputable source for each claim;
|
|
56101
|
+
do not cite duplicates.
|
|
56102
|
+
|
|
56103
|
+
4. BE CONCISE \u2014 the user wants an answer, not a committee report. Default
|
|
56104
|
+
length: a few short paragraphs plus a bulleted summary. Expand only when
|
|
56105
|
+
the question genuinely requires it.
|
|
56106
|
+
|
|
56107
|
+
================================================================
|
|
56108
|
+
HARD CONSTRAINTS
|
|
56109
|
+
================================================================
|
|
56110
|
+
|
|
56111
|
+
- You MUST NOT invent claims that are not present in the council's responses.
|
|
56112
|
+
- You MUST NOT add new web research. If something was missed, say so.
|
|
56113
|
+
- You MUST NOT favor a position based on member confidence alone \u2014 evidence
|
|
56114
|
+
quality is the tie-breaker.
|
|
56115
|
+
- You have NO tools. You write the final synthesis from the input given.
|
|
56116
|
+
|
|
56117
|
+
================================================================
|
|
56118
|
+
OUTPUT FORMAT
|
|
56119
|
+
================================================================
|
|
56120
|
+
|
|
56121
|
+
Plain markdown. No code fences. No JSON. Suggested structure:
|
|
56122
|
+
|
|
56123
|
+
# Answer
|
|
56124
|
+
|
|
56125
|
+
<lead consensus position with citation(s)>
|
|
56126
|
+
|
|
56127
|
+
<remaining consensus / context paragraphs as needed>
|
|
56128
|
+
|
|
56129
|
+
## Where Experts Disagree
|
|
56130
|
+
|
|
56131
|
+
- <topic 1>: <position A> vs <position B>, with sources for each
|
|
56132
|
+
- <topic 2>: ...
|
|
56133
|
+
|
|
56134
|
+
## Sources
|
|
56135
|
+
|
|
56136
|
+
- [title](url)
|
|
56137
|
+
- ...
|
|
56138
|
+
|
|
56139
|
+
(Omit any section that is empty.)
|
|
56140
|
+
`;
|
|
56141
|
+
|
|
55704
56142
|
// src/agents/critic.ts
|
|
55705
56143
|
function parseSoundingBoardResponse(raw) {
|
|
55706
56144
|
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
@@ -57345,6 +57783,19 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
|
|
|
57345
57783
|
testEngineer.name = prefixName("test_engineer");
|
|
57346
57784
|
agents.push(applyOverrides(testEngineer, swarmAgents, swarmPrefix));
|
|
57347
57785
|
}
|
|
57786
|
+
if (pluginConfig?.council?.general?.enabled === true && !isAgentDisabled("council_member", swarmAgents, swarmPrefix)) {
|
|
57787
|
+
const councilMemberPrompts = getPrompts("council_member");
|
|
57788
|
+
const councilMember = createCouncilMemberAgent(getModel("council_member"), councilMemberPrompts.prompt, councilMemberPrompts.appendPrompt);
|
|
57789
|
+
councilMember.name = prefixName("council_member");
|
|
57790
|
+
agents.push(applyOverrides(councilMember, swarmAgents, swarmPrefix));
|
|
57791
|
+
}
|
|
57792
|
+
if (pluginConfig?.council?.general?.enabled === true && pluginConfig?.council?.general?.moderator === true && !isAgentDisabled("council_moderator", swarmAgents, swarmPrefix)) {
|
|
57793
|
+
const moderatorPrompts = getPrompts("council_moderator");
|
|
57794
|
+
const moderatorModel = pluginConfig?.council?.general?.moderatorModel ?? getModel("council_moderator");
|
|
57795
|
+
const councilModerator = createCouncilModeratorAgent(moderatorModel, moderatorPrompts.prompt, moderatorPrompts.appendPrompt);
|
|
57796
|
+
councilModerator.name = prefixName("council_moderator");
|
|
57797
|
+
agents.push(applyOverrides(councilModerator, swarmAgents, swarmPrefix));
|
|
57798
|
+
}
|
|
57348
57799
|
if (!isAgentDisabled("docs", swarmAgents, swarmPrefix)) {
|
|
57349
57800
|
const docsPrompts = getPrompts("docs");
|
|
57350
57801
|
const docs = createDocsAgent(getModel("docs"), docsPrompts.prompt, docsPrompts.appendPrompt);
|
|
@@ -62357,7 +62808,7 @@ var init_curator_drift = __esm(() => {
|
|
|
62357
62808
|
|
|
62358
62809
|
// src/index.ts
|
|
62359
62810
|
init_agents();
|
|
62360
|
-
import * as
|
|
62811
|
+
import * as path103 from "path";
|
|
62361
62812
|
|
|
62362
62813
|
// src/background/index.ts
|
|
62363
62814
|
init_event_bus();
|
|
@@ -62656,6 +63107,7 @@ init_benchmark();
|
|
|
62656
63107
|
init_checkpoint2();
|
|
62657
63108
|
init_close();
|
|
62658
63109
|
init_config2();
|
|
63110
|
+
init_council();
|
|
62659
63111
|
init_curate();
|
|
62660
63112
|
init_dark_matter();
|
|
62661
63113
|
init_diagnose();
|
|
@@ -74014,6 +74466,496 @@ var convene_council = createSwarmTool({
|
|
|
74014
74466
|
}, null, 2);
|
|
74015
74467
|
}
|
|
74016
74468
|
});
|
|
74469
|
+
// src/tools/convene-general-council.ts
|
|
74470
|
+
init_dist();
|
|
74471
|
+
init_zod();
|
|
74472
|
+
init_loader();
|
|
74473
|
+
import * as fs59 from "fs";
|
|
74474
|
+
import * as path73 from "path";
|
|
74475
|
+
|
|
74476
|
+
// src/council/general-council-advisory.ts
|
|
74477
|
+
var ADVISORY_HEADER = "[general_council] (advisory; not blocking)";
|
|
74478
|
+
function pushGeneralCouncilAdvisory(session, result) {
|
|
74479
|
+
if (!session)
|
|
74480
|
+
return;
|
|
74481
|
+
const body2 = renderAdvisoryBody(result);
|
|
74482
|
+
if (!body2)
|
|
74483
|
+
return;
|
|
74484
|
+
session.pendingAdvisoryMessages ??= [];
|
|
74485
|
+
session.pendingAdvisoryMessages.push(`${ADVISORY_HEADER}
|
|
74486
|
+
${body2}`);
|
|
74487
|
+
}
|
|
74488
|
+
function renderAdvisoryBody(result) {
|
|
74489
|
+
const parts2 = [result.synthesis];
|
|
74490
|
+
if (result.moderatorOutput && result.moderatorOutput.trim().length > 0) {
|
|
74491
|
+
parts2.push("", "### Moderator Output", result.moderatorOutput);
|
|
74492
|
+
}
|
|
74493
|
+
return parts2.join(`
|
|
74494
|
+
`).trim();
|
|
74495
|
+
}
|
|
74496
|
+
|
|
74497
|
+
// src/council/disagreement-detector.ts
|
|
74498
|
+
var MAX_DISAGREEMENTS = 10;
|
|
74499
|
+
var EXPLICIT_DISAGREEMENT_MARKERS = [
|
|
74500
|
+
"i disagree with",
|
|
74501
|
+
"i would push back on",
|
|
74502
|
+
"contrary to",
|
|
74503
|
+
"this contradicts",
|
|
74504
|
+
"unlike "
|
|
74505
|
+
];
|
|
74506
|
+
var STRONG_RECOMMENDATION_MARKERS = [
|
|
74507
|
+
"recommend",
|
|
74508
|
+
"best approach",
|
|
74509
|
+
"should use",
|
|
74510
|
+
"i suggest",
|
|
74511
|
+
"the answer is",
|
|
74512
|
+
"the right choice is"
|
|
74513
|
+
];
|
|
74514
|
+
function tokenize(text) {
|
|
74515
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
|
|
74516
|
+
}
|
|
74517
|
+
function termOverlap(a, b) {
|
|
74518
|
+
const tokensA = new Set(tokenize(a));
|
|
74519
|
+
const tokensB = new Set(tokenize(b));
|
|
74520
|
+
if (tokensA.size === 0 || tokensB.size === 0)
|
|
74521
|
+
return 0;
|
|
74522
|
+
let intersection3 = 0;
|
|
74523
|
+
for (const t of tokensA) {
|
|
74524
|
+
if (tokensB.has(t))
|
|
74525
|
+
intersection3++;
|
|
74526
|
+
}
|
|
74527
|
+
const union3 = tokensA.size + tokensB.size - intersection3;
|
|
74528
|
+
return union3 === 0 ? 0 : intersection3 / union3;
|
|
74529
|
+
}
|
|
74530
|
+
function extractMarkerSentence(response, markers) {
|
|
74531
|
+
const lower = response.toLowerCase();
|
|
74532
|
+
const sentences = response.split(/(?<=[.!?])\s+/);
|
|
74533
|
+
for (const sentence of sentences) {
|
|
74534
|
+
const sentLower = sentence.toLowerCase();
|
|
74535
|
+
if (markers.some((m) => sentLower.includes(m))) {
|
|
74536
|
+
return sentence.trim();
|
|
74537
|
+
}
|
|
74538
|
+
}
|
|
74539
|
+
for (const marker of markers) {
|
|
74540
|
+
const idx = lower.indexOf(marker);
|
|
74541
|
+
if (idx !== -1) {
|
|
74542
|
+
const slice = response.slice(idx, idx + 200);
|
|
74543
|
+
return slice.split(/\n/)[0]?.trim() ?? slice.trim();
|
|
74544
|
+
}
|
|
74545
|
+
}
|
|
74546
|
+
return null;
|
|
74547
|
+
}
|
|
74548
|
+
function dedupeByTopic(disagreements) {
|
|
74549
|
+
const result = [];
|
|
74550
|
+
for (const d of disagreements) {
|
|
74551
|
+
const topicLower = d.topic.toLowerCase();
|
|
74552
|
+
const existing = result.find((r) => r.topic.toLowerCase().includes(topicLower) || topicLower.includes(r.topic.toLowerCase()));
|
|
74553
|
+
if (existing) {
|
|
74554
|
+
for (const pos of d.positions) {
|
|
74555
|
+
if (!existing.positions.some((p) => p.memberId === pos.memberId)) {
|
|
74556
|
+
existing.positions.push(pos);
|
|
74557
|
+
}
|
|
74558
|
+
}
|
|
74559
|
+
} else {
|
|
74560
|
+
result.push(d);
|
|
74561
|
+
}
|
|
74562
|
+
}
|
|
74563
|
+
return result;
|
|
74564
|
+
}
|
|
74565
|
+
function detectExplicitMarkers(responses) {
|
|
74566
|
+
const out2 = [];
|
|
74567
|
+
for (const member of responses) {
|
|
74568
|
+
const markerSentence = extractMarkerSentence(member.response, EXPLICIT_DISAGREEMENT_MARKERS);
|
|
74569
|
+
if (!markerSentence)
|
|
74570
|
+
continue;
|
|
74571
|
+
const position = {
|
|
74572
|
+
memberId: member.memberId,
|
|
74573
|
+
claim: markerSentence,
|
|
74574
|
+
evidence: member.sources[0]?.url ?? "(no source cited in marker sentence)"
|
|
74575
|
+
};
|
|
74576
|
+
out2.push({
|
|
74577
|
+
topic: markerSentence.slice(0, 80),
|
|
74578
|
+
positions: [position]
|
|
74579
|
+
});
|
|
74580
|
+
}
|
|
74581
|
+
return out2;
|
|
74582
|
+
}
|
|
74583
|
+
function extractRecommendation(response) {
|
|
74584
|
+
return extractMarkerSentence(response, STRONG_RECOMMENDATION_MARKERS);
|
|
74585
|
+
}
|
|
74586
|
+
function detectClaimDivergence(responses) {
|
|
74587
|
+
const recommendations = [];
|
|
74588
|
+
for (const member of responses) {
|
|
74589
|
+
const rec = extractRecommendation(member.response);
|
|
74590
|
+
if (!rec)
|
|
74591
|
+
continue;
|
|
74592
|
+
recommendations.push({
|
|
74593
|
+
memberId: member.memberId,
|
|
74594
|
+
text: rec,
|
|
74595
|
+
evidence: member.sources[0]?.url ?? "(no source cited)"
|
|
74596
|
+
});
|
|
74597
|
+
}
|
|
74598
|
+
const out2 = [];
|
|
74599
|
+
for (let i2 = 0;i2 < recommendations.length; i2++) {
|
|
74600
|
+
for (let j = i2 + 1;j < recommendations.length; j++) {
|
|
74601
|
+
const a = recommendations[i2];
|
|
74602
|
+
const b = recommendations[j];
|
|
74603
|
+
if (!a || !b)
|
|
74604
|
+
continue;
|
|
74605
|
+
const topicOverlap = termOverlap(a.text, b.text);
|
|
74606
|
+
if (topicOverlap > 0.4)
|
|
74607
|
+
continue;
|
|
74608
|
+
if (topicOverlap > 0 && topicOverlap < 0.3) {
|
|
74609
|
+
const topic = `${a.text.slice(0, 50)} vs ${b.text.slice(0, 50)}`;
|
|
74610
|
+
out2.push({
|
|
74611
|
+
topic,
|
|
74612
|
+
positions: [
|
|
74613
|
+
{ memberId: a.memberId, claim: a.text, evidence: a.evidence },
|
|
74614
|
+
{ memberId: b.memberId, claim: b.text, evidence: b.evidence }
|
|
74615
|
+
]
|
|
74616
|
+
});
|
|
74617
|
+
}
|
|
74618
|
+
}
|
|
74619
|
+
}
|
|
74620
|
+
return out2;
|
|
74621
|
+
}
|
|
74622
|
+
function detectDisagreements(responses) {
|
|
74623
|
+
if (!Array.isArray(responses) || responses.length < 2)
|
|
74624
|
+
return [];
|
|
74625
|
+
const safeResponses = responses.filter((r) => typeof r?.memberId === "string" && typeof r?.response === "string");
|
|
74626
|
+
const explicit = detectExplicitMarkers(safeResponses);
|
|
74627
|
+
const divergent = detectClaimDivergence(safeResponses);
|
|
74628
|
+
const combined = [...explicit, ...divergent];
|
|
74629
|
+
const deduped = dedupeByTopic(combined);
|
|
74630
|
+
return deduped.slice(0, MAX_DISAGREEMENTS);
|
|
74631
|
+
}
|
|
74632
|
+
|
|
74633
|
+
// src/council/general-council-service.ts
|
|
74634
|
+
var CONSENSUS_WEIGHT_THRESHOLD = 0.6;
|
|
74635
|
+
function tokenize2(text) {
|
|
74636
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4);
|
|
74637
|
+
}
|
|
74638
|
+
function similarity(a, b) {
|
|
74639
|
+
const tokensA = new Set(tokenize2(a));
|
|
74640
|
+
const tokensB = new Set(tokenize2(b));
|
|
74641
|
+
if (tokensA.size === 0 || tokensB.size === 0)
|
|
74642
|
+
return 0;
|
|
74643
|
+
let intersection3 = 0;
|
|
74644
|
+
for (const t of tokensA)
|
|
74645
|
+
if (tokensB.has(t))
|
|
74646
|
+
intersection3++;
|
|
74647
|
+
const union3 = tokensA.size + tokensB.size - intersection3;
|
|
74648
|
+
return union3 === 0 ? 0 : intersection3 / union3;
|
|
74649
|
+
}
|
|
74650
|
+
function extractClaims(response) {
|
|
74651
|
+
return response.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length >= 30 && s.length <= 400);
|
|
74652
|
+
}
|
|
74653
|
+
function buildConsensusClusters(responses) {
|
|
74654
|
+
if (responses.length < 2)
|
|
74655
|
+
return [];
|
|
74656
|
+
const totalMembers = responses.length;
|
|
74657
|
+
const clusters = [];
|
|
74658
|
+
for (const member of responses) {
|
|
74659
|
+
const confidence = clamp01(member.confidence ?? 0.5);
|
|
74660
|
+
const claims = extractClaims(member.response ?? "");
|
|
74661
|
+
for (const claim of claims) {
|
|
74662
|
+
let assigned = false;
|
|
74663
|
+
for (const cluster of clusters) {
|
|
74664
|
+
if (similarity(cluster.representative, claim) >= 0.5) {
|
|
74665
|
+
if (!cluster.memberIds.has(member.memberId)) {
|
|
74666
|
+
cluster.weightedAgreement += confidence;
|
|
74667
|
+
cluster.memberIds.add(member.memberId);
|
|
74668
|
+
}
|
|
74669
|
+
if (claim.length > cluster.representative.length) {
|
|
74670
|
+
cluster.representative = claim;
|
|
74671
|
+
}
|
|
74672
|
+
assigned = true;
|
|
74673
|
+
break;
|
|
74674
|
+
}
|
|
74675
|
+
}
|
|
74676
|
+
if (!assigned) {
|
|
74677
|
+
clusters.push({
|
|
74678
|
+
representative: claim,
|
|
74679
|
+
weightedAgreement: confidence,
|
|
74680
|
+
memberIds: new Set([member.memberId])
|
|
74681
|
+
});
|
|
74682
|
+
}
|
|
74683
|
+
}
|
|
74684
|
+
}
|
|
74685
|
+
return clusters.filter((c) => c.memberIds.size >= 2 && c.weightedAgreement / totalMembers >= CONSENSUS_WEIGHT_THRESHOLD).sort((a, b) => b.weightedAgreement - a.weightedAgreement || b.memberIds.size - a.memberIds.size).map((c) => c.representative);
|
|
74686
|
+
}
|
|
74687
|
+
function clamp01(n) {
|
|
74688
|
+
if (typeof n !== "number" || Number.isNaN(n))
|
|
74689
|
+
return 0;
|
|
74690
|
+
if (n < 0)
|
|
74691
|
+
return 0;
|
|
74692
|
+
if (n > 1)
|
|
74693
|
+
return 1;
|
|
74694
|
+
return n;
|
|
74695
|
+
}
|
|
74696
|
+
function computePersistingDisagreements(disagreements, round2) {
|
|
74697
|
+
if (disagreements.length === 0)
|
|
74698
|
+
return [];
|
|
74699
|
+
if (round2.length === 0)
|
|
74700
|
+
return disagreements;
|
|
74701
|
+
return disagreements.filter((d) => {
|
|
74702
|
+
const disputants = new Set(d.positions.map((p) => p.memberId));
|
|
74703
|
+
const conceded = round2.some((r) => {
|
|
74704
|
+
if (!disputants.has(r.memberId))
|
|
74705
|
+
return false;
|
|
74706
|
+
if (!r.disagreementTopics?.includes(d.topic))
|
|
74707
|
+
return false;
|
|
74708
|
+
return /\bconcede\b/i.test(r.response ?? "");
|
|
74709
|
+
});
|
|
74710
|
+
return !conceded;
|
|
74711
|
+
});
|
|
74712
|
+
}
|
|
74713
|
+
function dedupeSources(round1, round2) {
|
|
74714
|
+
const seen = new Set;
|
|
74715
|
+
const out2 = [];
|
|
74716
|
+
const allSources = [...round1, ...round2].flatMap((r) => r.sources ?? []);
|
|
74717
|
+
for (const src of allSources) {
|
|
74718
|
+
if (!src?.url)
|
|
74719
|
+
continue;
|
|
74720
|
+
if (seen.has(src.url))
|
|
74721
|
+
continue;
|
|
74722
|
+
seen.add(src.url);
|
|
74723
|
+
out2.push(src);
|
|
74724
|
+
}
|
|
74725
|
+
return out2;
|
|
74726
|
+
}
|
|
74727
|
+
function renderSynthesisMarkdown(question, mode, roundsCompleted, members, consensusPoints, persistingDisagreements, allSources) {
|
|
74728
|
+
const memberLines = members.map((m) => `- ${m.memberId} (${m.model}, ${m.role})`).join(`
|
|
74729
|
+
`);
|
|
74730
|
+
const consensusBlock = consensusPoints.length > 0 ? consensusPoints.map((c) => `- ${c}`).join(`
|
|
74731
|
+
`) : "_No consensus claims reached the weighted-agreement threshold._";
|
|
74732
|
+
const disagreementsBlock = persistingDisagreements.length > 0 ? persistingDisagreements.map((d) => `- **${d.topic}**
|
|
74733
|
+
` + d.positions.map((p) => ` - ${p.memberId}: ${p.claim}`).join(`
|
|
74734
|
+
`)).join(`
|
|
74735
|
+
`) : "_No persisting disagreements after deliberation._";
|
|
74736
|
+
const sourcesBlock = allSources.length > 0 ? allSources.map((s) => `- [${s.title || s.url}](${s.url})`).join(`
|
|
74737
|
+
`) : "_No sources cited._";
|
|
74738
|
+
return [
|
|
74739
|
+
"## General Council Synthesis",
|
|
74740
|
+
"",
|
|
74741
|
+
`**Question:** ${question}`,
|
|
74742
|
+
`**Mode:** ${mode}`,
|
|
74743
|
+
`**Members:**
|
|
74744
|
+
${memberLines}`,
|
|
74745
|
+
`**Rounds:** ${roundsCompleted}`,
|
|
74746
|
+
"",
|
|
74747
|
+
"### Consensus",
|
|
74748
|
+
consensusBlock,
|
|
74749
|
+
"",
|
|
74750
|
+
"### Persistent Disagreements",
|
|
74751
|
+
disagreementsBlock,
|
|
74752
|
+
"",
|
|
74753
|
+
"### Sources",
|
|
74754
|
+
sourcesBlock
|
|
74755
|
+
].join(`
|
|
74756
|
+
`);
|
|
74757
|
+
}
|
|
74758
|
+
function synthesizeGeneralCouncil(question, mode, round1Responses, round2Responses) {
|
|
74759
|
+
const safeRound1 = Array.isArray(round1Responses) ? round1Responses : [];
|
|
74760
|
+
const safeRound2 = Array.isArray(round2Responses) ? round2Responses : [];
|
|
74761
|
+
const disagreements = detectDisagreements(safeRound1);
|
|
74762
|
+
const consensusPoints = buildConsensusClusters(safeRound1);
|
|
74763
|
+
const persistingDisagreements = computePersistingDisagreements(disagreements, safeRound2);
|
|
74764
|
+
const allSources = dedupeSources(safeRound1, safeRound2);
|
|
74765
|
+
const roundsCompleted = safeRound2.length > 0 ? 2 : 1;
|
|
74766
|
+
const synthesis = renderSynthesisMarkdown(question, mode, roundsCompleted, safeRound1, consensusPoints, persistingDisagreements, allSources);
|
|
74767
|
+
return {
|
|
74768
|
+
question,
|
|
74769
|
+
mode,
|
|
74770
|
+
round1Responses: safeRound1,
|
|
74771
|
+
disagreements,
|
|
74772
|
+
round2Responses: safeRound2,
|
|
74773
|
+
synthesis,
|
|
74774
|
+
consensusPoints,
|
|
74775
|
+
persistingDisagreements: persistingDisagreements.map((d) => d.topic),
|
|
74776
|
+
allSources,
|
|
74777
|
+
timestamp: new Date().toISOString()
|
|
74778
|
+
};
|
|
74779
|
+
}
|
|
74780
|
+
|
|
74781
|
+
// src/tools/convene-general-council.ts
|
|
74782
|
+
init_state();
|
|
74783
|
+
init_create_tool();
|
|
74784
|
+
init_resolve_working_directory();
|
|
74785
|
+
var WebSearchResultSchema = exports_external.object({
|
|
74786
|
+
title: exports_external.string(),
|
|
74787
|
+
url: exports_external.string(),
|
|
74788
|
+
snippet: exports_external.string(),
|
|
74789
|
+
query: exports_external.string()
|
|
74790
|
+
});
|
|
74791
|
+
var MemberRoleEnum = exports_external.enum([
|
|
74792
|
+
"generalist",
|
|
74793
|
+
"skeptic",
|
|
74794
|
+
"domain_expert",
|
|
74795
|
+
"devil_advocate",
|
|
74796
|
+
"synthesizer"
|
|
74797
|
+
]);
|
|
74798
|
+
var Round1ResponseSchema = exports_external.object({
|
|
74799
|
+
memberId: exports_external.string().min(1),
|
|
74800
|
+
model: exports_external.string().min(1),
|
|
74801
|
+
role: MemberRoleEnum,
|
|
74802
|
+
response: exports_external.string(),
|
|
74803
|
+
sources: exports_external.array(WebSearchResultSchema).default([]),
|
|
74804
|
+
searchQueries: exports_external.array(exports_external.string()).default([]),
|
|
74805
|
+
confidence: exports_external.number().min(0).max(1),
|
|
74806
|
+
areasOfUncertainty: exports_external.array(exports_external.string()).default([]),
|
|
74807
|
+
durationMs: exports_external.number().nonnegative().default(0)
|
|
74808
|
+
});
|
|
74809
|
+
var Round2ResponseSchema = Round1ResponseSchema.extend({
|
|
74810
|
+
disagreementTopics: exports_external.array(exports_external.string()).default([])
|
|
74811
|
+
});
|
|
74812
|
+
var ArgsSchema2 = exports_external.object({
|
|
74813
|
+
question: exports_external.string().min(1).max(8000),
|
|
74814
|
+
mode: exports_external.enum(["general", "spec_review"]).default("general"),
|
|
74815
|
+
members: exports_external.array(exports_external.string()).default([]),
|
|
74816
|
+
round1Responses: exports_external.array(Round1ResponseSchema).min(1),
|
|
74817
|
+
round2Responses: exports_external.array(Round2ResponseSchema).optional(),
|
|
74818
|
+
working_directory: exports_external.string().optional()
|
|
74819
|
+
});
|
|
74820
|
+
function buildModeratorPrompt(question, synthesis) {
|
|
74821
|
+
return [
|
|
74822
|
+
"A multi-model council has deliberated on the following question. Your job is to synthesize",
|
|
74823
|
+
"the council output into a single coherent answer for the user, following the rules in your",
|
|
74824
|
+
"system prompt (lead with consensus, acknowledge persisting disagreement honestly, cite the",
|
|
74825
|
+
"strongest sources, be concise, do not invent claims, do not run new searches).",
|
|
74826
|
+
"",
|
|
74827
|
+
`QUESTION:
|
|
74828
|
+
${question}`,
|
|
74829
|
+
"",
|
|
74830
|
+
"COUNCIL OUTPUT:",
|
|
74831
|
+
synthesis
|
|
74832
|
+
].join(`
|
|
74833
|
+
`);
|
|
74834
|
+
}
|
|
74835
|
+
var convene_general_council = createSwarmTool({
|
|
74836
|
+
description: "Synthesize responses from a multi-model General Council. Accepts parallel member " + "responses (Round 1, optionally Round 2), detects disagreements, and returns " + "consensus points, persisting disagreements, a structured synthesis, and an optional " + "moderator prompt. Architect-only. Config-gated on council.general.enabled.",
|
|
74837
|
+
args: {
|
|
74838
|
+
question: tool.schema.string().min(1).max(8000).describe("The question put to the council, or the spec text to review."),
|
|
74839
|
+
mode: tool.schema.enum(["general", "spec_review"]).optional().describe('"general" for /swarm council; "spec_review" for SPECIFY-COUNCIL-REVIEW gate.'),
|
|
74840
|
+
members: tool.schema.array(tool.schema.string()).optional().describe("Optional list of member IDs convened (for evidence/audit)."),
|
|
74841
|
+
round1Responses: tool.schema.array(tool.schema.object({
|
|
74842
|
+
memberId: tool.schema.string().min(1),
|
|
74843
|
+
model: tool.schema.string().min(1),
|
|
74844
|
+
role: tool.schema.enum([
|
|
74845
|
+
"generalist",
|
|
74846
|
+
"skeptic",
|
|
74847
|
+
"domain_expert",
|
|
74848
|
+
"devil_advocate",
|
|
74849
|
+
"synthesizer"
|
|
74850
|
+
]),
|
|
74851
|
+
response: tool.schema.string(),
|
|
74852
|
+
sources: tool.schema.array(tool.schema.object({
|
|
74853
|
+
title: tool.schema.string(),
|
|
74854
|
+
url: tool.schema.string(),
|
|
74855
|
+
snippet: tool.schema.string(),
|
|
74856
|
+
query: tool.schema.string()
|
|
74857
|
+
})).optional(),
|
|
74858
|
+
searchQueries: tool.schema.array(tool.schema.string()).optional(),
|
|
74859
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
74860
|
+
areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
|
|
74861
|
+
durationMs: tool.schema.number().nonnegative().optional()
|
|
74862
|
+
})).describe("Round 1 member responses (one per council member)."),
|
|
74863
|
+
round2Responses: tool.schema.array(tool.schema.object({
|
|
74864
|
+
memberId: tool.schema.string().min(1),
|
|
74865
|
+
model: tool.schema.string().min(1),
|
|
74866
|
+
role: tool.schema.enum([
|
|
74867
|
+
"generalist",
|
|
74868
|
+
"skeptic",
|
|
74869
|
+
"domain_expert",
|
|
74870
|
+
"devil_advocate",
|
|
74871
|
+
"synthesizer"
|
|
74872
|
+
]),
|
|
74873
|
+
response: tool.schema.string(),
|
|
74874
|
+
sources: tool.schema.array(tool.schema.object({
|
|
74875
|
+
title: tool.schema.string(),
|
|
74876
|
+
url: tool.schema.string(),
|
|
74877
|
+
snippet: tool.schema.string(),
|
|
74878
|
+
query: tool.schema.string()
|
|
74879
|
+
})).optional(),
|
|
74880
|
+
searchQueries: tool.schema.array(tool.schema.string()).optional(),
|
|
74881
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
74882
|
+
areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
|
|
74883
|
+
durationMs: tool.schema.number().nonnegative().optional(),
|
|
74884
|
+
disagreementTopics: tool.schema.array(tool.schema.string()).optional()
|
|
74885
|
+
})).optional().describe("Round 2 deliberation responses (omit when no deliberation has occurred)."),
|
|
74886
|
+
working_directory: tool.schema.string().optional().describe("Project root for config + evidence path resolution.")
|
|
74887
|
+
},
|
|
74888
|
+
execute: async (args2, directory, ctx) => {
|
|
74889
|
+
const parsed = ArgsSchema2.safeParse(args2);
|
|
74890
|
+
if (!parsed.success) {
|
|
74891
|
+
const fail = {
|
|
74892
|
+
success: false,
|
|
74893
|
+
reason: "invalid_args",
|
|
74894
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
74895
|
+
};
|
|
74896
|
+
return JSON.stringify(fail, null, 2);
|
|
74897
|
+
}
|
|
74898
|
+
const input = parsed.data;
|
|
74899
|
+
const dirResult = resolveWorkingDirectory(input.working_directory, directory);
|
|
74900
|
+
if (!dirResult.success) {
|
|
74901
|
+
const fail = {
|
|
74902
|
+
success: false,
|
|
74903
|
+
reason: "invalid_working_directory",
|
|
74904
|
+
message: dirResult.message
|
|
74905
|
+
};
|
|
74906
|
+
return JSON.stringify(fail, null, 2);
|
|
74907
|
+
}
|
|
74908
|
+
const workingDir = dirResult.directory;
|
|
74909
|
+
const config3 = loadPluginConfig(workingDir);
|
|
74910
|
+
const generalConfig = config3.council?.general;
|
|
74911
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
74912
|
+
const fail = {
|
|
74913
|
+
success: false,
|
|
74914
|
+
reason: "council_general_disabled",
|
|
74915
|
+
message: "convene_general_council requires council.general.enabled: true in opencode-swarm.json."
|
|
74916
|
+
};
|
|
74917
|
+
return JSON.stringify(fail, null, 2);
|
|
74918
|
+
}
|
|
74919
|
+
const round1 = input.round1Responses;
|
|
74920
|
+
const round2 = input.round2Responses ?? [];
|
|
74921
|
+
const result = synthesizeGeneralCouncil(input.question, input.mode, round1, round2);
|
|
74922
|
+
const evidenceDir = path73.join(workingDir, ".swarm", "council", "general");
|
|
74923
|
+
const safeTimestamp = result.timestamp.replace(/[:.]/g, "-");
|
|
74924
|
+
const evidenceFile = `${safeTimestamp}-${input.mode}.json`;
|
|
74925
|
+
const evidencePath = path73.join(evidenceDir, evidenceFile);
|
|
74926
|
+
try {
|
|
74927
|
+
await fs59.promises.mkdir(evidenceDir, { recursive: true });
|
|
74928
|
+
await fs59.promises.writeFile(evidencePath, JSON.stringify(result, null, 2));
|
|
74929
|
+
} catch (err2) {
|
|
74930
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
74931
|
+
console.warn(`[convene_general_council] Failed to write evidence to ${evidencePath}: ${message}`);
|
|
74932
|
+
}
|
|
74933
|
+
try {
|
|
74934
|
+
const sessionID = ctx?.sessionID;
|
|
74935
|
+
if (sessionID) {
|
|
74936
|
+
const session = getAgentSession(sessionID);
|
|
74937
|
+
if (session) {
|
|
74938
|
+
pushGeneralCouncilAdvisory(session, result);
|
|
74939
|
+
}
|
|
74940
|
+
}
|
|
74941
|
+
} catch {}
|
|
74942
|
+
const moderatorPrompt = generalConfig.moderator === true ? buildModeratorPrompt(input.question, result.synthesis) : undefined;
|
|
74943
|
+
const ok = {
|
|
74944
|
+
success: true,
|
|
74945
|
+
question: input.question,
|
|
74946
|
+
mode: input.mode,
|
|
74947
|
+
roundsCompleted: round2.length > 0 ? 2 : 1,
|
|
74948
|
+
consensusPoints: result.consensusPoints,
|
|
74949
|
+
disagreementsCount: result.disagreements.length,
|
|
74950
|
+
persistingDisagreements: result.persistingDisagreements,
|
|
74951
|
+
allSourcesCount: result.allSources.length,
|
|
74952
|
+
synthesis: result.synthesis,
|
|
74953
|
+
...moderatorPrompt !== undefined && { moderatorPrompt },
|
|
74954
|
+
evidencePath
|
|
74955
|
+
};
|
|
74956
|
+
return JSON.stringify(ok, null, 2);
|
|
74957
|
+
}
|
|
74958
|
+
});
|
|
74017
74959
|
// src/tools/curator-analyze.ts
|
|
74018
74960
|
init_dist();
|
|
74019
74961
|
init_config();
|
|
@@ -74143,7 +75085,7 @@ var CriteriaItemSchema = exports_external.object({
|
|
|
74143
75085
|
description: exports_external.string().min(10).max(500),
|
|
74144
75086
|
mandatory: exports_external.boolean()
|
|
74145
75087
|
});
|
|
74146
|
-
var
|
|
75088
|
+
var ArgsSchema3 = exports_external.object({
|
|
74147
75089
|
taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format"),
|
|
74148
75090
|
criteria: exports_external.array(CriteriaItemSchema).min(1).max(20),
|
|
74149
75091
|
working_directory: exports_external.string().optional()
|
|
@@ -74160,7 +75102,7 @@ var declare_council_criteria = createSwarmTool({
|
|
|
74160
75102
|
working_directory: tool.schema.string().optional().describe("Explicit project root directory. When provided, .swarm/council/ is resolved relative to this path instead of the plugin context directory.")
|
|
74161
75103
|
},
|
|
74162
75104
|
async execute(args2, directory) {
|
|
74163
|
-
const parsed =
|
|
75105
|
+
const parsed = ArgsSchema3.safeParse(args2);
|
|
74164
75106
|
if (!parsed.success) {
|
|
74165
75107
|
return JSON.stringify({
|
|
74166
75108
|
success: false,
|
|
@@ -74221,8 +75163,8 @@ init_scope_persistence();
|
|
|
74221
75163
|
init_state();
|
|
74222
75164
|
init_task_id();
|
|
74223
75165
|
init_create_tool();
|
|
74224
|
-
import * as
|
|
74225
|
-
import * as
|
|
75166
|
+
import * as fs60 from "fs";
|
|
75167
|
+
import * as path74 from "path";
|
|
74226
75168
|
function validateTaskIdFormat2(taskId) {
|
|
74227
75169
|
return validateTaskIdFormat(taskId);
|
|
74228
75170
|
}
|
|
@@ -74296,8 +75238,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74296
75238
|
};
|
|
74297
75239
|
}
|
|
74298
75240
|
}
|
|
74299
|
-
normalizedDir =
|
|
74300
|
-
const pathParts = normalizedDir.split(
|
|
75241
|
+
normalizedDir = path74.normalize(args2.working_directory);
|
|
75242
|
+
const pathParts = normalizedDir.split(path74.sep);
|
|
74301
75243
|
if (pathParts.includes("..")) {
|
|
74302
75244
|
return {
|
|
74303
75245
|
success: false,
|
|
@@ -74307,11 +75249,11 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74307
75249
|
]
|
|
74308
75250
|
};
|
|
74309
75251
|
}
|
|
74310
|
-
const resolvedDir =
|
|
75252
|
+
const resolvedDir = path74.resolve(normalizedDir);
|
|
74311
75253
|
try {
|
|
74312
|
-
const realPath =
|
|
74313
|
-
const planPath2 =
|
|
74314
|
-
if (!
|
|
75254
|
+
const realPath = fs60.realpathSync(resolvedDir);
|
|
75255
|
+
const planPath2 = path74.join(realPath, ".swarm", "plan.json");
|
|
75256
|
+
if (!fs60.existsSync(planPath2)) {
|
|
74315
75257
|
return {
|
|
74316
75258
|
success: false,
|
|
74317
75259
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -74334,8 +75276,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74334
75276
|
console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
|
|
74335
75277
|
}
|
|
74336
75278
|
const directory = normalizedDir || fallbackDir;
|
|
74337
|
-
const planPath =
|
|
74338
|
-
if (!
|
|
75279
|
+
const planPath = path74.resolve(directory, ".swarm", "plan.json");
|
|
75280
|
+
if (!fs60.existsSync(planPath)) {
|
|
74339
75281
|
return {
|
|
74340
75282
|
success: false,
|
|
74341
75283
|
message: "No plan found",
|
|
@@ -74344,7 +75286,7 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74344
75286
|
}
|
|
74345
75287
|
let planContent;
|
|
74346
75288
|
try {
|
|
74347
|
-
planContent = JSON.parse(
|
|
75289
|
+
planContent = JSON.parse(fs60.readFileSync(planPath, "utf-8"));
|
|
74348
75290
|
} catch {
|
|
74349
75291
|
return {
|
|
74350
75292
|
success: false,
|
|
@@ -74374,8 +75316,8 @@ async function executeDeclareScope(args2, fallbackDir) {
|
|
|
74374
75316
|
const normalizeErrors = [];
|
|
74375
75317
|
const dir = normalizedDir || fallbackDir || process.cwd();
|
|
74376
75318
|
const mergedFiles = rawMergedFiles.map((file3) => {
|
|
74377
|
-
if (
|
|
74378
|
-
const relativePath =
|
|
75319
|
+
if (path74.isAbsolute(file3)) {
|
|
75320
|
+
const relativePath = path74.relative(dir, file3).replace(/\\/g, "/");
|
|
74379
75321
|
if (relativePath.startsWith("..")) {
|
|
74380
75322
|
normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
|
|
74381
75323
|
return file3;
|
|
@@ -74435,8 +75377,8 @@ var declare_scope = createSwarmTool({
|
|
|
74435
75377
|
// src/tools/diff.ts
|
|
74436
75378
|
init_dist();
|
|
74437
75379
|
import * as child_process7 from "child_process";
|
|
74438
|
-
import * as
|
|
74439
|
-
import * as
|
|
75380
|
+
import * as fs61 from "fs";
|
|
75381
|
+
import * as path75 from "path";
|
|
74440
75382
|
init_create_tool();
|
|
74441
75383
|
var MAX_DIFF_LINES = 500;
|
|
74442
75384
|
var DIFF_TIMEOUT_MS = 30000;
|
|
@@ -74465,20 +75407,20 @@ function validateBase(base) {
|
|
|
74465
75407
|
function validatePaths(paths) {
|
|
74466
75408
|
if (!paths)
|
|
74467
75409
|
return null;
|
|
74468
|
-
for (const
|
|
74469
|
-
if (!
|
|
75410
|
+
for (const path76 of paths) {
|
|
75411
|
+
if (!path76 || path76.length === 0) {
|
|
74470
75412
|
return "empty path not allowed";
|
|
74471
75413
|
}
|
|
74472
|
-
if (
|
|
75414
|
+
if (path76.length > MAX_PATH_LENGTH) {
|
|
74473
75415
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
74474
75416
|
}
|
|
74475
|
-
if (SHELL_METACHARACTERS2.test(
|
|
75417
|
+
if (SHELL_METACHARACTERS2.test(path76)) {
|
|
74476
75418
|
return "path contains shell metacharacters";
|
|
74477
75419
|
}
|
|
74478
|
-
if (
|
|
75420
|
+
if (path76.startsWith("-")) {
|
|
74479
75421
|
return 'path cannot start with "-" (option-like arguments not allowed)';
|
|
74480
75422
|
}
|
|
74481
|
-
if (CONTROL_CHAR_PATTERN2.test(
|
|
75423
|
+
if (CONTROL_CHAR_PATTERN2.test(path76)) {
|
|
74482
75424
|
return "path contains control characters";
|
|
74483
75425
|
}
|
|
74484
75426
|
}
|
|
@@ -74584,8 +75526,8 @@ var diff = createSwarmTool({
|
|
|
74584
75526
|
if (parts2.length >= 3) {
|
|
74585
75527
|
const additions = parseInt(parts2[0], 10) || 0;
|
|
74586
75528
|
const deletions = parseInt(parts2[1], 10) || 0;
|
|
74587
|
-
const
|
|
74588
|
-
files.push({ path:
|
|
75529
|
+
const path76 = parts2[2];
|
|
75530
|
+
files.push({ path: path76, additions, deletions });
|
|
74589
75531
|
}
|
|
74590
75532
|
}
|
|
74591
75533
|
const contractChanges = [];
|
|
@@ -74625,7 +75567,7 @@ var diff = createSwarmTool({
|
|
|
74625
75567
|
} else if (base === "unstaged") {
|
|
74626
75568
|
const oldRef = `:${file3.path}`;
|
|
74627
75569
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
74628
|
-
newContent =
|
|
75570
|
+
newContent = fs61.readFileSync(path75.join(directory, file3.path), "utf-8");
|
|
74629
75571
|
} else {
|
|
74630
75572
|
const oldRef = `${base}:${file3.path}`;
|
|
74631
75573
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
@@ -74699,8 +75641,8 @@ var diff = createSwarmTool({
|
|
|
74699
75641
|
// src/tools/diff-summary.ts
|
|
74700
75642
|
init_dist();
|
|
74701
75643
|
import * as child_process8 from "child_process";
|
|
74702
|
-
import * as
|
|
74703
|
-
import * as
|
|
75644
|
+
import * as fs62 from "fs";
|
|
75645
|
+
import * as path76 from "path";
|
|
74704
75646
|
init_create_tool();
|
|
74705
75647
|
var diff_summary = createSwarmTool({
|
|
74706
75648
|
description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
|
|
@@ -74748,7 +75690,7 @@ var diff_summary = createSwarmTool({
|
|
|
74748
75690
|
}
|
|
74749
75691
|
try {
|
|
74750
75692
|
let oldContent;
|
|
74751
|
-
const newContent =
|
|
75693
|
+
const newContent = fs62.readFileSync(path76.join(workingDir, filePath), "utf-8");
|
|
74752
75694
|
if (fileExistsInHead) {
|
|
74753
75695
|
oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
|
|
74754
75696
|
encoding: "utf-8",
|
|
@@ -74975,8 +75917,8 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
74975
75917
|
init_dist();
|
|
74976
75918
|
init_create_tool();
|
|
74977
75919
|
init_path_security();
|
|
74978
|
-
import * as
|
|
74979
|
-
import * as
|
|
75920
|
+
import * as fs63 from "fs";
|
|
75921
|
+
import * as path77 from "path";
|
|
74980
75922
|
var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
|
|
74981
75923
|
var MAX_EVIDENCE_FILES = 1000;
|
|
74982
75924
|
var EVIDENCE_DIR3 = ".swarm/evidence";
|
|
@@ -75003,9 +75945,9 @@ function validateRequiredTypes(input) {
|
|
|
75003
75945
|
return null;
|
|
75004
75946
|
}
|
|
75005
75947
|
function isPathWithinSwarm2(filePath, cwd) {
|
|
75006
|
-
const normalizedCwd =
|
|
75007
|
-
const swarmPath =
|
|
75008
|
-
const normalizedPath =
|
|
75948
|
+
const normalizedCwd = path77.resolve(cwd);
|
|
75949
|
+
const swarmPath = path77.join(normalizedCwd, ".swarm");
|
|
75950
|
+
const normalizedPath = path77.resolve(filePath);
|
|
75009
75951
|
return normalizedPath.startsWith(swarmPath);
|
|
75010
75952
|
}
|
|
75011
75953
|
function parseCompletedTasks(planContent) {
|
|
@@ -75021,12 +75963,12 @@ function parseCompletedTasks(planContent) {
|
|
|
75021
75963
|
}
|
|
75022
75964
|
function readEvidenceFiles(evidenceDir, _cwd) {
|
|
75023
75965
|
const evidence = [];
|
|
75024
|
-
if (!
|
|
75966
|
+
if (!fs63.existsSync(evidenceDir) || !fs63.statSync(evidenceDir).isDirectory()) {
|
|
75025
75967
|
return evidence;
|
|
75026
75968
|
}
|
|
75027
75969
|
let files;
|
|
75028
75970
|
try {
|
|
75029
|
-
files =
|
|
75971
|
+
files = fs63.readdirSync(evidenceDir);
|
|
75030
75972
|
} catch {
|
|
75031
75973
|
return evidence;
|
|
75032
75974
|
}
|
|
@@ -75035,14 +75977,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75035
75977
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
75036
75978
|
continue;
|
|
75037
75979
|
}
|
|
75038
|
-
const filePath =
|
|
75980
|
+
const filePath = path77.join(evidenceDir, filename);
|
|
75039
75981
|
try {
|
|
75040
|
-
const resolvedPath =
|
|
75041
|
-
const evidenceDirResolved =
|
|
75982
|
+
const resolvedPath = path77.resolve(filePath);
|
|
75983
|
+
const evidenceDirResolved = path77.resolve(evidenceDir);
|
|
75042
75984
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
75043
75985
|
continue;
|
|
75044
75986
|
}
|
|
75045
|
-
const stat4 =
|
|
75987
|
+
const stat4 = fs63.lstatSync(filePath);
|
|
75046
75988
|
if (!stat4.isFile()) {
|
|
75047
75989
|
continue;
|
|
75048
75990
|
}
|
|
@@ -75051,7 +75993,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75051
75993
|
}
|
|
75052
75994
|
let fileStat;
|
|
75053
75995
|
try {
|
|
75054
|
-
fileStat =
|
|
75996
|
+
fileStat = fs63.statSync(filePath);
|
|
75055
75997
|
if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
|
|
75056
75998
|
continue;
|
|
75057
75999
|
}
|
|
@@ -75060,7 +76002,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
|
|
|
75060
76002
|
}
|
|
75061
76003
|
let content;
|
|
75062
76004
|
try {
|
|
75063
|
-
content =
|
|
76005
|
+
content = fs63.readFileSync(filePath, "utf-8");
|
|
75064
76006
|
} catch {
|
|
75065
76007
|
continue;
|
|
75066
76008
|
}
|
|
@@ -75156,7 +76098,7 @@ var evidence_check = createSwarmTool({
|
|
|
75156
76098
|
return JSON.stringify(errorResult, null, 2);
|
|
75157
76099
|
}
|
|
75158
76100
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
|
|
75159
|
-
const planPath =
|
|
76101
|
+
const planPath = path77.join(cwd, PLAN_FILE);
|
|
75160
76102
|
if (!isPathWithinSwarm2(planPath, cwd)) {
|
|
75161
76103
|
const errorResult = {
|
|
75162
76104
|
error: "plan file path validation failed",
|
|
@@ -75170,7 +76112,7 @@ var evidence_check = createSwarmTool({
|
|
|
75170
76112
|
}
|
|
75171
76113
|
let planContent;
|
|
75172
76114
|
try {
|
|
75173
|
-
planContent =
|
|
76115
|
+
planContent = fs63.readFileSync(planPath, "utf-8");
|
|
75174
76116
|
} catch {
|
|
75175
76117
|
const result2 = {
|
|
75176
76118
|
message: "No completed tasks found in plan.",
|
|
@@ -75188,7 +76130,7 @@ var evidence_check = createSwarmTool({
|
|
|
75188
76130
|
};
|
|
75189
76131
|
return JSON.stringify(result2, null, 2);
|
|
75190
76132
|
}
|
|
75191
|
-
const evidenceDir =
|
|
76133
|
+
const evidenceDir = path77.join(cwd, EVIDENCE_DIR3);
|
|
75192
76134
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
75193
76135
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
75194
76136
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -75205,8 +76147,8 @@ var evidence_check = createSwarmTool({
|
|
|
75205
76147
|
// src/tools/file-extractor.ts
|
|
75206
76148
|
init_tool();
|
|
75207
76149
|
init_create_tool();
|
|
75208
|
-
import * as
|
|
75209
|
-
import * as
|
|
76150
|
+
import * as fs64 from "fs";
|
|
76151
|
+
import * as path78 from "path";
|
|
75210
76152
|
var EXT_MAP = {
|
|
75211
76153
|
python: ".py",
|
|
75212
76154
|
py: ".py",
|
|
@@ -75268,8 +76210,8 @@ var extract_code_blocks = createSwarmTool({
|
|
|
75268
76210
|
execute: async (args2, directory) => {
|
|
75269
76211
|
const { content, output_dir, prefix } = args2;
|
|
75270
76212
|
const targetDir = output_dir || directory;
|
|
75271
|
-
if (!
|
|
75272
|
-
|
|
76213
|
+
if (!fs64.existsSync(targetDir)) {
|
|
76214
|
+
fs64.mkdirSync(targetDir, { recursive: true });
|
|
75273
76215
|
}
|
|
75274
76216
|
if (!content) {
|
|
75275
76217
|
return "Error: content is required";
|
|
@@ -75287,16 +76229,16 @@ var extract_code_blocks = createSwarmTool({
|
|
|
75287
76229
|
if (prefix) {
|
|
75288
76230
|
filename = `${prefix}_${filename}`;
|
|
75289
76231
|
}
|
|
75290
|
-
let filepath =
|
|
75291
|
-
const base =
|
|
75292
|
-
const ext =
|
|
76232
|
+
let filepath = path78.join(targetDir, filename);
|
|
76233
|
+
const base = path78.basename(filepath, path78.extname(filepath));
|
|
76234
|
+
const ext = path78.extname(filepath);
|
|
75293
76235
|
let counter = 1;
|
|
75294
|
-
while (
|
|
75295
|
-
filepath =
|
|
76236
|
+
while (fs64.existsSync(filepath)) {
|
|
76237
|
+
filepath = path78.join(targetDir, `${base}_${counter}${ext}`);
|
|
75296
76238
|
counter++;
|
|
75297
76239
|
}
|
|
75298
76240
|
try {
|
|
75299
|
-
|
|
76241
|
+
fs64.writeFileSync(filepath, code.trim(), "utf-8");
|
|
75300
76242
|
savedFiles.push(filepath);
|
|
75301
76243
|
} catch (error93) {
|
|
75302
76244
|
errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
|
|
@@ -75555,8 +76497,8 @@ var gitingest = createSwarmTool({
|
|
|
75555
76497
|
init_dist();
|
|
75556
76498
|
init_create_tool();
|
|
75557
76499
|
init_path_security();
|
|
75558
|
-
import * as
|
|
75559
|
-
import * as
|
|
76500
|
+
import * as fs65 from "fs";
|
|
76501
|
+
import * as path79 from "path";
|
|
75560
76502
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
75561
76503
|
var MAX_SYMBOL_LENGTH = 256;
|
|
75562
76504
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
@@ -75604,7 +76546,7 @@ function validateSymbolInput(symbol3) {
|
|
|
75604
76546
|
return null;
|
|
75605
76547
|
}
|
|
75606
76548
|
function isBinaryFile2(filePath, buffer) {
|
|
75607
|
-
const ext =
|
|
76549
|
+
const ext = path79.extname(filePath).toLowerCase();
|
|
75608
76550
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
75609
76551
|
return false;
|
|
75610
76552
|
}
|
|
@@ -75628,15 +76570,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
75628
76570
|
const imports = [];
|
|
75629
76571
|
let _resolvedTarget;
|
|
75630
76572
|
try {
|
|
75631
|
-
_resolvedTarget =
|
|
76573
|
+
_resolvedTarget = path79.resolve(targetFile);
|
|
75632
76574
|
} catch {
|
|
75633
76575
|
_resolvedTarget = targetFile;
|
|
75634
76576
|
}
|
|
75635
|
-
const targetBasename =
|
|
76577
|
+
const targetBasename = path79.basename(targetFile, path79.extname(targetFile));
|
|
75636
76578
|
const targetWithExt = targetFile;
|
|
75637
76579
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
75638
|
-
const normalizedTargetWithExt =
|
|
75639
|
-
const normalizedTargetWithoutExt =
|
|
76580
|
+
const normalizedTargetWithExt = path79.normalize(targetWithExt).replace(/\\/g, "/");
|
|
76581
|
+
const normalizedTargetWithoutExt = path79.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
75640
76582
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
75641
76583
|
for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
|
|
75642
76584
|
const modulePath = match[1] || match[2] || match[3];
|
|
@@ -75659,9 +76601,9 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
75659
76601
|
}
|
|
75660
76602
|
const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
75661
76603
|
let isMatch = false;
|
|
75662
|
-
const _targetDir =
|
|
75663
|
-
const targetExt =
|
|
75664
|
-
const targetBasenameNoExt =
|
|
76604
|
+
const _targetDir = path79.dirname(targetFile);
|
|
76605
|
+
const targetExt = path79.extname(targetFile);
|
|
76606
|
+
const targetBasenameNoExt = path79.basename(targetFile, targetExt);
|
|
75665
76607
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
75666
76608
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
75667
76609
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
@@ -75718,7 +76660,7 @@ var SKIP_DIRECTORIES4 = new Set([
|
|
|
75718
76660
|
function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
|
|
75719
76661
|
let entries;
|
|
75720
76662
|
try {
|
|
75721
|
-
entries =
|
|
76663
|
+
entries = fs65.readdirSync(dir);
|
|
75722
76664
|
} catch (e) {
|
|
75723
76665
|
stats.fileErrors.push({
|
|
75724
76666
|
path: dir,
|
|
@@ -75729,13 +76671,13 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
75729
76671
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
75730
76672
|
for (const entry of entries) {
|
|
75731
76673
|
if (SKIP_DIRECTORIES4.has(entry)) {
|
|
75732
|
-
stats.skippedDirs.push(
|
|
76674
|
+
stats.skippedDirs.push(path79.join(dir, entry));
|
|
75733
76675
|
continue;
|
|
75734
76676
|
}
|
|
75735
|
-
const fullPath =
|
|
76677
|
+
const fullPath = path79.join(dir, entry);
|
|
75736
76678
|
let stat4;
|
|
75737
76679
|
try {
|
|
75738
|
-
stat4 =
|
|
76680
|
+
stat4 = fs65.statSync(fullPath);
|
|
75739
76681
|
} catch (e) {
|
|
75740
76682
|
stats.fileErrors.push({
|
|
75741
76683
|
path: fullPath,
|
|
@@ -75746,7 +76688,7 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
75746
76688
|
if (stat4.isDirectory()) {
|
|
75747
76689
|
findSourceFiles3(fullPath, files, stats);
|
|
75748
76690
|
} else if (stat4.isFile()) {
|
|
75749
|
-
const ext =
|
|
76691
|
+
const ext = path79.extname(fullPath).toLowerCase();
|
|
75750
76692
|
if (SUPPORTED_EXTENSIONS3.includes(ext)) {
|
|
75751
76693
|
files.push(fullPath);
|
|
75752
76694
|
}
|
|
@@ -75803,8 +76745,8 @@ var imports = createSwarmTool({
|
|
|
75803
76745
|
return JSON.stringify(errorResult, null, 2);
|
|
75804
76746
|
}
|
|
75805
76747
|
try {
|
|
75806
|
-
const targetFile =
|
|
75807
|
-
if (!
|
|
76748
|
+
const targetFile = path79.resolve(file3);
|
|
76749
|
+
if (!fs65.existsSync(targetFile)) {
|
|
75808
76750
|
const errorResult = {
|
|
75809
76751
|
error: `target file not found: ${file3}`,
|
|
75810
76752
|
target: file3,
|
|
@@ -75814,7 +76756,7 @@ var imports = createSwarmTool({
|
|
|
75814
76756
|
};
|
|
75815
76757
|
return JSON.stringify(errorResult, null, 2);
|
|
75816
76758
|
}
|
|
75817
|
-
const targetStat =
|
|
76759
|
+
const targetStat = fs65.statSync(targetFile);
|
|
75818
76760
|
if (!targetStat.isFile()) {
|
|
75819
76761
|
const errorResult = {
|
|
75820
76762
|
error: "target must be a file, not a directory",
|
|
@@ -75825,7 +76767,7 @@ var imports = createSwarmTool({
|
|
|
75825
76767
|
};
|
|
75826
76768
|
return JSON.stringify(errorResult, null, 2);
|
|
75827
76769
|
}
|
|
75828
|
-
const baseDir =
|
|
76770
|
+
const baseDir = path79.dirname(targetFile);
|
|
75829
76771
|
const scanStats = {
|
|
75830
76772
|
skippedDirs: [],
|
|
75831
76773
|
skippedFiles: 0,
|
|
@@ -75840,12 +76782,12 @@ var imports = createSwarmTool({
|
|
|
75840
76782
|
if (consumers.length >= MAX_CONSUMERS)
|
|
75841
76783
|
break;
|
|
75842
76784
|
try {
|
|
75843
|
-
const stat4 =
|
|
76785
|
+
const stat4 = fs65.statSync(filePath);
|
|
75844
76786
|
if (stat4.size > MAX_FILE_SIZE_BYTES7) {
|
|
75845
76787
|
skippedFileCount++;
|
|
75846
76788
|
continue;
|
|
75847
76789
|
}
|
|
75848
|
-
const buffer =
|
|
76790
|
+
const buffer = fs65.readFileSync(filePath);
|
|
75849
76791
|
if (isBinaryFile2(filePath, buffer)) {
|
|
75850
76792
|
skippedFileCount++;
|
|
75851
76793
|
continue;
|
|
@@ -76357,8 +77299,8 @@ init_schema();
|
|
|
76357
77299
|
init_qa_gate_profile();
|
|
76358
77300
|
init_manager2();
|
|
76359
77301
|
init_curator();
|
|
76360
|
-
import * as
|
|
76361
|
-
import * as
|
|
77302
|
+
import * as fs66 from "fs";
|
|
77303
|
+
import * as path80 from "path";
|
|
76362
77304
|
init_knowledge_curator();
|
|
76363
77305
|
init_knowledge_reader();
|
|
76364
77306
|
init_knowledge_store();
|
|
@@ -76589,11 +77531,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76589
77531
|
safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
|
|
76590
77532
|
}
|
|
76591
77533
|
try {
|
|
76592
|
-
const driftEvidencePath =
|
|
77534
|
+
const driftEvidencePath = path80.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
|
|
76593
77535
|
let driftVerdictFound = false;
|
|
76594
77536
|
let driftVerdictApproved = false;
|
|
76595
77537
|
try {
|
|
76596
|
-
const driftEvidenceContent =
|
|
77538
|
+
const driftEvidenceContent = fs66.readFileSync(driftEvidencePath, "utf-8");
|
|
76597
77539
|
const driftEvidence = JSON.parse(driftEvidenceContent);
|
|
76598
77540
|
const entries = driftEvidence.entries ?? [];
|
|
76599
77541
|
for (const entry of entries) {
|
|
@@ -76623,14 +77565,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76623
77565
|
driftVerdictFound = false;
|
|
76624
77566
|
}
|
|
76625
77567
|
if (!driftVerdictFound) {
|
|
76626
|
-
const specPath =
|
|
76627
|
-
const specExists =
|
|
77568
|
+
const specPath = path80.join(dir, ".swarm", "spec.md");
|
|
77569
|
+
const specExists = fs66.existsSync(specPath);
|
|
76628
77570
|
if (!specExists) {
|
|
76629
77571
|
let incompleteTaskCount = 0;
|
|
76630
77572
|
let planPhaseFound = false;
|
|
76631
77573
|
try {
|
|
76632
77574
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76633
|
-
const planRaw =
|
|
77575
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76634
77576
|
const plan = JSON.parse(planRaw);
|
|
76635
77577
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76636
77578
|
if (targetPhase) {
|
|
@@ -76681,11 +77623,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76681
77623
|
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
76682
77624
|
const effective = getEffectiveGates(profile, overrides);
|
|
76683
77625
|
if (effective.hallucination_guard === true) {
|
|
76684
|
-
const hgPath =
|
|
77626
|
+
const hgPath = path80.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
|
|
76685
77627
|
let hgVerdictFound = false;
|
|
76686
77628
|
let hgVerdictApproved = false;
|
|
76687
77629
|
try {
|
|
76688
|
-
const hgContent =
|
|
77630
|
+
const hgContent = fs66.readFileSync(hgPath, "utf-8");
|
|
76689
77631
|
const hgBundle = JSON.parse(hgContent);
|
|
76690
77632
|
for (const entry of hgBundle.entries ?? []) {
|
|
76691
77633
|
if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
|
|
@@ -76753,11 +77695,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76753
77695
|
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
76754
77696
|
const effective = getEffectiveGates(profile, overrides);
|
|
76755
77697
|
if (effective.mutation_test === true) {
|
|
76756
|
-
const mgPath =
|
|
77698
|
+
const mgPath = path80.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
|
|
76757
77699
|
let mgVerdictFound = false;
|
|
76758
77700
|
let mgVerdict;
|
|
76759
77701
|
try {
|
|
76760
|
-
const mgContent =
|
|
77702
|
+
const mgContent = fs66.readFileSync(mgPath, "utf-8");
|
|
76761
77703
|
const mgBundle = JSON.parse(mgContent);
|
|
76762
77704
|
for (const entry of mgBundle.entries ?? []) {
|
|
76763
77705
|
if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
|
|
@@ -76825,7 +77767,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76825
77767
|
}
|
|
76826
77768
|
if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
|
|
76827
77769
|
try {
|
|
76828
|
-
const projectName =
|
|
77770
|
+
const projectName = path80.basename(dir);
|
|
76829
77771
|
const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
|
|
76830
77772
|
if (curationResult) {
|
|
76831
77773
|
const sessionState = swarmState.agentSessions.get(sessionID);
|
|
@@ -76905,7 +77847,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76905
77847
|
let phaseRequiredAgents;
|
|
76906
77848
|
try {
|
|
76907
77849
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76908
|
-
const planRaw =
|
|
77850
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76909
77851
|
const plan = JSON.parse(planRaw);
|
|
76910
77852
|
const phaseObj = plan.phases.find((p) => p.id === phase);
|
|
76911
77853
|
phaseRequiredAgents = phaseObj?.required_agents;
|
|
@@ -76920,7 +77862,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76920
77862
|
if (agentsMissing.length > 0) {
|
|
76921
77863
|
try {
|
|
76922
77864
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76923
|
-
const planRaw =
|
|
77865
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76924
77866
|
const plan = JSON.parse(planRaw);
|
|
76925
77867
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76926
77868
|
if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
|
|
@@ -76960,7 +77902,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
76960
77902
|
if (phaseCompleteConfig.regression_sweep?.enforce) {
|
|
76961
77903
|
try {
|
|
76962
77904
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
76963
|
-
const planRaw =
|
|
77905
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
76964
77906
|
const plan = JSON.parse(planRaw);
|
|
76965
77907
|
const targetPhase = plan.phases.find((p) => p.id === phase);
|
|
76966
77908
|
if (targetPhase) {
|
|
@@ -77014,7 +77956,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77014
77956
|
}
|
|
77015
77957
|
try {
|
|
77016
77958
|
const eventsPath = validateSwarmPath(dir, "events.jsonl");
|
|
77017
|
-
|
|
77959
|
+
fs66.appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
77018
77960
|
`, "utf-8");
|
|
77019
77961
|
} catch (writeError) {
|
|
77020
77962
|
warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
@@ -77089,12 +78031,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77089
78031
|
warnings.push(`Warning: failed to update plan.json phase status`);
|
|
77090
78032
|
try {
|
|
77091
78033
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
77092
|
-
const planRaw =
|
|
78034
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
77093
78035
|
const plan2 = JSON.parse(planRaw);
|
|
77094
78036
|
const phaseObj = plan2.phases.find((p) => p.id === phase);
|
|
77095
78037
|
if (phaseObj) {
|
|
77096
78038
|
phaseObj.status = "complete";
|
|
77097
|
-
|
|
78039
|
+
fs66.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
|
|
77098
78040
|
}
|
|
77099
78041
|
} catch {}
|
|
77100
78042
|
} else if (plan) {
|
|
@@ -77131,12 +78073,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
77131
78073
|
warnings.push(`Warning: failed to update plan.json phase status`);
|
|
77132
78074
|
try {
|
|
77133
78075
|
const planPath = validateSwarmPath(dir, "plan.json");
|
|
77134
|
-
const planRaw =
|
|
78076
|
+
const planRaw = fs66.readFileSync(planPath, "utf-8");
|
|
77135
78077
|
const plan = JSON.parse(planRaw);
|
|
77136
78078
|
const phaseObj = plan.phases.find((p) => p.id === phase);
|
|
77137
78079
|
if (phaseObj) {
|
|
77138
78080
|
phaseObj.status = "complete";
|
|
77139
|
-
|
|
78081
|
+
fs66.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
77140
78082
|
}
|
|
77141
78083
|
} catch {}
|
|
77142
78084
|
}
|
|
@@ -77193,8 +78135,8 @@ init_dist();
|
|
|
77193
78135
|
init_discovery();
|
|
77194
78136
|
init_utils();
|
|
77195
78137
|
init_create_tool();
|
|
77196
|
-
import * as
|
|
77197
|
-
import * as
|
|
78138
|
+
import * as fs67 from "fs";
|
|
78139
|
+
import * as path81 from "path";
|
|
77198
78140
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
77199
78141
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
77200
78142
|
function isValidEcosystem(value) {
|
|
@@ -77222,31 +78164,31 @@ function validateArgs3(args2) {
|
|
|
77222
78164
|
function detectEcosystems(directory) {
|
|
77223
78165
|
const ecosystems = [];
|
|
77224
78166
|
const cwd = directory;
|
|
77225
|
-
if (
|
|
78167
|
+
if (fs67.existsSync(path81.join(cwd, "package.json"))) {
|
|
77226
78168
|
ecosystems.push("npm");
|
|
77227
78169
|
}
|
|
77228
|
-
if (
|
|
78170
|
+
if (fs67.existsSync(path81.join(cwd, "pyproject.toml")) || fs67.existsSync(path81.join(cwd, "requirements.txt"))) {
|
|
77229
78171
|
ecosystems.push("pip");
|
|
77230
78172
|
}
|
|
77231
|
-
if (
|
|
78173
|
+
if (fs67.existsSync(path81.join(cwd, "Cargo.toml"))) {
|
|
77232
78174
|
ecosystems.push("cargo");
|
|
77233
78175
|
}
|
|
77234
|
-
if (
|
|
78176
|
+
if (fs67.existsSync(path81.join(cwd, "go.mod"))) {
|
|
77235
78177
|
ecosystems.push("go");
|
|
77236
78178
|
}
|
|
77237
78179
|
try {
|
|
77238
|
-
const files =
|
|
78180
|
+
const files = fs67.readdirSync(cwd);
|
|
77239
78181
|
if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
|
|
77240
78182
|
ecosystems.push("dotnet");
|
|
77241
78183
|
}
|
|
77242
78184
|
} catch {}
|
|
77243
|
-
if (
|
|
78185
|
+
if (fs67.existsSync(path81.join(cwd, "Gemfile")) || fs67.existsSync(path81.join(cwd, "Gemfile.lock"))) {
|
|
77244
78186
|
ecosystems.push("ruby");
|
|
77245
78187
|
}
|
|
77246
|
-
if (
|
|
78188
|
+
if (fs67.existsSync(path81.join(cwd, "pubspec.yaml"))) {
|
|
77247
78189
|
ecosystems.push("dart");
|
|
77248
78190
|
}
|
|
77249
|
-
if (
|
|
78191
|
+
if (fs67.existsSync(path81.join(cwd, "composer.lock"))) {
|
|
77250
78192
|
ecosystems.push("composer");
|
|
77251
78193
|
}
|
|
77252
78194
|
return ecosystems;
|
|
@@ -78405,8 +79347,8 @@ var pkg_audit = createSwarmTool({
|
|
|
78405
79347
|
// src/tools/placeholder-scan.ts
|
|
78406
79348
|
init_dist();
|
|
78407
79349
|
init_manager2();
|
|
78408
|
-
import * as
|
|
78409
|
-
import * as
|
|
79350
|
+
import * as fs68 from "fs";
|
|
79351
|
+
import * as path82 from "path";
|
|
78410
79352
|
init_utils();
|
|
78411
79353
|
init_create_tool();
|
|
78412
79354
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
@@ -78529,7 +79471,7 @@ function isScaffoldFile(filePath) {
|
|
|
78529
79471
|
if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
|
|
78530
79472
|
return true;
|
|
78531
79473
|
}
|
|
78532
|
-
const filename =
|
|
79474
|
+
const filename = path82.basename(filePath);
|
|
78533
79475
|
if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
|
|
78534
79476
|
return true;
|
|
78535
79477
|
}
|
|
@@ -78546,7 +79488,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
|
|
|
78546
79488
|
if (regex.test(normalizedPath)) {
|
|
78547
79489
|
return true;
|
|
78548
79490
|
}
|
|
78549
|
-
const filename =
|
|
79491
|
+
const filename = path82.basename(filePath);
|
|
78550
79492
|
const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
78551
79493
|
if (filenameRegex.test(filename)) {
|
|
78552
79494
|
return true;
|
|
@@ -78555,7 +79497,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
|
|
|
78555
79497
|
return false;
|
|
78556
79498
|
}
|
|
78557
79499
|
function isParserSupported(filePath) {
|
|
78558
|
-
const ext =
|
|
79500
|
+
const ext = path82.extname(filePath).toLowerCase();
|
|
78559
79501
|
return SUPPORTED_PARSER_EXTENSIONS.has(ext);
|
|
78560
79502
|
}
|
|
78561
79503
|
function isPlanFile(filePath) {
|
|
@@ -78802,28 +79744,28 @@ async function placeholderScan(input, directory) {
|
|
|
78802
79744
|
let filesScanned = 0;
|
|
78803
79745
|
const filesWithFindings = new Set;
|
|
78804
79746
|
for (const filePath of changed_files) {
|
|
78805
|
-
const fullPath =
|
|
78806
|
-
const resolvedDirectory =
|
|
78807
|
-
if (!fullPath.startsWith(resolvedDirectory +
|
|
79747
|
+
const fullPath = path82.isAbsolute(filePath) ? filePath : path82.resolve(directory, filePath);
|
|
79748
|
+
const resolvedDirectory = path82.resolve(directory);
|
|
79749
|
+
if (!fullPath.startsWith(resolvedDirectory + path82.sep) && fullPath !== resolvedDirectory) {
|
|
78808
79750
|
continue;
|
|
78809
79751
|
}
|
|
78810
|
-
if (!
|
|
79752
|
+
if (!fs68.existsSync(fullPath)) {
|
|
78811
79753
|
continue;
|
|
78812
79754
|
}
|
|
78813
79755
|
if (isAllowedByGlobs(filePath, allow_globs)) {
|
|
78814
79756
|
continue;
|
|
78815
79757
|
}
|
|
78816
|
-
const relativeFilePath =
|
|
79758
|
+
const relativeFilePath = path82.relative(directory, fullPath).replace(/\\/g, "/");
|
|
78817
79759
|
if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
|
|
78818
79760
|
continue;
|
|
78819
79761
|
}
|
|
78820
79762
|
let content;
|
|
78821
79763
|
try {
|
|
78822
|
-
const stat4 =
|
|
79764
|
+
const stat4 = fs68.statSync(fullPath);
|
|
78823
79765
|
if (stat4.size > MAX_FILE_SIZE) {
|
|
78824
79766
|
continue;
|
|
78825
79767
|
}
|
|
78826
|
-
content =
|
|
79768
|
+
content = fs68.readFileSync(fullPath, "utf-8");
|
|
78827
79769
|
} catch {
|
|
78828
79770
|
continue;
|
|
78829
79771
|
}
|
|
@@ -78885,8 +79827,8 @@ var placeholder_scan = createSwarmTool({
|
|
|
78885
79827
|
});
|
|
78886
79828
|
// src/tools/pre-check-batch.ts
|
|
78887
79829
|
init_dist();
|
|
78888
|
-
import * as
|
|
78889
|
-
import * as
|
|
79830
|
+
import * as fs71 from "fs";
|
|
79831
|
+
import * as path85 from "path";
|
|
78890
79832
|
init_manager2();
|
|
78891
79833
|
init_utils();
|
|
78892
79834
|
init_create_tool();
|
|
@@ -79021,8 +79963,8 @@ var quality_budget = createSwarmTool({
|
|
|
79021
79963
|
init_dist();
|
|
79022
79964
|
init_manager2();
|
|
79023
79965
|
init_detector();
|
|
79024
|
-
import * as
|
|
79025
|
-
import * as
|
|
79966
|
+
import * as fs70 from "fs";
|
|
79967
|
+
import * as path84 from "path";
|
|
79026
79968
|
import { extname as extname18 } from "path";
|
|
79027
79969
|
|
|
79028
79970
|
// src/sast/rules/c.ts
|
|
@@ -79915,25 +80857,25 @@ init_create_tool();
|
|
|
79915
80857
|
// src/tools/sast-baseline.ts
|
|
79916
80858
|
init_utils2();
|
|
79917
80859
|
import * as crypto8 from "crypto";
|
|
79918
|
-
import * as
|
|
79919
|
-
import * as
|
|
80860
|
+
import * as fs69 from "fs";
|
|
80861
|
+
import * as path83 from "path";
|
|
79920
80862
|
var BASELINE_SCHEMA_VERSION = "1.0.0";
|
|
79921
80863
|
var MAX_BASELINE_FINDINGS = 2000;
|
|
79922
80864
|
var MAX_BASELINE_BYTES = 2 * 1048576;
|
|
79923
80865
|
var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
|
|
79924
80866
|
function normalizeFindingPath(directory, file3) {
|
|
79925
|
-
const resolved =
|
|
79926
|
-
const rel =
|
|
80867
|
+
const resolved = path83.isAbsolute(file3) ? file3 : path83.resolve(directory, file3);
|
|
80868
|
+
const rel = path83.relative(path83.resolve(directory), resolved);
|
|
79927
80869
|
return rel.replace(/\\/g, "/");
|
|
79928
80870
|
}
|
|
79929
80871
|
function baselineRelPath(phase) {
|
|
79930
|
-
return
|
|
80872
|
+
return path83.join("evidence", String(phase), "sast-baseline.json");
|
|
79931
80873
|
}
|
|
79932
80874
|
function tempRelPath(phase) {
|
|
79933
|
-
return
|
|
80875
|
+
return path83.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
|
|
79934
80876
|
}
|
|
79935
80877
|
function lockRelPath(phase) {
|
|
79936
|
-
return
|
|
80878
|
+
return path83.join("evidence", String(phase), "sast-baseline.json.lock");
|
|
79937
80879
|
}
|
|
79938
80880
|
function getLine(lines, idx) {
|
|
79939
80881
|
if (idx < 0 || idx >= lines.length)
|
|
@@ -79950,7 +80892,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
|
|
|
79950
80892
|
}
|
|
79951
80893
|
const lineNum = finding.location.line;
|
|
79952
80894
|
try {
|
|
79953
|
-
const content =
|
|
80895
|
+
const content = fs69.readFileSync(finding.location.file, "utf-8");
|
|
79954
80896
|
const lines = content.split(`
|
|
79955
80897
|
`);
|
|
79956
80898
|
const idx = lineNum - 1;
|
|
@@ -79981,7 +80923,7 @@ function assignOccurrenceIndices(findings, directory) {
|
|
|
79981
80923
|
try {
|
|
79982
80924
|
if (relFile.startsWith(".."))
|
|
79983
80925
|
throw new Error("escapes workspace");
|
|
79984
|
-
const content =
|
|
80926
|
+
const content = fs69.readFileSync(finding.location.file, "utf-8");
|
|
79985
80927
|
const lines = content.split(`
|
|
79986
80928
|
`);
|
|
79987
80929
|
const idx = lineNum - 1;
|
|
@@ -80010,11 +80952,11 @@ function assignOccurrenceIndices(findings, directory) {
|
|
|
80010
80952
|
async function acquireLock(lockPath) {
|
|
80011
80953
|
for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
|
|
80012
80954
|
try {
|
|
80013
|
-
const fd =
|
|
80014
|
-
|
|
80955
|
+
const fd = fs69.openSync(lockPath, "wx");
|
|
80956
|
+
fs69.closeSync(fd);
|
|
80015
80957
|
return () => {
|
|
80016
80958
|
try {
|
|
80017
|
-
|
|
80959
|
+
fs69.unlinkSync(lockPath);
|
|
80018
80960
|
} catch {}
|
|
80019
80961
|
};
|
|
80020
80962
|
} catch {
|
|
@@ -80054,12 +80996,12 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80054
80996
|
message: e instanceof Error ? e.message : "Path validation failed"
|
|
80055
80997
|
};
|
|
80056
80998
|
}
|
|
80057
|
-
|
|
80999
|
+
fs69.mkdirSync(path83.dirname(baselinePath), { recursive: true });
|
|
80058
81000
|
const releaseLock = await acquireLock(lockPath);
|
|
80059
81001
|
try {
|
|
80060
81002
|
let existing = null;
|
|
80061
81003
|
try {
|
|
80062
|
-
const raw =
|
|
81004
|
+
const raw = fs69.readFileSync(baselinePath, "utf-8");
|
|
80063
81005
|
const parsed = JSON.parse(raw);
|
|
80064
81006
|
if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
|
|
80065
81007
|
existing = parsed;
|
|
@@ -80119,8 +81061,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80119
81061
|
message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
|
|
80120
81062
|
};
|
|
80121
81063
|
}
|
|
80122
|
-
|
|
80123
|
-
|
|
81064
|
+
fs69.writeFileSync(tempPath, json4, "utf-8");
|
|
81065
|
+
fs69.renameSync(tempPath, baselinePath);
|
|
80124
81066
|
return {
|
|
80125
81067
|
status: "merged",
|
|
80126
81068
|
path: baselinePath,
|
|
@@ -80151,8 +81093,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
|
|
|
80151
81093
|
message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
|
|
80152
81094
|
};
|
|
80153
81095
|
}
|
|
80154
|
-
|
|
80155
|
-
|
|
81096
|
+
fs69.writeFileSync(tempPath, json3, "utf-8");
|
|
81097
|
+
fs69.renameSync(tempPath, baselinePath);
|
|
80156
81098
|
return {
|
|
80157
81099
|
status: "written",
|
|
80158
81100
|
path: baselinePath,
|
|
@@ -80177,7 +81119,7 @@ function loadBaseline(directory, phase) {
|
|
|
80177
81119
|
};
|
|
80178
81120
|
}
|
|
80179
81121
|
try {
|
|
80180
|
-
const raw =
|
|
81122
|
+
const raw = fs69.readFileSync(baselinePath, "utf-8");
|
|
80181
81123
|
const parsed = JSON.parse(raw);
|
|
80182
81124
|
if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
|
|
80183
81125
|
return {
|
|
@@ -80219,17 +81161,17 @@ var SEVERITY_ORDER = {
|
|
|
80219
81161
|
};
|
|
80220
81162
|
function shouldSkipFile(filePath) {
|
|
80221
81163
|
try {
|
|
80222
|
-
const stats =
|
|
81164
|
+
const stats = fs70.statSync(filePath);
|
|
80223
81165
|
if (stats.size > MAX_FILE_SIZE_BYTES8) {
|
|
80224
81166
|
return { skip: true, reason: "file too large" };
|
|
80225
81167
|
}
|
|
80226
81168
|
if (stats.size === 0) {
|
|
80227
81169
|
return { skip: true, reason: "empty file" };
|
|
80228
81170
|
}
|
|
80229
|
-
const fd =
|
|
81171
|
+
const fd = fs70.openSync(filePath, "r");
|
|
80230
81172
|
const buffer = Buffer.alloc(8192);
|
|
80231
|
-
const bytesRead =
|
|
80232
|
-
|
|
81173
|
+
const bytesRead = fs70.readSync(fd, buffer, 0, 8192, 0);
|
|
81174
|
+
fs70.closeSync(fd);
|
|
80233
81175
|
if (bytesRead > 0) {
|
|
80234
81176
|
let nullCount = 0;
|
|
80235
81177
|
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
@@ -80268,7 +81210,7 @@ function countBySeverity(findings) {
|
|
|
80268
81210
|
}
|
|
80269
81211
|
function scanFileWithTierA(filePath, language) {
|
|
80270
81212
|
try {
|
|
80271
|
-
const content =
|
|
81213
|
+
const content = fs70.readFileSync(filePath, "utf-8");
|
|
80272
81214
|
const findings = executeRulesSync(filePath, content, language);
|
|
80273
81215
|
return findings.map((f) => ({
|
|
80274
81216
|
rule_id: f.rule_id,
|
|
@@ -80321,13 +81263,13 @@ async function sastScan(input, directory, config3) {
|
|
|
80321
81263
|
_filesSkipped++;
|
|
80322
81264
|
continue;
|
|
80323
81265
|
}
|
|
80324
|
-
const resolvedPath =
|
|
80325
|
-
const resolvedDirectory =
|
|
80326
|
-
if (!resolvedPath.startsWith(resolvedDirectory +
|
|
81266
|
+
const resolvedPath = path84.isAbsolute(filePath) ? filePath : path84.resolve(directory, filePath);
|
|
81267
|
+
const resolvedDirectory = path84.resolve(directory);
|
|
81268
|
+
if (!resolvedPath.startsWith(resolvedDirectory + path84.sep) && resolvedPath !== resolvedDirectory) {
|
|
80327
81269
|
_filesSkipped++;
|
|
80328
81270
|
continue;
|
|
80329
81271
|
}
|
|
80330
|
-
if (!
|
|
81272
|
+
if (!fs70.existsSync(resolvedPath)) {
|
|
80331
81273
|
_filesSkipped++;
|
|
80332
81274
|
continue;
|
|
80333
81275
|
}
|
|
@@ -80634,18 +81576,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
|
|
|
80634
81576
|
let resolved;
|
|
80635
81577
|
const isWinAbs = isWindowsAbsolutePath(inputPath);
|
|
80636
81578
|
if (isWinAbs) {
|
|
80637
|
-
resolved =
|
|
80638
|
-
} else if (
|
|
80639
|
-
resolved =
|
|
81579
|
+
resolved = path85.win32.resolve(inputPath);
|
|
81580
|
+
} else if (path85.isAbsolute(inputPath)) {
|
|
81581
|
+
resolved = path85.resolve(inputPath);
|
|
80640
81582
|
} else {
|
|
80641
|
-
resolved =
|
|
81583
|
+
resolved = path85.resolve(baseDir, inputPath);
|
|
80642
81584
|
}
|
|
80643
|
-
const workspaceResolved =
|
|
81585
|
+
const workspaceResolved = path85.resolve(workspaceDir);
|
|
80644
81586
|
let relative20;
|
|
80645
81587
|
if (isWinAbs) {
|
|
80646
|
-
relative20 =
|
|
81588
|
+
relative20 = path85.win32.relative(workspaceResolved, resolved);
|
|
80647
81589
|
} else {
|
|
80648
|
-
relative20 =
|
|
81590
|
+
relative20 = path85.relative(workspaceResolved, resolved);
|
|
80649
81591
|
}
|
|
80650
81592
|
if (relative20.startsWith("..")) {
|
|
80651
81593
|
return "path traversal detected";
|
|
@@ -80710,7 +81652,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
|
|
|
80710
81652
|
if (typeof file3 !== "string") {
|
|
80711
81653
|
continue;
|
|
80712
81654
|
}
|
|
80713
|
-
const resolvedPath =
|
|
81655
|
+
const resolvedPath = path85.resolve(file3);
|
|
80714
81656
|
const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
|
|
80715
81657
|
if (validationError) {
|
|
80716
81658
|
continue;
|
|
@@ -80867,7 +81809,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80867
81809
|
skippedFiles++;
|
|
80868
81810
|
continue;
|
|
80869
81811
|
}
|
|
80870
|
-
const resolvedPath =
|
|
81812
|
+
const resolvedPath = path85.resolve(file3);
|
|
80871
81813
|
const validationError = validatePath(resolvedPath, directory, directory);
|
|
80872
81814
|
if (validationError) {
|
|
80873
81815
|
skippedFiles++;
|
|
@@ -80885,14 +81827,14 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80885
81827
|
};
|
|
80886
81828
|
}
|
|
80887
81829
|
for (const file3 of validatedFiles) {
|
|
80888
|
-
const ext =
|
|
81830
|
+
const ext = path85.extname(file3).toLowerCase();
|
|
80889
81831
|
if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
|
|
80890
81832
|
skippedFiles++;
|
|
80891
81833
|
continue;
|
|
80892
81834
|
}
|
|
80893
81835
|
let stat4;
|
|
80894
81836
|
try {
|
|
80895
|
-
stat4 =
|
|
81837
|
+
stat4 = fs71.statSync(file3);
|
|
80896
81838
|
} catch {
|
|
80897
81839
|
skippedFiles++;
|
|
80898
81840
|
continue;
|
|
@@ -80903,7 +81845,7 @@ async function runSecretscanWithFiles(files, directory) {
|
|
|
80903
81845
|
}
|
|
80904
81846
|
let content;
|
|
80905
81847
|
try {
|
|
80906
|
-
const buffer =
|
|
81848
|
+
const buffer = fs71.readFileSync(file3);
|
|
80907
81849
|
if (buffer.includes(0)) {
|
|
80908
81850
|
skippedFiles++;
|
|
80909
81851
|
continue;
|
|
@@ -81104,7 +82046,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
|
|
|
81104
82046
|
const preexistingFindings = [];
|
|
81105
82047
|
for (const finding of findings) {
|
|
81106
82048
|
const filePath = finding.location.file;
|
|
81107
|
-
const normalised =
|
|
82049
|
+
const normalised = path85.relative(directory, filePath).replace(/\\/g, "/");
|
|
81108
82050
|
const changedLines = changedLineRanges.get(normalised);
|
|
81109
82051
|
if (changedLines?.has(finding.location.line)) {
|
|
81110
82052
|
newFindings.push(finding);
|
|
@@ -81155,7 +82097,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
|
|
|
81155
82097
|
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
81156
82098
|
continue;
|
|
81157
82099
|
}
|
|
81158
|
-
changedFiles.push(
|
|
82100
|
+
changedFiles.push(path85.resolve(directory, file3));
|
|
81159
82101
|
}
|
|
81160
82102
|
if (changedFiles.length === 0) {
|
|
81161
82103
|
warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
|
|
@@ -81356,7 +82298,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
81356
82298
|
};
|
|
81357
82299
|
return JSON.stringify(errorResult, null, 2);
|
|
81358
82300
|
}
|
|
81359
|
-
const resolvedDirectory =
|
|
82301
|
+
const resolvedDirectory = path85.resolve(typedArgs.directory);
|
|
81360
82302
|
const workspaceAnchor = resolvedDirectory;
|
|
81361
82303
|
const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
|
|
81362
82304
|
if (dirError) {
|
|
@@ -81397,7 +82339,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
81397
82339
|
});
|
|
81398
82340
|
// src/tools/repo-map.ts
|
|
81399
82341
|
init_dist();
|
|
81400
|
-
import * as
|
|
82342
|
+
import * as path86 from "path";
|
|
81401
82343
|
init_path_security();
|
|
81402
82344
|
init_create_tool();
|
|
81403
82345
|
var VALID_ACTIONS = [
|
|
@@ -81422,7 +82364,7 @@ function validateFile(p) {
|
|
|
81422
82364
|
return "file contains control characters";
|
|
81423
82365
|
if (containsPathTraversal(p))
|
|
81424
82366
|
return "file contains path traversal";
|
|
81425
|
-
if (
|
|
82367
|
+
if (path86.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
|
|
81426
82368
|
return "file must be a workspace-relative path, not absolute";
|
|
81427
82369
|
}
|
|
81428
82370
|
return null;
|
|
@@ -81445,8 +82387,8 @@ function ok(action, payload) {
|
|
|
81445
82387
|
}
|
|
81446
82388
|
function toRelativeGraphPath(input, workspaceRoot) {
|
|
81447
82389
|
const normalized = input.replace(/\\/g, "/");
|
|
81448
|
-
if (
|
|
81449
|
-
const rel =
|
|
82390
|
+
if (path86.isAbsolute(normalized)) {
|
|
82391
|
+
const rel = path86.relative(workspaceRoot, normalized).replace(/\\/g, "/");
|
|
81450
82392
|
return normalizeGraphPath2(rel);
|
|
81451
82393
|
}
|
|
81452
82394
|
return normalizeGraphPath2(normalized);
|
|
@@ -81590,8 +82532,8 @@ var repo_map = createSwarmTool({
|
|
|
81590
82532
|
// src/tools/req-coverage.ts
|
|
81591
82533
|
init_dist();
|
|
81592
82534
|
init_create_tool();
|
|
81593
|
-
import * as
|
|
81594
|
-
import * as
|
|
82535
|
+
import * as fs72 from "fs";
|
|
82536
|
+
import * as path87 from "path";
|
|
81595
82537
|
var SPEC_FILE = ".swarm/spec.md";
|
|
81596
82538
|
var EVIDENCE_DIR4 = ".swarm/evidence";
|
|
81597
82539
|
var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
|
|
@@ -81650,19 +82592,19 @@ function extractObligationAndText(id, lineText) {
|
|
|
81650
82592
|
var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
|
|
81651
82593
|
function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
81652
82594
|
const touchedFiles = new Set;
|
|
81653
|
-
if (!
|
|
82595
|
+
if (!fs72.existsSync(evidenceDir) || !fs72.statSync(evidenceDir).isDirectory()) {
|
|
81654
82596
|
return [];
|
|
81655
82597
|
}
|
|
81656
82598
|
let entries;
|
|
81657
82599
|
try {
|
|
81658
|
-
entries =
|
|
82600
|
+
entries = fs72.readdirSync(evidenceDir);
|
|
81659
82601
|
} catch {
|
|
81660
82602
|
return [];
|
|
81661
82603
|
}
|
|
81662
82604
|
for (const entry of entries) {
|
|
81663
|
-
const entryPath =
|
|
82605
|
+
const entryPath = path87.join(evidenceDir, entry);
|
|
81664
82606
|
try {
|
|
81665
|
-
const stat4 =
|
|
82607
|
+
const stat4 = fs72.statSync(entryPath);
|
|
81666
82608
|
if (!stat4.isDirectory()) {
|
|
81667
82609
|
continue;
|
|
81668
82610
|
}
|
|
@@ -81676,14 +82618,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81676
82618
|
if (entryPhase !== String(phase)) {
|
|
81677
82619
|
continue;
|
|
81678
82620
|
}
|
|
81679
|
-
const evidenceFilePath =
|
|
82621
|
+
const evidenceFilePath = path87.join(entryPath, "evidence.json");
|
|
81680
82622
|
try {
|
|
81681
|
-
const resolvedPath =
|
|
81682
|
-
const evidenceDirResolved =
|
|
81683
|
-
if (!resolvedPath.startsWith(evidenceDirResolved +
|
|
82623
|
+
const resolvedPath = path87.resolve(evidenceFilePath);
|
|
82624
|
+
const evidenceDirResolved = path87.resolve(evidenceDir);
|
|
82625
|
+
if (!resolvedPath.startsWith(evidenceDirResolved + path87.sep)) {
|
|
81684
82626
|
continue;
|
|
81685
82627
|
}
|
|
81686
|
-
const stat4 =
|
|
82628
|
+
const stat4 = fs72.lstatSync(evidenceFilePath);
|
|
81687
82629
|
if (!stat4.isFile()) {
|
|
81688
82630
|
continue;
|
|
81689
82631
|
}
|
|
@@ -81695,7 +82637,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81695
82637
|
}
|
|
81696
82638
|
let content;
|
|
81697
82639
|
try {
|
|
81698
|
-
content =
|
|
82640
|
+
content = fs72.readFileSync(evidenceFilePath, "utf-8");
|
|
81699
82641
|
} catch {
|
|
81700
82642
|
continue;
|
|
81701
82643
|
}
|
|
@@ -81714,7 +82656,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81714
82656
|
if (Array.isArray(diffEntry.files_changed)) {
|
|
81715
82657
|
for (const file3 of diffEntry.files_changed) {
|
|
81716
82658
|
if (typeof file3 === "string") {
|
|
81717
|
-
touchedFiles.add(
|
|
82659
|
+
touchedFiles.add(path87.resolve(cwd, file3));
|
|
81718
82660
|
}
|
|
81719
82661
|
}
|
|
81720
82662
|
}
|
|
@@ -81727,12 +82669,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
|
|
|
81727
82669
|
}
|
|
81728
82670
|
function searchFileForKeywords(filePath, keywords, cwd) {
|
|
81729
82671
|
try {
|
|
81730
|
-
const resolvedPath =
|
|
81731
|
-
const cwdResolved =
|
|
82672
|
+
const resolvedPath = path87.resolve(filePath);
|
|
82673
|
+
const cwdResolved = path87.resolve(cwd);
|
|
81732
82674
|
if (!resolvedPath.startsWith(cwdResolved)) {
|
|
81733
82675
|
return false;
|
|
81734
82676
|
}
|
|
81735
|
-
const content =
|
|
82677
|
+
const content = fs72.readFileSync(resolvedPath, "utf-8");
|
|
81736
82678
|
for (const keyword of keywords) {
|
|
81737
82679
|
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
81738
82680
|
if (regex.test(content)) {
|
|
@@ -81862,10 +82804,10 @@ var req_coverage = createSwarmTool({
|
|
|
81862
82804
|
}, null, 2);
|
|
81863
82805
|
}
|
|
81864
82806
|
const cwd = inputDirectory || directory;
|
|
81865
|
-
const specPath =
|
|
82807
|
+
const specPath = path87.join(cwd, SPEC_FILE);
|
|
81866
82808
|
let specContent;
|
|
81867
82809
|
try {
|
|
81868
|
-
specContent =
|
|
82810
|
+
specContent = fs72.readFileSync(specPath, "utf-8");
|
|
81869
82811
|
} catch (readError) {
|
|
81870
82812
|
return JSON.stringify({
|
|
81871
82813
|
success: false,
|
|
@@ -81889,7 +82831,7 @@ var req_coverage = createSwarmTool({
|
|
|
81889
82831
|
message: "No FR requirements found in spec.md"
|
|
81890
82832
|
}, null, 2);
|
|
81891
82833
|
}
|
|
81892
|
-
const evidenceDir =
|
|
82834
|
+
const evidenceDir = path87.join(cwd, EVIDENCE_DIR4);
|
|
81893
82835
|
const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
|
|
81894
82836
|
const analyzedRequirements = [];
|
|
81895
82837
|
let coveredCount = 0;
|
|
@@ -81915,12 +82857,12 @@ var req_coverage = createSwarmTool({
|
|
|
81915
82857
|
requirements: analyzedRequirements
|
|
81916
82858
|
};
|
|
81917
82859
|
const reportFilename = `req-coverage-phase-${phase}.json`;
|
|
81918
|
-
const reportPath =
|
|
82860
|
+
const reportPath = path87.join(evidenceDir, reportFilename);
|
|
81919
82861
|
try {
|
|
81920
|
-
if (!
|
|
81921
|
-
|
|
82862
|
+
if (!fs72.existsSync(evidenceDir)) {
|
|
82863
|
+
fs72.mkdirSync(evidenceDir, { recursive: true });
|
|
81922
82864
|
}
|
|
81923
|
-
|
|
82865
|
+
fs72.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
|
|
81924
82866
|
} catch (writeError) {
|
|
81925
82867
|
console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
|
|
81926
82868
|
}
|
|
@@ -81999,6 +82941,7 @@ ${paginatedContent}`;
|
|
|
81999
82941
|
// src/tools/save-plan.ts
|
|
82000
82942
|
init_tool();
|
|
82001
82943
|
init_plan_schema();
|
|
82944
|
+
init_qa_gate_profile();
|
|
82002
82945
|
init_file_locks();
|
|
82003
82946
|
init_checkpoint3();
|
|
82004
82947
|
init_ledger();
|
|
@@ -82006,8 +82949,8 @@ init_manager();
|
|
|
82006
82949
|
init_state();
|
|
82007
82950
|
init_create_tool();
|
|
82008
82951
|
import * as crypto9 from "crypto";
|
|
82009
|
-
import * as
|
|
82010
|
-
import * as
|
|
82952
|
+
import * as fs73 from "fs";
|
|
82953
|
+
import * as path88 from "path";
|
|
82011
82954
|
function detectPlaceholderContent(args2) {
|
|
82012
82955
|
const issues = [];
|
|
82013
82956
|
const placeholderPattern = /^\[\w[\w\s]*\]$/;
|
|
@@ -82081,17 +83024,17 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82081
83024
|
};
|
|
82082
83025
|
}
|
|
82083
83026
|
if (args2.working_directory && fallbackDir) {
|
|
82084
|
-
const resolvedTarget =
|
|
82085
|
-
const resolvedRoot =
|
|
83027
|
+
const resolvedTarget = path88.resolve(args2.working_directory);
|
|
83028
|
+
const resolvedRoot = path88.resolve(fallbackDir);
|
|
82086
83029
|
let fallbackExists = false;
|
|
82087
83030
|
try {
|
|
82088
|
-
|
|
83031
|
+
fs73.accessSync(resolvedRoot, fs73.constants.F_OK);
|
|
82089
83032
|
fallbackExists = true;
|
|
82090
83033
|
} catch {
|
|
82091
83034
|
fallbackExists = false;
|
|
82092
83035
|
}
|
|
82093
83036
|
if (fallbackExists) {
|
|
82094
|
-
const isSubdirectory = resolvedTarget.startsWith(resolvedRoot +
|
|
83037
|
+
const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path88.sep);
|
|
82095
83038
|
if (isSubdirectory) {
|
|
82096
83039
|
return {
|
|
82097
83040
|
success: false,
|
|
@@ -82107,11 +83050,11 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82107
83050
|
let specMtime;
|
|
82108
83051
|
let specHash;
|
|
82109
83052
|
if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
|
|
82110
|
-
const specPath =
|
|
83053
|
+
const specPath = path88.join(targetWorkspace, ".swarm", "spec.md");
|
|
82111
83054
|
try {
|
|
82112
|
-
const stat4 = await
|
|
83055
|
+
const stat4 = await fs73.promises.stat(specPath);
|
|
82113
83056
|
specMtime = stat4.mtime.toISOString();
|
|
82114
|
-
const content = await
|
|
83057
|
+
const content = await fs73.promises.readFile(specPath, "utf8");
|
|
82115
83058
|
specHash = crypto9.createHash("sha256").update(content).digest("hex");
|
|
82116
83059
|
} catch {
|
|
82117
83060
|
return {
|
|
@@ -82122,6 +83065,32 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82122
83065
|
};
|
|
82123
83066
|
}
|
|
82124
83067
|
}
|
|
83068
|
+
if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
|
|
83069
|
+
const contextPath = path88.join(targetWorkspace, ".swarm", "context.md");
|
|
83070
|
+
let contextContent = "";
|
|
83071
|
+
try {
|
|
83072
|
+
contextContent = await fs73.promises.readFile(contextPath, "utf8");
|
|
83073
|
+
} catch {}
|
|
83074
|
+
const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
|
|
83075
|
+
if (!hasPendingSection) {
|
|
83076
|
+
const candidatePlanId = `${args2.swarm_id}-${args2.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
83077
|
+
let existingProfile = null;
|
|
83078
|
+
try {
|
|
83079
|
+
existingProfile = getProfile(targetWorkspace, candidatePlanId);
|
|
83080
|
+
} catch {}
|
|
83081
|
+
if (!existingProfile) {
|
|
83082
|
+
return {
|
|
83083
|
+
success: false,
|
|
83084
|
+
message: "QA_GATE_SELECTION_REQUIRED: QA gate selection has not been completed. " + "Present the gate selection question to the user (step 5b of MODE: SPECIFY " + "or Phase 6 of MODE: BRAINSTORM), write their response to .swarm/context.md " + 'under "## Pending QA Gate Selection", then retry save_plan.',
|
|
83085
|
+
errors: [
|
|
83086
|
+
"Missing ## Pending QA Gate Selection in .swarm/context.md",
|
|
83087
|
+
"No existing QaGateProfile found for this plan"
|
|
83088
|
+
],
|
|
83089
|
+
recovery_guidance: "Do not call set_qa_gates with assumed values. Present the gate dialogue, " + "wait for the user's answer, write it to context.md, then retry save_plan."
|
|
83090
|
+
};
|
|
83091
|
+
}
|
|
83092
|
+
}
|
|
83093
|
+
}
|
|
82125
83094
|
const dir = targetWorkspace;
|
|
82126
83095
|
const existingStatusMap = new Map;
|
|
82127
83096
|
let preservedExecutionProfile;
|
|
@@ -82247,14 +83216,14 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82247
83216
|
}
|
|
82248
83217
|
await writeCheckpoint(dir).catch(() => {});
|
|
82249
83218
|
try {
|
|
82250
|
-
const markerPath =
|
|
83219
|
+
const markerPath = path88.join(dir, ".swarm", ".plan-write-marker");
|
|
82251
83220
|
const marker = JSON.stringify({
|
|
82252
83221
|
source: "save_plan",
|
|
82253
83222
|
timestamp: new Date().toISOString(),
|
|
82254
83223
|
phases_count: plan.phases.length,
|
|
82255
83224
|
tasks_count: tasksCount
|
|
82256
83225
|
});
|
|
82257
|
-
await
|
|
83226
|
+
await fs73.promises.writeFile(markerPath, marker, "utf8");
|
|
82258
83227
|
} catch {}
|
|
82259
83228
|
const warnings = [];
|
|
82260
83229
|
let criticReviewFound = false;
|
|
@@ -82270,7 +83239,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
82270
83239
|
return {
|
|
82271
83240
|
success: true,
|
|
82272
83241
|
message: "Plan saved successfully",
|
|
82273
|
-
plan_path:
|
|
83242
|
+
plan_path: path88.join(dir, ".swarm", "plan.json"),
|
|
82274
83243
|
phases_count: plan.phases.length,
|
|
82275
83244
|
tasks_count: tasksCount,
|
|
82276
83245
|
...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
|
|
@@ -82322,8 +83291,8 @@ var save_plan = createSwarmTool({
|
|
|
82322
83291
|
// src/tools/sbom-generate.ts
|
|
82323
83292
|
init_dist();
|
|
82324
83293
|
init_manager2();
|
|
82325
|
-
import * as
|
|
82326
|
-
import * as
|
|
83294
|
+
import * as fs74 from "fs";
|
|
83295
|
+
import * as path89 from "path";
|
|
82327
83296
|
|
|
82328
83297
|
// src/sbom/detectors/index.ts
|
|
82329
83298
|
init_utils();
|
|
@@ -83171,9 +84140,9 @@ function findManifestFiles(rootDir) {
|
|
|
83171
84140
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
83172
84141
|
function searchDir(dir) {
|
|
83173
84142
|
try {
|
|
83174
|
-
const entries =
|
|
84143
|
+
const entries = fs74.readdirSync(dir, { withFileTypes: true });
|
|
83175
84144
|
for (const entry of entries) {
|
|
83176
|
-
const fullPath =
|
|
84145
|
+
const fullPath = path89.join(dir, entry.name);
|
|
83177
84146
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
83178
84147
|
continue;
|
|
83179
84148
|
}
|
|
@@ -83182,7 +84151,7 @@ function findManifestFiles(rootDir) {
|
|
|
83182
84151
|
} else if (entry.isFile()) {
|
|
83183
84152
|
for (const pattern of patterns) {
|
|
83184
84153
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
83185
|
-
manifestFiles.push(
|
|
84154
|
+
manifestFiles.push(path89.relative(rootDir, fullPath));
|
|
83186
84155
|
break;
|
|
83187
84156
|
}
|
|
83188
84157
|
}
|
|
@@ -83198,13 +84167,13 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
83198
84167
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
83199
84168
|
for (const dir of directories) {
|
|
83200
84169
|
try {
|
|
83201
|
-
const entries =
|
|
84170
|
+
const entries = fs74.readdirSync(dir, { withFileTypes: true });
|
|
83202
84171
|
for (const entry of entries) {
|
|
83203
|
-
const fullPath =
|
|
84172
|
+
const fullPath = path89.join(dir, entry.name);
|
|
83204
84173
|
if (entry.isFile()) {
|
|
83205
84174
|
for (const pattern of patterns) {
|
|
83206
84175
|
if (simpleGlobToRegex(pattern).test(entry.name)) {
|
|
83207
|
-
found.push(
|
|
84176
|
+
found.push(path89.relative(workingDir, fullPath));
|
|
83208
84177
|
break;
|
|
83209
84178
|
}
|
|
83210
84179
|
}
|
|
@@ -83217,11 +84186,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
83217
84186
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
83218
84187
|
const dirs = new Set;
|
|
83219
84188
|
for (const file3 of changedFiles) {
|
|
83220
|
-
let currentDir =
|
|
84189
|
+
let currentDir = path89.dirname(file3);
|
|
83221
84190
|
while (true) {
|
|
83222
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
83223
|
-
dirs.add(
|
|
83224
|
-
const parent =
|
|
84191
|
+
if (currentDir && currentDir !== "." && currentDir !== path89.sep) {
|
|
84192
|
+
dirs.add(path89.join(workingDir, currentDir));
|
|
84193
|
+
const parent = path89.dirname(currentDir);
|
|
83225
84194
|
if (parent === currentDir)
|
|
83226
84195
|
break;
|
|
83227
84196
|
currentDir = parent;
|
|
@@ -83235,7 +84204,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
83235
84204
|
}
|
|
83236
84205
|
function ensureOutputDir(outputDir) {
|
|
83237
84206
|
try {
|
|
83238
|
-
|
|
84207
|
+
fs74.mkdirSync(outputDir, { recursive: true });
|
|
83239
84208
|
} catch (error93) {
|
|
83240
84209
|
if (!error93 || error93.code !== "EEXIST") {
|
|
83241
84210
|
throw error93;
|
|
@@ -83305,7 +84274,7 @@ var sbom_generate = createSwarmTool({
|
|
|
83305
84274
|
const changedFiles = obj.changed_files;
|
|
83306
84275
|
const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
|
|
83307
84276
|
const workingDir = directory;
|
|
83308
|
-
const outputDir =
|
|
84277
|
+
const outputDir = path89.isAbsolute(relativeOutputDir) ? relativeOutputDir : path89.join(workingDir, relativeOutputDir);
|
|
83309
84278
|
let manifestFiles = [];
|
|
83310
84279
|
if (scope === "all") {
|
|
83311
84280
|
manifestFiles = findManifestFiles(workingDir);
|
|
@@ -83328,11 +84297,11 @@ var sbom_generate = createSwarmTool({
|
|
|
83328
84297
|
const processedFiles = [];
|
|
83329
84298
|
for (const manifestFile of manifestFiles) {
|
|
83330
84299
|
try {
|
|
83331
|
-
const fullPath =
|
|
83332
|
-
if (!
|
|
84300
|
+
const fullPath = path89.isAbsolute(manifestFile) ? manifestFile : path89.join(workingDir, manifestFile);
|
|
84301
|
+
if (!fs74.existsSync(fullPath)) {
|
|
83333
84302
|
continue;
|
|
83334
84303
|
}
|
|
83335
|
-
const content =
|
|
84304
|
+
const content = fs74.readFileSync(fullPath, "utf-8");
|
|
83336
84305
|
const components = detectComponents(manifestFile, content);
|
|
83337
84306
|
processedFiles.push(manifestFile);
|
|
83338
84307
|
if (components.length > 0) {
|
|
@@ -83345,8 +84314,8 @@ var sbom_generate = createSwarmTool({
|
|
|
83345
84314
|
const bom = generateCycloneDX(allComponents);
|
|
83346
84315
|
const bomJson = serializeCycloneDX(bom);
|
|
83347
84316
|
const filename = generateSbomFilename();
|
|
83348
|
-
const outputPath =
|
|
83349
|
-
|
|
84317
|
+
const outputPath = path89.join(outputDir, filename);
|
|
84318
|
+
fs74.writeFileSync(outputPath, bomJson, "utf-8");
|
|
83350
84319
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
83351
84320
|
try {
|
|
83352
84321
|
const timestamp = new Date().toISOString();
|
|
@@ -83388,8 +84357,8 @@ var sbom_generate = createSwarmTool({
|
|
|
83388
84357
|
// src/tools/schema-drift.ts
|
|
83389
84358
|
init_dist();
|
|
83390
84359
|
init_create_tool();
|
|
83391
|
-
import * as
|
|
83392
|
-
import * as
|
|
84360
|
+
import * as fs75 from "fs";
|
|
84361
|
+
import * as path90 from "path";
|
|
83393
84362
|
var SPEC_CANDIDATES = [
|
|
83394
84363
|
"openapi.json",
|
|
83395
84364
|
"openapi.yaml",
|
|
@@ -83421,28 +84390,28 @@ function normalizePath3(p) {
|
|
|
83421
84390
|
}
|
|
83422
84391
|
function discoverSpecFile(cwd, specFileArg) {
|
|
83423
84392
|
if (specFileArg) {
|
|
83424
|
-
const resolvedPath =
|
|
83425
|
-
const normalizedCwd = cwd.endsWith(
|
|
84393
|
+
const resolvedPath = path90.resolve(cwd, specFileArg);
|
|
84394
|
+
const normalizedCwd = cwd.endsWith(path90.sep) ? cwd : cwd + path90.sep;
|
|
83426
84395
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
83427
84396
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
83428
84397
|
}
|
|
83429
|
-
const ext =
|
|
84398
|
+
const ext = path90.extname(resolvedPath).toLowerCase();
|
|
83430
84399
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
83431
84400
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
83432
84401
|
}
|
|
83433
|
-
const stats =
|
|
84402
|
+
const stats = fs75.statSync(resolvedPath);
|
|
83434
84403
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
83435
84404
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
83436
84405
|
}
|
|
83437
|
-
if (!
|
|
84406
|
+
if (!fs75.existsSync(resolvedPath)) {
|
|
83438
84407
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
83439
84408
|
}
|
|
83440
84409
|
return resolvedPath;
|
|
83441
84410
|
}
|
|
83442
84411
|
for (const candidate of SPEC_CANDIDATES) {
|
|
83443
|
-
const candidatePath =
|
|
83444
|
-
if (
|
|
83445
|
-
const stats =
|
|
84412
|
+
const candidatePath = path90.resolve(cwd, candidate);
|
|
84413
|
+
if (fs75.existsSync(candidatePath)) {
|
|
84414
|
+
const stats = fs75.statSync(candidatePath);
|
|
83446
84415
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
83447
84416
|
return candidatePath;
|
|
83448
84417
|
}
|
|
@@ -83451,8 +84420,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
83451
84420
|
return null;
|
|
83452
84421
|
}
|
|
83453
84422
|
function parseSpec(specFile) {
|
|
83454
|
-
const content =
|
|
83455
|
-
const ext =
|
|
84423
|
+
const content = fs75.readFileSync(specFile, "utf-8");
|
|
84424
|
+
const ext = path90.extname(specFile).toLowerCase();
|
|
83456
84425
|
if (ext === ".json") {
|
|
83457
84426
|
return parseJsonSpec(content);
|
|
83458
84427
|
}
|
|
@@ -83523,12 +84492,12 @@ function extractRoutes(cwd) {
|
|
|
83523
84492
|
function walkDir(dir) {
|
|
83524
84493
|
let entries;
|
|
83525
84494
|
try {
|
|
83526
|
-
entries =
|
|
84495
|
+
entries = fs75.readdirSync(dir, { withFileTypes: true });
|
|
83527
84496
|
} catch {
|
|
83528
84497
|
return;
|
|
83529
84498
|
}
|
|
83530
84499
|
for (const entry of entries) {
|
|
83531
|
-
const fullPath =
|
|
84500
|
+
const fullPath = path90.join(dir, entry.name);
|
|
83532
84501
|
if (entry.isSymbolicLink()) {
|
|
83533
84502
|
continue;
|
|
83534
84503
|
}
|
|
@@ -83538,7 +84507,7 @@ function extractRoutes(cwd) {
|
|
|
83538
84507
|
}
|
|
83539
84508
|
walkDir(fullPath);
|
|
83540
84509
|
} else if (entry.isFile()) {
|
|
83541
|
-
const ext =
|
|
84510
|
+
const ext = path90.extname(entry.name).toLowerCase();
|
|
83542
84511
|
const baseName = entry.name.toLowerCase();
|
|
83543
84512
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
83544
84513
|
continue;
|
|
@@ -83556,7 +84525,7 @@ function extractRoutes(cwd) {
|
|
|
83556
84525
|
}
|
|
83557
84526
|
function extractRoutesFromFile(filePath) {
|
|
83558
84527
|
const routes = [];
|
|
83559
|
-
const content =
|
|
84528
|
+
const content = fs75.readFileSync(filePath, "utf-8");
|
|
83560
84529
|
const lines = content.split(/\r?\n/);
|
|
83561
84530
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
83562
84531
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -83704,8 +84673,8 @@ var schema_drift = createSwarmTool({
|
|
|
83704
84673
|
init_tool();
|
|
83705
84674
|
init_path_security();
|
|
83706
84675
|
init_create_tool();
|
|
83707
|
-
import * as
|
|
83708
|
-
import * as
|
|
84676
|
+
import * as fs76 from "fs";
|
|
84677
|
+
import * as path91 from "path";
|
|
83709
84678
|
var DEFAULT_MAX_RESULTS = 100;
|
|
83710
84679
|
var DEFAULT_MAX_LINES = 200;
|
|
83711
84680
|
var REGEX_TIMEOUT_MS = 5000;
|
|
@@ -83741,11 +84710,11 @@ function containsWindowsAttacks3(str) {
|
|
|
83741
84710
|
}
|
|
83742
84711
|
function isPathInWorkspace3(filePath, workspace) {
|
|
83743
84712
|
try {
|
|
83744
|
-
const resolvedPath =
|
|
83745
|
-
const realWorkspace =
|
|
83746
|
-
const realResolvedPath =
|
|
83747
|
-
const relativePath =
|
|
83748
|
-
if (relativePath.startsWith("..") ||
|
|
84713
|
+
const resolvedPath = path91.resolve(workspace, filePath);
|
|
84714
|
+
const realWorkspace = fs76.realpathSync(workspace);
|
|
84715
|
+
const realResolvedPath = fs76.realpathSync(resolvedPath);
|
|
84716
|
+
const relativePath = path91.relative(realWorkspace, realResolvedPath);
|
|
84717
|
+
if (relativePath.startsWith("..") || path91.isAbsolute(relativePath)) {
|
|
83749
84718
|
return false;
|
|
83750
84719
|
}
|
|
83751
84720
|
return true;
|
|
@@ -83758,12 +84727,12 @@ function validatePathForRead2(filePath, workspace) {
|
|
|
83758
84727
|
}
|
|
83759
84728
|
function findRgInEnvPath() {
|
|
83760
84729
|
const searchPath = process.env.PATH ?? "";
|
|
83761
|
-
for (const dir of searchPath.split(
|
|
84730
|
+
for (const dir of searchPath.split(path91.delimiter)) {
|
|
83762
84731
|
if (!dir)
|
|
83763
84732
|
continue;
|
|
83764
84733
|
const isWindows = process.platform === "win32";
|
|
83765
|
-
const candidate =
|
|
83766
|
-
if (
|
|
84734
|
+
const candidate = path91.join(dir, isWindows ? "rg.exe" : "rg");
|
|
84735
|
+
if (fs76.existsSync(candidate))
|
|
83767
84736
|
return candidate;
|
|
83768
84737
|
}
|
|
83769
84738
|
return null;
|
|
@@ -83890,10 +84859,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
|
|
|
83890
84859
|
return files;
|
|
83891
84860
|
}
|
|
83892
84861
|
try {
|
|
83893
|
-
const entries =
|
|
84862
|
+
const entries = fs76.readdirSync(dir, { withFileTypes: true });
|
|
83894
84863
|
for (const entry of entries) {
|
|
83895
|
-
const fullPath =
|
|
83896
|
-
const relativePath =
|
|
84864
|
+
const fullPath = path91.join(dir, entry.name);
|
|
84865
|
+
const relativePath = path91.relative(workspace, fullPath);
|
|
83897
84866
|
if (!validatePathForRead2(fullPath, workspace)) {
|
|
83898
84867
|
continue;
|
|
83899
84868
|
}
|
|
@@ -83934,13 +84903,13 @@ async function fallbackSearch(opts) {
|
|
|
83934
84903
|
const matches = [];
|
|
83935
84904
|
let total = 0;
|
|
83936
84905
|
for (const file3 of files) {
|
|
83937
|
-
const fullPath =
|
|
84906
|
+
const fullPath = path91.join(opts.workspace, file3);
|
|
83938
84907
|
if (!validatePathForRead2(fullPath, opts.workspace)) {
|
|
83939
84908
|
continue;
|
|
83940
84909
|
}
|
|
83941
84910
|
let stats;
|
|
83942
84911
|
try {
|
|
83943
|
-
stats =
|
|
84912
|
+
stats = fs76.statSync(fullPath);
|
|
83944
84913
|
if (stats.size > MAX_FILE_SIZE_BYTES10) {
|
|
83945
84914
|
continue;
|
|
83946
84915
|
}
|
|
@@ -83949,7 +84918,7 @@ async function fallbackSearch(opts) {
|
|
|
83949
84918
|
}
|
|
83950
84919
|
let content;
|
|
83951
84920
|
try {
|
|
83952
|
-
content =
|
|
84921
|
+
content = fs76.readFileSync(fullPath, "utf-8");
|
|
83953
84922
|
} catch {
|
|
83954
84923
|
continue;
|
|
83955
84924
|
}
|
|
@@ -84061,7 +85030,7 @@ var search = createSwarmTool({
|
|
|
84061
85030
|
message: "Exclude pattern contains invalid Windows-specific sequence"
|
|
84062
85031
|
}, null, 2);
|
|
84063
85032
|
}
|
|
84064
|
-
if (!
|
|
85033
|
+
if (!fs76.existsSync(directory)) {
|
|
84065
85034
|
return JSON.stringify({
|
|
84066
85035
|
error: true,
|
|
84067
85036
|
type: "unknown",
|
|
@@ -84129,7 +85098,8 @@ async function executeSetQaGates(args2, directory) {
|
|
|
84129
85098
|
"critic_pre_plan",
|
|
84130
85099
|
"hallucination_guard",
|
|
84131
85100
|
"sast_enabled",
|
|
84132
|
-
"mutation_test"
|
|
85101
|
+
"mutation_test",
|
|
85102
|
+
"council_general_review"
|
|
84133
85103
|
]) {
|
|
84134
85104
|
if (args2[key] !== undefined)
|
|
84135
85105
|
partial3[key] = args2[key];
|
|
@@ -84175,6 +85145,7 @@ var set_qa_gates = createSwarmTool({
|
|
|
84175
85145
|
hallucination_guard: tool.schema.boolean().optional().describe("Enable hallucination_guard checks on plan and implementation claims."),
|
|
84176
85146
|
sast_enabled: tool.schema.boolean().optional().describe("Enable SAST scanning as a required QA gate."),
|
|
84177
85147
|
mutation_test: tool.schema.boolean().optional().describe("Enable the mutation-testing gate (default: off). Requires mutation " + "tests to achieve a passing kill rate before phase completion; " + "WARN verdict allows advancement, FAIL blocks."),
|
|
85148
|
+
council_general_review: tool.schema.boolean().optional().describe("Enable the council_general_review gate (default: off). When on, " + "MODE: SPECIFY runs convene_general_council on the draft spec " + "before the critic-gate, folding multi-model deliberation into " + "the spec. Requires council.general.enabled and a search API key."),
|
|
84178
85149
|
project_type: tool.schema.string().optional().describe('Project type label (e.g. "ts", "python"). Only applied when the profile is being created for the first time.')
|
|
84179
85150
|
},
|
|
84180
85151
|
execute: async (args2, directory) => {
|
|
@@ -84186,8 +85157,8 @@ var set_qa_gates = createSwarmTool({
|
|
|
84186
85157
|
init_tool();
|
|
84187
85158
|
init_path_security();
|
|
84188
85159
|
init_create_tool();
|
|
84189
|
-
import * as
|
|
84190
|
-
import * as
|
|
85160
|
+
import * as fs77 from "fs";
|
|
85161
|
+
import * as path92 from "path";
|
|
84191
85162
|
var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
84192
85163
|
function containsWindowsAttacks4(str) {
|
|
84193
85164
|
if (/:[^\\/]/.test(str))
|
|
@@ -84201,14 +85172,14 @@ function containsWindowsAttacks4(str) {
|
|
|
84201
85172
|
}
|
|
84202
85173
|
function isPathInWorkspace4(filePath, workspace) {
|
|
84203
85174
|
try {
|
|
84204
|
-
const resolvedPath =
|
|
84205
|
-
if (!
|
|
85175
|
+
const resolvedPath = path92.resolve(workspace, filePath);
|
|
85176
|
+
if (!fs77.existsSync(resolvedPath)) {
|
|
84206
85177
|
return true;
|
|
84207
85178
|
}
|
|
84208
|
-
const realWorkspace =
|
|
84209
|
-
const realResolvedPath =
|
|
84210
|
-
const relativePath =
|
|
84211
|
-
if (relativePath.startsWith("..") ||
|
|
85179
|
+
const realWorkspace = fs77.realpathSync(workspace);
|
|
85180
|
+
const realResolvedPath = fs77.realpathSync(resolvedPath);
|
|
85181
|
+
const relativePath = path92.relative(realWorkspace, realResolvedPath);
|
|
85182
|
+
if (relativePath.startsWith("..") || path92.isAbsolute(relativePath)) {
|
|
84212
85183
|
return false;
|
|
84213
85184
|
}
|
|
84214
85185
|
return true;
|
|
@@ -84380,7 +85351,7 @@ var suggestPatch = createSwarmTool({
|
|
|
84380
85351
|
message: "changes cannot be empty"
|
|
84381
85352
|
}, null, 2);
|
|
84382
85353
|
}
|
|
84383
|
-
if (!
|
|
85354
|
+
if (!fs77.existsSync(directory)) {
|
|
84384
85355
|
return JSON.stringify({
|
|
84385
85356
|
success: false,
|
|
84386
85357
|
error: true,
|
|
@@ -84416,8 +85387,8 @@ var suggestPatch = createSwarmTool({
|
|
|
84416
85387
|
});
|
|
84417
85388
|
continue;
|
|
84418
85389
|
}
|
|
84419
|
-
const fullPath =
|
|
84420
|
-
if (!
|
|
85390
|
+
const fullPath = path92.resolve(directory, change.file);
|
|
85391
|
+
if (!fs77.existsSync(fullPath)) {
|
|
84421
85392
|
errors5.push({
|
|
84422
85393
|
success: false,
|
|
84423
85394
|
error: true,
|
|
@@ -84431,7 +85402,7 @@ var suggestPatch = createSwarmTool({
|
|
|
84431
85402
|
}
|
|
84432
85403
|
let content;
|
|
84433
85404
|
try {
|
|
84434
|
-
content =
|
|
85405
|
+
content = fs77.readFileSync(fullPath, "utf-8");
|
|
84435
85406
|
} catch (err3) {
|
|
84436
85407
|
errors5.push({
|
|
84437
85408
|
success: false,
|
|
@@ -84665,8 +85636,8 @@ var generate_mutants = createSwarmTool({
|
|
|
84665
85636
|
// src/tools/lint-spec.ts
|
|
84666
85637
|
init_spec_schema();
|
|
84667
85638
|
init_create_tool();
|
|
84668
|
-
import * as
|
|
84669
|
-
import * as
|
|
85639
|
+
import * as fs78 from "fs";
|
|
85640
|
+
import * as path93 from "path";
|
|
84670
85641
|
var SPEC_FILE_NAME = "spec.md";
|
|
84671
85642
|
var SWARM_DIR2 = ".swarm";
|
|
84672
85643
|
var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
|
|
@@ -84719,8 +85690,8 @@ var lint_spec = createSwarmTool({
|
|
|
84719
85690
|
async execute(_args, directory) {
|
|
84720
85691
|
const errors5 = [];
|
|
84721
85692
|
const warnings = [];
|
|
84722
|
-
const specPath =
|
|
84723
|
-
if (!
|
|
85693
|
+
const specPath = path93.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
|
|
85694
|
+
if (!fs78.existsSync(specPath)) {
|
|
84724
85695
|
const result2 = {
|
|
84725
85696
|
valid: false,
|
|
84726
85697
|
specMtime: null,
|
|
@@ -84739,12 +85710,12 @@ var lint_spec = createSwarmTool({
|
|
|
84739
85710
|
}
|
|
84740
85711
|
let specMtime = null;
|
|
84741
85712
|
try {
|
|
84742
|
-
const stats =
|
|
85713
|
+
const stats = fs78.statSync(specPath);
|
|
84743
85714
|
specMtime = stats.mtime.toISOString();
|
|
84744
85715
|
} catch {}
|
|
84745
85716
|
let content;
|
|
84746
85717
|
try {
|
|
84747
|
-
content =
|
|
85718
|
+
content = fs78.readFileSync(specPath, "utf-8");
|
|
84748
85719
|
} catch (e) {
|
|
84749
85720
|
const result2 = {
|
|
84750
85721
|
valid: false,
|
|
@@ -84789,13 +85760,13 @@ var lint_spec = createSwarmTool({
|
|
|
84789
85760
|
});
|
|
84790
85761
|
// src/tools/mutation-test.ts
|
|
84791
85762
|
init_dist();
|
|
84792
|
-
import * as
|
|
84793
|
-
import * as
|
|
85763
|
+
import * as fs79 from "fs";
|
|
85764
|
+
import * as path95 from "path";
|
|
84794
85765
|
|
|
84795
85766
|
// src/mutation/engine.ts
|
|
84796
85767
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
84797
85768
|
import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
|
|
84798
|
-
import * as
|
|
85769
|
+
import * as path94 from "path";
|
|
84799
85770
|
|
|
84800
85771
|
// src/mutation/equivalence.ts
|
|
84801
85772
|
function isStaticallyEquivalent(originalCode, mutatedCode) {
|
|
@@ -84930,7 +85901,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
|
|
|
84930
85901
|
let patchFile;
|
|
84931
85902
|
try {
|
|
84932
85903
|
const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
84933
|
-
patchFile =
|
|
85904
|
+
patchFile = path94.join(workingDir, `.mutation_patch_${safeId2}.diff`);
|
|
84934
85905
|
try {
|
|
84935
85906
|
writeFileSync18(patchFile, patch.patch);
|
|
84936
85907
|
} catch (writeErr) {
|
|
@@ -85324,8 +86295,8 @@ var mutation_test = createSwarmTool({
|
|
|
85324
86295
|
];
|
|
85325
86296
|
for (const filePath of uniquePaths) {
|
|
85326
86297
|
try {
|
|
85327
|
-
const resolvedPath =
|
|
85328
|
-
sourceFiles.set(filePath,
|
|
86298
|
+
const resolvedPath = path95.resolve(cwd, filePath);
|
|
86299
|
+
sourceFiles.set(filePath, fs79.readFileSync(resolvedPath, "utf-8"));
|
|
85329
86300
|
} catch {}
|
|
85330
86301
|
}
|
|
85331
86302
|
const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
|
|
@@ -85343,8 +86314,8 @@ var mutation_test = createSwarmTool({
|
|
|
85343
86314
|
init_dist();
|
|
85344
86315
|
init_manager2();
|
|
85345
86316
|
init_detector();
|
|
85346
|
-
import * as
|
|
85347
|
-
import * as
|
|
86317
|
+
import * as fs80 from "fs";
|
|
86318
|
+
import * as path96 from "path";
|
|
85348
86319
|
init_create_tool();
|
|
85349
86320
|
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
85350
86321
|
var BINARY_CHECK_BYTES = 8192;
|
|
@@ -85410,7 +86381,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85410
86381
|
if (languages?.length) {
|
|
85411
86382
|
const lowerLangs = languages.map((l) => l.toLowerCase());
|
|
85412
86383
|
filesToCheck = filesToCheck.filter((file3) => {
|
|
85413
|
-
const ext =
|
|
86384
|
+
const ext = path96.extname(file3.path).toLowerCase();
|
|
85414
86385
|
const langDef = getLanguageForExtension(ext);
|
|
85415
86386
|
const fileProfile = getProfileForFile(file3.path);
|
|
85416
86387
|
const langId = fileProfile?.id || langDef?.id;
|
|
@@ -85423,7 +86394,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85423
86394
|
let skippedCount = 0;
|
|
85424
86395
|
for (const fileInfo of filesToCheck) {
|
|
85425
86396
|
const { path: filePath } = fileInfo;
|
|
85426
|
-
const fullPath =
|
|
86397
|
+
const fullPath = path96.isAbsolute(filePath) ? filePath : path96.join(directory, filePath);
|
|
85427
86398
|
const result = {
|
|
85428
86399
|
path: filePath,
|
|
85429
86400
|
language: "",
|
|
@@ -85453,7 +86424,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85453
86424
|
}
|
|
85454
86425
|
let content;
|
|
85455
86426
|
try {
|
|
85456
|
-
content =
|
|
86427
|
+
content = fs80.readFileSync(fullPath, "utf8");
|
|
85457
86428
|
} catch {
|
|
85458
86429
|
result.skipped_reason = "file_read_error";
|
|
85459
86430
|
skippedCount++;
|
|
@@ -85472,7 +86443,7 @@ async function syntaxCheck(input, directory, config3) {
|
|
|
85472
86443
|
results.push(result);
|
|
85473
86444
|
continue;
|
|
85474
86445
|
}
|
|
85475
|
-
const ext =
|
|
86446
|
+
const ext = path96.extname(filePath).toLowerCase();
|
|
85476
86447
|
const langDef = getLanguageForExtension(ext);
|
|
85477
86448
|
result.language = profile?.id || langDef?.id || "unknown";
|
|
85478
86449
|
const errors5 = extractSyntaxErrors(parser, content);
|
|
@@ -85564,8 +86535,8 @@ init_dist();
|
|
|
85564
86535
|
init_utils();
|
|
85565
86536
|
init_create_tool();
|
|
85566
86537
|
init_path_security();
|
|
85567
|
-
import * as
|
|
85568
|
-
import * as
|
|
86538
|
+
import * as fs81 from "fs";
|
|
86539
|
+
import * as path97 from "path";
|
|
85569
86540
|
var MAX_TEXT_LENGTH = 200;
|
|
85570
86541
|
var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
|
|
85571
86542
|
var SUPPORTED_EXTENSIONS4 = new Set([
|
|
@@ -85631,9 +86602,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
85631
86602
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
85632
86603
|
}
|
|
85633
86604
|
try {
|
|
85634
|
-
const resolvedPath =
|
|
85635
|
-
const normalizedCwd =
|
|
85636
|
-
const normalizedResolved =
|
|
86605
|
+
const resolvedPath = path97.resolve(paths);
|
|
86606
|
+
const normalizedCwd = path97.resolve(cwd);
|
|
86607
|
+
const normalizedResolved = path97.resolve(resolvedPath);
|
|
85637
86608
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
85638
86609
|
return {
|
|
85639
86610
|
error: "paths must be within the current working directory",
|
|
@@ -85649,13 +86620,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
85649
86620
|
}
|
|
85650
86621
|
}
|
|
85651
86622
|
function isSupportedExtension(filePath) {
|
|
85652
|
-
const ext =
|
|
86623
|
+
const ext = path97.extname(filePath).toLowerCase();
|
|
85653
86624
|
return SUPPORTED_EXTENSIONS4.has(ext);
|
|
85654
86625
|
}
|
|
85655
86626
|
function findSourceFiles4(dir, files = []) {
|
|
85656
86627
|
let entries;
|
|
85657
86628
|
try {
|
|
85658
|
-
entries =
|
|
86629
|
+
entries = fs81.readdirSync(dir);
|
|
85659
86630
|
} catch {
|
|
85660
86631
|
return files;
|
|
85661
86632
|
}
|
|
@@ -85664,10 +86635,10 @@ function findSourceFiles4(dir, files = []) {
|
|
|
85664
86635
|
if (SKIP_DIRECTORIES5.has(entry)) {
|
|
85665
86636
|
continue;
|
|
85666
86637
|
}
|
|
85667
|
-
const fullPath =
|
|
86638
|
+
const fullPath = path97.join(dir, entry);
|
|
85668
86639
|
let stat4;
|
|
85669
86640
|
try {
|
|
85670
|
-
stat4 =
|
|
86641
|
+
stat4 = fs81.statSync(fullPath);
|
|
85671
86642
|
} catch {
|
|
85672
86643
|
continue;
|
|
85673
86644
|
}
|
|
@@ -85760,7 +86731,7 @@ var todo_extract = createSwarmTool({
|
|
|
85760
86731
|
return JSON.stringify(errorResult, null, 2);
|
|
85761
86732
|
}
|
|
85762
86733
|
const scanPath = resolvedPath;
|
|
85763
|
-
if (!
|
|
86734
|
+
if (!fs81.existsSync(scanPath)) {
|
|
85764
86735
|
const errorResult = {
|
|
85765
86736
|
error: `path not found: ${pathsInput}`,
|
|
85766
86737
|
total: 0,
|
|
@@ -85770,13 +86741,13 @@ var todo_extract = createSwarmTool({
|
|
|
85770
86741
|
return JSON.stringify(errorResult, null, 2);
|
|
85771
86742
|
}
|
|
85772
86743
|
const filesToScan = [];
|
|
85773
|
-
const stat4 =
|
|
86744
|
+
const stat4 = fs81.statSync(scanPath);
|
|
85774
86745
|
if (stat4.isFile()) {
|
|
85775
86746
|
if (isSupportedExtension(scanPath)) {
|
|
85776
86747
|
filesToScan.push(scanPath);
|
|
85777
86748
|
} else {
|
|
85778
86749
|
const errorResult = {
|
|
85779
|
-
error: `unsupported file extension: ${
|
|
86750
|
+
error: `unsupported file extension: ${path97.extname(scanPath)}`,
|
|
85780
86751
|
total: 0,
|
|
85781
86752
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
85782
86753
|
entries: []
|
|
@@ -85789,11 +86760,11 @@ var todo_extract = createSwarmTool({
|
|
|
85789
86760
|
const allEntries = [];
|
|
85790
86761
|
for (const filePath of filesToScan) {
|
|
85791
86762
|
try {
|
|
85792
|
-
const fileStat =
|
|
86763
|
+
const fileStat = fs81.statSync(filePath);
|
|
85793
86764
|
if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
|
|
85794
86765
|
continue;
|
|
85795
86766
|
}
|
|
85796
|
-
const content =
|
|
86767
|
+
const content = fs81.readFileSync(filePath, "utf-8");
|
|
85797
86768
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
85798
86769
|
allEntries.push(...entries);
|
|
85799
86770
|
} catch {}
|
|
@@ -85823,18 +86794,18 @@ init_tool();
|
|
|
85823
86794
|
init_loader();
|
|
85824
86795
|
init_schema();
|
|
85825
86796
|
init_gate_evidence();
|
|
85826
|
-
import * as
|
|
85827
|
-
import * as
|
|
86797
|
+
import * as fs83 from "fs";
|
|
86798
|
+
import * as path99 from "path";
|
|
85828
86799
|
|
|
85829
86800
|
// src/hooks/diff-scope.ts
|
|
85830
|
-
import * as
|
|
85831
|
-
import * as
|
|
86801
|
+
import * as fs82 from "fs";
|
|
86802
|
+
import * as path98 from "path";
|
|
85832
86803
|
function getDeclaredScope(taskId, directory) {
|
|
85833
86804
|
try {
|
|
85834
|
-
const planPath =
|
|
85835
|
-
if (!
|
|
86805
|
+
const planPath = path98.join(directory, ".swarm", "plan.json");
|
|
86806
|
+
if (!fs82.existsSync(planPath))
|
|
85836
86807
|
return null;
|
|
85837
|
-
const raw =
|
|
86808
|
+
const raw = fs82.readFileSync(planPath, "utf-8");
|
|
85838
86809
|
const plan = JSON.parse(raw);
|
|
85839
86810
|
for (const phase of plan.phases ?? []) {
|
|
85840
86811
|
for (const task of phase.tasks ?? []) {
|
|
@@ -85950,7 +86921,7 @@ var TIER_3_PATTERNS = [
|
|
|
85950
86921
|
];
|
|
85951
86922
|
function matchesTier3Pattern(files) {
|
|
85952
86923
|
for (const file3 of files) {
|
|
85953
|
-
const fileName =
|
|
86924
|
+
const fileName = path99.basename(file3);
|
|
85954
86925
|
for (const pattern of TIER_3_PATTERNS) {
|
|
85955
86926
|
if (pattern.test(fileName)) {
|
|
85956
86927
|
return true;
|
|
@@ -85964,8 +86935,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
85964
86935
|
if (hasActiveTurboMode()) {
|
|
85965
86936
|
const resolvedDir2 = workingDirectory;
|
|
85966
86937
|
try {
|
|
85967
|
-
const planPath =
|
|
85968
|
-
const planRaw =
|
|
86938
|
+
const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
|
|
86939
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
85969
86940
|
const plan = JSON.parse(planRaw);
|
|
85970
86941
|
for (const planPhase of plan.phases ?? []) {
|
|
85971
86942
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -86034,8 +87005,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
86034
87005
|
}
|
|
86035
87006
|
try {
|
|
86036
87007
|
const resolvedDir2 = workingDirectory;
|
|
86037
|
-
const planPath =
|
|
86038
|
-
const planRaw =
|
|
87008
|
+
const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
|
|
87009
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
86039
87010
|
const plan = JSON.parse(planRaw);
|
|
86040
87011
|
for (const planPhase of plan.phases ?? []) {
|
|
86041
87012
|
for (const task of planPhase.tasks ?? []) {
|
|
@@ -86259,8 +87230,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86259
87230
|
};
|
|
86260
87231
|
}
|
|
86261
87232
|
}
|
|
86262
|
-
normalizedDir =
|
|
86263
|
-
const pathParts = normalizedDir.split(
|
|
87233
|
+
normalizedDir = path99.normalize(args2.working_directory);
|
|
87234
|
+
const pathParts = normalizedDir.split(path99.sep);
|
|
86264
87235
|
if (pathParts.includes("..")) {
|
|
86265
87236
|
return {
|
|
86266
87237
|
success: false,
|
|
@@ -86270,11 +87241,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86270
87241
|
]
|
|
86271
87242
|
};
|
|
86272
87243
|
}
|
|
86273
|
-
const resolvedDir =
|
|
87244
|
+
const resolvedDir = path99.resolve(normalizedDir);
|
|
86274
87245
|
try {
|
|
86275
|
-
const realPath =
|
|
86276
|
-
const planPath =
|
|
86277
|
-
if (!
|
|
87246
|
+
const realPath = fs83.realpathSync(resolvedDir);
|
|
87247
|
+
const planPath = path99.join(realPath, ".swarm", "plan.json");
|
|
87248
|
+
if (!fs83.existsSync(planPath)) {
|
|
86278
87249
|
return {
|
|
86279
87250
|
success: false,
|
|
86280
87251
|
message: `Invalid working_directory: plan not found in "${realPath}"`,
|
|
@@ -86305,22 +87276,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86305
87276
|
}
|
|
86306
87277
|
if (args2.status === "in_progress") {
|
|
86307
87278
|
try {
|
|
86308
|
-
const evidencePath =
|
|
86309
|
-
|
|
86310
|
-
const fd =
|
|
87279
|
+
const evidencePath = path99.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
|
|
87280
|
+
fs83.mkdirSync(path99.dirname(evidencePath), { recursive: true });
|
|
87281
|
+
const fd = fs83.openSync(evidencePath, "wx");
|
|
86311
87282
|
let writeOk = false;
|
|
86312
87283
|
try {
|
|
86313
|
-
|
|
87284
|
+
fs83.writeSync(fd, JSON.stringify({
|
|
86314
87285
|
taskId: args2.task_id,
|
|
86315
87286
|
required_gates: ["reviewer", "test_engineer"],
|
|
86316
87287
|
gates: {}
|
|
86317
87288
|
}, null, 2));
|
|
86318
87289
|
writeOk = true;
|
|
86319
87290
|
} finally {
|
|
86320
|
-
|
|
87291
|
+
fs83.closeSync(fd);
|
|
86321
87292
|
if (!writeOk) {
|
|
86322
87293
|
try {
|
|
86323
|
-
|
|
87294
|
+
fs83.unlinkSync(evidencePath);
|
|
86324
87295
|
} catch {}
|
|
86325
87296
|
}
|
|
86326
87297
|
}
|
|
@@ -86330,8 +87301,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
86330
87301
|
recoverTaskStateFromDelegations(args2.task_id);
|
|
86331
87302
|
let phaseRequiresReviewer = true;
|
|
86332
87303
|
try {
|
|
86333
|
-
const planPath =
|
|
86334
|
-
const planRaw =
|
|
87304
|
+
const planPath = path99.join(directory, ".swarm", "plan.json");
|
|
87305
|
+
const planRaw = fs83.readFileSync(planPath, "utf-8");
|
|
86335
87306
|
const plan = JSON.parse(planRaw);
|
|
86336
87307
|
const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
|
|
86337
87308
|
if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
|
|
@@ -86433,6 +87404,216 @@ var update_task_status = createSwarmTool({
|
|
|
86433
87404
|
return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
|
|
86434
87405
|
}
|
|
86435
87406
|
});
|
|
87407
|
+
// src/tools/web-search.ts
|
|
87408
|
+
init_dist();
|
|
87409
|
+
init_zod();
|
|
87410
|
+
init_loader();
|
|
87411
|
+
|
|
87412
|
+
// src/council/web-search-provider.ts
|
|
87413
|
+
class WebSearchError extends Error {
|
|
87414
|
+
cause;
|
|
87415
|
+
constructor(message, cause) {
|
|
87416
|
+
super(message);
|
|
87417
|
+
this.cause = cause;
|
|
87418
|
+
this.name = "WebSearchError";
|
|
87419
|
+
}
|
|
87420
|
+
}
|
|
87421
|
+
|
|
87422
|
+
class WebSearchConfigError extends Error {
|
|
87423
|
+
constructor(message) {
|
|
87424
|
+
super(message);
|
|
87425
|
+
this.name = "WebSearchConfigError";
|
|
87426
|
+
}
|
|
87427
|
+
}
|
|
87428
|
+
|
|
87429
|
+
class TavilyProvider {
|
|
87430
|
+
apiKey;
|
|
87431
|
+
constructor(apiKey) {
|
|
87432
|
+
this.apiKey = apiKey;
|
|
87433
|
+
}
|
|
87434
|
+
async search(query, maxResults) {
|
|
87435
|
+
let response;
|
|
87436
|
+
try {
|
|
87437
|
+
response = await fetch("https://api.tavily.com/search", {
|
|
87438
|
+
method: "POST",
|
|
87439
|
+
headers: { "Content-Type": "application/json" },
|
|
87440
|
+
body: JSON.stringify({
|
|
87441
|
+
api_key: this.apiKey,
|
|
87442
|
+
query,
|
|
87443
|
+
max_results: maxResults,
|
|
87444
|
+
search_depth: "advanced"
|
|
87445
|
+
})
|
|
87446
|
+
});
|
|
87447
|
+
} catch (err3) {
|
|
87448
|
+
throw new WebSearchError(`Tavily network error for query "${query}"`, err3);
|
|
87449
|
+
}
|
|
87450
|
+
if (!response.ok) {
|
|
87451
|
+
throw new WebSearchError(`Tavily HTTP ${response.status} for query "${query}"`);
|
|
87452
|
+
}
|
|
87453
|
+
let body2;
|
|
87454
|
+
try {
|
|
87455
|
+
body2 = await response.json();
|
|
87456
|
+
} catch (err3) {
|
|
87457
|
+
throw new WebSearchError("Tavily returned non-JSON response", err3);
|
|
87458
|
+
}
|
|
87459
|
+
const results = body2?.results;
|
|
87460
|
+
if (!Array.isArray(results)) {
|
|
87461
|
+
return [];
|
|
87462
|
+
}
|
|
87463
|
+
return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.content === "string").map((r) => ({
|
|
87464
|
+
title: r.title,
|
|
87465
|
+
url: r.url,
|
|
87466
|
+
snippet: r.content,
|
|
87467
|
+
query
|
|
87468
|
+
}));
|
|
87469
|
+
}
|
|
87470
|
+
}
|
|
87471
|
+
|
|
87472
|
+
class BraveProvider {
|
|
87473
|
+
apiKey;
|
|
87474
|
+
constructor(apiKey) {
|
|
87475
|
+
this.apiKey = apiKey;
|
|
87476
|
+
}
|
|
87477
|
+
async search(query, maxResults) {
|
|
87478
|
+
const url3 = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
87479
|
+
url3.searchParams.set("q", query);
|
|
87480
|
+
url3.searchParams.set("count", String(maxResults));
|
|
87481
|
+
let response;
|
|
87482
|
+
try {
|
|
87483
|
+
response = await fetch(url3.toString(), {
|
|
87484
|
+
method: "GET",
|
|
87485
|
+
headers: {
|
|
87486
|
+
"X-Subscription-Token": this.apiKey,
|
|
87487
|
+
Accept: "application/json"
|
|
87488
|
+
}
|
|
87489
|
+
});
|
|
87490
|
+
} catch (err3) {
|
|
87491
|
+
throw new WebSearchError(`Brave network error for query "${query}"`, err3);
|
|
87492
|
+
}
|
|
87493
|
+
if (!response.ok) {
|
|
87494
|
+
throw new WebSearchError(`Brave HTTP ${response.status} for query "${query}"`);
|
|
87495
|
+
}
|
|
87496
|
+
let body2;
|
|
87497
|
+
try {
|
|
87498
|
+
body2 = await response.json();
|
|
87499
|
+
} catch (err3) {
|
|
87500
|
+
throw new WebSearchError("Brave returned non-JSON response", err3);
|
|
87501
|
+
}
|
|
87502
|
+
const results = body2?.web?.results;
|
|
87503
|
+
if (!Array.isArray(results)) {
|
|
87504
|
+
return [];
|
|
87505
|
+
}
|
|
87506
|
+
return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.description === "string").map((r) => ({
|
|
87507
|
+
title: r.title,
|
|
87508
|
+
url: r.url,
|
|
87509
|
+
snippet: r.description,
|
|
87510
|
+
query
|
|
87511
|
+
}));
|
|
87512
|
+
}
|
|
87513
|
+
}
|
|
87514
|
+
function resolveApiKey(provider, configKey) {
|
|
87515
|
+
if (configKey && configKey.length > 0) {
|
|
87516
|
+
return configKey;
|
|
87517
|
+
}
|
|
87518
|
+
const envName = provider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
|
|
87519
|
+
const fromEnv = process.env[envName];
|
|
87520
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : undefined;
|
|
87521
|
+
}
|
|
87522
|
+
function createWebSearchProvider(config3) {
|
|
87523
|
+
const apiKey = resolveApiKey(config3.searchProvider, config3.searchApiKey);
|
|
87524
|
+
if (!apiKey) {
|
|
87525
|
+
const envName = config3.searchProvider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
|
|
87526
|
+
throw new WebSearchConfigError(`No API key for search provider "${config3.searchProvider}". Set ` + `council.general.searchApiKey in opencode-swarm.json or export ${envName}.`);
|
|
87527
|
+
}
|
|
87528
|
+
switch (config3.searchProvider) {
|
|
87529
|
+
case "tavily":
|
|
87530
|
+
return new TavilyProvider(apiKey);
|
|
87531
|
+
case "brave":
|
|
87532
|
+
return new BraveProvider(apiKey);
|
|
87533
|
+
}
|
|
87534
|
+
}
|
|
87535
|
+
|
|
87536
|
+
// src/tools/web-search.ts
|
|
87537
|
+
init_create_tool();
|
|
87538
|
+
init_resolve_working_directory();
|
|
87539
|
+
var MAX_RESULTS_HARD_CAP = 10;
|
|
87540
|
+
var ArgsSchema4 = exports_external.object({
|
|
87541
|
+
query: exports_external.string().min(1).max(500),
|
|
87542
|
+
max_results: exports_external.number().int().min(1).max(20).optional(),
|
|
87543
|
+
working_directory: exports_external.string().optional()
|
|
87544
|
+
});
|
|
87545
|
+
var web_search = createSwarmTool({
|
|
87546
|
+
description: "External web search for council member agents. Returns titled results with snippets and URLs. " + "Restricted to council_member agents via AGENT_TOOL_MAP. Requires council.general.enabled and a " + "configured search API key (Tavily or Brave). max_results is capped at 10 with default from council.general.maxSourcesPerMember.",
|
|
87547
|
+
args: {
|
|
87548
|
+
query: tool.schema.string().min(1).max(500).describe("Search query string (1\u2013500 characters)."),
|
|
87549
|
+
max_results: tool.schema.number().int().min(1).max(20).optional().describe(`Number of results to request (1\u201320). Hard-capped at ${MAX_RESULTS_HARD_CAP}. Defaults to council.general.maxSourcesPerMember.`),
|
|
87550
|
+
working_directory: tool.schema.string().optional().describe("Project root for config resolution. Optional.")
|
|
87551
|
+
},
|
|
87552
|
+
execute: async (args2, directory) => {
|
|
87553
|
+
const parsed = ArgsSchema4.safeParse(args2);
|
|
87554
|
+
if (!parsed.success) {
|
|
87555
|
+
const fail = {
|
|
87556
|
+
success: false,
|
|
87557
|
+
reason: "invalid_args",
|
|
87558
|
+
message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
|
|
87559
|
+
};
|
|
87560
|
+
return JSON.stringify(fail, null, 2);
|
|
87561
|
+
}
|
|
87562
|
+
const dirResult = resolveWorkingDirectory(parsed.data.working_directory, directory);
|
|
87563
|
+
if (!dirResult.success) {
|
|
87564
|
+
const fail = {
|
|
87565
|
+
success: false,
|
|
87566
|
+
reason: "invalid_working_directory",
|
|
87567
|
+
message: dirResult.message
|
|
87568
|
+
};
|
|
87569
|
+
return JSON.stringify(fail, null, 2);
|
|
87570
|
+
}
|
|
87571
|
+
const config3 = loadPluginConfig(dirResult.directory);
|
|
87572
|
+
const generalConfig = config3.council?.general;
|
|
87573
|
+
if (!generalConfig || generalConfig.enabled !== true) {
|
|
87574
|
+
const fail = {
|
|
87575
|
+
success: false,
|
|
87576
|
+
reason: "council_general_disabled",
|
|
87577
|
+
message: "web_search is disabled \u2014 set council.general.enabled: true in opencode-swarm.json."
|
|
87578
|
+
};
|
|
87579
|
+
return JSON.stringify(fail, null, 2);
|
|
87580
|
+
}
|
|
87581
|
+
const requested = parsed.data.max_results ?? generalConfig.maxSourcesPerMember;
|
|
87582
|
+
const maxResults = Math.min(requested, MAX_RESULTS_HARD_CAP);
|
|
87583
|
+
let provider;
|
|
87584
|
+
try {
|
|
87585
|
+
provider = createWebSearchProvider(generalConfig);
|
|
87586
|
+
} catch (err3) {
|
|
87587
|
+
const fail = {
|
|
87588
|
+
success: false,
|
|
87589
|
+
reason: err3 instanceof WebSearchConfigError ? "missing_api_key" : "provider_init_failed",
|
|
87590
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
87591
|
+
};
|
|
87592
|
+
return JSON.stringify(fail, null, 2);
|
|
87593
|
+
}
|
|
87594
|
+
try {
|
|
87595
|
+
const results = await provider.search(parsed.data.query, maxResults);
|
|
87596
|
+
const ok2 = {
|
|
87597
|
+
success: true,
|
|
87598
|
+
query: parsed.data.query,
|
|
87599
|
+
totalResults: results.length,
|
|
87600
|
+
results: results.map(({ title, url: url3, snippet }) => ({
|
|
87601
|
+
title,
|
|
87602
|
+
url: url3,
|
|
87603
|
+
snippet
|
|
87604
|
+
}))
|
|
87605
|
+
};
|
|
87606
|
+
return JSON.stringify(ok2, null, 2);
|
|
87607
|
+
} catch (err3) {
|
|
87608
|
+
const fail = {
|
|
87609
|
+
success: false,
|
|
87610
|
+
reason: err3 instanceof WebSearchError ? "search_failed" : "unknown",
|
|
87611
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
87612
|
+
};
|
|
87613
|
+
return JSON.stringify(fail, null, 2);
|
|
87614
|
+
}
|
|
87615
|
+
}
|
|
87616
|
+
});
|
|
86436
87617
|
// src/tools/write-drift-evidence.ts
|
|
86437
87618
|
init_tool();
|
|
86438
87619
|
init_qa_gate_profile();
|
|
@@ -86440,8 +87621,8 @@ init_utils2();
|
|
|
86440
87621
|
init_ledger();
|
|
86441
87622
|
init_manager();
|
|
86442
87623
|
init_create_tool();
|
|
86443
|
-
import
|
|
86444
|
-
import
|
|
87624
|
+
import fs84 from "fs";
|
|
87625
|
+
import path100 from "path";
|
|
86445
87626
|
function derivePlanId5(plan) {
|
|
86446
87627
|
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
86447
87628
|
}
|
|
@@ -86492,7 +87673,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
86492
87673
|
entries: [evidenceEntry]
|
|
86493
87674
|
};
|
|
86494
87675
|
const filename = "drift-verifier.json";
|
|
86495
|
-
const relativePath =
|
|
87676
|
+
const relativePath = path100.join("evidence", String(phase), filename);
|
|
86496
87677
|
let validatedPath;
|
|
86497
87678
|
try {
|
|
86498
87679
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86503,12 +87684,12 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
86503
87684
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86504
87685
|
}, null, 2);
|
|
86505
87686
|
}
|
|
86506
|
-
const evidenceDir =
|
|
87687
|
+
const evidenceDir = path100.dirname(validatedPath);
|
|
86507
87688
|
try {
|
|
86508
|
-
await
|
|
86509
|
-
const tempPath =
|
|
86510
|
-
await
|
|
86511
|
-
await
|
|
87689
|
+
await fs84.promises.mkdir(evidenceDir, { recursive: true });
|
|
87690
|
+
const tempPath = path100.join(evidenceDir, `.${filename}.tmp`);
|
|
87691
|
+
await fs84.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87692
|
+
await fs84.promises.rename(tempPath, validatedPath);
|
|
86512
87693
|
let snapshotInfo;
|
|
86513
87694
|
let snapshotError;
|
|
86514
87695
|
let qaProfileLocked;
|
|
@@ -86602,8 +87783,8 @@ var write_drift_evidence = createSwarmTool({
|
|
|
86602
87783
|
init_tool();
|
|
86603
87784
|
init_utils2();
|
|
86604
87785
|
init_create_tool();
|
|
86605
|
-
import
|
|
86606
|
-
import
|
|
87786
|
+
import fs85 from "fs";
|
|
87787
|
+
import path101 from "path";
|
|
86607
87788
|
function normalizeVerdict2(verdict) {
|
|
86608
87789
|
switch (verdict) {
|
|
86609
87790
|
case "APPROVED":
|
|
@@ -86651,7 +87832,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
86651
87832
|
entries: [evidenceEntry]
|
|
86652
87833
|
};
|
|
86653
87834
|
const filename = "hallucination-guard.json";
|
|
86654
|
-
const relativePath =
|
|
87835
|
+
const relativePath = path101.join("evidence", String(phase), filename);
|
|
86655
87836
|
let validatedPath;
|
|
86656
87837
|
try {
|
|
86657
87838
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86662,12 +87843,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
86662
87843
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86663
87844
|
}, null, 2);
|
|
86664
87845
|
}
|
|
86665
|
-
const evidenceDir =
|
|
87846
|
+
const evidenceDir = path101.dirname(validatedPath);
|
|
86666
87847
|
try {
|
|
86667
|
-
await
|
|
86668
|
-
const tempPath =
|
|
86669
|
-
await
|
|
86670
|
-
await
|
|
87848
|
+
await fs85.promises.mkdir(evidenceDir, { recursive: true });
|
|
87849
|
+
const tempPath = path101.join(evidenceDir, `.${filename}.tmp`);
|
|
87850
|
+
await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87851
|
+
await fs85.promises.rename(tempPath, validatedPath);
|
|
86671
87852
|
return JSON.stringify({
|
|
86672
87853
|
success: true,
|
|
86673
87854
|
phase,
|
|
@@ -86713,8 +87894,8 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
86713
87894
|
init_tool();
|
|
86714
87895
|
init_utils2();
|
|
86715
87896
|
init_create_tool();
|
|
86716
|
-
import
|
|
86717
|
-
import
|
|
87897
|
+
import fs86 from "fs";
|
|
87898
|
+
import path102 from "path";
|
|
86718
87899
|
function normalizeVerdict3(verdict) {
|
|
86719
87900
|
switch (verdict) {
|
|
86720
87901
|
case "PASS":
|
|
@@ -86788,7 +87969,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
86788
87969
|
entries: [evidenceEntry]
|
|
86789
87970
|
};
|
|
86790
87971
|
const filename = "mutation-gate.json";
|
|
86791
|
-
const relativePath =
|
|
87972
|
+
const relativePath = path102.join("evidence", String(phase), filename);
|
|
86792
87973
|
let validatedPath;
|
|
86793
87974
|
try {
|
|
86794
87975
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -86799,12 +87980,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
86799
87980
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
86800
87981
|
}, null, 2);
|
|
86801
87982
|
}
|
|
86802
|
-
const evidenceDir =
|
|
87983
|
+
const evidenceDir = path102.dirname(validatedPath);
|
|
86803
87984
|
try {
|
|
86804
|
-
await
|
|
86805
|
-
const tempPath =
|
|
86806
|
-
await
|
|
86807
|
-
await
|
|
87985
|
+
await fs86.promises.mkdir(evidenceDir, { recursive: true });
|
|
87986
|
+
const tempPath = path102.join(evidenceDir, `.${filename}.tmp`);
|
|
87987
|
+
await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
87988
|
+
await fs86.promises.rename(tempPath, validatedPath);
|
|
86808
87989
|
return JSON.stringify({
|
|
86809
87990
|
success: true,
|
|
86810
87991
|
phase,
|
|
@@ -87022,7 +88203,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87022
88203
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
87023
88204
|
preflightTriggerManager = new PTM(automationConfig);
|
|
87024
88205
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
87025
|
-
const swarmDir =
|
|
88206
|
+
const swarmDir = path103.resolve(ctx.directory, ".swarm");
|
|
87026
88207
|
statusArtifact = new ASA(swarmDir);
|
|
87027
88208
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
87028
88209
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -87128,6 +88309,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87128
88309
|
completion_verify,
|
|
87129
88310
|
complexity_hotspots,
|
|
87130
88311
|
convene_council,
|
|
88312
|
+
convene_general_council,
|
|
87131
88313
|
curator_analyze,
|
|
87132
88314
|
declare_council_criteria,
|
|
87133
88315
|
knowledge_add,
|
|
@@ -87174,6 +88356,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
87174
88356
|
build_check,
|
|
87175
88357
|
suggest_patch: suggestPatch,
|
|
87176
88358
|
update_task_status,
|
|
88359
|
+
web_search,
|
|
87177
88360
|
write_retro,
|
|
87178
88361
|
write_drift_evidence,
|
|
87179
88362
|
write_hallucination_evidence,
|