opencode-swarm 7.99.5 → 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.
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-tn5exv94.js";
5
- import"./index-gjyrjr08.js";
4
+ } from "./index-ksc4ftms.js";
5
+ import"./index-1kcp4q4y.js";
6
6
  import"./index-xsswhy6k.js";
7
7
  import"./index-ydgy7vg5.js";
8
8
  import"./index-vwyjkzgs.js";
@@ -909,7 +909,7 @@ var init_executor = __esm(() => {
909
909
  // package.json
910
910
  var package_default = {
911
911
  name: "opencode-swarm",
912
- version: "7.99.5",
912
+ version: "7.99.6",
913
913
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
914
914
  main: "dist/index.js",
915
915
  types: "dist/index.d.ts",
@@ -1562,6 +1562,9 @@ async function syncBundledProjectSkillsIfMissingAsync(projectDirectory, packageR
1562
1562
  }
1563
1563
  }
1564
1564
 
1565
+ // src/commands/registry.ts
1566
+ init_logger();
1567
+
1565
1568
  // src/state.ts
1566
1569
  import * as fs6 from "fs/promises";
1567
1570
  import * as path11 from "path";
@@ -32275,6 +32278,13 @@ function findSimilarCommands(query) {
32275
32278
  scored.sort((a, b) => a.score - b.score);
32276
32279
  return scored.slice(0, 3).map((s) => s.cmd);
32277
32280
  }
32281
+ function emitValidationWarnings(prefix, warnings) {
32282
+ if (warnings.length === 0)
32283
+ return;
32284
+ warn(`${prefix}:
32285
+ ${warnings.join(`
32286
+ `)}`);
32287
+ }
32278
32288
  function buildDetailedHelp(commandName, entry) {
32279
32289
  const lines = [];
32280
32290
  lines.push(`## /swarm ${commandName}`, "");
@@ -32296,7 +32306,7 @@ function buildDetailedHelp(commandName, entry) {
32296
32306
  async function handleHelpCommand(ctx) {
32297
32307
  const targetCommand = ctx.args.join(" ");
32298
32308
  if (!targetCommand) {
32299
- const { buildHelpText } = await import("./index-s0nsx5v6.js");
32309
+ const { buildHelpText } = await import("./index-etssqrff.js");
32300
32310
  return buildHelpText();
32301
32311
  }
32302
32312
  const tokens = targetCommand.split(/\s+/);
@@ -32305,7 +32315,7 @@ async function handleHelpCommand(ctx) {
32305
32315
  return _internals46.buildDetailedHelp(resolved.key, resolved.entry);
32306
32316
  }
32307
32317
  const similar = _internals46.findSimilarCommands(targetCommand);
32308
- const { buildHelpText: fullHelp } = await import("./index-s0nsx5v6.js");
32318
+ const { buildHelpText: fullHelp } = await import("./index-etssqrff.js");
32309
32319
  if (similar.length > 0) {
32310
32320
  return `Command '/swarm ${targetCommand}' not found.
32311
32321
 
@@ -32438,7 +32448,7 @@ var COMMAND_REGISTRY = {
32438
32448
  },
32439
32449
  "guardrail explain": {
32440
32450
  handler: async (ctx) => {
32441
- const { handleGuardrailExplain } = await import("./guardrail-explain-2q5q86jv.js");
32451
+ const { handleGuardrailExplain } = await import("./guardrail-explain-s4c2h8mv.js");
32442
32452
  return handleGuardrailExplain(ctx.directory, ctx.args);
32443
32453
  },
32444
32454
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -33251,6 +33261,7 @@ var _internals46 = {
33251
33261
  handleHelpCommand,
33252
33262
  validateAliases,
33253
33263
  validateToolPolicy,
33264
+ emitValidationWarnings,
33254
33265
  resolveCommand,
33255
33266
  levenshteinDistance,
33256
33267
  findSimilarCommands,
@@ -33262,20 +33273,12 @@ if (!validation.valid) {
33262
33273
  ${validation.errors.join(`
33263
33274
  `)}`);
33264
33275
  }
33265
- if (validation.warnings.length > 0) {
33266
- console.warn(`COMMAND_REGISTRY alias warnings:
33267
- ${validation.warnings.join(`
33268
- `)}`);
33269
- }
33276
+ _internals46.emitValidationWarnings("COMMAND_REGISTRY alias warnings", validation.warnings);
33270
33277
  try {
33271
33278
  const toolPolicyValidation = _internals46.validateToolPolicy();
33272
- if (toolPolicyValidation.warnings.length > 0) {
33273
- console.warn(`COMMAND_REGISTRY toolPolicy warnings:
33274
- ${toolPolicyValidation.warnings.join(`
33275
- `)}`);
33276
- }
33279
+ _internals46.emitValidationWarnings("COMMAND_REGISTRY toolPolicy warnings", toolPolicyValidation.warnings);
33277
33280
  } catch (e) {
33278
- console.warn(`COMMAND_REGISTRY toolPolicy validation failed (non-fatal): ${e.message}`);
33281
+ warn(`COMMAND_REGISTRY toolPolicy validation failed (non-fatal): ${e.message}`);
33279
33282
  }
33280
33283
  function resolveCommand(tokens) {
33281
33284
  if (tokens.length === 0)
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-tn5exv94.js";
4
+ } from "./index-ksc4ftms.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-h9ptj4b1.js";
@@ -79,7 +79,7 @@ import {
79
79
  handleWriteRetroCommand,
80
80
  normalizeSwarmCommandInput,
81
81
  resolveCommand
82
- } from "./index-gjyrjr08.js";
82
+ } from "./index-1kcp4q4y.js";
83
83
  import"./index-xsswhy6k.js";
84
84
  import"./index-ydgy7vg5.js";
85
85
  import"./index-vwyjkzgs.js";
@@ -12,7 +12,7 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-gjyrjr08.js";
15
+ } from "./index-1kcp4q4y.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-gjyrjr08.js";
10
+ } from "./index-1kcp4q4y.js";
11
11
  import"./index-xsswhy6k.js";
12
12
  import"./index-ydgy7vg5.js";
13
13
  import"./index-vwyjkzgs.js";
@@ -1,6 +1,7 @@
1
1
  import type { AgentDefinition } from '../agents/index.js';
2
2
  declare function levenshteinDistance(a: string, b: string): number;
3
3
  declare function findSimilarCommands(query: string): string[];
4
+ declare function emitValidationWarnings(prefix: string, warnings: readonly string[]): void;
4
5
  declare function buildDetailedHelp(commandName: string, entry: CommandEntry): string;
5
6
  export declare function handleHelpCommand(ctx: CommandContext): Promise<string>;
6
7
  export type CommandContext = {
@@ -924,6 +925,7 @@ export declare const _internals: {
924
925
  handleHelpCommand: typeof handleHelpCommand;
925
926
  validateAliases: typeof validateAliases;
926
927
  validateToolPolicy: typeof validateToolPolicy;
928
+ emitValidationWarnings: typeof emitValidationWarnings;
927
929
  resolveCommand: typeof resolveCommand;
928
930
  levenshteinDistance: typeof levenshteinDistance;
929
931
  findSimilarCommands: typeof findSimilarCommands;
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.5",
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, "\\$&");
@@ -101085,6 +101085,13 @@ function findSimilarCommands(query) {
101085
101085
  scored.sort((a, b) => a.score - b.score);
101086
101086
  return scored.slice(0, 3).map((s) => s.cmd);
101087
101087
  }
101088
+ function emitValidationWarnings(prefix, warnings) {
101089
+ if (warnings.length === 0)
101090
+ return;
101091
+ warn(`${prefix}:
101092
+ ${warnings.join(`
101093
+ `)}`);
101094
+ }
101088
101095
  function buildDetailedHelp(commandName, entry) {
101089
101096
  const lines = [];
101090
101097
  lines.push(`## /swarm ${commandName}`, "");
@@ -101219,6 +101226,7 @@ function resolveCommand(tokens) {
101219
101226
  var COMMAND_REGISTRY, VALID_COMMANDS, _internals15, validation;
101220
101227
  var init_registry = __esm(() => {
101221
101228
  init_bundled_skills();
101229
+ init_logger();
101222
101230
  init_acknowledge_spec_drift();
101223
101231
  init_agents();
101224
101232
  init_archive();
@@ -102147,6 +102155,7 @@ Subcommands:
102147
102155
  handleHelpCommand,
102148
102156
  validateAliases,
102149
102157
  validateToolPolicy,
102158
+ emitValidationWarnings,
102150
102159
  resolveCommand,
102151
102160
  levenshteinDistance: levenshteinDistance2,
102152
102161
  findSimilarCommands,
@@ -102158,20 +102167,12 @@ Subcommands:
102158
102167
  ${validation.errors.join(`
102159
102168
  `)}`);
102160
102169
  }
102161
- if (validation.warnings.length > 0) {
102162
- console.warn(`COMMAND_REGISTRY alias warnings:
102163
- ${validation.warnings.join(`
102164
- `)}`);
102165
- }
102170
+ _internals15.emitValidationWarnings("COMMAND_REGISTRY alias warnings", validation.warnings);
102166
102171
  try {
102167
102172
  const toolPolicyValidation = _internals15.validateToolPolicy();
102168
- if (toolPolicyValidation.warnings.length > 0) {
102169
- console.warn(`COMMAND_REGISTRY toolPolicy warnings:
102170
- ${toolPolicyValidation.warnings.join(`
102171
- `)}`);
102172
- }
102173
+ _internals15.emitValidationWarnings("COMMAND_REGISTRY toolPolicy warnings", toolPolicyValidation.warnings);
102173
102174
  } catch (e) {
102174
- console.warn(`COMMAND_REGISTRY toolPolicy validation failed (non-fatal): ${e.message}`);
102175
+ warn(`COMMAND_REGISTRY toolPolicy validation failed (non-fatal): ${e.message}`);
102175
102176
  }
102176
102177
  });
102177
102178
 
@@ -116575,6 +116576,7 @@ var SUPPORTED_EXTENSIONS = new Set(LANGUAGE_REGISTRY.getAll().filter((p) => !p.p
116575
116576
  var DEFAULT_WALK_FILE_CAP = 1e4;
116576
116577
  var DEFAULT_WALK_BUDGET_MS = 5000;
116577
116578
  var ASYNC_WALK_YIELD_INTERVAL = 200;
116579
+ var MAX_DIAGNOSTIC_ENTRIES = 200;
116578
116580
  var EXTENSION_TO_LANGUAGE = {};
116579
116581
  for (const profile of LANGUAGE_REGISTRY.getAll()) {
116580
116582
  if (profile.parserOnly)
@@ -116705,6 +116707,64 @@ function isRefusedWorkspaceRoot(target) {
116705
116707
  }
116706
116708
  return refused.has(resolved);
116707
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
+ }
116708
116768
  var REGEX_ALLOWED_AFTER = new Set("(,=:[!&|?{};*+-~^<>%".split(""));
116709
116769
  function stripComments2(content) {
116710
116770
  let out = "";
@@ -116897,6 +116957,28 @@ function computeUsedSymbols(strippedContent, bindings) {
116897
116957
  }
116898
116958
  return [...used].sort((a, b) => a.localeCompare(b));
116899
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
+ }
116900
116982
  function parseImportedSymbols(matchedString, importType) {
116901
116983
  if (importType === "namespace")
116902
116984
  return ["*"];
@@ -117023,44 +117105,126 @@ function isBinaryContent(content) {
117023
117105
  }
117024
117106
  return false;
117025
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
+ }
117026
117175
  async function scanFileAsync(filePath, absoluteRoot, maxFileSize) {
117027
117176
  let content;
117028
117177
  let fileStats;
117029
117178
  try {
117030
117179
  fileStats = fsSync8.statSync(filePath);
117031
117180
  if (fileStats.size > maxFileSize) {
117032
- return { node: null, edges: [], symbolEdges: [] };
117181
+ return {
117182
+ node: null,
117183
+ edges: [],
117184
+ symbolEdges: [],
117185
+ diagnostics: { oversizedFiles: [toModuleName(filePath, absoluteRoot)] }
117186
+ };
117033
117187
  }
117034
117188
  content = fsSync8.readFileSync(filePath, "utf-8");
117035
117189
  } catch {
117036
- return { node: null, edges: [], symbolEdges: [] };
117190
+ return {
117191
+ node: null,
117192
+ edges: [],
117193
+ symbolEdges: [],
117194
+ diagnostics: { unreadableFiles: [toModuleName(filePath, absoluteRoot)] }
117195
+ };
117037
117196
  }
117038
117197
  if (isBinaryContent(content)) {
117039
- return { node: null, edges: [], symbolEdges: [] };
117198
+ return {
117199
+ node: null,
117200
+ edges: [],
117201
+ symbolEdges: [],
117202
+ diagnostics: { binaryFiles: [toModuleName(filePath, absoluteRoot)] }
117203
+ };
117040
117204
  }
117041
117205
  const grammarId = getLanguage(filePath);
117042
117206
  const facts = await _internals82.extractFileSymbols(grammarId, content);
117043
117207
  if (facts === null) {
117044
- 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
+ }
117045
117215
  return {
117046
- node: {
117047
- filePath,
117048
- moduleName: moduleName2,
117049
- exports: [],
117050
- imports: [],
117051
- language: grammarId,
117052
- mtime: fileStats.mtime.toISOString(),
117053
- ontology: _internals82.extractFileOntology({
117054
- moduleName: moduleName2,
117055
- filePath,
117056
- content,
117057
- language: grammarId,
117058
- exports: [],
117059
- imports: []
117060
- })
117061
- },
117062
- edges: [],
117063
- 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
+ }
117064
117228
  };
117065
117229
  }
117066
117230
  const exportedDefs = facts.defs.filter((d) => d.exported);
@@ -117111,6 +117275,10 @@ async function scanFileAsync(filePath, absoluteRoot, maxFileSize) {
117111
117275
  });
117112
117276
  }
117113
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
+ }));
117114
117282
  const symbolEdges = [];
117115
117283
  const localToImported = new Map;
117116
117284
  for (const imp of facts.imports) {
@@ -117141,7 +117309,12 @@ async function scanFileAsync(filePath, absoluteRoot, maxFileSize) {
117141
117309
  toSymbol: mapping.imported
117142
117310
  });
117143
117311
  }
117144
- return { node, edges, symbolEdges };
117312
+ return {
117313
+ node,
117314
+ edges,
117315
+ symbolEdges,
117316
+ diagnostics: unresolvedImports.length > 0 ? { unresolvedImports } : undefined
117317
+ };
117145
117318
  }
117146
117319
  async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117147
117320
  validateWorkspace(workspaceRoot);
@@ -117163,6 +117336,7 @@ async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117163
117336
  skippedFiles: 0,
117164
117337
  truncated: false
117165
117338
  };
117339
+ const diagnostics = createEmptyDiagnostics();
117166
117340
  const sourceFiles = await findSourceFilesAsync(absoluteRoot, stats2, {
117167
117341
  walkBudgetMs,
117168
117342
  maxFiles,
@@ -117183,6 +117357,7 @@ async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117183
117357
  let processedSinceYield = 0;
117184
117358
  for (const filePath of sourceFiles) {
117185
117359
  const result = await scanFileAsync(filePath, absoluteRoot, maxFileSize);
117360
+ mergeDiagnostics(diagnostics, result.diagnostics);
117186
117361
  if (result.node) {
117187
117362
  let appended = false;
117188
117363
  try {
@@ -117223,6 +117398,7 @@ async function buildWorkspaceGraphAsync(workspaceRoot, options) {
117223
117398
  if (allSymbolEdges.length > 0) {
117224
117399
  graph.symbolEdges = allSymbolEdges;
117225
117400
  }
117401
+ graph.diagnostics = diagnosticsHaveEntries(diagnostics) ? diagnostics : createEmptyDiagnostics();
117226
117402
  if (stats2.skippedFiles > 0 || stats2.skippedDirs > 0 || stats2.truncated) {
117227
117403
  log(`[repo-graph] Scan stats: ${stats2.filesScanned} files scanned, ` + `${stats2.skippedFiles} files skipped, ${stats2.skippedDirs} dirs skipped` + (stats2.truncated ? ", TRUNCATED" : ""));
117228
117404
  }
@@ -117264,7 +117440,11 @@ import * as fsPromises7 from "node:fs/promises";
117264
117440
  import * as path138 from "node:path";
117265
117441
 
117266
117442
  // src/tools/repo-graph/query.ts
117443
+ init_path_security();
117444
+ import * as fsSync9 from "node:fs";
117267
117445
  import * as path136 from "node:path";
117446
+ var GRAPH_HEALTH_OUTPUT_LIMIT = 50;
117447
+ var MAX_HEALTH_PATH_LENGTH = 500;
117268
117448
  var cachedReverseIndex = null;
117269
117449
  function normalizeLookupPath(input) {
117270
117450
  return normalizeGraphPath(input).replace(/^(?:\.\/)+/, "");
@@ -117363,6 +117543,139 @@ function isGraphFresh(graph, maxAgeMs = 5 * 60 * 1000) {
117363
117543
  return false;
117364
117544
  return Date.now() - built <= maxAgeMs;
117365
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
+ }
117366
117679
  function getImporters(graph, filePath) {
117367
117680
  const node = getGraphNode(graph, filePath);
117368
117681
  if (!node)
@@ -117951,7 +118264,7 @@ function buildOntologyPreflightPacket(graph, filePaths = [], options = {}) {
117951
118264
  init_utils2();
117952
118265
  init_logger();
117953
118266
  init_path_security();
117954
- 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";
117955
118268
  import * as fsPromises6 from "node:fs/promises";
117956
118269
  import * as path137 from "node:path";
117957
118270
  var WINDOWS_RENAME_MAX_RETRIES2 = 5;
@@ -118076,7 +118389,7 @@ function loadGraphSync(workspace) {
118076
118389
  const graphPath = getGraphPath(workspace);
118077
118390
  if (!existsSync77(graphPath))
118078
118391
  return null;
118079
- const stats2 = statSync27(graphPath);
118392
+ const stats2 = statSync28(graphPath);
118080
118393
  const content = readFileSync53(graphPath, "utf-8");
118081
118394
  if (content.includes("\x00") || content.includes("�")) {
118082
118395
  throw Object.assign(new Error("repo-graph.json contains null bytes or invalid encoding"), { code: "CORRUPTION" });
@@ -123102,9 +123415,9 @@ function parseDelegateDirectiveBlock(text) {
123102
123415
  }
123103
123416
  async function injectForDelegate(params) {
123104
123417
  const { directory, agent, taskTitle, sessionId, config: config3 } = params;
123105
- const cap = config3.delegate_max_inject_count ?? 8;
123418
+ const cap2 = config3.delegate_max_inject_count ?? 8;
123106
123419
  const expectedTools = params.expectedTools && params.expectedTools.length > 0 ? params.expectedTools : defaultExpectedToolsForAgent(agent);
123107
- if (cap <= 0)
123420
+ if (cap2 <= 0)
123108
123421
  return { entries: [], trace_id: "" };
123109
123422
  const role = stripKnownSwarmPrefix(agent).toLowerCase();
123110
123423
  const firstTool = expectedTools.length > 0 ? expectedTools[0] : undefined;
@@ -123128,11 +123441,11 @@ async function injectForDelegate(params) {
123128
123441
  tier: "all",
123129
123442
  applyScopeFilter: true,
123130
123443
  applyRoleScope: false,
123131
- maxResults: Math.max(40, cap * 4),
123444
+ maxResults: Math.max(40, cap2 * 4),
123132
123445
  emitEvent: false
123133
123446
  });
123134
123447
  const scoped = search.results.filter((e) => matchesDelegateScope(e, role, expectedTools));
123135
- const capped = scoped.slice(0, cap);
123448
+ const capped = scoped.slice(0, cap2);
123136
123449
  if (capped.length > 0) {
123137
123450
  const ranks = {};
123138
123451
  const scores = {};
@@ -129968,7 +130281,7 @@ ${body}`);
129968
130281
  init_zod();
129969
130282
  init_task_file();
129970
130283
  import { appendFileSync as appendFileSync18, existsSync as existsSync95, mkdirSync as mkdirSync42, readFileSync as readFileSync64 } from "node:fs";
129971
- import { join as join131 } from "node:path";
130284
+ import { join as join132 } from "node:path";
129972
130285
  var EVIDENCE_DIR2 = ".swarm/evidence";
129973
130286
  var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
129974
130287
  var COUNCIL_GATE_NAME = "council";
@@ -130006,7 +130319,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
130006
130319
  if (!VALID_TASK_ID.test(synthesis.taskId)) {
130007
130320
  throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" — must match N.M or N.M.P format`);
130008
130321
  }
130009
- const dir = join131(workingDir, EVIDENCE_DIR2);
130322
+ const dir = join132(workingDir, EVIDENCE_DIR2);
130010
130323
  mkdirSync42(dir, { recursive: true });
130011
130324
  const filePath = taskEvidencePath(workingDir, synthesis.taskId);
130012
130325
  await _internals96.withTaskEvidenceLock(workingDir, synthesis.taskId, COUNCIL_AGENT_ID, async () => {
@@ -130042,7 +130355,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
130042
130355
  await atomicWriteFile(filePath, JSON.stringify(updated, null, 2));
130043
130356
  });
130044
130357
  try {
130045
- const councilDir = join131(workingDir, ".swarm", "council");
130358
+ const councilDir = join132(workingDir, ".swarm", "council");
130046
130359
  mkdirSync42(councilDir, { recursive: true });
130047
130360
  const auditLine = JSON.stringify({
130048
130361
  round: synthesis.roundNumber,
@@ -130050,7 +130363,7 @@ async function writeCouncilEvidence(workingDir, synthesis) {
130050
130363
  timestamp: synthesis.timestamp,
130051
130364
  vetoedBy: synthesis.vetoedBy
130052
130365
  });
130053
- appendFileSync18(join131(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
130366
+ appendFileSync18(join132(councilDir, `${synthesis.taskId}.rounds.jsonl`), `${auditLine}
130054
130367
  `);
130055
130368
  } catch (auditError) {
130056
130369
  console.warn(`writeCouncilEvidence: failed to append round-history audit log: ${auditError instanceof Error ? auditError.message : String(auditError)}`);
@@ -130407,7 +130720,7 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
130407
130720
  init_zod();
130408
130721
  init_task_file();
130409
130722
  import { existsSync as existsSync96, mkdirSync as mkdirSync43, readFileSync as readFileSync65 } from "node:fs";
130410
- import { join as join132 } from "node:path";
130723
+ import { join as join133 } from "node:path";
130411
130724
  var COUNCIL_DIR = ".swarm/council";
130412
130725
  var CouncilCriteriaSchema = exports_external.object({
130413
130726
  taskId: exports_external.string(),
@@ -130419,17 +130732,17 @@ var CouncilCriteriaSchema = exports_external.object({
130419
130732
  declaredAt: exports_external.string()
130420
130733
  });
130421
130734
  async function writeCriteria(workingDir, taskId, criteria) {
130422
- const dir = join132(workingDir, COUNCIL_DIR);
130735
+ const dir = join133(workingDir, COUNCIL_DIR);
130423
130736
  mkdirSync43(dir, { recursive: true });
130424
130737
  const payload = {
130425
130738
  taskId,
130426
130739
  criteria,
130427
130740
  declaredAt: new Date().toISOString()
130428
130741
  };
130429
- 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));
130430
130743
  }
130431
130744
  function readCriteria(workingDir, taskId) {
130432
- const filePath = join132(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
130745
+ const filePath = join133(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
130433
130746
  if (!existsSync96(filePath))
130434
130747
  return null;
130435
130748
  try {
@@ -149644,7 +149957,8 @@ var VALID_ACTIONS = [
149644
149957
  "preflight_packet",
149645
149958
  "callers",
149646
149959
  "dead_exports",
149647
- "context_pack"
149960
+ "context_pack",
149961
+ "graph_health"
149648
149962
  ];
149649
149963
  var MAX_FILE_PATH_LENGTH3 = 500;
149650
149964
  var MAX_SYMBOL_LENGTH2 = 256;
@@ -149708,7 +150022,7 @@ async function loadOrError(directory, action) {
149708
150022
  }
149709
150023
  }
149710
150024
  var repo_map = createSwarmTool({
149711
- 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.',
149712
150026
  args: {
149713
150027
  action: exports_external.enum([
149714
150028
  "build",
@@ -149722,8 +150036,9 @@ var repo_map = createSwarmTool({
149722
150036
  "preflight_packet",
149723
150037
  "callers",
149724
150038
  "dead_exports",
149725
- "context_pack"
149726
- ]).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"'),
149727
150042
  file: exports_external.string().optional().describe("Target file (workspace-relative or absolute). Required for importers/dependencies/localization/ontology. Optional for preflight_packet."),
149728
150043
  files: exports_external.array(exports_external.string()).optional().describe("Multiple target files for blast_radius/preflight_packet. If omitted, falls back to `file`."),
149729
150044
  symbol: exports_external.string().optional().describe('Exported symbol name. Restricts consumers on action="importers"; required for action="callers"/"context_pack".'),
@@ -149758,6 +150073,15 @@ var repo_map = createSwarmTool({
149758
150073
  return err(action, `build failed: ${message}`);
149759
150074
  }
149760
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
+ }
149761
150085
  const loaded = await loadOrError(directory, action);
149762
150086
  if (!loaded.ok)
149763
150087
  return loaded.response;
@@ -153111,7 +153435,7 @@ init_skill_generator();
153111
153435
  init_create_tool();
153112
153436
  import { existsSync as existsSync119 } from "node:fs";
153113
153437
  import { readdir as readdir12, readFile as readFile33 } from "node:fs/promises";
153114
- import { join as join166 } from "node:path";
153438
+ import { join as join167 } from "node:path";
153115
153439
  var run_stale_reconciliation = createSwarmTool({
153116
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).",
153117
153441
  args: {
@@ -153138,8 +153462,8 @@ var run_stale_reconciliation = createSwarmTool({
153138
153462
  } catch {}
153139
153463
  const skillEntries = [];
153140
153464
  for (const dir of [
153141
- join166(directory, ".opencode", "skills", "generated"),
153142
- join166(directory, ".swarm", "skills", "proposals")
153465
+ join167(directory, ".opencode", "skills", "generated"),
153466
+ join167(directory, ".swarm", "skills", "proposals")
153143
153467
  ]) {
153144
153468
  if (!_internals125.existsSync(dir))
153145
153469
  continue;
@@ -153148,14 +153472,14 @@ var run_stale_reconciliation = createSwarmTool({
153148
153472
  if (entry.isDirectory()) {
153149
153473
  skillEntries.push({
153150
153474
  slug: entry.name,
153151
- path: join166(dir, entry.name),
153475
+ path: join167(dir, entry.name),
153152
153476
  isProposal: false
153153
153477
  });
153154
153478
  } else if (entry.name.endsWith(".md")) {
153155
153479
  const slug = entry.name.replace(/\.md$/, "");
153156
153480
  skillEntries.push({
153157
153481
  slug,
153158
- path: join166(dir, entry.name),
153482
+ path: join167(dir, entry.name),
153159
153483
  isProposal: true
153160
153484
  });
153161
153485
  }
@@ -153163,7 +153487,7 @@ var run_stale_reconciliation = createSwarmTool({
153163
153487
  }
153164
153488
  const results = [];
153165
153489
  for (const { slug, path: path210, isProposal } of skillEntries) {
153166
- const skillMdPath = isProposal ? path210 : join166(path210, "SKILL.md");
153490
+ const skillMdPath = isProposal ? path210 : join167(path210, "SKILL.md");
153167
153491
  if (!_internals125.existsSync(skillMdPath))
153168
153492
  continue;
153169
153493
  const content = await _internals125.readFile(skillMdPath, "utf-8");
@@ -153176,7 +153500,7 @@ var run_stale_reconciliation = createSwarmTool({
153176
153500
  continue;
153177
153501
  if (args2.clear) {
153178
153502
  if (!isProposal) {
153179
- const markerPath = join166(path210, "stale.marker");
153503
+ const markerPath = join167(path210, "stale.marker");
153180
153504
  if (_internals125.existsSync(markerPath)) {
153181
153505
  try {
153182
153506
  await _internals125.clearSkillStale(path210);
@@ -158389,14 +158713,6 @@ async function initializeOpenCodeSwarm(ctx) {
158389
158713
  prEventCleanup?.();
158390
158714
  };
158391
158715
  process.on("exit", cleanupAutomation);
158392
- process.once("SIGINT", () => {
158393
- cleanupAutomation();
158394
- process.exit(130);
158395
- });
158396
- process.once("SIGTERM", () => {
158397
- cleanupAutomation();
158398
- process.exit(143);
158399
- });
158400
158716
  if (shouldRunOnStartup(automationConfig)) {
158401
158717
  const enableAutofix = automationConfig.capabilities?.config_doctor_autofix === true;
158402
158718
  Promise.resolve().then(() => (init_config_doctor(), exports_config_doctor)).then(({ runConfigDoctorWithFixes: runConfigDoctorWithFixes2 }) => {
@@ -158412,9 +158728,19 @@ async function initializeOpenCodeSwarm(ctx) {
158412
158728
  try {
158413
158729
  const autoFixableCount = doctorResult.result.findings.filter((f) => f.autoFixable).length;
158414
158730
  if (!enableAutofix && autoFixableCount > 0) {
158415
- 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
+ }
158416
158737
  } else if (enableAutofix && doctorResult.appliedFixes.length > 0) {
158417
- 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
+ }
158418
158744
  }
158419
158745
  } catch {}
158420
158746
  }
@@ -158957,7 +159283,9 @@ async function initializeOpenCodeSwarm(ctx) {
158957
159283
  argsRecord.prompt = `SKILLS: none
158958
159284
 
158959
159285
  ${promptRaw}`;
158960
- 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
+ }
158961
159289
  } else {
158962
159290
  const topSkills = qualified.slice(0, 5);
158963
159291
  const skillPaths = topSkills.map((s) => {
@@ -158975,7 +159303,9 @@ ${promptRaw}`;
158975
159303
  ${promptRaw}`;
158976
159304
  argsRecord.prompt = newPrompt;
158977
159305
  const skillNames = topSkills.map((s) => `${path225.basename(s.skillPath)} (score: ${s.score.toFixed(2)})`).join(", ");
158978
- console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
159306
+ if (!config3.quiet) {
159307
+ console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
159308
+ }
158979
159309
  for (const skill of topSkills) {
158980
159310
  try {
158981
159311
  appendSkillUsageEntry(ctx.directory, {
@@ -14,7 +14,7 @@ import { extractFileSymbols } from '../../lang/symbol-graph';
14
14
  import { extractPythonSymbols, extractTSSymbols } from '../symbols';
15
15
  import { extractFileOntology } from './ontology';
16
16
  import { safeRealpathSync } from './safe-realpath';
17
- import type { BuildWorkspaceGraphOptions, GraphEdge, GraphNode, RepoGraph, SymbolEdge } from './types';
17
+ import type { BuildWorkspaceGraphOptions, GraphEdge, GraphNode, RepoGraph, RepoGraphDiagnostics, SymbolEdge } from './types';
18
18
  /**
19
19
  * _internals DI seam for safeRealpathSync.
20
20
  * Defaults to the real implementation. Tests can override this to inject
@@ -141,6 +141,8 @@ export interface AsyncScanResult {
141
141
  edges: GraphEdge[];
142
142
  /** Symbol-to-symbol reference edges (schema >= 1.2.0). */
143
143
  symbolEdges: SymbolEdge[];
144
+ /** Optional file-level diagnostics collected during async scanning. */
145
+ diagnostics?: RepoGraphDiagnostics;
144
146
  }
145
147
  /**
146
148
  * Scan a single file and extract its graph node and edges.
@@ -157,11 +159,11 @@ export declare function scanFile(filePath: string, absoluteRoot: string, maxFile
157
159
  * (extractFileSymbols) to populate exportRanges and symbolEdges.
158
160
  *
159
161
  * Fail-open: if extractFileSymbols returns null (grammar unavailable,
160
- * timeout, parse error), falls back to a minimal file-level node with
161
- * empty exports/imports/symbolEdges never throws.
162
+ * timeout, parse error), falls back to the file-level scanner so imports,
163
+ * exports, and dependency edges are preserved.
162
164
  *
163
- * Oversized, binary, or unreadable files return { node: null, edges: [], symbolEdges: [] }
164
- * matching the sync scanFile contract.
165
+ * Oversized, binary, or unreadable files return
166
+ * { node: null, edges: [], symbolEdges: [] } with bounded diagnostics.
165
167
  *
166
168
  * @param filePath - Absolute path to the file to scan
167
169
  * @param absoluteRoot - Absolute path to workspace root
@@ -1,7 +1,8 @@
1
- import type { BlastRadiusResult, CallerReference, ContextPackResult, DeadExportsResult, FileOntology, FileReference, GraphNode, LocalizationBlock, PackageBoundarySummary, RepoGraph, SymbolReference } from './types';
1
+ import type { BlastRadiusResult, CallerReference, ContextPackResult, DeadExportsResult, FileOntology, FileReference, GraphHealthResult, GraphNode, LocalizationBlock, PackageBoundarySummary, RepoGraph, SymbolReference } from './types';
2
2
  export declare function getGraphNode(graph: RepoGraph, input: string): GraphNode | undefined;
3
3
  export declare function resetQueryCache(): void;
4
4
  export declare function isGraphFresh(graph: RepoGraph | null, maxAgeMs?: number): boolean;
5
+ export declare function getGraphHealth(graph: RepoGraph | null, workspaceRoot?: string): GraphHealthResult;
5
6
  export declare function getImporters(graph: RepoGraph, filePath: string): FileReference[];
6
7
  export declare function getDependencies(graph: RepoGraph, filePath: string): FileReference[];
7
8
  export declare function getSymbolConsumers(graph: RepoGraph, filePath: string, symbolName: string): SymbolReference[];
@@ -22,6 +22,10 @@ export declare const REPO_GRAPH_FILENAME = "repo-graph.json";
22
22
  * symbol reference edges). Both fields are optional, so 1.0.0 and 1.1.0 graphs
23
23
  * still load without corruption. New queries may use these fields to provide
24
24
  * more precise context-packing and symbol-level navigation.
25
+ *
26
+ * Diagnostics are additive and optional on all schema versions. Old graphs
27
+ * without diagnostics remain readable; graph-health queries surface empty
28
+ * diagnostics with an explicit rebuild note.
25
29
  */
26
30
  export declare const GRAPH_SCHEMA_VERSION = "1.2.0";
27
31
  /**
@@ -248,6 +252,37 @@ export interface DeadExportsResult {
248
252
  /** Human-readable note describing scope and limitations of the result. */
249
253
  note: string;
250
254
  }
255
+ export interface GraphExtractionFailure {
256
+ file: string;
257
+ language: string;
258
+ reason: string;
259
+ }
260
+ export interface GraphUnresolvedImport {
261
+ file: string;
262
+ specifier: string;
263
+ }
264
+ export interface RepoGraphDiagnostics {
265
+ extractionFailures?: GraphExtractionFailure[];
266
+ unresolvedImports?: GraphUnresolvedImport[];
267
+ oversizedFiles?: string[];
268
+ unsupportedFiles?: string[];
269
+ binaryFiles?: string[];
270
+ unreadableFiles?: string[];
271
+ lowConfidenceEdgeCount?: number;
272
+ }
273
+ export interface GraphHealthResult {
274
+ schemaVersion: string | null;
275
+ fresh: boolean;
276
+ staleFiles: string[];
277
+ extractionFailures: GraphExtractionFailure[];
278
+ unresolvedImports: GraphUnresolvedImport[];
279
+ oversizedFiles: string[];
280
+ unsupportedFiles: string[];
281
+ binaryFiles: string[];
282
+ unreadableFiles: string[];
283
+ lowConfidenceEdgeCount: number;
284
+ notes: string[];
285
+ }
251
286
  export interface BlastRadiusResult {
252
287
  target: string[];
253
288
  directDependents: string[];
@@ -299,6 +334,8 @@ export interface RepoGraph {
299
334
  };
300
335
  /** Symbol-level reference edges (schema >= 1.2.0; absent on older graphs). */
301
336
  symbolEdges?: SymbolEdge[];
337
+ /** Optional bounded diagnostics from the last graph build. */
338
+ diagnostics?: RepoGraphDiagnostics;
302
339
  }
303
340
  /**
304
341
  * Options for building a workspace graph.
@@ -21,8 +21,8 @@ export { updateGraphForFiles } from './repo-graph/incremental';
21
21
  export type { ExtractFileOntologyInput } from './repo-graph/ontology';
22
22
  export { extractFileOntology } from './repo-graph/ontology';
23
23
  export type { DeadExportsOptions } from './repo-graph/query';
24
- export { buildOntologyPreflightPacket, getBlastRadius, getCallers, getContextPack, getDeadExports, getDependencies, getFileOntology, getGraphNode, getImporters, getKeyFiles, getLocalizationContext, getPackageBoundaries, getSymbolConsumers, isGraphFresh, resetQueryCache, } from './repo-graph/query';
24
+ export { buildOntologyPreflightPacket, getBlastRadius, getCallers, getContextPack, getDeadExports, getDependencies, getFileOntology, getGraphHealth, getGraphNode, getImporters, getKeyFiles, getLocalizationContext, getPackageBoundaries, getSymbolConsumers, isGraphFresh, resetQueryCache, } from './repo-graph/query';
25
25
  export { getGraphPath, loadGraph, loadGraphSync, loadOrCreateGraph, saveGraph, saveIfDirty, } from './repo-graph/storage';
26
- export type { BlastRadiusResult, BuildWorkspaceGraphOptions, CallerReference, ContextPackResult, ContextPackSpan, ConventionFact, DataOperationFact, DeadExportCandidate, DeadExportsResult, FileOntology, FileReference, FileRole, GraphEdge, GraphNode, LocalizationBlock, OntologyFinding, PackageBoundarySummary, RepoGraph, RouteFact, RouteMethod, SecurityFact, SymbolEdge, SymbolReference, } from './repo-graph/types';
26
+ export type { BlastRadiusResult, BuildWorkspaceGraphOptions, CallerReference, ContextPackResult, ContextPackSpan, ConventionFact, DataOperationFact, DeadExportCandidate, DeadExportsResult, FileOntology, FileReference, FileRole, GraphEdge, GraphExtractionFailure, GraphHealthResult, GraphNode, GraphUnresolvedImport, LocalizationBlock, OntologyFinding, PackageBoundarySummary, RepoGraph, RepoGraphDiagnostics, RouteFact, RouteMethod, SecurityFact, SymbolEdge, SymbolReference, } from './repo-graph/types';
27
27
  export { createEmptyGraph, GRAPH_SCHEMA_VERSION, isSchemaVersionAtLeast, normalizeGraphPath, REPO_GRAPH_FILENAME, updateGraphMetadata, } from './repo-graph/types';
28
28
  export { validateGraphEdge, validateGraphNode, validateWorkspace, } from './repo-graph/validation';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.99.5",
3
+ "version": "7.99.6",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",