opencode-swarm 7.99.4 → 7.99.6

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/index.js CHANGED
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.99.4",
72
+ version: "7.99.6",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -17516,21 +17516,6 @@ var init_bundled_skills = __esm(() => {
17516
17516
  syncedProjectSkillTargets = new Set;
17517
17517
  });
17518
17518
 
17519
- // src/utils/errors.ts
17520
- var SwarmError;
17521
- var init_errors3 = __esm(() => {
17522
- SwarmError = class SwarmError extends Error {
17523
- code;
17524
- guidance;
17525
- constructor(message, code, guidance) {
17526
- super(message);
17527
- this.name = "SwarmError";
17528
- this.code = code;
17529
- this.guidance = guidance;
17530
- }
17531
- };
17532
- });
17533
-
17534
17519
  // src/utils/logger.ts
17535
17520
  function isDebug() {
17536
17521
  return process.env.OPENCODE_SWARM_DEBUG === "1";
@@ -17573,6 +17558,21 @@ function error48(message, data) {
17573
17558
  }
17574
17559
  var init_logger = () => {};
17575
17560
 
17561
+ // src/utils/errors.ts
17562
+ var SwarmError;
17563
+ var init_errors3 = __esm(() => {
17564
+ SwarmError = class SwarmError extends Error {
17565
+ code;
17566
+ guidance;
17567
+ constructor(message, code, guidance) {
17568
+ super(message);
17569
+ this.name = "SwarmError";
17570
+ this.code = code;
17571
+ this.guidance = guidance;
17572
+ }
17573
+ };
17574
+ });
17575
+
17576
17576
  // src/utils/regex.ts
17577
17577
  function escapeRegex2(s) {
17578
17578
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -77880,6 +77880,21 @@ function emitObjectTypeMismatch(key, value, findings) {
77880
77880
  });
77881
77881
  }
77882
77882
  }
77883
+ function emitWorktreeIsolationLayeringAdvisory(config3, findings) {
77884
+ const parallelization = config3.parallelization;
77885
+ const worktreePolicy = config3.worktree?.policy ?? "auto";
77886
+ if (parallelization?.enabled === true && (parallelization.maxConcurrentTasks ?? 1) > 1 && worktreePolicy !== "disabled") {
77887
+ findings.push({
77888
+ id: "worktree-isolation-baseline-active",
77889
+ title: "Worktree isolation is already active for standard parallel coders",
77890
+ description: "Standard parallel coders already use baseline worktree isolation through the parallel execution profile plus top-level worktree.policy. Lean Turbo and Epic are additive strategies, not requirements for obtaining worktree isolation.",
77891
+ severity: "warn",
77892
+ path: "worktree.policy",
77893
+ currentValue: worktreePolicy,
77894
+ autoFixable: false
77895
+ });
77896
+ }
77897
+ }
77883
77898
  function getUserConfigDir3() {
77884
77899
  return process.env.XDG_CONFIG_HOME || path69.join(os15.homedir(), ".config");
77885
77900
  }
@@ -78440,6 +78455,14 @@ function validateConfigKey(path70, value) {
78440
78455
  emitObjectTypeMismatch("summaries", value, findings);
78441
78456
  break;
78442
78457
  }
78458
+ case "auto_review": {
78459
+ emitObjectTypeMismatch("auto_review", value, findings);
78460
+ break;
78461
+ }
78462
+ case "repo_graph": {
78463
+ emitObjectTypeMismatch("repo_graph", value, findings);
78464
+ break;
78465
+ }
78443
78466
  case "review_passes": {
78444
78467
  emitObjectTypeMismatch("review_passes", value, findings);
78445
78468
  break;
@@ -78516,6 +78539,10 @@ function validateConfigKey(path70, value) {
78516
78539
  emitObjectTypeMismatch("skill_improver", value, findings);
78517
78540
  break;
78518
78541
  }
78542
+ case "skills": {
78543
+ emitObjectTypeMismatch("skills", value, findings);
78544
+ break;
78545
+ }
78519
78546
  case "spec_writer": {
78520
78547
  emitObjectTypeMismatch("spec_writer", value, findings);
78521
78548
  break;
@@ -78701,6 +78728,7 @@ function walkConfigAndValidate(obj, path70, findings, visited = new WeakSet) {
78701
78728
  function runConfigDoctor(config3, directory) {
78702
78729
  const findings = [];
78703
78730
  walkConfigAndValidate(config3, "", findings);
78731
+ emitWorktreeIsolationLayeringAdvisory(config3, findings);
78704
78732
  const summary = {
78705
78733
  info: findings.filter((f) => f.severity === "info").length,
78706
78734
  warn: findings.filter((f) => f.severity === "warn").length,
@@ -101057,6 +101085,13 @@ function findSimilarCommands(query) {
101057
101085
  scored.sort((a, b) => a.score - b.score);
101058
101086
  return scored.slice(0, 3).map((s) => s.cmd);
101059
101087
  }
101088
+ function emitValidationWarnings(prefix, warnings) {
101089
+ if (warnings.length === 0)
101090
+ return;
101091
+ warn(`${prefix}:
101092
+ ${warnings.join(`
101093
+ `)}`);
101094
+ }
101060
101095
  function buildDetailedHelp(commandName, entry) {
101061
101096
  const lines = [];
101062
101097
  lines.push(`## /swarm ${commandName}`, "");
@@ -101191,6 +101226,7 @@ function resolveCommand(tokens) {
101191
101226
  var COMMAND_REGISTRY, VALID_COMMANDS, _internals15, validation;
101192
101227
  var init_registry = __esm(() => {
101193
101228
  init_bundled_skills();
101229
+ init_logger();
101194
101230
  init_acknowledge_spec_drift();
101195
101231
  init_agents();
101196
101232
  init_archive();
@@ -101693,7 +101729,7 @@ Subcommands:
101693
101729
  handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handlePrReviewCommand),
101694
101730
  description: "Launch deep PR review with multi-lane analysis [url] [--council]",
101695
101731
  args: "<pr-url|owner/repo#N|N> [--council]",
101696
- details: "Launches a structured PR review: reconstructs PR intent via obligation extraction cascade, runs 6 parallel explorer lanes through the deterministic dispatch_lanes join barrier (correctness, security, dependencies, docs-intent-vs-actual, tests, performance-architecture), validates findings through independent reviewer confirmation, applies critic challenge to HIGH/CRITICAL findings, synthesizes structured report. --council variant fires adversarial multi-model review. Supports full GitHub URL, owner/repo#N shorthand, or bare PR number (resolves against origin remote).",
101732
+ details: "Launches a structured PR review: reconstructs PR intent via obligation extraction cascade, launches all 6 fixed base explorer lanes through dispatch_lanes_async while the architect keeps doing non-dependent work, polls collect_lane_results incrementally, runs every triggered micro-lane, validates findings through independent reviewer confirmation, applies critic challenge to HIGH/CRITICAL findings, then synthesizes only after coverage is closed. If lane tools cannot close coverage, Task-tool dispatch is the final verified-equivalent fallback; if equivalence cannot be proven, the review is BLOCKED rather than degraded. --council variant fires adversarial multi-model review. Supports full GitHub URL, owner/repo#N shorthand, or bare PR number (resolves against origin remote).",
101697
101733
  category: "agent",
101698
101734
  toolPolicy: "none"
101699
101735
  },
@@ -102119,6 +102155,7 @@ Subcommands:
102119
102155
  handleHelpCommand,
102120
102156
  validateAliases,
102121
102157
  validateToolPolicy,
102158
+ emitValidationWarnings,
102122
102159
  resolveCommand,
102123
102160
  levenshteinDistance: levenshteinDistance2,
102124
102161
  findSimilarCommands,
@@ -102130,20 +102167,12 @@ Subcommands:
102130
102167
  ${validation.errors.join(`
102131
102168
  `)}`);
102132
102169
  }
102133
- if (validation.warnings.length > 0) {
102134
- console.warn(`COMMAND_REGISTRY alias warnings:
102135
- ${validation.warnings.join(`
102136
- `)}`);
102137
- }
102170
+ _internals15.emitValidationWarnings("COMMAND_REGISTRY alias warnings", validation.warnings);
102138
102171
  try {
102139
102172
  const toolPolicyValidation = _internals15.validateToolPolicy();
102140
- if (toolPolicyValidation.warnings.length > 0) {
102141
- console.warn(`COMMAND_REGISTRY toolPolicy warnings:
102142
- ${toolPolicyValidation.warnings.join(`
102143
- `)}`);
102144
- }
102173
+ _internals15.emitValidationWarnings("COMMAND_REGISTRY toolPolicy warnings", toolPolicyValidation.warnings);
102145
102174
  } catch (e) {
102146
- console.warn(`COMMAND_REGISTRY toolPolicy validation failed (non-fatal): ${e.message}`);
102175
+ warn(`COMMAND_REGISTRY toolPolicy validation failed (non-fatal): ${e.message}`);
102147
102176
  }
102148
102177
  });
102149
102178
 
@@ -102726,6 +102755,8 @@ If a tool modifies a file, it is a CODER tool. Delegate.
102726
102755
  2. ONE agent per message. Send, STOP, wait for response.
102727
102756
  Exception: Stage B reviewer/test_engineer gate agents for the SAME completed coder task may be dispatched together before waiting when both gates are required. This exception NEVER applies to coder delegations. Preserve ONE task per coder call.
102728
102757
  Separate parallel-mode exception (distinct from the Stage B exception above, and the ONLY case where more than one coder may be dispatched before waiting): when an active \`[PARALLEL EXECUTION PROFILE]\` directive is present in your context (parallelization_enabled=true), you MAY dispatch multiple {{AGENT_PREFIX}}coder agents in a single message — up to the stated max_concurrent_tasks — but ONLY for distinct, dependency-ready tasks whose declared file scopes do NOT overlap. Each coder still requires its own \`declare_scope\` call and carries exactly ONE task (Rule 3 still holds: never batch multiple objectives into one coder). Parallel coders each run in an isolated git worktree, so their writes never collide and are merged back automatically. If no \`[PARALLEL EXECUTION PROFILE]\` directive is present, dispatch coders one at a time.
102758
+
102759
+ > **WORKTREE ISOLATION IS BASELINE.** Standard parallel coders use isolated git worktrees by default; this is governed by the top-level \`worktree.policy\` setting (default \`auto\`) in \`PluginConfig\` — a sibling of \`parallelization:\`, not nested under it — and is active whenever the plan's \`parallelization_enabled=true\`. \`turbo.lean.worktree_isolation\` is a separate, Lean-Turbo-internal flag (default \`false\`); it is one possible SOURCE but NOT the recommended one. Do NOT recommend Lean Turbo (or Epic) SOLELY to obtain worktree isolation; recommend them only for what they add beyond baseline (Lean Turbo: lane planning, file locks, phase reviewer, integrated diff; Epic: co-change awareness + auto-decide). Lean Turbo users can also enable isolation via \`turbo.lean.worktree_isolation: true\`, but this is the secondary/legacy path — the recommended path is \`worktree.policy\`.
102729
102760
  Read-only advisory-lane exception (NON-BLOCKING; distinct from both exceptions above): the "Send, STOP, wait" rule governs MUTATION delegations (coder, and the test_engineer/reviewer Stage B completion gates). It does NOT govern read-only advisory exploration/review lanes. When you dispatch read-only advisory lanes — \`{{AGENT_PREFIX}}explorer\`, \`{{AGENT_PREFIX}}sme\`, \`{{AGENT_PREFIX}}researcher\`, the council members (\`council_generalist\`/\`council_skeptic\`/\`council_domain_expert\`), or an advisory \`{{AGENT_PREFIX}}critic\` lane — use the NON-BLOCKING path so you keep working while they run. Dispatch PROMPTLY: emit the \`dispatch_lanes_async\` call EARLY with compact lane prompts — do not accumulate long planning prose or build oversized inline prompts first, or the tool call can be truncated out of your message and the lanes never launch (a real failure mode on smaller models). The lane mechanism is a SINGLE \`dispatch_lanes_async\` call carrying all lane specs — NOT a per-agent Task/run-in-background pattern. Call \`dispatch_lanes_async\` with all lane specs in one call, record the returned \`batch_id\`, then IMMEDIATELY continue non-dependent architect work (refine the plan/obligation ledger, inspect metadata, prepare the synthesis/reviewer structure, run deterministic read-only tools). Poll incrementally with \`collect_lane_results\` without \`wait\` (or with \`wait: false\`) to harvest lanes as they settle; process completed lane output immediately while other lanes remain pending/running, then continue independent work between polls. Do NOT sit idle waiting on running lanes, and do NOT synthesize findings from still-running lanes. Join later by calling \`collect_lane_results\` with \`wait: true\` as the explicit barrier immediately before you synthesize. Use blocking \`dispatch_lanes\` only when \`dispatch_lanes_async\`/promptAsync is unavailable. Keep each lane prompt compact: send large shared context (PR diff, ledger, scope) ONCE via the \`common_prompt\` field, or have lanes read it from a file by absolute path, instead of inlining the same blob into every lane prompt — inlining large context into many lanes is what produces malformed or truncated tool-call JSON and forces clumsy file workarounds. This non-blocking exception applies ONLY to read-only advisory lanes; it NEVER applies to coder delegations, to the test_engineer/reviewer Stage B completion gates, or to the critic PLAN-review gate, which all still follow "Send, STOP, wait" (or the Stage B parallel-dispatch exception above).
102730
102761
  3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
102731
102762
  3a. PRE-DELEGATION SCOPE CALL (required): BEFORE every {{AGENT_PREFIX}}coder delegation, you MUST call \`declare_scope\` with { taskId, files } listing the exact file(s) this task will modify (including generated/lockfile paths). No \`declare_scope\` call → no coder delegation. See Rule 1a.
@@ -103331,6 +103362,7 @@ ACTION: Load skill file:.opencode/skills/pre-phase-briefing/SKILL.md immediately
103331
103362
 
103332
103363
  HARD CONSTRAINTS:
103333
103364
  - Complete the codebase reality report before spec finalization, plan generation, plan ingestion, declare_scope, or starting/resuming phase implementation. Dispatching the reality-check lanes asynchronously is allowed and preferred; settling all lanes before any of that downstream work is not optional.
103365
+ - When reality-check lanes are dispatched asynchronously, record the \`batch_id\`, keep doing non-dependent architect work, poll with \`collect_lane_results\` without \`wait\`, process settled lanes immediately, and use \`wait: true\` only when no independent work remains.
103334
103366
 
103335
103367
  ### MODE: COUNCIL
103336
103368
  Activates when the user invokes /swarm council or requests a council-style decision review.
@@ -103341,6 +103373,7 @@ ACTION: Load skill file:.opencode/skills/council/SKILL.md immediately. Follow th
103341
103373
 
103342
103374
  HARD CONSTRAINTS:
103343
103375
  - Provide research context up front and synthesize only from returned council member responses.
103376
+ - For async council lanes, record the \`batch_id\`, keep doing non-dependent architect work, poll with \`collect_lane_results\` without \`wait\`, process settled lanes immediately, and use \`wait: true\` only when no independent work remains.
103344
103377
 
103345
103378
  ### MODE: DEEP_DIVE
103346
103379
  Activates when: architect receives \`[MODE: DEEP_DIVE profile=X max_explorers=N output=X update_main=X allow_dirty=X] <scope>\` signal from the deep-dive command handler.
@@ -103357,6 +103390,7 @@ HARD CONSTRAINTS (apply regardless of skill load success):
103357
103390
  - No final finding may appear in the report without reviewer verification
103358
103391
  - Explorers generate candidate findings only — reviewers verify or reject
103359
103392
  - Critics challenge only HIGH/CRITICAL findings — do NOT waste cycles on lower severity
103393
+ - For async explorer waves, record the \`batch_id\`, keep doing non-dependent architect work, poll with \`collect_lane_results\` without \`wait\`, process settled lanes immediately, and use \`wait: true\` only when no independent work remains.
103360
103394
 
103361
103395
  ### MODE: LOOP
103362
103396
  Activates when: architect receives \`[MODE: LOOP max_cycles=N autonomy=checkpoint|auto depth=standard|exhaustive resume=true|false] <objective>\` signal from the loop command handler.
@@ -103390,6 +103424,8 @@ HARD CONSTRAINTS (apply regardless of skill load success):
103390
103424
  - Critics challenge only high-stakes / contested claims — do NOT waste cycles on well-supported ones
103391
103425
  - If council.general.enabled is false or no search API key is configured, surface that and STOP — do not produce ungrounded research
103392
103426
 
103427
+ - For async synthesis lanes, record the \`batch_id\`, keep doing non-dependent architect work, poll with \`collect_lane_results\` without \`wait\`, process settled lanes immediately, and use \`wait: true\` only when no independent work remains.
103428
+
103393
103429
  ### MODE: CODEBASE_REVIEW
103394
103430
  Activates when: architect receives \`[MODE: CODEBASE_REVIEW mode=X output=X update_main=X allow_dirty=X tracks="..." continue_run="..."] scope="..."\` signal from the codebase-review command handler.
103395
103431
 
@@ -103409,6 +103445,8 @@ HARD CONSTRAINTS (apply regardless of skill load success):
103409
103445
  - Every repo-derived factual claim needs quote-grounded evidence with file path and line/range
103410
103446
  - Final report is forbidden until selected-track coverage is closed and final critic passes
103411
103447
 
103448
+ - For async inventory or candidate-generation lanes, record the \`batch_id\`, keep doing non-dependent architect work, poll with \`collect_lane_results\` without \`wait\`, process settled lanes immediately, and use \`wait: true\` only when no independent work remains.
103449
+
103412
103450
  ### MODE: DESIGN_DOCS
103413
103451
  Activates when: architect receives \`[MODE: DESIGN_DOCS out=X lang=X update=X] <description>\` signal from the design-docs command handler (issue #1080).
103414
103452
 
@@ -103440,9 +103478,11 @@ HARD CONSTRAINTS (apply regardless of skill load success):
103440
103478
  - No finding may appear as CONFIRMED in the final report without reviewer validation provenance
103441
103479
  - Test execution, explorer lanes, reviewer dispatch, and critic challenge are all permitted within this mode
103442
103480
  - Quality is the only metric — time, tokens, and agent dispatches are irrelevant to correctness
103443
- - FOLLOW THE SKILL EXACTLY: execute every phase of the loaded SKILL.md in order with no shortcuts, no phase-skipping, and no premature synthesis. If a phase cannot complete, state the limitation explicitly and continue do not silently skip it.
103481
+ - FOLLOW THE SKILL EXACTLY: execute every phase of the loaded SKILL.md in order with no shortcuts, no phase-skipping, and no premature synthesis. If a required coverage phase cannot complete, apply the skill's coverage gate (retry or verified equivalent alternative). If the gap still cannot be closed, stop and surface the lane failure to the user as BLOCKED; do not produce a degraded review, partial verdict, or final synthesis.
103444
103482
  - CHECK OUT THE PR BRANCH LOCALLY before launching explorer lanes: fetch the PR head ref if it is not present, verify the working tree is clean (git status --porcelain) and stash/abort if not, then check out the head branch. Explorers read the working-tree filesystem (Read/Glob/Grep), so without a checkout they read the base branch and produce invalid candidates. Always pass the base..head commit range in explorer delegations.
103445
- - RUN THE TRIGGERED MICRO-LANES: after the base explorer lanes start, inspect the context pack risk triggers and launch every matching Swarm plugin micro-lane from the skill's risk-trigger map (launch only triggered lanes, never irrelevant ones). Do not skip micro-lanes that match the diff.
103483
+ - RUN ALL BASE LANES: the default PR_REVIEW path always launches the fixed six base check-type lanes from the skill (correctness, security, dependencies/deployment, docs/intent, tests, performance/architecture). Do not collapse, omit, or scale down the base lanes for a small, docs-only, or CI-only PR.
103484
+ - USE ASYNC DISPATCH WITHOUT IDLING: launch the base lanes with one \`dispatch_lanes_async\` call when available, record the \`batch_id\`, then keep doing non-dependent architect work while they run. Poll with \`collect_lane_results\` without \`wait\` (or \`wait: false\`) to process settled lanes and continue independent work between polls; use \`wait: true\` only as the final join when no independent work remains.
103485
+ - RUN THE TRIGGERED MICRO-LANES: after the base explorer lanes settle, inspect the context pack risk triggers and launch every matching Swarm plugin micro-lane from the skill's risk-trigger map (launch only triggered lanes, never irrelevant ones). Do not skip micro-lanes that match the diff; when multiple micro-lanes are needed, dispatch them with \`dispatch_lanes_async\` and the same non-idling incremental collection pattern.
103446
103486
  - Honor any free-text instructions that follow the closing bracket of the signal as additional reviewer focus, without weakening the validation ladder above.
103447
103487
 
103448
103488
  ### MODE: PR_FEEDBACK
@@ -103457,6 +103497,7 @@ HARD CONSTRAINTS (apply regardless of skill load success):
103457
103497
  - CHECK OUT THE PR BRANCH LOCALLY before verifying feedback or making fixes: fetch the PR head ref if absent, verify the working tree is clean (git status --porcelain) and stash/abort if not, then check out the head branch. Feedback verification and fix validation require the PR branch in the working tree.
103458
103498
  - Do NOT run a fresh broad PR review — inspect adjacent code only as needed to verify reachability, dependencies, shared root causes, regression risk, or sibling changes for a confirmed item.
103459
103499
  - Treat every review comment, CI failure, bot summary, and pasted note as a CLAIM until source evidence proves it; classify each ledger item (CONFIRMED, DISPROVED, PRE_EXISTING, or NEEDS_USER_DECISION) and never silently drop, defer, or mark items out of scope.
103500
+ - For async verification lanes, record the \`batch_id\`, keep doing ledger-safe non-dependent architect work, poll with \`collect_lane_results\` without \`wait\`, process settled lanes immediately, and use \`wait: true\` only when no independent work remains.
103460
103501
  - Patch only confirmed items plus the tests/docs they require; report closure status for every ledger item including disproved ones.
103461
103502
  - Do NOT resolve or mark GitHub review threads resolved unless the user explicitly instructs it.
103462
103503
  - Honor any free-text instructions that follow the closing bracket of the signal as additional scope, without dropping any ledger item.
@@ -116535,6 +116576,7 @@ var SUPPORTED_EXTENSIONS = new Set(LANGUAGE_REGISTRY.getAll().filter((p) => !p.p
116535
116576
  var DEFAULT_WALK_FILE_CAP = 1e4;
116536
116577
  var DEFAULT_WALK_BUDGET_MS = 5000;
116537
116578
  var ASYNC_WALK_YIELD_INTERVAL = 200;
116579
+ var MAX_DIAGNOSTIC_ENTRIES = 200;
116538
116580
  var EXTENSION_TO_LANGUAGE = {};
116539
116581
  for (const profile of LANGUAGE_REGISTRY.getAll()) {
116540
116582
  if (profile.parserOnly)
@@ -116665,6 +116707,64 @@ function isRefusedWorkspaceRoot(target) {
116665
116707
  }
116666
116708
  return refused.has(resolved);
116667
116709
  }
116710
+ function createEmptyDiagnostics() {
116711
+ return {
116712
+ extractionFailures: [],
116713
+ unresolvedImports: [],
116714
+ oversizedFiles: [],
116715
+ unsupportedFiles: [],
116716
+ binaryFiles: [],
116717
+ unreadableFiles: [],
116718
+ lowConfidenceEdgeCount: 0
116719
+ };
116720
+ }
116721
+ function diagnosticsHaveEntries(diagnostics) {
116722
+ return (diagnostics.extractionFailures?.length ?? 0) > 0 || (diagnostics.unresolvedImports?.length ?? 0) > 0 || (diagnostics.oversizedFiles?.length ?? 0) > 0 || (diagnostics.unsupportedFiles?.length ?? 0) > 0 || (diagnostics.binaryFiles?.length ?? 0) > 0 || (diagnostics.unreadableFiles?.length ?? 0) > 0 || (diagnostics.lowConfidenceEdgeCount ?? 0) > 0;
116723
+ }
116724
+ function pushCapped(target, value) {
116725
+ if (target.length < MAX_DIAGNOSTIC_ENTRIES) {
116726
+ target.push(value);
116727
+ }
116728
+ }
116729
+ function mergeDiagnostics(target, source) {
116730
+ if (!source)
116731
+ return;
116732
+ for (const entry of source.extractionFailures ?? []) {
116733
+ pushCapped(target.extractionFailures, entry);
116734
+ }
116735
+ for (const entry of source.unresolvedImports ?? []) {
116736
+ pushCapped(target.unresolvedImports, entry);
116737
+ }
116738
+ for (const entry of source.oversizedFiles ?? []) {
116739
+ pushCapped(target.oversizedFiles, entry);
116740
+ }
116741
+ for (const entry of source.unsupportedFiles ?? []) {
116742
+ pushCapped(target.unsupportedFiles, entry);
116743
+ }
116744
+ for (const entry of source.binaryFiles ?? []) {
116745
+ pushCapped(target.binaryFiles, entry);
116746
+ }
116747
+ for (const entry of source.unreadableFiles ?? []) {
116748
+ pushCapped(target.unreadableFiles, entry);
116749
+ }
116750
+ target.lowConfidenceEdgeCount += source.lowConfidenceEdgeCount ?? 0;
116751
+ }
116752
+ function isRelativeImportSpecifier(specifier) {
116753
+ return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
116754
+ }
116755
+ function unresolvedRelativeImportsFor(parsedImports, filePath, absoluteRoot) {
116756
+ const unresolved = [];
116757
+ const moduleName = toModuleName(filePath, absoluteRoot);
116758
+ for (const parsed of parsedImports) {
116759
+ if (!isRelativeImportSpecifier(parsed.specifier))
116760
+ continue;
116761
+ const resolvedTarget = resolveModuleSpecifier(absoluteRoot, filePath, parsed.specifier);
116762
+ if (resolvedTarget === null) {
116763
+ unresolved.push({ file: moduleName, specifier: parsed.specifier });
116764
+ }
116765
+ }
116766
+ return unresolved;
116767
+ }
116668
116768
  var REGEX_ALLOWED_AFTER = new Set("(,=:[!&|?{};*+-~^<>%".split(""));
116669
116769
  function stripComments2(content) {
116670
116770
  let out = "";
@@ -116857,6 +116957,28 @@ function computeUsedSymbols(strippedContent, bindings) {
116857
116957
  }
116858
116958
  return [...used].sort((a, b) => a.localeCompare(b));
116859
116959
  }
116960
+ function usedSymbolsForImport(parsed, strippedContent) {
116961
+ if (parsed.importType === "namespace" || parsed.importType === "sideeffect" || parsed.importType === "require") {
116962
+ return;
116963
+ }
116964
+ if (parsed.reExport) {
116965
+ return [...new Set(parsed.bindings.map((b) => b.imported))].sort((a, b) => a.localeCompare(b));
116966
+ }
116967
+ return computeUsedSymbols(strippedContent, parsed.bindings);
116968
+ }
116969
+ function collectExports(symbols2) {
116970
+ const exported = symbols2.filter((s) => s.exported);
116971
+ const exports = exported.map((s) => s.signature === `default ${s.name}` ? "default" : s.name);
116972
+ const exportLines = {};
116973
+ for (let i = 0;i < exported.length; i++) {
116974
+ const s = exported[i];
116975
+ const name = exports[i];
116976
+ if (typeof s.line === "number" && Number.isFinite(s.line) && exportLines[name] === undefined) {
116977
+ exportLines[name] = s.line;
116978
+ }
116979
+ }
116980
+ return { exports, exportLines };
116981
+ }
116860
116982
  function parseImportedSymbols(matchedString, importType) {
116861
116983
  if (importType === "namespace")
116862
116984
  return ["*"];
@@ -116983,44 +117105,126 @@ function isBinaryContent(content) {
116983
117105
  }
116984
117106
  return false;
116985
117107
  }
117108
+ function scanFile(filePath, absoluteRoot, maxFileSize) {
117109
+ let content;
117110
+ let fileStats;
117111
+ try {
117112
+ fileStats = fsSync8.statSync(filePath);
117113
+ if (fileStats.size > maxFileSize) {
117114
+ return { node: null, edges: [] };
117115
+ }
117116
+ content = fsSync8.readFileSync(filePath, "utf-8");
117117
+ } catch {
117118
+ return { node: null, edges: [] };
117119
+ }
117120
+ if (isBinaryContent(content)) {
117121
+ return { node: null, edges: [] };
117122
+ }
117123
+ const ext = path134.extname(filePath).toLowerCase();
117124
+ let exports = [];
117125
+ let exportLines = {};
117126
+ try {
117127
+ if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
117128
+ const relativePath = path134.relative(absoluteRoot, filePath);
117129
+ ({ exports, exportLines } = collectExports(_internals82.extractTSSymbols(relativePath, absoluteRoot)));
117130
+ } else if (ext === ".py") {
117131
+ const relativePath = path134.relative(absoluteRoot, filePath);
117132
+ ({ exports, exportLines } = collectExports(_internals82.extractPythonSymbols(relativePath, absoluteRoot)));
117133
+ }
117134
+ const parsedImports = _internals82.parseFileImports(content);
117135
+ const strippedForUsage = parsedImports.length > 0 ? _internals82.stripComments(content) : "";
117136
+ const moduleName = toModuleName(filePath, absoluteRoot);
117137
+ const node = {
117138
+ filePath,
117139
+ moduleName,
117140
+ exports,
117141
+ ...Object.keys(exportLines).length > 0 ? { exportLines } : {},
117142
+ imports: parsedImports.map((p) => p.specifier),
117143
+ language: getLanguage(filePath),
117144
+ mtime: fileStats.mtime.toISOString(),
117145
+ ontology: _internals82.extractFileOntology({
117146
+ moduleName,
117147
+ filePath,
117148
+ content,
117149
+ language: getLanguage(filePath),
117150
+ exports,
117151
+ imports: parsedImports.map((p) => p.specifier)
117152
+ })
117153
+ };
117154
+ const edges = [];
117155
+ const sortedImports = [...parsedImports].sort((a, b) => a.specifier.localeCompare(b.specifier));
117156
+ for (const parsed of sortedImports) {
117157
+ const resolvedTarget = resolveModuleSpecifier(absoluteRoot, filePath, parsed.specifier);
117158
+ if (resolvedTarget !== null) {
117159
+ const usedSymbols = usedSymbolsForImport(parsed, strippedForUsage);
117160
+ edges.push({
117161
+ source: filePath,
117162
+ target: resolvedTarget,
117163
+ importSpecifier: parsed.specifier,
117164
+ importType: parsed.importType,
117165
+ importedSymbols: parsed.importedSymbols,
117166
+ ...usedSymbols !== undefined ? { usedSymbols } : {}
117167
+ });
117168
+ }
117169
+ }
117170
+ return { node, edges };
117171
+ } catch {
117172
+ return { node: null, edges: [] };
117173
+ }
117174
+ }
116986
117175
  async function scanFileAsync(filePath, absoluteRoot, maxFileSize) {
116987
117176
  let content;
116988
117177
  let fileStats;
116989
117178
  try {
116990
117179
  fileStats = fsSync8.statSync(filePath);
116991
117180
  if (fileStats.size > maxFileSize) {
116992
- return { node: null, edges: [], symbolEdges: [] };
117181
+ return {
117182
+ node: null,
117183
+ edges: [],
117184
+ symbolEdges: [],
117185
+ diagnostics: { oversizedFiles: [toModuleName(filePath, absoluteRoot)] }
117186
+ };
116993
117187
  }
116994
117188
  content = fsSync8.readFileSync(filePath, "utf-8");
116995
117189
  } catch {
116996
- return { node: null, edges: [], symbolEdges: [] };
117190
+ return {
117191
+ node: null,
117192
+ edges: [],
117193
+ symbolEdges: [],
117194
+ diagnostics: { unreadableFiles: [toModuleName(filePath, absoluteRoot)] }
117195
+ };
116997
117196
  }
116998
117197
  if (isBinaryContent(content)) {
116999
- return { node: null, edges: [], symbolEdges: [] };
117198
+ return {
117199
+ node: null,
117200
+ edges: [],
117201
+ symbolEdges: [],
117202
+ diagnostics: { binaryFiles: [toModuleName(filePath, absoluteRoot)] }
117203
+ };
117000
117204
  }
117001
117205
  const grammarId = getLanguage(filePath);
117002
117206
  const facts = await _internals82.extractFileSymbols(grammarId, content);
117003
117207
  if (facts === null) {
117004
- const moduleName2 = toModuleName(filePath, absoluteRoot);
117208
+ const fallback = scanFile(filePath, absoluteRoot, maxFileSize);
117209
+ let parsedImports = [];
117210
+ try {
117211
+ parsedImports = _internals82.parseFileImports(content);
117212
+ } catch {
117213
+ parsedImports = [];
117214
+ }
117005
117215
  return {
117006
- node: {
117007
- filePath,
117008
- moduleName: moduleName2,
117009
- exports: [],
117010
- imports: [],
117011
- language: grammarId,
117012
- mtime: fileStats.mtime.toISOString(),
117013
- ontology: _internals82.extractFileOntology({
117014
- moduleName: moduleName2,
117015
- filePath,
117016
- content,
117017
- language: grammarId,
117018
- exports: [],
117019
- imports: []
117020
- })
117021
- },
117022
- edges: [],
117023
- symbolEdges: []
117216
+ ...fallback,
117217
+ symbolEdges: [],
117218
+ diagnostics: {
117219
+ extractionFailures: [
117220
+ {
117221
+ file: toModuleName(filePath, absoluteRoot),
117222
+ language: grammarId,
117223
+ reason: "symbol_extraction_failed"
117224
+ }
117225
+ ],
117226
+ unresolvedImports: unresolvedRelativeImportsFor(parsedImports, filePath, absoluteRoot)
117227
+ }
117024
117228
  };
117025
117229
  }
117026
117230
  const exportedDefs = facts.defs.filter((d) => d.exported);
@@ -117071,6 +117275,10 @@ async function scanFileAsync(filePath, absoluteRoot, maxFileSize) {
117071
117275
  });
117072
117276
  }
117073
117277
  }
117278
+ const unresolvedImports = sortedImports.filter((imp) => isRelativeImportSpecifier(imp.specifier)).filter((imp) => resolveModuleSpecifier(absoluteRoot, filePath, imp.specifier) === null).map((imp) => ({
117279
+ file: moduleName,
117280
+ specifier: imp.specifier
117281
+ }));
117074
117282
  const symbolEdges = [];
117075
117283
  const localToImported = new Map;
117076
117284
  for (const imp of facts.imports) {
@@ -117101,7 +117309,12 @@ async function scanFileAsync(filePath, absoluteRoot, maxFileSize) {
117101
117309
  toSymbol: mapping.imported
117102
117310
  });
117103
117311
  }
117104
- return { node, edges, symbolEdges };
117312
+ return {
117313
+ node,
117314
+ edges,
117315
+ symbolEdges,
117316
+ diagnostics: unresolvedImports.length > 0 ? { unresolvedImports } : undefined
117317
+ };
117105
117318
  }
117106
117319
  async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117107
117320
  validateWorkspace(workspaceRoot);
@@ -117123,6 +117336,7 @@ async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117123
117336
  skippedFiles: 0,
117124
117337
  truncated: false
117125
117338
  };
117339
+ const diagnostics = createEmptyDiagnostics();
117126
117340
  const sourceFiles = await findSourceFilesAsync(absoluteRoot, stats2, {
117127
117341
  walkBudgetMs,
117128
117342
  maxFiles,
@@ -117143,6 +117357,7 @@ async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117143
117357
  let processedSinceYield = 0;
117144
117358
  for (const filePath of sourceFiles) {
117145
117359
  const result = await scanFileAsync(filePath, absoluteRoot, maxFileSize);
117360
+ mergeDiagnostics(diagnostics, result.diagnostics);
117146
117361
  if (result.node) {
117147
117362
  let appended = false;
117148
117363
  try {
@@ -117183,6 +117398,7 @@ async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117183
117398
  if (allSymbolEdges.length > 0) {
117184
117399
  graph.symbolEdges = allSymbolEdges;
117185
117400
  }
117401
+ graph.diagnostics = diagnosticsHaveEntries(diagnostics) ? diagnostics : createEmptyDiagnostics();
117186
117402
  if (stats2.skippedFiles > 0 || stats2.skippedDirs > 0 || stats2.truncated) {
117187
117403
  log(`[repo-graph] Scan stats: ${stats2.filesScanned} files scanned, ` + `${stats2.skippedFiles} files skipped, ${stats2.skippedDirs} dirs skipped` + (stats2.truncated ? ", TRUNCATED" : ""));
117188
117404
  }
@@ -117224,7 +117440,11 @@ import * as fsPromises7 from "node:fs/promises";
117224
117440
  import * as path138 from "node:path";
117225
117441
 
117226
117442
  // src/tools/repo-graph/query.ts
117443
+ init_path_security();
117444
+ import * as fsSync9 from "node:fs";
117227
117445
  import * as path136 from "node:path";
117446
+ var GRAPH_HEALTH_OUTPUT_LIMIT = 50;
117447
+ var MAX_HEALTH_PATH_LENGTH = 500;
117228
117448
  var cachedReverseIndex = null;
117229
117449
  function normalizeLookupPath(input) {
117230
117450
  return normalizeGraphPath(input).replace(/^(?:\.\/)+/, "");
@@ -117323,6 +117543,139 @@ function isGraphFresh(graph, maxAgeMs = 5 * 60 * 1000) {
117323
117543
  return false;
117324
117544
  return Date.now() - built <= maxAgeMs;
117325
117545
  }
117546
+ function isSafeHealthPath(value) {
117547
+ if (typeof value !== "string")
117548
+ return false;
117549
+ if (value.length === 0 || value.length > MAX_HEALTH_PATH_LENGTH)
117550
+ return false;
117551
+ if (containsControlChars(value) || containsPathTraversal(value))
117552
+ return false;
117553
+ if (path136.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value))
117554
+ return false;
117555
+ return true;
117556
+ }
117557
+ function isSafeHealthText(value) {
117558
+ if (typeof value !== "string")
117559
+ return false;
117560
+ if (value.length === 0 || value.length > MAX_HEALTH_PATH_LENGTH)
117561
+ return false;
117562
+ if (containsControlChars(value))
117563
+ return false;
117564
+ return true;
117565
+ }
117566
+ function cap(entries) {
117567
+ return entries.slice(0, GRAPH_HEALTH_OUTPUT_LIMIT);
117568
+ }
117569
+ function sanitizeExtractionFailures(value) {
117570
+ if (!Array.isArray(value))
117571
+ return [];
117572
+ const entries = [];
117573
+ for (const raw of value) {
117574
+ if (!raw || typeof raw !== "object")
117575
+ continue;
117576
+ const entry = raw;
117577
+ if (isSafeHealthPath(entry.file) && isSafeHealthText(entry.language) && isSafeHealthText(entry.reason)) {
117578
+ entries.push({
117579
+ file: entry.file,
117580
+ language: entry.language,
117581
+ reason: entry.reason
117582
+ });
117583
+ }
117584
+ }
117585
+ return cap(entries);
117586
+ }
117587
+ function sanitizeUnresolvedImports(value) {
117588
+ if (!Array.isArray(value))
117589
+ return [];
117590
+ const entries = [];
117591
+ for (const raw of value) {
117592
+ if (!raw || typeof raw !== "object")
117593
+ continue;
117594
+ const entry = raw;
117595
+ if (isSafeHealthPath(entry.file) && isSafeHealthText(entry.specifier)) {
117596
+ entries.push({ file: entry.file, specifier: entry.specifier });
117597
+ }
117598
+ }
117599
+ return cap(entries);
117600
+ }
117601
+ function sanitizePathList(value) {
117602
+ if (!Array.isArray(value))
117603
+ return [];
117604
+ return cap(value.filter(isSafeHealthPath));
117605
+ }
117606
+ function getStaleFiles(graph, workspaceRoot) {
117607
+ const built = Date.parse(graph.metadata.generatedAt);
117608
+ if (!Number.isFinite(built))
117609
+ return [];
117610
+ const root = workspaceRoot ?? graph.workspaceRoot;
117611
+ const stale = [];
117612
+ for (const node of Object.values(graph.nodes)) {
117613
+ const moduleName = normalizeGraphPath(node.moduleName);
117614
+ if (!isSafeHealthPath(moduleName))
117615
+ continue;
117616
+ const filePath = path136.join(root, moduleName);
117617
+ try {
117618
+ if (fsSync9.statSync(filePath).mtimeMs > built) {
117619
+ stale.push(moduleName);
117620
+ if (stale.length >= GRAPH_HEALTH_OUTPUT_LIMIT)
117621
+ break;
117622
+ }
117623
+ } catch {}
117624
+ }
117625
+ return stale;
117626
+ }
117627
+ function getGraphHealth(graph, workspaceRoot) {
117628
+ if (!graph) {
117629
+ return {
117630
+ schemaVersion: null,
117631
+ fresh: false,
117632
+ staleFiles: [],
117633
+ extractionFailures: [],
117634
+ unresolvedImports: [],
117635
+ oversizedFiles: [],
117636
+ unsupportedFiles: [],
117637
+ binaryFiles: [],
117638
+ unreadableFiles: [],
117639
+ lowConfidenceEdgeCount: 0,
117640
+ notes: [
117641
+ 'No repo graph found at .swarm/repo-graph.json. Run repo_map with action="build" first.'
117642
+ ]
117643
+ };
117644
+ }
117645
+ const diagnostics = graph.diagnostics;
117646
+ const fresh = isGraphFresh(graph);
117647
+ const staleFiles = getStaleFiles(graph, workspaceRoot);
117648
+ const notes = [];
117649
+ if (!fresh || staleFiles.length > 0) {
117650
+ notes.push('Graph is stale. Run repo_map with action="build" to refresh.');
117651
+ }
117652
+ if (!diagnostics) {
117653
+ notes.push('Graph has no recorded diagnostics. Rebuild with repo_map action="build" to collect health details.');
117654
+ }
117655
+ const binaryFiles = sanitizePathList(diagnostics?.binaryFiles);
117656
+ const binaryCount = binaryFiles.length;
117657
+ if (binaryCount > 0) {
117658
+ notes.push(`${binaryCount} binary files skipped during last build.`);
117659
+ }
117660
+ const unreadableFiles = sanitizePathList(diagnostics?.unreadableFiles);
117661
+ const unreadableCount = unreadableFiles.length;
117662
+ if (unreadableCount > 0) {
117663
+ notes.push(`${unreadableCount} unreadable files skipped during last build.`);
117664
+ }
117665
+ return {
117666
+ schemaVersion: graph.schema_version,
117667
+ fresh,
117668
+ staleFiles,
117669
+ extractionFailures: sanitizeExtractionFailures(diagnostics?.extractionFailures),
117670
+ unresolvedImports: sanitizeUnresolvedImports(diagnostics?.unresolvedImports),
117671
+ oversizedFiles: sanitizePathList(diagnostics?.oversizedFiles),
117672
+ unsupportedFiles: sanitizePathList(diagnostics?.unsupportedFiles),
117673
+ binaryFiles,
117674
+ unreadableFiles,
117675
+ lowConfidenceEdgeCount: typeof diagnostics?.lowConfidenceEdgeCount === "number" && Number.isFinite(diagnostics.lowConfidenceEdgeCount) && diagnostics.lowConfidenceEdgeCount > 0 ? Math.floor(diagnostics.lowConfidenceEdgeCount) : 0,
117676
+ notes
117677
+ };
117678
+ }
117326
117679
  function getImporters(graph, filePath) {
117327
117680
  const node = getGraphNode(graph, filePath);
117328
117681
  if (!node)
@@ -117911,7 +118264,7 @@ function buildOntologyPreflightPacket(graph, filePaths = [], options = {}) {
117911
118264
  init_utils2();
117912
118265
  init_logger();
117913
118266
  init_path_security();
117914
- import { constants as constants5, existsSync as existsSync77, readFileSync as readFileSync53, statSync as statSync27 } from "node:fs";
118267
+ import { constants as constants5, existsSync as existsSync77, readFileSync as readFileSync53, statSync as statSync28 } from "node:fs";
117915
118268
  import * as fsPromises6 from "node:fs/promises";
117916
118269
  import * as path137 from "node:path";
117917
118270
  var WINDOWS_RENAME_MAX_RETRIES2 = 5;
@@ -118036,7 +118389,7 @@ function loadGraphSync(workspace) {
118036
118389
  const graphPath = getGraphPath(workspace);
118037
118390
  if (!existsSync77(graphPath))
118038
118391
  return null;
118039
- const stats2 = statSync27(graphPath);
118392
+ const stats2 = statSync28(graphPath);
118040
118393
  const content = readFileSync53(graphPath, "utf-8");
118041
118394
  if (content.includes("\x00") || content.includes("�")) {
118042
118395
  throw Object.assign(new Error("repo-graph.json contains null bytes or invalid encoding"), { code: "CORRUPTION" });
@@ -123062,9 +123415,9 @@ function parseDelegateDirectiveBlock(text) {
123062
123415
  }
123063
123416
  async function injectForDelegate(params) {
123064
123417
  const { directory, agent, taskTitle, sessionId, config: config3 } = params;
123065
- const cap = config3.delegate_max_inject_count ?? 8;
123418
+ const cap2 = config3.delegate_max_inject_count ?? 8;
123066
123419
  const expectedTools = params.expectedTools && params.expectedTools.length > 0 ? params.expectedTools : defaultExpectedToolsForAgent(agent);
123067
- if (cap <= 0)
123420
+ if (cap2 <= 0)
123068
123421
  return { entries: [], trace_id: "" };
123069
123422
  const role = stripKnownSwarmPrefix(agent).toLowerCase();
123070
123423
  const firstTool = expectedTools.length > 0 ? expectedTools[0] : undefined;
@@ -123088,11 +123441,11 @@ async function injectForDelegate(params) {
123088
123441
  tier: "all",
123089
123442
  applyScopeFilter: true,
123090
123443
  applyRoleScope: false,
123091
- maxResults: Math.max(40, cap * 4),
123444
+ maxResults: Math.max(40, cap2 * 4),
123092
123445
  emitEvent: false
123093
123446
  });
123094
123447
  const scoped = search.results.filter((e) => matchesDelegateScope(e, role, expectedTools));
123095
- const capped = scoped.slice(0, cap);
123448
+ const capped = scoped.slice(0, cap2);
123096
123449
  if (capped.length > 0) {
123097
123450
  const ranks = {};
123098
123451
  const scores = {};
@@ -129928,7 +130281,7 @@ ${body}`);
129928
130281
  init_zod();
129929
130282
  init_task_file();
129930
130283
  import { appendFileSync as appendFileSync18, existsSync as existsSync95, mkdirSync as mkdirSync42, readFileSync as readFileSync64 } from "node:fs";
129931
- import { join as join131 } from "node:path";
130284
+ import { join as join132 } from "node:path";
129932
130285
  var EVIDENCE_DIR2 = ".swarm/evidence";
129933
130286
  var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
129934
130287
  var COUNCIL_GATE_NAME = "council";
@@ -129966,7 +130319,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
129966
130319
  if (!VALID_TASK_ID.test(synthesis.taskId)) {
129967
130320
  throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" — must match N.M or N.M.P format`);
129968
130321
  }
129969
- const dir = join131(workingDir, EVIDENCE_DIR2);
130322
+ const dir = join132(workingDir, EVIDENCE_DIR2);
129970
130323
  mkdirSync42(dir, { recursive: true });
129971
130324
  const filePath = taskEvidencePath(workingDir, synthesis.taskId);
129972
130325
  await _internals96.withTaskEvidenceLock(workingDir, synthesis.taskId, COUNCIL_AGENT_ID, async () => {
@@ -130002,7 +130355,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
130002
130355
  await atomicWriteFile(filePath, JSON.stringify(updated, null, 2));
130003
130356
  });
130004
130357
  try {
130005
- const councilDir = join131(workingDir, ".swarm", "council");
130358
+ const councilDir = join132(workingDir, ".swarm", "council");
130006
130359
  mkdirSync42(councilDir, { recursive: true });
130007
130360
  const auditLine = JSON.stringify({
130008
130361
  round: synthesis.roundNumber,
@@ -130010,7 +130363,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
130010
130363
  timestamp: synthesis.timestamp,
130011
130364
  vetoedBy: synthesis.vetoedBy
130012
130365
  });
130013
- appendFileSync18(join131(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
130366
+ appendFileSync18(join132(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
130014
130367
  `);
130015
130368
  } catch (auditError) {
130016
130369
  console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
@@ -130367,7 +130720,7 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
130367
130720
  init_zod();
130368
130721
  init_task_file();
130369
130722
  import { existsSync as existsSync96, mkdirSync as mkdirSync43, readFileSync as readFileSync65 } from "node:fs";
130370
- import { join as join132 } from "node:path";
130723
+ import { join as join133 } from "node:path";
130371
130724
  var COUNCIL_DIR = ".swarm/council";
130372
130725
  var CouncilCriteriaSchema = exports_external.object({
130373
130726
  taskId: exports_external.string(),
@@ -130379,17 +130732,17 @@ var CouncilCriteriaSchema = exports_external.object({
130379
130732
  declaredAt: exports_external.string()
130380
130733
  });
130381
130734
  async function writeCriteria(workingDir, taskId, criteria) {
130382
- const dir = join132(workingDir, COUNCIL_DIR);
130735
+ const dir = join133(workingDir, COUNCIL_DIR);
130383
130736
  mkdirSync43(dir, { recursive: true });
130384
130737
  const payload = {
130385
130738
  taskId,
130386
130739
  criteria,
130387
130740
  declaredAt: new Date().toISOString()
130388
130741
  };
130389
- await atomicWriteFile(join132(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
130742
+ await atomicWriteFile(join133(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
130390
130743
  }
130391
130744
  function readCriteria(workingDir, taskId) {
130392
- const filePath = join132(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
130745
+ const filePath = join133(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
130393
130746
  if (!existsSync96(filePath))
130394
130747
  return null;
130395
130748
  try {
@@ -149604,7 +149957,8 @@ var VALID_ACTIONS = [
149604
149957
  "preflight_packet",
149605
149958
  "callers",
149606
149959
  "dead_exports",
149607
- "context_pack"
149960
+ "context_pack",
149961
+ "graph_health"
149608
149962
  ];
149609
149963
  var MAX_FILE_PATH_LENGTH3 = 500;
149610
149964
  var MAX_SYMBOL_LENGTH2 = 256;
@@ -149668,7 +150022,7 @@ async function loadOrError(directory, action) {
149668
150022
  }
149669
150023
  }
149670
150024
  var repo_map = createSwarmTool({
149671
- description: "Query the repository code graph for structural awareness before editing. " + 'Actions: "build" (build/refresh .swarm/repo-graph.json), "importers" (who imports a file), ' + '"dependencies" (what a file imports), "blast_radius" (transitive dependents + risk), ' + '"localization" (compact context block for a target file), "key_files" (top-N most-imported files), ' + '"ontology" (file roles/routes/data/security/findings), "package_boundaries" (inferred package/layer boundaries), ' + '"preflight_packet" (bounded ontology packet for planning), ' + '"callers" (files that reference an exported symbol, call-site granularity; needs file+symbol), ' + '"dead_exports" (advisory: exported symbols with no detected in-repo reference; results are review candidates, not delete directives), ' + '"context_pack" (token-budgeted slice of source spans for a target symbol — definition + transitive callers/callees; advisory/conservative; needs file+symbol; uses max_depth for traversal depth, top_n for span cap). ' + "Use this before refactoring shared modules to avoid breaking unseen consumers. " + 'Note: "callers"/"dead_exports"/"context_pack" use conservative regex analysis (TS/JS/Python) and cannot see ' + 'dynamic dispatch or namespace/barrel re-export usage; "dead_exports" results are review candidates, not delete directives.',
150025
+ description: "Query the repository code graph for structural awareness before editing. " + 'Actions: "build" (build/refresh .swarm/repo-graph.json), "importers" (who imports a file), ' + '"dependencies" (what a file imports), "blast_radius" (transitive dependents + risk), ' + '"localization" (compact context block for a target file), "key_files" (top-N most-imported files), ' + '"ontology" (file roles/routes/data/security/findings), "package_boundaries" (inferred package/layer boundaries), ' + '"preflight_packet" (bounded ontology packet for planning), ' + '"callers" (files that reference an exported symbol, call-site granularity; needs file+symbol), ' + '"dead_exports" (advisory: exported symbols with no detected in-repo reference; results are review candidates, not delete directives), ' + '"context_pack" (token-budgeted slice of source spans for a target symbol — definition + transitive callers/callees; advisory/conservative; needs file+symbol; uses max_depth for traversal depth, top_n for span cap), ' + '"graph_health" (freshness and bounded extraction diagnostics; no file required). ' + "Use this before refactoring shared modules to avoid breaking unseen consumers. " + 'Note: "callers"/"dead_exports"/"context_pack" use conservative regex analysis (TS/JS/Python) and cannot see ' + 'dynamic dispatch or namespace/barrel re-export usage; "dead_exports" results are review candidates, not delete directives.',
149672
150026
  args: {
149673
150027
  action: exports_external.enum([
149674
150028
  "build",
@@ -149682,8 +150036,9 @@ var repo_map = createSwarmTool({
149682
150036
  "preflight_packet",
149683
150037
  "callers",
149684
150038
  "dead_exports",
149685
- "context_pack"
149686
- ]).describe('Query action: "build" | "importers" | "dependencies" | "blast_radius" | "localization" | "key_files" | "ontology" | "package_boundaries" | "preflight_packet" | "callers" | "dead_exports" | "context_pack"'),
150039
+ "context_pack",
150040
+ "graph_health"
150041
+ ]).describe('Query action: "build" | "importers" | "dependencies" | "blast_radius" | "localization" | "key_files" | "ontology" | "package_boundaries" | "preflight_packet" | "callers" | "dead_exports" | "context_pack" | "graph_health"'),
149687
150042
  file: exports_external.string().optional().describe("Target file (workspace-relative or absolute). Required for importers/dependencies/localization/ontology. Optional for preflight_packet."),
149688
150043
  files: exports_external.array(exports_external.string()).optional().describe("Multiple target files for blast_radius/preflight_packet. If omitted, falls back to `file`."),
149689
150044
  symbol: exports_external.string().optional().describe('Exported symbol name. Restricts consumers on action="importers"; required for action="callers"/"context_pack".'),
@@ -149718,6 +150073,15 @@ var repo_map = createSwarmTool({
149718
150073
  return err(action, `build failed: ${message}`);
149719
150074
  }
149720
150075
  }
150076
+ if (action === "graph_health") {
150077
+ try {
150078
+ const graph2 = await loadGraph(directory);
150079
+ return ok(action, { ...getGraphHealth(graph2, directory) });
150080
+ } catch (e) {
150081
+ const message = e instanceof Error ? e.message : String(e);
150082
+ return err(action, `failed to load repo graph: ${message}`);
150083
+ }
150084
+ }
149721
150085
  const loaded = await loadOrError(directory, action);
149722
150086
  if (!loaded.ok)
149723
150087
  return loaded.response;
@@ -153071,7 +153435,7 @@ init_skill_generator();
153071
153435
  init_create_tool();
153072
153436
  import { existsSync as existsSync119 } from "node:fs";
153073
153437
  import { readdir as readdir12, readFile as readFile33 } from "node:fs/promises";
153074
- import { join as join166 } from "node:path";
153438
+ import { join as join167 } from "node:path";
153075
153439
  var run_stale_reconciliation = createSwarmTool({
153076
153440
  description: "Reconcile skills against the knowledge store. clear=false: mark skills stale when source knowledge is archived or deleted. clear=true: clear stale.marker on affected active skills (proposal files under .swarm/skills/proposals are scanned but not modified — they are drafts, not yet active skills).",
153077
153441
  args: {
@@ -153098,8 +153462,8 @@ var run_stale_reconciliation = createSwarmTool({
153098
153462
  } catch {}
153099
153463
  const skillEntries = [];
153100
153464
  for (const dir of [
153101
- join166(directory, ".opencode", "skills", "generated"),
153102
- join166(directory, ".swarm", "skills", "proposals")
153465
+ join167(directory, ".opencode", "skills", "generated"),
153466
+ join167(directory, ".swarm", "skills", "proposals")
153103
153467
  ]) {
153104
153468
  if (!_internals125.existsSync(dir))
153105
153469
  continue;
@@ -153108,14 +153472,14 @@ var run_stale_reconciliation = createSwarmTool({
153108
153472
  if (entry.isDirectory()) {
153109
153473
  skillEntries.push({
153110
153474
  slug: entry.name,
153111
- path: join166(dir, entry.name),
153475
+ path: join167(dir, entry.name),
153112
153476
  isProposal: false
153113
153477
  });
153114
153478
  } else if (entry.name.endsWith(".md")) {
153115
153479
  const slug = entry.name.replace(/\.md$/, "");
153116
153480
  skillEntries.push({
153117
153481
  slug,
153118
- path: join166(dir, entry.name),
153482
+ path: join167(dir, entry.name),
153119
153483
  isProposal: true
153120
153484
  });
153121
153485
  }
@@ -153123,7 +153487,7 @@ var run_stale_reconciliation = createSwarmTool({
153123
153487
  }
153124
153488
  const results = [];
153125
153489
  for (const { slug, path: path210, isProposal } of skillEntries) {
153126
- const skillMdPath = isProposal ? path210 : join166(path210, "SKILL.md");
153490
+ const skillMdPath = isProposal ? path210 : join167(path210, "SKILL.md");
153127
153491
  if (!_internals125.existsSync(skillMdPath))
153128
153492
  continue;
153129
153493
  const content = await _internals125.readFile(skillMdPath, "utf-8");
@@ -153136,7 +153500,7 @@ var run_stale_reconciliation = createSwarmTool({
153136
153500
  continue;
153137
153501
  if (args2.clear) {
153138
153502
  if (!isProposal) {
153139
- const markerPath = join166(path210, "stale.marker");
153503
+ const markerPath = join167(path210, "stale.marker");
153140
153504
  if (_internals125.existsSync(markerPath)) {
153141
153505
  try {
153142
153506
  await _internals125.clearSkillStale(path210);
@@ -158349,14 +158713,6 @@ async function initializeOpenCodeSwarm(ctx) {
158349
158713
  prEventCleanup?.();
158350
158714
  };
158351
158715
  process.on("exit", cleanupAutomation);
158352
- process.once("SIGINT", () => {
158353
- cleanupAutomation();
158354
- process.exit(130);
158355
- });
158356
- process.once("SIGTERM", () => {
158357
- cleanupAutomation();
158358
- process.exit(143);
158359
- });
158360
158716
  if (shouldRunOnStartup(automationConfig)) {
158361
158717
  const enableAutofix = automationConfig.capabilities?.config_doctor_autofix === true;
158362
158718
  Promise.resolve().then(() => (init_config_doctor(), exports_config_doctor)).then(({ runConfigDoctorWithFixes: runConfigDoctorWithFixes2 }) => {
@@ -158372,9 +158728,19 @@ async function initializeOpenCodeSwarm(ctx) {
158372
158728
  try {
158373
158729
  const autoFixableCount = doctorResult.result.findings.filter((f) => f.autoFixable).length;
158374
158730
  if (!enableAutofix && autoFixableCount > 0) {
158375
- console.warn(`[opencode-swarm] Config Doctor found ${autoFixableCount} auto-fixable issue(s). Run /swarm config doctor --fix to apply.`);
158731
+ const msg = `[opencode-swarm] Config Doctor found ${autoFixableCount} auto-fixable issue(s). Run /swarm config doctor --fix to apply.`;
158732
+ if (!config3.quiet) {
158733
+ console.warn(msg);
158734
+ } else {
158735
+ addDeferredWarning(msg);
158736
+ }
158376
158737
  } else if (enableAutofix && doctorResult.appliedFixes.length > 0) {
158377
- console.warn(`[opencode-swarm] Config Doctor applied ${doctorResult.appliedFixes.length} fix(es) automatically.`);
158738
+ const msg = `[opencode-swarm] Config Doctor applied ${doctorResult.appliedFixes.length} fix(es) automatically.`;
158739
+ if (!config3.quiet) {
158740
+ console.warn(msg);
158741
+ } else {
158742
+ addDeferredWarning(msg);
158743
+ }
158378
158744
  }
158379
158745
  } catch {}
158380
158746
  }
@@ -158917,7 +159283,9 @@ async function initializeOpenCodeSwarm(ctx) {
158917
159283
  argsRecord.prompt = `SKILLS: none
158918
159284
 
158919
159285
  ${promptRaw}`;
158920
- console.warn("[skill-propagation-gate] No skills above threshold 0.5 — injected SKILLS: none");
159286
+ if (!config3.quiet) {
159287
+ console.warn("[skill-propagation-gate] No skills above threshold 0.5 — injected SKILLS: none");
159288
+ }
158921
159289
  } else {
158922
159290
  const topSkills = qualified.slice(0, 5);
158923
159291
  const skillPaths = topSkills.map((s) => {
@@ -158935,7 +159303,9 @@ ${promptRaw}`;
158935
159303
  ${promptRaw}`;
158936
159304
  argsRecord.prompt = newPrompt;
158937
159305
  const skillNames = topSkills.map((s) => `${path225.basename(s.skillPath)} (score: ${s.score.toFixed(2)})`).join(", ");
158938
- console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
159306
+ if (!config3.quiet) {
159307
+ console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
159308
+ }
158939
159309
  for (const skill of topSkills) {
158940
159310
  try {
158941
159311
  appendSkillUsageEntry(ctx.directory, {