@swarmvaultai/cli 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +35 -0
  2. package/dist/index.js +70 -61
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -65,10 +65,13 @@ swarmvault explore "What should I research next?" --steps 3
65
65
  swarmvault lint --deep
66
66
  swarmvault graph blast ./src/index.ts
67
67
  swarmvault graph status ./src
68
+ swarmvault check-update ./src
68
69
  swarmvault graph stats
69
70
  swarmvault graph validate --strict
70
71
  swarmvault graph update .
72
+ swarmvault update .
71
73
  swarmvault graph cluster
74
+ swarmvault cluster-only
72
75
  swarmvault graph tree --output ./exports/tree.html
73
76
  swarmvault graph query "Which nodes bridge the biggest clusters?"
74
77
  swarmvault graph explain "concept:drift"
@@ -79,6 +82,7 @@ swarmvault graph serve
79
82
  swarmvault graph export --report ./exports/report.html
80
83
  swarmvault graph export --html ./exports/graph.html
81
84
  swarmvault graph export --cypher ./exports/graph.cypher
85
+ swarmvault graph export --neo4j ./exports/graph.cypher
82
86
  swarmvault graph merge ./exports/graph.json ./other-graph.json --out ./exports/merged-graph.json
83
87
  swarmvault graph push neo4j --dry-run
84
88
  ```
@@ -410,6 +414,22 @@ Refresh code-derived graph artifacts from tracked repo roots or one explicit rep
410
414
  - aborts if nodes or edges drop by more than 25% compared with the existing graph; pass `--force` or set `SWARMVAULT_FORCE_UPDATE=1` when the shrink is expected
411
415
  - `--json` returns the same one-shot watch result shape, including repo import/update/remove counts and pending semantic refresh entries
412
416
 
417
+ ### `swarmvault check-update [path]`
418
+
419
+ Compatibility alias for `swarmvault graph status [path]`.
420
+
421
+ - performs the same read-only graph/report freshness check
422
+ - keeps automation-friendly JSON output for cron or hook wrappers
423
+ - recommends `swarmvault update`/`swarmvault graph update` for code-only drift and `swarmvault compile` when semantic refresh is required
424
+
425
+ ### `swarmvault update [path]`
426
+
427
+ Compatibility alias for `swarmvault graph update [path]`.
428
+
429
+ - runs the same code-only repo refresh path
430
+ - accepts `--lint` and `--force`
431
+ - returns the same JSON shape as `graph update`
432
+
413
433
  ### `swarmvault graph tree [--output <html>] [--root <path>] [--label <name>] [--max-children <n>]`
414
434
 
415
435
  Write a collapsible HTML source tree for the current `state/graph.json`.
@@ -470,6 +490,21 @@ Recompute communities, node degrees, bridge scores, god-node flags, and graph re
470
490
  - splits oversized or low-cohesion communities after the initial Louvain pass so large-repo reports stay scannable
471
491
  - `--json` returns counts plus the graph/report paths
472
492
 
493
+ ### `swarmvault cluster-only [vault] [--resolution <n>]`
494
+
495
+ Compatibility alias for `swarmvault graph cluster`.
496
+
497
+ - recomputes communities and graph report artifacts without ingest or semantic analysis
498
+ - accepts an optional vault root when the command is run from outside the vault
499
+ - returns the same JSON shape as `graph cluster`
500
+
501
+ ### `swarmvault graph export --neo4j <path>`
502
+
503
+ Compatibility alias for `swarmvault graph export --cypher <path>`.
504
+
505
+ - writes a Neo4j-ready Cypher import file
506
+ - can be combined with other `graph export` formats in the same run
507
+
473
508
  ### `swarmvault hook install|uninstall|status`
474
509
 
475
510
  Manage SwarmVault's local git hook blocks for the nearest git repository.
package/dist/index.js CHANGED
@@ -315,9 +315,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
315
315
  function readCliVersion() {
316
316
  try {
317
317
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
318
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.9.0";
318
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.10.0";
319
319
  } catch {
320
- return "3.9.0";
320
+ return "3.10.0";
321
321
  }
322
322
  }
323
323
  function parsePositiveInt(value, fallback) {
@@ -488,6 +488,63 @@ function getCommandPath(command) {
488
488
  }
489
489
  return names;
490
490
  }
491
+ async function runGraphUpdateCommand(targetPath, options) {
492
+ const overrideRoots = targetPath ? [path2.resolve(process2.cwd(), targetPath)] : void 0;
493
+ const result = await runWatchCycle(process2.cwd(), {
494
+ repo: true,
495
+ codeOnly: true,
496
+ lint: options.lint ?? false,
497
+ force: options.force ?? false,
498
+ overrideRoots
499
+ });
500
+ if (isJson()) {
501
+ emitJson(result);
502
+ return;
503
+ }
504
+ log(
505
+ `Updated graph from ${result.watchedRepoRoots.length} repo root${result.watchedRepoRoots.length === 1 ? "" : "s"}. Imported ${result.repoImportedCount}, updated ${result.repoUpdatedCount}, removed ${result.repoRemovedCount}, pending semantic refresh ${result.pendingSemanticRefreshCount}.`
506
+ );
507
+ }
508
+ async function showGraphStatusCommand(targetPath) {
509
+ const overrideRoots = targetPath ? [path2.resolve(process2.cwd(), targetPath)] : void 0;
510
+ const status = await getGraphStatus(process2.cwd(), { repoRoots: overrideRoots });
511
+ if (isJson()) {
512
+ emitJson(status);
513
+ return;
514
+ }
515
+ log(`Graph: ${status.graphExists ? status.graphPath : `missing (${status.graphPath})`}`);
516
+ log(`Report: ${status.reportExists ? status.reportPath : `missing (${status.reportPath})`}`);
517
+ log(`Tracked repo roots: ${status.trackedRepoRoots.length ? status.trackedRepoRoots.join(", ") : "none"}`);
518
+ log(`Code changes: ${status.codeChangeCount}`);
519
+ log(`Semantic changes: ${status.semanticChangeCount}`);
520
+ log(`Pending semantic refresh: ${status.pendingSemanticRefresh.length}`);
521
+ if (status.changes.length) {
522
+ for (const change of status.changes.slice(0, 20)) {
523
+ log(`- ${change.refreshType} ${change.changeType} ${change.path}`);
524
+ }
525
+ if (status.changes.length > 20) {
526
+ log(`... and ${status.changes.length - 20} more`);
527
+ }
528
+ }
529
+ log(`State: ${status.stale ? "stale" : "fresh"}`);
530
+ if (status.recommendedCommand) {
531
+ log(`Recommended: ${status.recommendedCommand}`);
532
+ }
533
+ }
534
+ async function runGraphClusterCommand(options, rootDir = process2.cwd()) {
535
+ const resolution = parsePositiveNumber(options.resolution);
536
+ if (options.resolution && resolution === void 0) {
537
+ throw new Error("--resolution must be a positive number.");
538
+ }
539
+ const result = await refreshGraphClusters(rootDir, { resolution });
540
+ if (isJson()) {
541
+ emitJson(result);
542
+ return;
543
+ }
544
+ log(
545
+ `Refreshed ${result.communityCount} communities across ${result.nodeCount} nodes and ${result.edgeCount} edges. Report: ${result.reportPath}`
546
+ );
547
+ }
491
548
  program.hook("postAction", async (_thisCommand, actionCommand) => {
492
549
  const notices = await collectCliNotices({
493
550
  commandPath: getCommandPath(actionCommand),
@@ -1157,23 +1214,7 @@ program.command("lint").description("Run anti-drift and wiki-health checks.").op
1157
1214
  });
1158
1215
  var graph = program.command("graph").description("Graph-related commands.").enablePositionalOptions();
1159
1216
  var graphPush = graph.command("push").description("Push the compiled graph into external sinks.");
1160
- graph.command("update").alias("refresh").description("Refresh code-derived graph artifacts from tracked repo roots or one explicit repo path.").argument("[path]", "Optional repo root to refresh instead of configured/tracked roots").option("--lint", "Run lint after the refresh cycle", false).option("--force", "Allow graph updates even when node or edge counts shrink sharply", false).action(async (targetPath, options) => {
1161
- const overrideRoots = targetPath ? [path2.resolve(process2.cwd(), targetPath)] : void 0;
1162
- const result = await runWatchCycle(process2.cwd(), {
1163
- repo: true,
1164
- codeOnly: true,
1165
- lint: options.lint ?? false,
1166
- force: options.force ?? false,
1167
- overrideRoots
1168
- });
1169
- if (isJson()) {
1170
- emitJson(result);
1171
- return;
1172
- }
1173
- log(
1174
- `Updated graph from ${result.watchedRepoRoots.length} repo root${result.watchedRepoRoots.length === 1 ? "" : "s"}. Imported ${result.repoImportedCount}, updated ${result.repoUpdatedCount}, removed ${result.repoRemovedCount}, pending semantic refresh ${result.pendingSemanticRefreshCount}.`
1175
- );
1176
- });
1217
+ graph.command("update").alias("refresh").description("Refresh code-derived graph artifacts from tracked repo roots or one explicit repo path.").argument("[path]", "Optional repo root to refresh instead of configured/tracked roots").option("--lint", "Run lint after the refresh cycle", false).option("--force", "Allow graph updates even when node or edge counts shrink sharply", false).action(runGraphUpdateCommand);
1177
1218
  graph.command("tree").description("Write a collapsible source/module/symbol tree for the compiled graph.").option("--output <html>", "Output HTML path (default: wiki/graph/tree.html)").option("--root <path>", "Vault root to read instead of the current directory").option("--label <name>", "Tree title").option("--max-children <n>", "Maximum children to render per tree node", "250").action(async (options) => {
1178
1219
  const rootDir = options.root ? path2.resolve(process2.cwd(), options.root) : process2.cwd();
1179
1220
  const result = await exportGraphTree(rootDir, options.output, {
@@ -1204,32 +1245,7 @@ graph.command("merge").description("Merge SwarmVault or node-link JSON graph fil
1204
1245
  log(`Warning: ${warning}`);
1205
1246
  }
1206
1247
  });
1207
- graph.command("status").description("Read-only check for graph/report presence and tracked repo changes.").argument("[path]", "Optional repo root to check instead of configured/tracked roots").action(async (targetPath) => {
1208
- const overrideRoots = targetPath ? [path2.resolve(process2.cwd(), targetPath)] : void 0;
1209
- const status = await getGraphStatus(process2.cwd(), { repoRoots: overrideRoots });
1210
- if (isJson()) {
1211
- emitJson(status);
1212
- return;
1213
- }
1214
- log(`Graph: ${status.graphExists ? status.graphPath : `missing (${status.graphPath})`}`);
1215
- log(`Report: ${status.reportExists ? status.reportPath : `missing (${status.reportPath})`}`);
1216
- log(`Tracked repo roots: ${status.trackedRepoRoots.length ? status.trackedRepoRoots.join(", ") : "none"}`);
1217
- log(`Code changes: ${status.codeChangeCount}`);
1218
- log(`Semantic changes: ${status.semanticChangeCount}`);
1219
- log(`Pending semantic refresh: ${status.pendingSemanticRefresh.length}`);
1220
- if (status.changes.length) {
1221
- for (const change of status.changes.slice(0, 20)) {
1222
- log(`- ${change.refreshType} ${change.changeType} ${change.path}`);
1223
- }
1224
- if (status.changes.length > 20) {
1225
- log(`... and ${status.changes.length - 20} more`);
1226
- }
1227
- }
1228
- log(`State: ${status.stale ? "stale" : "fresh"}`);
1229
- if (status.recommendedCommand) {
1230
- log(`Recommended: ${status.recommendedCommand}`);
1231
- }
1232
- });
1248
+ graph.command("status").description("Read-only check for graph/report presence and tracked repo changes.").argument("[path]", "Optional repo root to check instead of configured/tracked roots").action(showGraphStatusCommand);
1233
1249
  graph.command("stats").description("Summarize compiled graph counts, node types, evidence classes, and relation mix.").action(async () => {
1234
1250
  const stats = await graphStatsVault(process2.cwd());
1235
1251
  if (isJson()) {
@@ -1270,20 +1286,7 @@ graph.command("validate").description("Validate a compiled graph artifact for da
1270
1286
  process2.exitCode = 1;
1271
1287
  }
1272
1288
  });
1273
- graph.command("cluster").alias("clusters").description("Recompute graph communities, degrees, god-node flags, and graph report artifacts without re-ingesting sources.").option("--resolution <number>", "Override the Louvain community resolution for this run").action(async (options) => {
1274
- const resolution = parsePositiveNumber(options.resolution);
1275
- if (options.resolution && resolution === void 0) {
1276
- throw new Error("--resolution must be a positive number.");
1277
- }
1278
- const result = await refreshGraphClusters(process2.cwd(), { resolution });
1279
- if (isJson()) {
1280
- emitJson(result);
1281
- return;
1282
- }
1283
- log(
1284
- `Refreshed ${result.communityCount} communities across ${result.nodeCount} nodes and ${result.edgeCount} edges. Report: ${result.reportPath}`
1285
- );
1286
- });
1289
+ graph.command("cluster").alias("clusters").description("Recompute graph communities, degrees, god-node flags, and graph report artifacts without re-ingesting sources.").option("--resolution <number>", "Override the Louvain community resolution for this run").action((options) => runGraphClusterCommand(options));
1287
1290
  graphPush.command("neo4j").description("Push the compiled graph directly into Neo4j over Bolt/Aura.").option("--uri <bolt-uri>", "Neo4j Bolt or Aura URI").option("--username <user>", "Neo4j username").option("--password-env <env-var>", "Environment variable containing the Neo4j password").option("--database <name>", "Neo4j database name").option("--vault-id <id>", "Stable vault identifier used for shared-database namespacing").option("--batch-size <n>", "Maximum rows to write per Neo4j transaction batch").option("--include-third-party", "Also push third-party repo material", false).option("--include-resources", "Also push resource-like content", false).option("--include-generated", "Also push generated output", false).option("--dry-run", "Show what would be pushed without writing to Neo4j", false).action(
1288
1291
  async (options) => {
1289
1292
  const batchSize = typeof options.batchSize === "string" && options.batchSize.trim() ? parsePositiveInt(options.batchSize, 0) || void 0 : void 0;
@@ -1339,7 +1342,7 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
1339
1342
  });
1340
1343
  graph.command("export").description(
1341
1344
  "Export the graph as HTML, report, SVG, GraphML, Cypher, JSON, Obsidian vault, or Obsidian canvas. Combine flags to write multiple formats in one run."
1342
- ).option("--html <output>", "Output HTML file path").option("--html-standalone <output>", "Output lightweight standalone HTML file path (vis.js, no build tooling)").option("--report <output>", "Output self-contained HTML report (graph stats, key nodes, communities)").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--json <output>", "Output JSON file path").option("--obsidian <output>", "Output Obsidian vault directory path").option("--canvas <output>", "Output Obsidian canvas file path").option("--full", "Include the full graph in HTML export (default; queries traverse complete graph)", true).option("--overview", "Use overview sampling for HTML export (smaller file, queries limited to sampled nodes)", false).action(
1345
+ ).option("--html <output>", "Output HTML file path").option("--html-standalone <output>", "Output lightweight standalone HTML file path (vis.js, no build tooling)").option("--report <output>", "Output self-contained HTML report (graph stats, key nodes, communities)").option("--svg <output>", "Output SVG file path").option("--graphml <output>", "Output GraphML file path").option("--cypher <output>", "Output Cypher file path").option("--neo4j <output>", "Compatibility alias for --cypher, writing a Neo4j Cypher import file").option("--json <output>", "Output JSON file path").option("--obsidian <output>", "Output Obsidian vault directory path").option("--canvas <output>", "Output Obsidian canvas file path").option("--full", "Include the full graph in HTML export (default; queries traverse complete graph)", true).option("--overview", "Use overview sampling for HTML export (smaller file, queries limited to sampled nodes)", false).action(
1343
1346
  async (options) => {
1344
1347
  const useFullGraph = options.overview ? false : options.full ?? true;
1345
1348
  const targets = [
@@ -1349,13 +1352,14 @@ graph.command("export").description(
1349
1352
  options.svg ? { format: "svg", outputPath: options.svg } : null,
1350
1353
  options.graphml ? { format: "graphml", outputPath: options.graphml } : null,
1351
1354
  options.cypher ? { format: "cypher", outputPath: options.cypher } : null,
1355
+ options.neo4j ? { format: "cypher", outputPath: options.neo4j } : null,
1352
1356
  options.json ? { format: "json", outputPath: options.json } : null,
1353
1357
  options.obsidian ? { format: "obsidian", outputPath: options.obsidian } : null,
1354
1358
  options.canvas ? { format: "canvas", outputPath: options.canvas } : null
1355
1359
  ].filter((target) => Boolean(target));
1356
1360
  if (targets.length === 0) {
1357
1361
  throw new Error(
1358
- "Pass at least one of --html, --html-standalone, --report, --svg, --graphml, --cypher, --json, --obsidian, or --canvas."
1362
+ "Pass at least one of --html, --html-standalone, --report, --svg, --graphml, --cypher, --neo4j, --json, --obsidian, or --canvas."
1359
1363
  );
1360
1364
  }
1361
1365
  const results = [];
@@ -1776,6 +1780,11 @@ async function showWatchStatus() {
1776
1780
  }
1777
1781
  watch.command("status").description("Show the latest watch run plus pending semantic refresh entries.").action(showWatchStatus);
1778
1782
  program.command("watch-status").description("Show the latest watch run plus pending semantic refresh entries.").action(showWatchStatus);
1783
+ program.command("check-update").description("Compatibility alias for graph status: read-only graph/report freshness and tracked repo change check.").argument("[path]", "Optional repo root to check instead of configured/tracked roots").action(showGraphStatusCommand);
1784
+ program.command("update").description("Compatibility alias for graph update: refresh code-derived graph artifacts from tracked repo roots.").argument("[path]", "Optional repo root to refresh instead of configured/tracked roots").option("--lint", "Run lint after the refresh cycle", false).option("--force", "Allow graph updates even when node or edge counts shrink sharply", false).action(runGraphUpdateCommand);
1785
+ program.command("cluster-only").description("Compatibility alias for graph cluster: recompute graph communities and report artifacts without re-ingesting.").argument("[vault]", "Optional vault root to cluster instead of the current directory").option("--resolution <number>", "Override the Louvain community resolution for this run").action(
1786
+ (vaultPath, options) => runGraphClusterCommand(options, vaultPath ? path2.resolve(process2.cwd(), vaultPath) : process2.cwd())
1787
+ );
1779
1788
  var hook = program.command("hook").description("Install local git hooks that keep tracked repos and the vault in sync.");
1780
1789
  hook.command("install").description("Install post-commit and post-checkout hooks for the nearest git repository.").action(async () => {
1781
1790
  const status = await installGitHooks(process2.cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "3.9.0",
3
+ "version": "3.10.0",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,7 @@
44
44
  "prepublishOnly": "node ../../scripts/check-release-sync.mjs && node ../../scripts/check-published-manifests.mjs"
45
45
  },
46
46
  "dependencies": {
47
- "@swarmvaultai/engine": "3.9.0",
47
+ "@swarmvaultai/engine": "3.10.0",
48
48
  "commander": "^14.0.1"
49
49
  },
50
50
  "devDependencies": {