@swarmvaultai/cli 0.7.29 → 0.7.31

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 (2) hide show
  1. package/dist/index.js +49 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  listSchedules,
43
43
  loadVaultConfig,
44
44
  pathGraphVault,
45
+ previewCandidatePromotions,
45
46
  promoteCandidate,
46
47
  pushGraphNeo4j,
47
48
  queryGraphVault,
@@ -52,6 +53,7 @@ import {
52
53
  resumeSourceSession,
53
54
  reviewManagedSource,
54
55
  reviewSourceScope,
56
+ runAutoPromotion,
55
57
  runSchedule,
56
58
  runWatchCycle,
57
59
  serveSchedules,
@@ -276,9 +278,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
276
278
  function readCliVersion() {
277
279
  try {
278
280
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
279
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.29";
281
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.7.31";
280
282
  } catch {
281
- return "0.7.29";
283
+ return "0.7.31";
282
284
  }
283
285
  }
284
286
  function parsePositiveInt(value, fallback) {
@@ -435,7 +437,7 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
435
437
  log("Initialized SwarmVault workspace.");
436
438
  }
437
439
  });
438
- program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--review", "Stage a source review artifact after ingest and compile", false).option("--guide", "Stage a guided source integration bundle after ingest and compile (default: from config)").option("--no-guide", "Skip guided mode even if enabled in config").option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--include-third-party", "Also ingest repo files classified as third-party", false).option("--include-resources", "Also ingest repo files classified as resources", false).option("--include-generated", "Also ingest repo files classified as generated output", false).option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").option("--commit", "Auto-commit wiki and state changes after ingest").action(
440
+ program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--review", "Stage a source review artifact after ingest and compile", false).option("--guide", "Stage a guided source integration bundle after ingest and compile (default: from config)").option("--no-guide", "Skip guided mode even if enabled in config").option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--include-third-party", "Also ingest repo files classified as third-party", false).option("--include-resources", "Also ingest repo files classified as resources", false).option("--include-generated", "Also ingest repo files classified as generated output", false).option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").option("--resume <run-id>", "Re-run only the failed files from a prior ingest run id").option("--commit", "Auto-commit wiki and state changes after ingest").action(
439
441
  async (input, options) => {
440
442
  const guideAnswers = readGuideAnswersFile(options.answersFile);
441
443
  const vaultConfig = await loadVaultConfig(process2.cwd()).catch(() => null);
@@ -456,7 +458,8 @@ program.command("ingest").description("Ingest a local file path, directory path,
456
458
  exclude: options.exclude,
457
459
  maxFiles,
458
460
  gitignore: options.gitignore,
459
- extractClasses
461
+ extractClasses,
462
+ resume: options.resume
460
463
  };
461
464
  const directoryResult = !/^https?:\/\//i.test(input) ? await import("fs/promises").then(
462
465
  (fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
@@ -494,9 +497,18 @@ program.command("ingest").description("Ingest a local file path, directory path,
494
497
  completedGuide2 ? { ingest: directoryResult, guide: completedGuide2 } : review3 ? { ingest: directoryResult, review: review3 } : directoryResult
495
498
  );
496
499
  } else {
500
+ const failedCount = directoryResult.failed?.length ?? 0;
497
501
  log(
498
- `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}.`
502
+ `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}, failed ${failedCount}.`
499
503
  );
504
+ if (failedCount && directoryResult.runId) {
505
+ log(`Run id: ${directoryResult.runId}`);
506
+ log(`Retry with: swarmvault ingest ${input} --resume ${directoryResult.runId}`);
507
+ for (const failure of (directoryResult.failed ?? []).slice(0, 5)) {
508
+ log(` failed ${failure.stage}: ${failure.path}: ${failure.error}`);
509
+ }
510
+ if (failedCount > 5) log(` ... ${failedCount - 5} more`);
511
+ }
500
512
  if (review3) {
501
513
  log(`Staged source review at ${review3.reviewPath}.`);
502
514
  }
@@ -849,8 +861,9 @@ graph.command("serve").description("Serve the local graph viewer.").option("--po
849
861
  });
850
862
  graph.command("export").description(
851
863
  "Export the graph as HTML, report, SVG, GraphML, Cypher, JSON, Obsidian vault, or Obsidian canvas. Combine flags to write multiple formats in one run."
852
- ).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", "Disable overview sampling for HTML export", false).action(
864
+ ).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(
853
865
  async (options) => {
866
+ const useFullGraph = options.overview ? false : options.full ?? true;
854
867
  const targets = [
855
868
  options.html ? { format: "html", outputPath: options.html } : null,
856
869
  options.htmlStandalone ? { format: "html-standalone", outputPath: options.htmlStandalone } : null,
@@ -870,7 +883,7 @@ graph.command("export").description(
870
883
  const results = [];
871
884
  for (const target of targets) {
872
885
  if (target.format === "html") {
873
- const outputPath = await exportGraphHtml(process2.cwd(), target.outputPath, { full: options.full ?? false });
886
+ const outputPath = await exportGraphHtml(process2.cwd(), target.outputPath, { full: useFullGraph });
874
887
  results.push({ format: target.format, outputPath });
875
888
  } else if (target.format === "report") {
876
889
  const result = await exportGraphReportHtml(process2.cwd(), target.outputPath);
@@ -1032,6 +1045,35 @@ candidate.command("archive").description("Archive a candidate by removing it fro
1032
1045
  log(`Archived ${result.pageId}`);
1033
1046
  }
1034
1047
  });
1048
+ candidate.command("auto-promote").description("Apply configured auto-promotion rules to staged candidates. Requires candidate.autoPromote.enabled in config.").option("--dry-run", "Score candidates without moving files", false).action(async (options) => {
1049
+ const result = await runAutoPromotion(process2.cwd(), { dryRun: options.dryRun ?? false });
1050
+ if (isJson()) {
1051
+ emitJson(result);
1052
+ return;
1053
+ }
1054
+ log(
1055
+ `${result.dryRun ? "Dry-run" : "Promoted"} ${result.promotedPageIds.length} of ${result.decisions.length} candidates. Session: ${result.sessionPath ?? "none"}`
1056
+ );
1057
+ for (const decision of result.decisions) {
1058
+ const mark = decision.promote ? result.promotedPageIds.includes(decision.pageId) ? "+" : "~" : "-";
1059
+ log(` ${mark} ${decision.pageId} score=${decision.score.toFixed(2)} ${decision.reasons.join("; ")}`);
1060
+ }
1061
+ });
1062
+ candidate.command("preview-scores").description("Show promotion scores for staged candidates without promoting.").action(async () => {
1063
+ const decisions = await previewCandidatePromotions(process2.cwd());
1064
+ if (isJson()) {
1065
+ emitJson(decisions);
1066
+ return;
1067
+ }
1068
+ if (!decisions.length) {
1069
+ log("No candidates to score.");
1070
+ return;
1071
+ }
1072
+ for (const decision of decisions) {
1073
+ const verdict = decision.promote ? "promote" : "skip";
1074
+ log(`${verdict} ${decision.pageId} score=${decision.score.toFixed(2)} ${decision.reasons.join("; ")}`);
1075
+ }
1076
+ });
1035
1077
  var watch = program.command("watch").description("Watch the inbox directory and optionally tracked repos, or run one refresh cycle immediately.").option("--lint", "Run lint after each compile cycle", false).option("--repo", "Also refresh tracked repo sources and watch their repo roots", false).option("--once", "Run one import/refresh cycle immediately instead of starting a watcher", false).option("--code-only", "Only re-extract code sources (AST-only, no LLM re-analysis)", false).option("--debounce <ms>", "Debounce window in milliseconds", "900").action(async (options) => {
1036
1078
  const debounceMs = parsePositiveInt(options.debounce, 900);
1037
1079
  if (options.once) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.7.29",
3
+ "version": "0.7.31",
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": "0.7.29",
47
+ "@swarmvaultai/engine": "0.7.31",
48
48
  "commander": "^14.0.1"
49
49
  },
50
50
  "devDependencies": {