@swarmvaultai/engine 0.1.31 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -174,11 +174,13 @@ This matters because many "OpenAI-compatible" backends only implement part of th
174
174
  - `addInput(rootDir, input, { author, contributor })` captures supported URLs into normalized markdown before ingesting them, or falls back to generic URL ingest
175
175
  - `ingestDirectory(rootDir, inputDir, { repoRoot, include, exclude, maxFiles, gitignore, extractClasses })` recursively ingests a local directory as a repo-aware code/content source tree
176
176
  - `importInbox(rootDir, inputDir?)` recursively imports supported inbox files plus markdown and HTML browser-clipper style bundles
177
- - JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, PHP, Ruby, and PowerShell inputs are treated as code sources and compiled into both source pages and `wiki/code/` module pages
177
+ - JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, PHP, Ruby, PowerShell, Kotlin, and Scala inputs are treated as code sources and compiled into both source pages and `wiki/code/` module pages
178
+ - `.rst` and `.rest` inputs are treated as first-class text sources with lightweight heading and directive normalization before analysis
178
179
  - code manifests can carry `repoRelativePath`, and compile writes `state/code-index.json` so local imports can resolve across an ingested repo tree
179
180
  - repo-aware manifests, graph nodes, and graph pages can also carry `sourceClass` so first-party, third-party, resource, and generated material can be filtered and reported separately
180
181
  - HTML and markdown URL ingests localize remote image references into `raw/assets/<sourceId>/` by default and rewrite the stored markdown to local relative paths
181
182
  - PDF and DOCX ingests now write extracted-text and metadata sidecars under `state/extracts/`, and image ingest keeps the same sidecar model for vision extraction
183
+ - Tree-sitter-backed languages now verify runtime and grammar compatibility per language; failures stay local to the affected source and surface as diagnostics instead of aborting the whole compile
182
184
 
183
185
  ### Compile + Query
184
186
 
@@ -206,6 +208,7 @@ This matters because many "OpenAI-compatible" backends only implement part of th
206
208
  - `getWatchStatus(rootDir)` reads the latest watch-status artifact plus pending semantic refresh entries
207
209
  - `syncTrackedRepos(rootDir)` refreshes previously ingested repo roots, updates changed manifests, and removes deleted repo manifests
208
210
  - `syncTrackedReposForWatch(rootDir)` is the repo-watch sync path that defers non-code semantic refresh into `state/watch/`
211
+ - large ingest and compile passes emit low-noise progress on TTYs, and report presentation rolls up tiny fragmented communities without mutating the canonical graph artifact
209
212
  - `installGitHooks(rootDir)`, `uninstallGitHooks(rootDir)`, and `getGitHookStatus(rootDir)` manage local `post-commit` and `post-checkout` hook blocks for the nearest git repository
210
213
  - `installAgent(rootDir, agent, { hook })` writes agent instructions and returns the primary `target`, all touched `targets`, and optional merge warnings for agents such as Aider
211
214
  - `lintVault(rootDir, options)` runs structural lint, optional deep lint, optional contradiction-only filtering through `{ conflicts: true }`, and optional web-augmented evidence gathering
package/dist/index.d.ts CHANGED
@@ -57,7 +57,7 @@ type ApprovalChangeType = "create" | "update" | "delete" | "promote";
57
57
  type SourceKind = "markdown" | "text" | "pdf" | "image" | "html" | "docx" | "binary" | "code";
58
58
  type SourceCaptureType = "arxiv" | "doi" | "tweet" | "article" | "url";
59
59
  type SourceClass = "first_party" | "third_party" | "resource" | "generated";
60
- type CodeLanguage = "javascript" | "jsx" | "typescript" | "tsx" | "python" | "go" | "rust" | "java" | "csharp" | "c" | "cpp" | "php" | "ruby" | "powershell";
60
+ type CodeLanguage = "javascript" | "jsx" | "typescript" | "tsx" | "python" | "go" | "rust" | "java" | "kotlin" | "scala" | "csharp" | "c" | "cpp" | "php" | "ruby" | "powershell";
61
61
  type CodeSymbolKind = "function" | "class" | "interface" | "type_alias" | "enum" | "variable" | "struct" | "trait";
62
62
  type OrchestrationRole = "research" | "audit" | "context" | "safety";
63
63
  declare const webSearchProviderTypeSchema: z.ZodEnum<{
@@ -966,6 +966,12 @@ interface GraphReportArtifact {
966
966
  path?: string;
967
967
  title?: string;
968
968
  }>;
969
+ fragmentedCommunityRollup?: {
970
+ totalCommunities: number;
971
+ rolledUpCount: number;
972
+ rolledUpNodes: number;
973
+ exampleLabels: string[];
974
+ };
969
975
  surprisingConnections: Array<{
970
976
  id: string;
971
977
  sourceNodeId: string;
package/dist/index.js CHANGED
@@ -1741,7 +1741,9 @@ import fs5 from "fs/promises";
1741
1741
  import { createRequire } from "module";
1742
1742
  import path5 from "path";
1743
1743
  var require2 = createRequire(import.meta.url);
1744
- var TREE_SITTER_PACKAGE_ROOT = path5.dirname(path5.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
1744
+ var TREE_SITTER_RUNTIME_PACKAGE = "@vscode/tree-sitter-wasm";
1745
+ var TREE_SITTER_EXTRA_GRAMMARS_PACKAGE = "tree-sitter-wasms";
1746
+ var packageRootCache = /* @__PURE__ */ new Map();
1745
1747
  var RATIONALE_MARKERS = ["NOTE:", "IMPORTANT:", "HACK:", "WHY:", "RATIONALE:"];
1746
1748
  function stripKnownCommentPrefix(line) {
1747
1749
  let next = line.trim();
@@ -1755,21 +1757,41 @@ function stripKnownCommentPrefix(line) {
1755
1757
  var treeSitterModulePromise;
1756
1758
  var treeSitterInitPromise;
1757
1759
  var languageCache = /* @__PURE__ */ new Map();
1758
- var grammarFileByLanguage = {
1759
- python: "tree-sitter-python.wasm",
1760
- go: "tree-sitter-go.wasm",
1761
- rust: "tree-sitter-rust.wasm",
1762
- java: "tree-sitter-java.wasm",
1763
- csharp: "tree-sitter-c-sharp.wasm",
1764
- c: "tree-sitter-cpp.wasm",
1765
- cpp: "tree-sitter-cpp.wasm",
1766
- php: "tree-sitter-php.wasm",
1767
- ruby: "tree-sitter-ruby.wasm",
1768
- powershell: "tree-sitter-powershell.wasm"
1760
+ var grammarAssetByLanguage = {
1761
+ python: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-python.wasm" },
1762
+ go: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-go.wasm" },
1763
+ rust: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-rust.wasm" },
1764
+ java: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-java.wasm" },
1765
+ kotlin: { packageName: TREE_SITTER_EXTRA_GRAMMARS_PACKAGE, relativePath: "out/tree-sitter-kotlin.wasm" },
1766
+ scala: { packageName: TREE_SITTER_EXTRA_GRAMMARS_PACKAGE, relativePath: "out/tree-sitter-scala.wasm" },
1767
+ csharp: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-c-sharp.wasm" },
1768
+ c: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-cpp.wasm" },
1769
+ cpp: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-cpp.wasm" },
1770
+ php: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-php.wasm" },
1771
+ ruby: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-ruby.wasm" },
1772
+ powershell: { packageName: TREE_SITTER_RUNTIME_PACKAGE, relativePath: "wasm/tree-sitter-powershell.wasm" }
1769
1773
  };
1774
+ function resolvePackageRoot(packageName) {
1775
+ const cached = packageRootCache.get(packageName);
1776
+ if (cached) {
1777
+ return cached;
1778
+ }
1779
+ let resolved;
1780
+ try {
1781
+ resolved = path5.dirname(require2.resolve(`${packageName}/package.json`));
1782
+ } catch {
1783
+ resolved = path5.dirname(path5.dirname(require2.resolve(packageName)));
1784
+ }
1785
+ packageRootCache.set(packageName, resolved);
1786
+ return resolved;
1787
+ }
1788
+ function grammarAssetPath(language) {
1789
+ const asset = grammarAssetByLanguage[language];
1790
+ return path5.join(resolvePackageRoot(asset.packageName), asset.relativePath);
1791
+ }
1770
1792
  async function getTreeSitterModule() {
1771
1793
  if (!treeSitterModulePromise) {
1772
- treeSitterModulePromise = import(require2.resolve("@vscode/tree-sitter-wasm")).then(
1794
+ treeSitterModulePromise = import(require2.resolve(TREE_SITTER_RUNTIME_PACKAGE)).then(
1773
1795
  (module) => module.default ?? module
1774
1796
  );
1775
1797
  }
@@ -1777,8 +1799,9 @@ async function getTreeSitterModule() {
1777
1799
  }
1778
1800
  async function ensureTreeSitterInit(module) {
1779
1801
  if (!treeSitterInitPromise) {
1802
+ const runtimeRoot = resolvePackageRoot(TREE_SITTER_RUNTIME_PACKAGE);
1780
1803
  treeSitterInitPromise = module.Parser.init({
1781
- locateFile: () => path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
1804
+ locateFile: () => path5.join(runtimeRoot, "wasm", "tree-sitter.wasm")
1782
1805
  });
1783
1806
  }
1784
1807
  return treeSitterInitPromise;
@@ -1791,7 +1814,7 @@ async function loadLanguage(language) {
1791
1814
  const loader = (async () => {
1792
1815
  const module = await getTreeSitterModule();
1793
1816
  await ensureTreeSitterInit(module);
1794
- const bytes = await fs5.readFile(path5.join(TREE_SITTER_PACKAGE_ROOT, "wasm", grammarFileByLanguage[language]));
1817
+ const bytes = await fs5.readFile(grammarAssetPath(language));
1795
1818
  return module.Language.load(bytes);
1796
1819
  })();
1797
1820
  languageCache.set(language, loader);
@@ -1805,7 +1828,7 @@ function normalizeSymbolReference(value) {
1805
1828
  return lastSegment.replace(/[,:;]+$/g, "").trim();
1806
1829
  }
1807
1830
  function stripCodeExtension(filePath) {
1808
- return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
1831
+ return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|kt|kts|scala|sc|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
1809
1832
  }
1810
1833
  function manifestModuleName(manifest, language) {
1811
1834
  const repoPath = manifest.repoRelativePath ?? path5.basename(manifest.originalPath ?? manifest.storedPath);
@@ -2044,6 +2067,7 @@ function extractIdentifier(node) {
2044
2067
  }
2045
2068
  if ([
2046
2069
  "identifier",
2070
+ "simple_identifier",
2047
2071
  "field_identifier",
2048
2072
  "type_identifier",
2049
2073
  "name",
@@ -2057,6 +2081,7 @@ function extractIdentifier(node) {
2057
2081
  const preferred = node.childForFieldName("name") ?? node.namedChildren.find(
2058
2082
  (child) => child && [
2059
2083
  "identifier",
2084
+ "simple_identifier",
2060
2085
  "field_identifier",
2061
2086
  "type_identifier",
2062
2087
  "name",
@@ -2119,6 +2144,25 @@ function diagnosticsFromTree(rootNode) {
2119
2144
  visit(rootNode);
2120
2145
  return diagnostics.slice(0, 20);
2121
2146
  }
2147
+ function treeSitterCompatibilityMessage(language, error) {
2148
+ const message = error instanceof Error ? error.message : String(error);
2149
+ if (typeof error === "object" && error && "code" in error && error.code === "MODULE_NOT_FOUND") {
2150
+ return `Tree-sitter runtime support for ${language} is unavailable. Reinstall @swarmvaultai/engine so the packaged parser runtime is present.`;
2151
+ }
2152
+ if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
2153
+ return `Missing tree-sitter grammar asset for ${language}. Reinstall @swarmvaultai/engine so the packaged grammar files are present.`;
2154
+ }
2155
+ return `Tree-sitter support for ${language} could not load: ${truncate(normalizeWhitespace(message), 220)}.`;
2156
+ }
2157
+ function treeSitterCompatibilityDiagnostic(language, error) {
2158
+ return {
2159
+ code: 9010,
2160
+ category: "error",
2161
+ message: treeSitterCompatibilityMessage(language, error),
2162
+ line: 1,
2163
+ column: 1
2164
+ };
2165
+ }
2122
2166
  function parsePythonImportStatement(text) {
2123
2167
  const match = text.trim().match(/^import\s+(.+)$/);
2124
2168
  if (!match) {
@@ -2188,6 +2232,36 @@ function parseJavaImport(text) {
2188
2232
  reExport: false
2189
2233
  };
2190
2234
  }
2235
+ function parseKotlinImport(text) {
2236
+ const cleaned = text.trim().replace(/^import\s+/, "");
2237
+ if (!cleaned) {
2238
+ return void 0;
2239
+ }
2240
+ const aliasMatch = cleaned.match(/^(.+?)\s+as\s+([A-Za-z_]\w*)$/);
2241
+ const specifier = (aliasMatch ? aliasMatch[1] : cleaned).trim();
2242
+ if (!specifier) {
2243
+ return void 0;
2244
+ }
2245
+ return {
2246
+ specifier,
2247
+ importedSymbols: [],
2248
+ namespaceImport: aliasMatch?.[2],
2249
+ isExternal: !specifier.startsWith("."),
2250
+ reExport: false
2251
+ };
2252
+ }
2253
+ function parseScalaImport(text) {
2254
+ const cleaned = text.trim().replace(/^import\s+/, "");
2255
+ if (!cleaned) {
2256
+ return [];
2257
+ }
2258
+ return cleaned.split(",").map((item) => item.trim()).filter(Boolean).map((item) => ({
2259
+ specifier: item.replace(/\s*=>\s*/g, " => "),
2260
+ importedSymbols: [],
2261
+ isExternal: !item.startsWith("."),
2262
+ reExport: false
2263
+ }));
2264
+ }
2191
2265
  function parseCSharpUsing(text) {
2192
2266
  const aliasMatch = text.trim().match(/^using\s+([A-Za-z_]\w*)\s*=\s*([^;]+);$/);
2193
2267
  if (aliasMatch) {
@@ -2280,6 +2354,38 @@ function parsePowerShellImport(commandNode) {
2280
2354
  }
2281
2355
  return void 0;
2282
2356
  }
2357
+ function keywordVisible(text, hiddenKeywords) {
2358
+ return !hiddenKeywords.some((keyword) => new RegExp(`\\b${keyword}\\b`).test(text));
2359
+ }
2360
+ function declarationVisible(node, hiddenKeywords) {
2361
+ const modifierText = nodeText(findNamedChild(node, "modifiers") ?? node.childForFieldName("modifiers"));
2362
+ return modifierText ? keywordVisible(modifierText, hiddenKeywords) : true;
2363
+ }
2364
+ function kotlinClassKind(text) {
2365
+ const trimmed = text.trimStart();
2366
+ if (trimmed.startsWith("interface ")) {
2367
+ return "interface";
2368
+ }
2369
+ if (trimmed.startsWith("enum class ")) {
2370
+ return "enum";
2371
+ }
2372
+ return "class";
2373
+ }
2374
+ function scalaDefinitionKind(node) {
2375
+ if (node.type === "trait_definition") {
2376
+ return "trait";
2377
+ }
2378
+ if (node.type === "class_definition") {
2379
+ return /\bcase\s+class\b/.test(node.text) ? "class" : "class";
2380
+ }
2381
+ if (node.type === "object_definition") {
2382
+ return "class";
2383
+ }
2384
+ if (node.type === "function_definition") {
2385
+ return "function";
2386
+ }
2387
+ return void 0;
2388
+ }
2283
2389
  function pythonCodeAnalysis(manifest, rootNode, diagnostics) {
2284
2390
  const imports = [];
2285
2391
  const draftSymbols = [];
@@ -2533,6 +2639,181 @@ function javaCodeAnalysis(manifest, rootNode, diagnostics) {
2533
2639
  namespace: packageName
2534
2640
  });
2535
2641
  }
2642
+ function kotlinCodeAnalysis(manifest, rootNode, diagnostics) {
2643
+ const imports = [];
2644
+ const draftSymbols = [];
2645
+ const exportLabels = [];
2646
+ let packageName;
2647
+ const pushBodyFunctions = (bodyNode, scopeName) => {
2648
+ if (!bodyNode) {
2649
+ return;
2650
+ }
2651
+ for (const child of bodyNode.namedChildren) {
2652
+ if (!child || child.type !== "function_declaration") {
2653
+ continue;
2654
+ }
2655
+ const functionName = extractIdentifier(child.childForFieldName("name") ?? findNamedChild(child, "simple_identifier"));
2656
+ if (!functionName) {
2657
+ continue;
2658
+ }
2659
+ const exported = declarationVisible(child, ["private", "internal", "protected"]);
2660
+ const symbolName = scopeName ? `${scopeName}.${functionName}` : functionName;
2661
+ draftSymbols.push({
2662
+ name: symbolName,
2663
+ kind: "function",
2664
+ signature: singleLineSignature(child.text),
2665
+ exported,
2666
+ callNames: [],
2667
+ extendsNames: [],
2668
+ implementsNames: [],
2669
+ bodyText: nodeText(child.childForFieldName("body") ?? findNamedChild(child, "function_body"))
2670
+ });
2671
+ if (exported) {
2672
+ exportLabels.push(symbolName);
2673
+ }
2674
+ }
2675
+ };
2676
+ for (const child of rootNode.namedChildren) {
2677
+ if (!child) {
2678
+ continue;
2679
+ }
2680
+ if (child.type === "package_header") {
2681
+ packageName = nodeText(findNamedChild(child, "identifier") ?? child.namedChildren.at(0) ?? null) || packageName;
2682
+ continue;
2683
+ }
2684
+ if (child.type === "import_list") {
2685
+ for (const importNode of child.descendantsOfType("import_header").filter((item) => item !== null)) {
2686
+ const parsed = parseKotlinImport(importNode.text);
2687
+ if (parsed) {
2688
+ imports.push(parsed);
2689
+ }
2690
+ }
2691
+ continue;
2692
+ }
2693
+ if (child.type === "function_declaration") {
2694
+ pushBodyFunctions({
2695
+ ...child,
2696
+ namedChildren: [child]
2697
+ });
2698
+ continue;
2699
+ }
2700
+ if (child.type !== "class_declaration" && child.type !== "object_declaration") {
2701
+ continue;
2702
+ }
2703
+ const name = extractIdentifier(child.childForFieldName("name") ?? findNamedChild(child, "type_identifier"));
2704
+ if (!name) {
2705
+ continue;
2706
+ }
2707
+ const kind = child.type === "object_declaration" ? "class" : kotlinClassKind(child.text);
2708
+ const delegationNames = uniqueBy(
2709
+ child.namedChildren.filter((item) => item !== null && item.type === "delegation_specifier").flatMap((item) => descendantTypeNames(item)),
2710
+ (item) => item
2711
+ );
2712
+ const exported = declarationVisible(child, ["private", "internal"]);
2713
+ const bodyNode = findNamedChild(child, "class_body") ?? child.childForFieldName("body");
2714
+ draftSymbols.push({
2715
+ name,
2716
+ kind,
2717
+ signature: singleLineSignature(child.text),
2718
+ exported,
2719
+ callNames: [],
2720
+ extendsNames: kind === "interface" ? delegationNames : delegationNames.slice(0, 1),
2721
+ implementsNames: kind === "class" ? delegationNames.slice(1) : [],
2722
+ bodyText: nodeText(bodyNode) || child.text
2723
+ });
2724
+ if (exported) {
2725
+ exportLabels.push(name);
2726
+ }
2727
+ pushBodyFunctions(bodyNode, name);
2728
+ }
2729
+ return finalizeCodeAnalysis(manifest, "kotlin", imports, draftSymbols, exportLabels, diagnostics, {
2730
+ namespace: packageName
2731
+ });
2732
+ }
2733
+ function scalaCodeAnalysis(manifest, rootNode, diagnostics) {
2734
+ const imports = [];
2735
+ const draftSymbols = [];
2736
+ const exportLabels = [];
2737
+ let packageName;
2738
+ const pushTemplateFunctions = (bodyNode, scopeName) => {
2739
+ if (!bodyNode) {
2740
+ return;
2741
+ }
2742
+ for (const child of bodyNode.namedChildren) {
2743
+ if (!child || child.type !== "function_definition") {
2744
+ continue;
2745
+ }
2746
+ const functionName = extractIdentifier(child.childForFieldName("name") ?? findNamedChild(child, "identifier"));
2747
+ if (!functionName) {
2748
+ continue;
2749
+ }
2750
+ const exported = declarationVisible(child, ["private", "protected"]);
2751
+ const symbolName = scopeName ? `${scopeName}.${functionName}` : functionName;
2752
+ draftSymbols.push({
2753
+ name: symbolName,
2754
+ kind: "function",
2755
+ signature: singleLineSignature(child.text),
2756
+ exported,
2757
+ callNames: [],
2758
+ extendsNames: [],
2759
+ implementsNames: [],
2760
+ bodyText: child.text
2761
+ });
2762
+ if (exported) {
2763
+ exportLabels.push(symbolName);
2764
+ }
2765
+ }
2766
+ };
2767
+ for (const child of rootNode.namedChildren) {
2768
+ if (!child) {
2769
+ continue;
2770
+ }
2771
+ if (child.type === "package_clause") {
2772
+ packageName = nodeText(findNamedChild(child, "package_identifier") ?? child.namedChildren.at(0) ?? null) || packageName;
2773
+ continue;
2774
+ }
2775
+ if (child.type === "import_declaration") {
2776
+ imports.push(...parseScalaImport(child.text));
2777
+ continue;
2778
+ }
2779
+ if (child.type === "function_definition") {
2780
+ pushTemplateFunctions({
2781
+ ...child,
2782
+ namedChildren: [child]
2783
+ });
2784
+ continue;
2785
+ }
2786
+ if (!["trait_definition", "class_definition", "object_definition"].includes(child.type)) {
2787
+ continue;
2788
+ }
2789
+ const name = extractIdentifier(child.childForFieldName("name") ?? findNamedChild(child, "identifier"));
2790
+ const kind = scalaDefinitionKind(child);
2791
+ if (!name || !kind) {
2792
+ continue;
2793
+ }
2794
+ const extendsClause = findNamedChild(child, "extends_clause") ?? child.childForFieldName("extends");
2795
+ const inheritance = uniqueBy(descendantTypeNames(extendsClause), (item) => item);
2796
+ const bodyNode = findNamedChild(child, "template_body") ?? child.childForFieldName("body");
2797
+ const exported = declarationVisible(child, ["private", "protected"]);
2798
+ draftSymbols.push({
2799
+ name,
2800
+ kind,
2801
+ signature: singleLineSignature(child.text),
2802
+ exported,
2803
+ callNames: [],
2804
+ extendsNames: kind === "trait" ? inheritance : inheritance.slice(0, 1),
2805
+ implementsNames: kind === "class" ? inheritance.slice(1) : [],
2806
+ bodyText: nodeText(bodyNode) || child.text
2807
+ });
2808
+ if (exported) {
2809
+ exportLabels.push(name);
2810
+ }
2811
+ pushTemplateFunctions(bodyNode, name);
2812
+ }
2813
+ return finalizeCodeAnalysis(manifest, "scala", imports, draftSymbols, exportLabels, diagnostics, {
2814
+ namespace: packageName
2815
+ });
2816
+ }
2536
2817
  function csharpCodeAnalysis(manifest, rootNode, diagnostics) {
2537
2818
  const imports = [];
2538
2819
  const draftSymbols = [];
@@ -2915,11 +3196,19 @@ function cFamilyCodeAnalysis(manifest, language, rootNode, diagnostics) {
2915
3196
  return finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics);
2916
3197
  }
2917
3198
  async function analyzeTreeSitterCode(manifest, content, language) {
2918
- const module = await getTreeSitterModule();
2919
- await ensureTreeSitterInit(module);
2920
- const parser = new module.Parser();
2921
- parser.setLanguage(await loadLanguage(language));
2922
- const tree = parser.parse(content);
3199
+ let tree = null;
3200
+ try {
3201
+ const module = await getTreeSitterModule();
3202
+ await ensureTreeSitterInit(module);
3203
+ const parser = new module.Parser();
3204
+ parser.setLanguage(await loadLanguage(language));
3205
+ tree = parser.parse(content);
3206
+ } catch (error) {
3207
+ return {
3208
+ code: finalizeCodeAnalysis(manifest, language, [], [], [], [treeSitterCompatibilityDiagnostic(language, error)]),
3209
+ rationales: []
3210
+ };
3211
+ }
2923
3212
  if (!tree) {
2924
3213
  return {
2925
3214
  code: finalizeCodeAnalysis(
@@ -2953,6 +3242,10 @@ async function analyzeTreeSitterCode(manifest, content, language) {
2953
3242
  return { code: rustCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
2954
3243
  case "java":
2955
3244
  return { code: javaCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
3245
+ case "kotlin":
3246
+ return { code: kotlinCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
3247
+ case "scala":
3248
+ return { code: scalaCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
2956
3249
  case "csharp":
2957
3250
  return { code: csharpCodeAnalysis(manifest, tree.rootNode, diagnostics), rationales };
2958
3251
  case "php":
@@ -2964,6 +3257,26 @@ async function analyzeTreeSitterCode(manifest, content, language) {
2964
3257
  case "c":
2965
3258
  case "cpp":
2966
3259
  return { code: cFamilyCodeAnalysis(manifest, language, tree.rootNode, diagnostics), rationales };
3260
+ default:
3261
+ return {
3262
+ code: finalizeCodeAnalysis(
3263
+ manifest,
3264
+ language,
3265
+ [],
3266
+ [],
3267
+ [],
3268
+ [
3269
+ {
3270
+ code: 9011,
3271
+ category: "error",
3272
+ message: `No parser-backed analyzer is registered for ${language}.`,
3273
+ line: 1,
3274
+ column: 1
3275
+ }
3276
+ ]
3277
+ ),
3278
+ rationales
3279
+ };
2967
3280
  }
2968
3281
  } finally {
2969
3282
  tree.delete();
@@ -3219,7 +3532,7 @@ function makeRationale2(manifest, index, text, kind, symbolName) {
3219
3532
  };
3220
3533
  }
3221
3534
  function stripCodeExtension2(filePath) {
3222
- return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
3535
+ return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|kt|kts|scala|sc|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
3223
3536
  }
3224
3537
  function manifestModuleName2(manifest, language) {
3225
3538
  const repoPath = manifest.repoRelativePath ?? path6.basename(manifest.originalPath ?? manifest.storedPath);
@@ -3550,6 +3863,12 @@ function inferCodeLanguage(filePath, mimeType = "") {
3550
3863
  if (extension === ".java") {
3551
3864
  return "java";
3552
3865
  }
3866
+ if (extension === ".kt" || extension === ".kts") {
3867
+ return "kotlin";
3868
+ }
3869
+ if (extension === ".scala" || extension === ".sc") {
3870
+ return "scala";
3871
+ }
3553
3872
  if (extension === ".cs") {
3554
3873
  return "csharp";
3555
3874
  }
@@ -3654,6 +3973,10 @@ function candidateExtensionsFor(language) {
3654
3973
  return [".rs"];
3655
3974
  case "java":
3656
3975
  return [".java"];
3976
+ case "kotlin":
3977
+ return [".kt", ".kts"];
3978
+ case "scala":
3979
+ return [".scala", ".sc"];
3657
3980
  case "csharp":
3658
3981
  return [".cs"];
3659
3982
  case "php":
@@ -3714,10 +4037,17 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
3714
4037
  break;
3715
4038
  }
3716
4039
  case "java":
4040
+ case "kotlin":
4041
+ case "scala":
3717
4042
  case "csharp":
3718
4043
  if (normalizedNamespace) {
3719
4044
  recordAlias(aliases, `${normalizedNamespace}.${basename}`);
3720
4045
  }
4046
+ if (normalizedNamespace) {
4047
+ for (const symbol of analysis.code.symbols) {
4048
+ recordAlias(aliases, `${normalizedNamespace}.${symbol.name}`);
4049
+ }
4050
+ }
3721
4051
  break;
3722
4052
  case "php":
3723
4053
  if (normalizedNamespace) {
@@ -3824,6 +4154,8 @@ function findImportCandidates(manifest, codeImport, lookup) {
3824
4154
  return aliasMatches(lookup, codeImport.specifier);
3825
4155
  case "go":
3826
4156
  case "java":
4157
+ case "kotlin":
4158
+ case "scala":
3827
4159
  case "csharp":
3828
4160
  return aliasMatches(lookup, codeImport.specifier);
3829
4161
  case "php":
@@ -3870,6 +4202,8 @@ function importLooksLocal(manifest, codeImport, candidates) {
3870
4202
  case "powershell":
3871
4203
  case "c":
3872
4204
  case "cpp":
4205
+ case "kotlin":
4206
+ case "scala":
3873
4207
  return !codeImport.isExternal;
3874
4208
  default:
3875
4209
  return false;
@@ -4520,6 +4854,9 @@ async function markPagesStaleForSources(rootDir, sourceIds) {
4520
4854
  var DEFAULT_MAX_ASSET_SIZE = 10 * 1024 * 1024;
4521
4855
  var DEFAULT_MAX_DIRECTORY_FILES = 5e3;
4522
4856
  var HARD_REPO_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
4857
+ var PROGRESS_FILE_THRESHOLD = 150;
4858
+ var PROGRESS_UPDATE_INTERVAL = 100;
4859
+ var RST_HEADING_MARKERS = /* @__PURE__ */ new Set(["=", "-", "~", "^", '"', "#", "*", "+"]);
4523
4860
  function uniqueStrings(values) {
4524
4861
  return [...new Set(values.filter(Boolean))];
4525
4862
  }
@@ -4527,6 +4864,9 @@ function inferKind(mimeType, filePath) {
4527
4864
  if (inferCodeLanguage(filePath, mimeType)) {
4528
4865
  return "code";
4529
4866
  }
4867
+ if (isRstFilePath(filePath)) {
4868
+ return "text";
4869
+ }
4530
4870
  if (mimeType.includes("markdown")) {
4531
4871
  return "markdown";
4532
4872
  }
@@ -4547,13 +4887,146 @@ function inferKind(mimeType, filePath) {
4547
4887
  }
4548
4888
  return "binary";
4549
4889
  }
4550
- function titleFromText(fallback, content) {
4890
+ function isRstFilePath(filePath) {
4891
+ const extension = path11.extname(filePath).toLowerCase();
4892
+ return extension === ".rst" || extension === ".rest";
4893
+ }
4894
+ function titleFromText(fallback, content, filePath) {
4895
+ if (filePath && isRstFilePath(filePath)) {
4896
+ const rstTitle = titleFromRst(fallback, content);
4897
+ if (rstTitle) {
4898
+ return rstTitle;
4899
+ }
4900
+ }
4551
4901
  const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
4552
4902
  return heading || fallback;
4553
4903
  }
4554
4904
  function guessMimeType(target) {
4905
+ if (isRstFilePath(target)) {
4906
+ return "text/x-rst";
4907
+ }
4555
4908
  return mime.lookup(target) || "application/octet-stream";
4556
4909
  }
4910
+ function rstAdornmentLine(line) {
4911
+ const trimmed = line.trim();
4912
+ if (trimmed.length < 3) {
4913
+ return void 0;
4914
+ }
4915
+ const marker = trimmed[0] ?? "";
4916
+ if (!RST_HEADING_MARKERS.has(marker) || ![...trimmed].every((char) => char === marker)) {
4917
+ return void 0;
4918
+ }
4919
+ return marker;
4920
+ }
4921
+ function rstHeadingLevel(marker) {
4922
+ switch (marker) {
4923
+ case "=":
4924
+ return "#";
4925
+ case "-":
4926
+ return "##";
4927
+ case "~":
4928
+ return "###";
4929
+ case "^":
4930
+ return "####";
4931
+ default:
4932
+ return "##";
4933
+ }
4934
+ }
4935
+ function normalizeRstDirective(line) {
4936
+ const trimmed = line.trim();
4937
+ if (!trimmed.startsWith(".. ")) {
4938
+ return void 0;
4939
+ }
4940
+ const directiveMatch = trimmed.match(/^\.\.\s+([A-Za-z][\w-]*)::\s*(.*)$/);
4941
+ if (!directiveMatch) {
4942
+ return "";
4943
+ }
4944
+ const label = directiveMatch[1].replace(/-/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
4945
+ const detail = directiveMatch[2]?.trim();
4946
+ return detail ? `${label}: ${detail}` : `${label}:`;
4947
+ }
4948
+ function normalizeRstExtractedText(content) {
4949
+ const lines = content.split(/\r?\n/);
4950
+ const normalized = [];
4951
+ for (let index = 0; index < lines.length; index += 1) {
4952
+ const current = lines[index] ?? "";
4953
+ const next = lines[index + 1] ?? "";
4954
+ const afterNext = lines[index + 2] ?? "";
4955
+ const trimmed = current.trim();
4956
+ if (!trimmed) {
4957
+ normalized.push("");
4958
+ continue;
4959
+ }
4960
+ const currentAdornment = rstAdornmentLine(current);
4961
+ const nextAdornment = rstAdornmentLine(next);
4962
+ const afterNextAdornment = rstAdornmentLine(afterNext);
4963
+ if (currentAdornment && next.trim() && afterNextAdornment && currentAdornment === afterNextAdornment) {
4964
+ normalized.push(`${rstHeadingLevel(currentAdornment)} ${next.trim()}`);
4965
+ normalized.push("");
4966
+ index += 2;
4967
+ continue;
4968
+ }
4969
+ if (nextAdornment && trimmed.length > 0) {
4970
+ normalized.push(`${rstHeadingLevel(nextAdornment)} ${trimmed}`);
4971
+ normalized.push("");
4972
+ index += 1;
4973
+ continue;
4974
+ }
4975
+ const directive = normalizeRstDirective(current);
4976
+ if (directive !== void 0) {
4977
+ if (directive) {
4978
+ normalized.push(directive);
4979
+ }
4980
+ continue;
4981
+ }
4982
+ normalized.push(current);
4983
+ }
4984
+ return normalized.join("\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
4985
+ }
4986
+ function titleFromRst(fallback, content) {
4987
+ const normalized = normalizeRstExtractedText(content);
4988
+ const heading = normalized.match(/^#+\s+(.+)$/m)?.[1]?.trim();
4989
+ return heading || fallback;
4990
+ }
4991
+ function extractedTextForPlainSource(filePath, sourceKind, content) {
4992
+ if (sourceKind === "text" && isRstFilePath(filePath)) {
4993
+ return normalizeRstExtractedText(content);
4994
+ }
4995
+ return content;
4996
+ }
4997
+ function shouldEmitProgress(totalItems) {
4998
+ return totalItems >= PROGRESS_FILE_THRESHOLD && Boolean(process.stderr?.isTTY);
4999
+ }
5000
+ function createProgressReporter(prefix, totalItems) {
5001
+ if (!shouldEmitProgress(totalItems)) {
5002
+ return {
5003
+ tick: () => {
5004
+ },
5005
+ finish: () => {
5006
+ }
5007
+ };
5008
+ }
5009
+ let completed = 0;
5010
+ let nextUpdate = Math.min(PROGRESS_UPDATE_INTERVAL, totalItems);
5011
+ process.stderr.write(`[swarmvault ${prefix}] starting ${totalItems} file(s)
5012
+ `);
5013
+ return {
5014
+ tick: () => {
5015
+ completed += 1;
5016
+ if (completed >= nextUpdate || completed === totalItems) {
5017
+ process.stderr.write(`[swarmvault ${prefix}] ${completed}/${totalItems}
5018
+ `);
5019
+ while (completed >= nextUpdate) {
5020
+ nextUpdate += PROGRESS_UPDATE_INTERVAL;
5021
+ }
5022
+ }
5023
+ },
5024
+ finish: (summary) => {
5025
+ process.stderr.write(`[swarmvault ${prefix}] finished ${totalItems} file(s)${summary ? ` (${summary})` : ""}
5026
+ `);
5027
+ }
5028
+ };
5029
+ }
4557
5030
  function normalizeIngestOptions(options) {
4558
5031
  return {
4559
5032
  includeAssets: options?.includeAssets ?? true,
@@ -5490,6 +5963,7 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
5490
5963
  }))
5491
5964
  );
5492
5965
  scannedCount += files.length;
5966
+ const progress = createProgressReporter("sync", files.length);
5493
5967
  const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
5494
5968
  for (const absolutePath of files) {
5495
5969
  const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
@@ -5500,7 +5974,9 @@ async function syncTrackedRepos(rootDir, options, repoRoots) {
5500
5974
  } else if (result.wasUpdated) {
5501
5975
  updated.push(result.manifest);
5502
5976
  }
5977
+ progress.tick();
5503
5978
  }
5979
+ progress.finish(`repo=${toPosix(path11.relative(rootDir, repoRoot)) || "."}`);
5504
5980
  for (const manifest of repoManifests) {
5505
5981
  const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
5506
5982
  if (originalPath && !currentPaths.has(originalPath)) {
@@ -5590,6 +6066,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
5590
6066
  }))
5591
6067
  );
5592
6068
  scannedCount += files.length;
6069
+ const progress = createProgressReporter("sync-watch", files.length);
5593
6070
  const currentPaths = new Set(files.map((absolutePath) => path11.resolve(absolutePath)));
5594
6071
  for (const absolutePath of files) {
5595
6072
  const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
@@ -5616,6 +6093,7 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
5616
6093
  staleSourceIds.add(existing.sourceId);
5617
6094
  }
5618
6095
  }
6096
+ progress.tick();
5619
6097
  continue;
5620
6098
  }
5621
6099
  const result = await persistPreparedInput(rootDir, prepared, paths);
@@ -5624,7 +6102,9 @@ async function syncTrackedReposForWatch(rootDir, options, repoRoots) {
5624
6102
  } else if (result.wasUpdated) {
5625
6103
  updated.push(result.manifest);
5626
6104
  }
6105
+ progress.tick();
5627
6106
  }
6107
+ progress.finish(`repo=${toPosix(path11.relative(rootDir, repoRoot)) || "."}`);
5628
6108
  for (const manifest of repoManifests) {
5629
6109
  const originalPath = manifest.originalPath ? path11.resolve(manifest.originalPath) : null;
5630
6110
  if (originalPath && !currentPaths.has(originalPath)) {
@@ -5685,8 +6165,8 @@ async function prepareFileInput(rootDir, absoluteInput, repoRoot, sourceClass) {
5685
6165
  let extractedText;
5686
6166
  let extractionArtifact;
5687
6167
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
5688
- extractedText = payloadBytes.toString("utf8");
5689
- title = titleFromText(path11.basename(absoluteInput, path11.extname(absoluteInput)), extractedText);
6168
+ extractedText = extractedTextForPlainSource(absoluteInput, sourceKind, payloadBytes.toString("utf8"));
6169
+ title = titleFromText(path11.basename(absoluteInput, path11.extname(absoluteInput)), extractedText, absoluteInput);
5690
6170
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
5691
6171
  } else if (sourceKind === "html") {
5692
6172
  const html = payloadBytes.toString("utf8");
@@ -5796,8 +6276,8 @@ async function prepareUrlInput(rootDir, input, options) {
5796
6276
  const extension = path11.extname(inputUrl.pathname);
5797
6277
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
5798
6278
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
5799
- extractedText = payloadBytes.toString("utf8");
5800
- title = titleFromText(title || inputUrl.hostname, extractedText);
6279
+ extractedText = extractedTextForPlainSource(inputUrl.pathname, sourceKind, payloadBytes.toString("utf8"));
6280
+ title = titleFromText(title || inputUrl.hostname, extractedText, inputUrl.pathname);
5801
6281
  extractionArtifact = createPlainTextExtractionArtifact(sourceKind, mimeType);
5802
6282
  if (sourceKind === "markdown" && options.includeAssets) {
5803
6283
  const { attachments: remoteAttachments, skippedCount } = await collectRemoteImageAttachments(
@@ -6090,6 +6570,7 @@ async function ingestDirectory(rootDir, inputDir, options) {
6090
6570
  const { files, skipped } = await collectDirectoryFiles(rootDir, absoluteInputDir, repoRoot, normalizedOptions);
6091
6571
  const imported = [];
6092
6572
  const updated = [];
6573
+ const progress = createProgressReporter("ingest", files.length);
6093
6574
  for (const absolutePath of files) {
6094
6575
  const relativePath = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path11.relative(repoRoot, absolutePath));
6095
6576
  const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot, sourceClassForRelativePath(relativePath, normalizedOptions));
@@ -6101,7 +6582,9 @@ async function ingestDirectory(rootDir, inputDir, options) {
6101
6582
  } else {
6102
6583
  skipped.push({ path: toPosix(path11.relative(rootDir, absolutePath)), reason: "duplicate_content" });
6103
6584
  }
6585
+ progress.tick();
6104
6586
  }
6587
+ progress.finish(`imported=${imported.length}, updated=${updated.length}, skipped=${skipped.length}`);
6105
6588
  await appendLogEntry(rootDir, "ingest_directory", toPosix(path11.relative(rootDir, absoluteInputDir)) || ".", [
6106
6589
  `repo_root=${toPosix(path11.relative(rootDir, repoRoot)) || "."}`,
6107
6590
  `scanned=${files.length}`,
@@ -8889,6 +9372,35 @@ function topGroupPatterns(graph) {
8889
9372
  (left, right) => right.confidence - left.confidence || right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label)
8890
9373
  ).slice(0, 8);
8891
9374
  }
9375
+ function fragmentedCommunityPresentation(graph, communityPages) {
9376
+ const thinCommunities = (graph.communities ?? []).filter((community) => community.nodeIds.length <= 2).sort((left, right) => right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label));
9377
+ const visibleCommunities = thinCommunities.slice(0, 6).map((community) => {
9378
+ const page = communityPages.find((candidate) => candidate.id === `graph:${community.id}`);
9379
+ return {
9380
+ id: community.id,
9381
+ label: community.label,
9382
+ nodeCount: community.nodeIds.length,
9383
+ pageId: page?.id,
9384
+ path: page?.path,
9385
+ title: page?.title
9386
+ };
9387
+ });
9388
+ const rolledUp = thinCommunities.slice(visibleCommunities.length);
9389
+ if (!rolledUp.length) {
9390
+ return {
9391
+ thinCommunities: visibleCommunities
9392
+ };
9393
+ }
9394
+ return {
9395
+ thinCommunities: visibleCommunities,
9396
+ fragmentedCommunityRollup: {
9397
+ totalCommunities: graph.communities?.length ?? 0,
9398
+ rolledUpCount: rolledUp.length,
9399
+ rolledUpNodes: rolledUp.reduce((sum, community) => sum + community.nodeIds.length, 0),
9400
+ exampleLabels: rolledUp.slice(0, 4).map((community) => community.label)
9401
+ }
9402
+ };
9403
+ }
8892
9404
  function suggestedGraphQuestions(graph) {
8893
9405
  const thinCommunities = (graph.communities ?? []).filter((community) => community.nodeIds.length <= 2);
8894
9406
  const bridgeNodes = graph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 3);
@@ -8903,17 +9415,7 @@ function buildGraphReportArtifact(input) {
8903
9415
  const pagesById = new Map(reportGraph.pages.map((page) => [page.id, page]));
8904
9416
  const godNodes = reportGraph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
8905
9417
  const bridgeNodes = reportGraph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
8906
- const thinCommunities = (reportGraph.communities ?? []).filter((community) => community.nodeIds.length <= 2).map((community) => {
8907
- const page = input.communityPages.find((candidate) => candidate.id === `graph:${community.id}`);
8908
- return {
8909
- id: community.id,
8910
- label: community.label,
8911
- nodeCount: community.nodeIds.length,
8912
- pageId: page?.id,
8913
- path: page?.path,
8914
- title: page?.title
8915
- };
8916
- });
9418
+ const communityPresentation = fragmentedCommunityPresentation(reportGraph, input.communityPages);
8917
9419
  const surprisingConnections = topSurprisingConnections(reportGraph, pagesById);
8918
9420
  const groupPatterns = topGroupPatterns(reportGraph);
8919
9421
  const breakdown = sourceClassBreakdown(input.graph);
@@ -8927,6 +9429,11 @@ function buildGraphReportArtifact(input) {
8927
9429
  `Non-first-party material accounts for ${(nonFirstPartyNodes / Math.max(1, input.graph.nodes.length) * 100).toFixed(1)}% of graph nodes.`
8928
9430
  );
8929
9431
  }
9432
+ if (communityPresentation.fragmentedCommunityRollup) {
9433
+ warnings.push(
9434
+ `First-party report view is fragmented: ${communityPresentation.fragmentedCommunityRollup.rolledUpCount} tiny communities were rolled up for readability.`
9435
+ );
9436
+ }
8930
9437
  return {
8931
9438
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8932
9439
  graphHash: input.graphHash,
@@ -8964,10 +9471,11 @@ function buildGraphReportArtifact(input) {
8964
9471
  degree: node.degree,
8965
9472
  bridgeScore: node.bridgeScore
8966
9473
  })),
8967
- thinCommunities,
9474
+ thinCommunities: communityPresentation.thinCommunities,
9475
+ fragmentedCommunityRollup: communityPresentation.fragmentedCommunityRollup,
8968
9476
  surprisingConnections,
8969
9477
  groupPatterns,
8970
- suggestedQuestions: suggestedGraphQuestions(input.graph),
9478
+ suggestedQuestions: suggestedGraphQuestions(reportGraph),
8971
9479
  communityPages: input.communityPages.map((page) => ({
8972
9480
  id: page.id,
8973
9481
  path: page.path,
@@ -9094,6 +9602,10 @@ function buildGraphReportPage(input) {
9094
9602
  ...input.report.thinCommunities.length ? input.report.thinCommunities.map(
9095
9603
  (community) => community.path ? `- [[${community.path.replace(/\.md$/, "")}|${community.title ?? community.label}]] (${community.nodeCount} node(s))` : `- ${community.label} (${community.nodeCount} node(s))`
9096
9604
  ) : ["- No thin communities detected."],
9605
+ ...input.report.fragmentedCommunityRollup ? [
9606
+ `- Rolled up ${input.report.fragmentedCommunityRollup.rolledUpCount} additional tiny communities covering ${input.report.fragmentedCommunityRollup.rolledUpNodes} node(s).`,
9607
+ `- Example rolled-up labels: ${input.report.fragmentedCommunityRollup.exampleLabels.join(", ")}`
9608
+ ] : [],
9097
9609
  "",
9098
9610
  "## Surprising Connections",
9099
9611
  "",
@@ -10236,9 +10748,41 @@ function searchPages(dbPath, query, limitOrOptions = 5) {
10236
10748
  }
10237
10749
 
10238
10750
  // src/vault.ts
10751
+ var COMPILE_PROGRESS_THRESHOLD = 120;
10752
+ var COMPILE_PROGRESS_UPDATE_INTERVAL = 50;
10239
10753
  function uniqueStrings3(values) {
10240
10754
  return uniqueBy(values.filter(Boolean), (value) => value);
10241
10755
  }
10756
+ function createCompileProgressReporter(phase, totalItems) {
10757
+ if (totalItems < COMPILE_PROGRESS_THRESHOLD || !process.stderr?.isTTY) {
10758
+ return {
10759
+ tick: () => {
10760
+ },
10761
+ finish: () => {
10762
+ }
10763
+ };
10764
+ }
10765
+ let completed = 0;
10766
+ let nextUpdate = Math.min(COMPILE_PROGRESS_UPDATE_INTERVAL, totalItems);
10767
+ process.stderr.write(`[swarmvault compile] ${phase}: 0/${totalItems}
10768
+ `);
10769
+ return {
10770
+ tick: (label) => {
10771
+ completed += 1;
10772
+ if (completed >= nextUpdate || completed === totalItems) {
10773
+ process.stderr.write(`[swarmvault compile] ${phase}: ${completed}/${totalItems}${label ? ` (${label})` : ""}
10774
+ `);
10775
+ while (completed >= nextUpdate) {
10776
+ nextUpdate += COMPILE_PROGRESS_UPDATE_INTERVAL;
10777
+ }
10778
+ }
10779
+ },
10780
+ finish: (summary) => {
10781
+ process.stderr.write(`[swarmvault compile] ${phase}: ${totalItems}/${totalItems}${summary ? ` (${summary})` : ""}
10782
+ `);
10783
+ }
10784
+ };
10785
+ }
10242
10786
  function normalizeOutputFormat2(format) {
10243
10787
  return format === "report" || format === "slides" || format === "chart" || format === "image" ? format : "markdown";
10244
10788
  }
@@ -13117,34 +13661,41 @@ async function compileVault(rootDir, options = {}) {
13117
13661
  candidatePageCount: (graph?.pages ?? []).filter((page) => page.status === "candidate").length
13118
13662
  };
13119
13663
  }
13664
+ const analysisProgress = createCompileProgressReporter("analyze", manifests.length);
13120
13665
  const [dirtyAnalyses, cleanAnalyses] = await Promise.all([
13121
13666
  Promise.all(
13122
- dirty.map(
13123
- async (manifest) => analyzeSource(
13667
+ dirty.map(async (manifest) => {
13668
+ const analysis = await analyzeSource(
13124
13669
  manifest,
13125
13670
  await readExtractedText(rootDir, manifest),
13126
13671
  provider,
13127
13672
  paths,
13128
13673
  getEffectiveSchema(schemas, sourceProjects[manifest.sourceId] ?? null)
13129
- )
13130
- )
13674
+ );
13675
+ analysisProgress.tick(manifest.title);
13676
+ return analysis;
13677
+ })
13131
13678
  ),
13132
13679
  Promise.all(
13133
13680
  clean.map(async (manifest) => {
13134
13681
  const cached = await readJsonFile(path21.join(paths.analysesDir, `${manifest.sourceId}.json`));
13135
13682
  if (cached) {
13683
+ analysisProgress.tick(manifest.title);
13136
13684
  return cached;
13137
13685
  }
13138
- return analyzeSource(
13686
+ const analysis = await analyzeSource(
13139
13687
  manifest,
13140
13688
  await readExtractedText(rootDir, manifest),
13141
13689
  provider,
13142
13690
  paths,
13143
13691
  getEffectiveSchema(schemas, sourceProjects[manifest.sourceId] ?? null)
13144
13692
  );
13693
+ analysisProgress.tick(manifest.title);
13694
+ return analysis;
13145
13695
  })
13146
13696
  )
13147
13697
  ]);
13698
+ analysisProgress.finish(`dirty=${dirty.length}, clean=${clean.length}`);
13148
13699
  const initialAnalyses = [...dirtyAnalyses, ...cleanAnalyses];
13149
13700
  const codeIndex = await buildCodeIndex(rootDir, manifests, initialAnalyses);
13150
13701
  const analyses = await Promise.all(
@@ -13913,7 +14464,7 @@ async function bootstrapDemo(rootDir, input) {
13913
14464
  }
13914
14465
 
13915
14466
  // src/mcp.ts
13916
- var SERVER_VERSION = "0.1.31";
14467
+ var SERVER_VERSION = "0.1.33";
13917
14468
  async function createMcpServer(rootDir) {
13918
14469
  const server = new McpServer({
13919
14470
  name: "swarmvault",
@@ -14743,6 +15294,7 @@ async function watchVault(rootDir, options = {}) {
14743
15294
  let consecutiveFailures = 0;
14744
15295
  let currentDebounceMs = baseDebounceMs;
14745
15296
  const reasons = /* @__PURE__ */ new Set();
15297
+ let activeCycle = null;
14746
15298
  const watcher = chokidar.watch(watchTargets, {
14747
15299
  ignoreInitial: true,
14748
15300
  usePolling: true,
@@ -14787,7 +15339,12 @@ async function watchVault(rootDir, options = {}) {
14787
15339
  clearTimeout(timer);
14788
15340
  }
14789
15341
  timer = setTimeout(() => {
14790
- void runCycle();
15342
+ const cycle = runCycle();
15343
+ activeCycle = cycle.finally(() => {
15344
+ if (activeCycle === cycle) {
15345
+ activeCycle = null;
15346
+ }
15347
+ });
14791
15348
  }, currentDebounceMs);
14792
15349
  };
14793
15350
  const runCycle = async () => {
@@ -14955,6 +15512,7 @@ async function watchVault(rootDir, options = {}) {
14955
15512
  clearTimeout(timer);
14956
15513
  }
14957
15514
  await watcher.close();
15515
+ await activeCycle;
14958
15516
  }
14959
15517
  };
14960
15518
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/engine",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "Core engine for SwarmVault: ingest, compile, query, lint, and provider abstractions.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -57,6 +57,7 @@
57
57
  "mime-types": "^3.0.1",
58
58
  "neo4j-driver": "^5.28.3",
59
59
  "pdfjs-dist": "^5.4.394",
60
+ "tree-sitter-wasms": "^0.1.13",
60
61
  "turndown": "^7.2.1",
61
62
  "typescript": "^5.9.3",
62
63
  "yaml": "^2.8.1",