@swarmvaultai/cli 1.3.0 → 3.0.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 +75 -15
  2. package/dist/index.js +341 -14
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -41,8 +41,12 @@ swarmvault compile --max-tokens 120000
41
41
  swarmvault diff
42
42
  swarmvault graph share --post
43
43
  swarmvault graph share --svg ./share-card.svg
44
+ swarmvault graph share --bundle ./share-kit
44
45
  swarmvault benchmark
45
46
  swarmvault query "What keeps recurring?" --commit
47
+ swarmvault context build "Ship this feature safely" --target ./src --budget 8000
48
+ swarmvault task start "Ship this feature safely" --target ./src --agent codex
49
+ swarmvault retrieval status
46
50
  swarmvault query "Turn this into slides" --format slides
47
51
  swarmvault explore "What should I research next?" --steps 3
48
52
  swarmvault lint --deep
@@ -87,7 +91,7 @@ Quick-start a scratch vault from a local directory in one command.
87
91
  - initializes the current directory as a SwarmVault workspace
88
92
  - ingests the supplied directory as local sources
89
93
  - compiles the vault immediately
90
- - writes `wiki/graph/share-card.md` and `wiki/graph/share-card.svg`, then prints both paths
94
+ - writes `wiki/graph/share-card.md`, `wiki/graph/share-card.svg`, and `wiki/graph/share-kit/`, then prints the paths
91
95
  - starts `graph serve` unless you pass `--no-serve`
92
96
  - respects `--port` when you want a specific viewer port
93
97
 
@@ -100,7 +104,7 @@ Create a temporary sample vault with bundled sources, compile it immediately, an
100
104
  - writes the demo vault under the system temp directory
101
105
  - requires no API keys or extra setup
102
106
  - is the fastest way to inspect the full init + ingest + compile + graph workflow on a clean machine
103
- - writes `wiki/graph/share-card.md` and `wiki/graph/share-card.svg` inside the demo vault
107
+ - writes `wiki/graph/share-card.md`, `wiki/graph/share-card.svg`, and `wiki/graph/share-kit/` inside the demo vault
104
108
  - respects `--port` when you want a specific viewer port
105
109
 
106
110
  ### `swarmvault diff`
@@ -210,8 +214,8 @@ Compile the current manifests into:
210
214
 
211
215
  - generated markdown in `wiki/`
212
216
  - structured graph data in `state/graph.json`
213
- - local search data in `state/search.sqlite`
214
- - share cards at `wiki/graph/share-card.md` and `wiki/graph/share-card.svg`
217
+ - local retrieval data in `state/retrieval/`
218
+ - share cards at `wiki/graph/share-card.md` and `wiki/graph/share-card.svg`, plus a portable share kit at `wiki/graph/share-kit/`
215
219
 
216
220
  The compiler also reads `swarmvault.schema.md` and records a `schema_hash` plus lifecycle metadata such as `status`, `created_at`, `updated_at`, `compiled_from`, and `managed_by` in generated pages so schema edits can mark pages stale without losing lifecycle state.
217
221
 
@@ -269,13 +273,47 @@ By default, the answer is written into `wiki/outputs/` and immediately registere
269
273
  - `wiki/index.md`
270
274
  - `wiki/outputs/index.md`
271
275
  - `state/graph.json`
272
- - `state/search.sqlite`
276
+ - `state/retrieval/`
273
277
 
274
278
  Saved outputs also carry related page, node, and source metadata so SwarmVault can refresh related source, concept, and entity pages immediately.
275
279
 
276
280
  Human-authored pages in `wiki/insights/` are also indexed into search and query context, but SwarmVault does not rewrite them after initialization.
277
281
 
278
- By default, query uses the local SQLite search index. When an embedding-capable provider is available and `search.hybrid` is not disabled, semantic page matches are fused into the same candidate set before answer generation. `tasks.embeddingProvider` is the explicit way to choose that backend, but SwarmVault can also fall back to a `queryProvider` with embeddings support. Set `search.rerank: true` when you want the configured `queryProvider` to rerank the merged top hits. `--commit` immediately commits saved `wiki/` and `state/` changes when the vault root is inside a git repo.
282
+ By default, query uses the local SQLite retrieval index. When an embedding-capable provider is available and `retrieval.hybrid` is not disabled, semantic page matches are fused into the same candidate set before answer generation. `tasks.embeddingProvider` is the explicit way to choose that backend, but SwarmVault can also fall back to a `queryProvider` with embeddings support. Set `retrieval.rerank: true` when you want the configured `queryProvider` to rerank the merged top hits. `--commit` immediately commits saved `wiki/` and `state/` changes when the vault root is inside a git repo.
283
+
284
+ ### `swarmvault context build|list|show|delete`
285
+
286
+ Build and manage agent-ready context packs from the compiled vault.
287
+
288
+ - `context build "<goal>"` assembles relevant pages, graph nodes, edges, hyperedges, citations, and explicit omitted entries into a bounded bundle
289
+ - `--target <path-or-node>` anchors the pack around a file, page id, node id, or graph label
290
+ - `--task <id>` links the newly built context pack to an active task
291
+ - `--memory <id>` remains a compatibility alias for `--task`
292
+ - `--budget <tokens>` caps the estimated token budget; over-budget candidates are listed in `omittedItems`
293
+ - `--format markdown|json|llms` controls the printed output shape, while every pack is still saved as JSON
294
+ - saved artifacts live under `state/context-packs/`, with companion markdown pages under `wiki/context/`
295
+ - `context list`, `context show <id>`, and `context delete <id>` manage saved packs
296
+
297
+ Use this before handing work to an agent, starting a PR review, or preserving the evidence bundle behind a design/debugging decision.
298
+
299
+ ### `swarmvault task start|update|finish|list|show|resume`
300
+
301
+ Record a durable local task ledger for agent work.
302
+
303
+ - `task start "<goal>" --target <path-or-node>` creates `state/memory/tasks/<id>.json`, `wiki/memory/tasks/<id>.md`, updates `wiki/memory/index.md`, and builds an initial context pack
304
+ - `task update <id>` records notes, decisions, changed paths, context packs, sessions, sources, pages, nodes, git refs, or status changes
305
+ - `task finish <id> --outcome <text>` marks the task completed and can add one or more `--follow-up` entries
306
+ - `task list`, `task show <id>`, and `task resume <id> --format markdown|json|llms` expose the task history for the next agent
307
+
308
+ `query`, `explore`, and `context build` accept `--task <id>` so saved outputs and context packs can attach to an active task. The 2.0 `memory` command group and `--memory <id>` flag remain compatibility aliases.
309
+
310
+ ### `swarmvault retrieval status|rebuild|doctor`
311
+
312
+ Inspect and maintain the local retrieval index under `state/retrieval/`.
313
+
314
+ - `retrieval status` reports backend, configured hybrid/rerank behavior, manifest freshness, page count, and the current SQLite shard path
315
+ - `retrieval rebuild` rebuilds the local shard from current wiki pages and refreshes `state/retrieval/manifest.json`
316
+ - `retrieval doctor` checks for stale or missing retrieval artifacts; add `--repair` to rebuild missing or stale artifacts immediately
279
317
 
280
318
  ### `swarmvault explore "<question>" [--steps <n>] [--format markdown|report|slides|chart|image]`
281
319
 
@@ -344,10 +382,28 @@ Run SwarmVault as a local MCP server over stdio. This exposes the vault to compa
344
382
  - `compile_vault`
345
383
  - `lint_vault`
346
384
  - `blast_radius`
347
-
348
- `compile_vault` also accepts `maxTokens` for bounded wiki output, and `blast_radius` traces reverse import impact for a file or module target.
349
-
350
- The MCP surface also exposes `swarmvault://schema`, `swarmvault://sessions`, `swarmvault://sessions/{path}`, and includes `schemaPath` in `workspace_info`.
385
+ - `build_context_pack`
386
+ - `list_context_packs`
387
+ - `read_context_pack`
388
+ - `start_task`
389
+ - `update_task`
390
+ - `finish_task`
391
+ - `list_tasks`
392
+ - `read_task`
393
+ - `resume_task`
394
+ - `start_memory_task`
395
+ - `update_memory_task`
396
+ - `finish_memory_task`
397
+ - `list_memory_tasks`
398
+ - `read_memory_task`
399
+ - `resume_memory_task`
400
+ - `retrieval_status`
401
+ - `rebuild_retrieval`
402
+ - `doctor_retrieval`
403
+
404
+ `compile_vault` also accepts `maxTokens` for bounded wiki output, `blast_radius` traces reverse import impact for a file or module target, `build_context_pack` creates the same bounded agent evidence bundles as `swarmvault context build`, the task tools mirror `swarmvault task`, the memory tools mirror the compatibility command group, and retrieval tools inspect or repair the local index.
405
+
406
+ The MCP surface also exposes `swarmvault://schema`, `swarmvault://sessions`, `swarmvault://sessions/{path}`, `swarmvault://context-packs`, `swarmvault://tasks`, `swarmvault://memory-tasks`, and includes `schemaPath` in `workspace_info`.
351
407
 
352
408
  ### `swarmvault graph serve`
353
409
 
@@ -371,14 +427,15 @@ Inspect graph metadata, community membership, neighbors, provenance, and group-p
371
427
 
372
428
  List the most connected bridge-heavy nodes in the current graph.
373
429
 
374
- ### `swarmvault graph share [--post] [--svg [path]]`
430
+ ### `swarmvault graph share [--post] [--svg [path]] [--bundle [dir]]`
375
431
 
376
432
  Print a shareable summary of the compiled graph.
377
433
 
378
434
  - default output is the same markdown shape written to `wiki/graph/share-card.md`
379
435
  - `--post` prints only the concise social-post text
380
436
  - `--svg [path]` writes the 1200x630 visual share card, defaulting to `wiki/graph/share-card.svg`
381
- - `--json` emits the structured share artifact for automation; with `--svg`, it also includes `svgPath`
437
+ - `--bundle [dir]` writes `share-card.md`, `share-post.txt`, `share-card.svg`, `share-preview.html`, and `share-artifact.json`, defaulting to `wiki/graph/share-kit`
438
+ - `--json` emits the structured share artifact for automation; with `--svg`, it also includes `svgPath`; with `--bundle`, it includes `bundlePath` and named output paths
382
439
  - useful immediately after `swarmvault scan`, `swarmvault demo`, or a normal compile
383
440
 
384
441
  ### `swarmvault graph blast <target> [--depth <n>]`
@@ -537,15 +594,18 @@ Search behavior is configurable separately from provider routing:
537
594
 
538
595
  ```json
539
596
  {
540
- "search": {
597
+ "retrieval": {
598
+ "backend": "sqlite",
599
+ "shardSize": 25000,
541
600
  "hybrid": true,
542
601
  "rerank": false
543
602
  }
544
603
  }
545
604
  ```
546
605
 
547
- - `search.hybrid` defaults to enabled and merges full-text hits with semantic page matches when an embedding-capable provider is available
548
- - `search.rerank` optionally asks the current `queryProvider` to rerank the merged top hits before query answers are generated
606
+ - `retrieval.hybrid` defaults to enabled and merges full-text hits with semantic page matches when an embedding-capable provider is available
607
+ - `retrieval.rerank` optionally asks the current `queryProvider` to rerank the merged top hits before query answers are generated
608
+ - `retrieval.backend` currently supports the local SQLite backend
549
609
 
550
610
  ## Troubleshooting
551
611
 
package/dist/index.js CHANGED
@@ -15,11 +15,14 @@ import {
15
15
  autoCommitWikiChanges,
16
16
  benchmarkVault,
17
17
  blastRadiusVault,
18
+ buildContextPack,
18
19
  buildGraphShareArtifact,
19
20
  compileVault,
20
21
  consolidateVault,
21
22
  createSupersessionEdge,
23
+ deleteContextPack,
22
24
  deleteManagedSource,
25
+ doctorRetrieval,
23
26
  downloadWhisperModel,
24
27
  explainGraphVault,
25
28
  exploreVault,
@@ -28,7 +31,9 @@ import {
28
31
  exportGraphReportHtml,
29
32
  exportObsidianCanvas,
30
33
  exportObsidianVault,
34
+ finishMemoryTask,
31
35
  getGitHookStatus,
36
+ getRetrievalStatus,
32
37
  getWatchStatus,
33
38
  graphDiff,
34
39
  guideManagedSource,
@@ -42,9 +47,11 @@ import {
42
47
  lintVault,
43
48
  listApprovals,
44
49
  listCandidates,
50
+ listContextPacks,
45
51
  listGodNodes,
46
52
  listManagedSourceRecords,
47
53
  listManifests,
54
+ listMemoryTasks,
48
55
  listSchedules,
49
56
  listWatchedRoots,
50
57
  loadVaultConfig,
@@ -55,13 +62,20 @@ import {
55
62
  queryGraphVault,
56
63
  queryVault,
57
64
  readApproval,
65
+ readContextPack,
58
66
  readGraphReport,
67
+ readMemoryTask,
68
+ rebuildRetrievalIndex,
59
69
  registerLocalWhisperProvider,
60
70
  rejectApproval,
61
71
  reloadManagedSources,
62
72
  removeWatchedRoot,
73
+ renderContextPackLlms,
74
+ renderContextPackMarkdown,
75
+ renderGraphShareBundleFiles,
63
76
  renderGraphShareMarkdown,
64
77
  renderGraphShareSvg,
78
+ resumeMemoryTask,
65
79
  resumeSourceSession,
66
80
  reviewManagedSource,
67
81
  reviewSourceScope,
@@ -72,8 +86,10 @@ import {
72
86
  serveSchedules,
73
87
  startGraphServer,
74
88
  startMcpServer,
89
+ startMemoryTask,
75
90
  summarizeLocalWhisperSetup,
76
91
  uninstallGitHooks,
92
+ updateMemoryTask,
77
93
  watchVault
78
94
  } from "@swarmvaultai/engine";
79
95
  import { Command, Option } from "commander";
@@ -292,9 +308,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
292
308
  function readCliVersion() {
293
309
  try {
294
310
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
295
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "1.3.0";
311
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "3.0.0";
296
312
  } catch {
297
- return "1.3.0";
313
+ return "3.0.0";
298
314
  }
299
315
  }
300
316
  function parsePositiveInt(value, fallback) {
@@ -348,6 +364,20 @@ function log(message) {
348
364
  `);
349
365
  }
350
366
  }
367
+ async function writeShareBundle(bundlePath, files) {
368
+ await mkdir2(bundlePath, { recursive: true });
369
+ const bundleFiles = {
370
+ markdownPath: path2.join(bundlePath, "share-card.md"),
371
+ postPath: path2.join(bundlePath, "share-post.txt"),
372
+ svgPath: path2.join(bundlePath, "share-card.svg"),
373
+ previewHtmlPath: path2.join(bundlePath, "share-preview.html"),
374
+ artifactJsonPath: path2.join(bundlePath, "share-artifact.json")
375
+ };
376
+ for (const file of files) {
377
+ await writeFile2(path2.join(bundlePath, file.relativePath), file.content, "utf8");
378
+ }
379
+ return bundleFiles;
380
+ }
351
381
  function emitNotice(message) {
352
382
  process2.stderr.write(`[swarmvault] ${message}
353
383
  `);
@@ -359,8 +389,8 @@ async function maybeEmitHeuristicNotice(commandPath) {
359
389
  try {
360
390
  const { config } = await loadVaultConfig(process2.cwd());
361
391
  const analysisTaskKeys = ["compileProvider", "queryProvider", "lintProvider"];
362
- const usingHeuristic = analysisTaskKeys.every((task) => {
363
- const providerId = config.tasks[task];
392
+ const usingHeuristic = analysisTaskKeys.every((task2) => {
393
+ const providerId = config.tasks[task2];
364
394
  const providerConfig = config.providers[providerId];
365
395
  return !providerConfig || providerConfig.type === "heuristic";
366
396
  });
@@ -786,7 +816,7 @@ program.command("consolidate").description("Roll working-tier insights up into e
786
816
  log(decision);
787
817
  }
788
818
  });
789
- program.command("query").description("Query the compiled SwarmVault wiki.").argument("<question>", "Question to ask SwarmVault").option("--no-save", "Do not persist the answer to wiki/outputs").option("--commit", "Auto-commit wiki changes after query").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.queryProvider).").addOption(
819
+ program.command("query").description("Query the compiled SwarmVault wiki.").argument("<question>", "Question to ask SwarmVault").option("--no-save", "Do not persist the answer to wiki/outputs").option("--commit", "Auto-commit wiki changes after query").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.queryProvider).").option("--task <id>", "Attach this query output to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(
790
820
  new Option("--format <format>", "Output format").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
791
821
  ).action(
792
822
  async (question, options) => {
@@ -794,7 +824,8 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
794
824
  question,
795
825
  save: options.save ?? true,
796
826
  format: options.format,
797
- gapFill: options.gapFill ?? false
827
+ gapFill: options.gapFill ?? false,
828
+ memoryTaskId: options.task ?? options.memory
798
829
  });
799
830
  if (isJson()) {
800
831
  emitJson(result);
@@ -811,7 +842,235 @@ program.command("query").description("Query the compiled SwarmVault wiki.").argu
811
842
  await maybeEmitHeuristicNotice(["query"]);
812
843
  }
813
844
  );
814
- program.command("explore").description("Run a save-first multi-step exploration loop against the vault.").argument("<question>", "Root question to explore").option("--steps <n>", "Maximum number of exploration steps", "3").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.exploreProvider).").addOption(
845
+ var context = program.command("context").description("Build and manage token-bounded agent context packs.");
846
+ context.command("build").description("Build a cited, token-bounded context pack for an agent task.").argument("<goal>", "Task, question, or goal the agent needs context for").option("--target <target>", "Optional page, node, path, project, or label to anchor the pack").option("--budget <tokens>", "Approximate token budget for included context", String(8e3)).option("--task <id>", "Attach the context pack to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(
847
+ async (goal, options) => {
848
+ const budgetTokens = parsePositiveInt(options.budget, 8e3);
849
+ const result = await buildContextPack(process2.cwd(), {
850
+ goal,
851
+ target: options.target,
852
+ budgetTokens,
853
+ format: options.format,
854
+ memoryTaskId: options.task ?? options.memory
855
+ });
856
+ if (isJson()) {
857
+ emitJson(result);
858
+ return;
859
+ }
860
+ log(result.rendered);
861
+ log(`Saved context pack to ${result.markdownPath}`);
862
+ log(`Saved context artifact to ${result.artifactPath}`);
863
+ }
864
+ );
865
+ context.command("list").description("List saved context packs.").action(async () => {
866
+ const packs = await listContextPacks(process2.cwd());
867
+ if (isJson()) {
868
+ emitJson(packs);
869
+ return;
870
+ }
871
+ if (!packs.length) {
872
+ log("No context packs.");
873
+ return;
874
+ }
875
+ for (const pack of packs) {
876
+ log(`${pack.id} \u2014 ${pack.goal} (${pack.itemCount} item(s), ${pack.omittedCount} omitted)`);
877
+ }
878
+ });
879
+ context.command("show").description("Print a saved context pack.").argument("<id>", "Context pack id").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(async (id, options) => {
880
+ const pack = await readContextPack(process2.cwd(), id);
881
+ if (!pack) {
882
+ throw new Error(`Context pack not found: ${id}`);
883
+ }
884
+ if (isJson() || options.format === "json") {
885
+ emitJson(pack);
886
+ return;
887
+ }
888
+ log(options.format === "llms" ? renderContextPackLlms(pack) : renderContextPackMarkdown(pack));
889
+ });
890
+ context.command("delete").description("Delete a saved context pack artifact and markdown page.").argument("<id>", "Context pack id").action(async (id) => {
891
+ const deleted = await deleteContextPack(process2.cwd(), id);
892
+ if (!deleted) {
893
+ throw new Error(`Context pack not found: ${id}`);
894
+ }
895
+ if (isJson()) {
896
+ emitJson(deleted);
897
+ return;
898
+ }
899
+ log(`Deleted context pack ${deleted.id}.`);
900
+ });
901
+ var memory = program.command("memory").description("Manage git-backed agent memory task ledger entries.");
902
+ memory.command("start").description("Start a durable agent memory task and build its initial context pack.").argument("<goal>", "Task goal to preserve in agent memory").option("--target <target>", "Optional page, node, path, project, or label to anchor the initial context pack").option("--budget <tokens>", "Approximate token budget for the initial context pack", String(8e3)).option("--agent <name>", "Agent name to record on the task").option("--context-pack <id>", "Attach an existing context pack instead of building a new one").action(async (goal, options) => {
903
+ const result = await startMemoryTask(process2.cwd(), {
904
+ goal,
905
+ target: options.target,
906
+ budgetTokens: parsePositiveInt(options.budget, 8e3),
907
+ agent: options.agent,
908
+ contextPackId: options.contextPack
909
+ });
910
+ if (isJson()) {
911
+ emitJson(result);
912
+ return;
913
+ }
914
+ log(result.task.id);
915
+ log(`Saved memory task to ${result.markdownPath}`);
916
+ });
917
+ memory.command("update").description("Append a note, decision, path, context pack, or status change to a memory task.").argument("<id>", "Memory task id").option("--note <text>", "Append a task note").option("--decision <text>", "Append a decision").option("--changed-path <path>", "Record a changed file or wiki path").option("--context-pack <id>", "Attach a context pack").option("--session <id>", "Attach a session id").option("--source <id>", "Attach a source id").option("--page <id>", "Attach a page id").option("--node <id>", "Attach a graph node id").option("--git-ref <ref>", "Attach a git ref").addOption(new Option("--status <status>", "Task status").choices(["active", "blocked", "completed", "archived"])).action(
918
+ async (id, options) => {
919
+ const result = await updateMemoryTask(process2.cwd(), id, {
920
+ note: options.note,
921
+ decision: options.decision,
922
+ changedPath: options.changedPath,
923
+ contextPackId: options.contextPack,
924
+ sessionId: options.session,
925
+ sourceId: options.source,
926
+ pageId: options.page,
927
+ nodeId: options.node,
928
+ gitRef: options.gitRef,
929
+ status: options.status
930
+ });
931
+ if (isJson()) {
932
+ emitJson(result);
933
+ return;
934
+ }
935
+ log(`Updated memory task ${result.task.id}.`);
936
+ }
937
+ );
938
+ memory.command("finish").description("Finish a memory task with an outcome and optional follow-up.").argument("<id>", "Memory task id").requiredOption("--outcome <text>", "Outcome to record").option("--follow-up <text>", "Follow-up to preserve for the next agent").action(async (id, options) => {
939
+ const result = await finishMemoryTask(process2.cwd(), id, {
940
+ outcome: options.outcome,
941
+ followUp: options.followUp
942
+ });
943
+ if (isJson()) {
944
+ emitJson(result);
945
+ return;
946
+ }
947
+ log(`Finished memory task ${result.task.id}.`);
948
+ });
949
+ memory.command("list").description("List saved agent memory tasks.").action(async () => {
950
+ const tasks = await listMemoryTasks(process2.cwd());
951
+ if (isJson()) {
952
+ emitJson(tasks);
953
+ return;
954
+ }
955
+ if (!tasks.length) {
956
+ log("No memory tasks.");
957
+ return;
958
+ }
959
+ for (const task2 of tasks) {
960
+ log(`${task2.id} \u2014 ${task2.status} \u2014 ${task2.goal}`);
961
+ }
962
+ });
963
+ memory.command("show").description("Print a saved agent memory task.").argument("<id>", "Memory task id").action(async (id) => {
964
+ const task2 = await readMemoryTask(process2.cwd(), id);
965
+ if (!task2) {
966
+ throw new Error(`Memory task not found: ${id}`);
967
+ }
968
+ if (isJson()) {
969
+ emitJson(task2);
970
+ return;
971
+ }
972
+ log(`Task: ${task2.title}`);
973
+ log(`Status: ${task2.status}`);
974
+ log(`Goal: ${task2.goal}`);
975
+ if (task2.outcome) log(`Outcome: ${task2.outcome}`);
976
+ if (task2.followUps.length) log(`Follow-ups: ${task2.followUps.join("; ")}`);
977
+ log(`Markdown: ${task2.markdownPath}`);
978
+ });
979
+ memory.command("resume").description("Render a memory task handoff for the next agent.").argument("<id>", "Memory task id").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(async (id, options) => {
980
+ const result = await resumeMemoryTask(process2.cwd(), id, { format: options.format });
981
+ if (isJson() || options.format === "json") {
982
+ emitJson(result);
983
+ return;
984
+ }
985
+ log(result.rendered);
986
+ });
987
+ var task = program.command("task").description("Manage git-backed agent task ledger entries.");
988
+ task.command("start").description("Start a durable agent task and build its initial context pack.").argument("<goal>", "Task goal to preserve").option("--target <target>", "Optional page, node, path, project, or label to anchor the initial context pack").option("--budget <tokens>", "Approximate token budget for the initial context pack", String(8e3)).option("--agent <name>", "Agent name to record on the task").option("--context-pack <id>", "Attach an existing context pack instead of building a new one").action(async (goal, options) => {
989
+ const result = await startMemoryTask(process2.cwd(), {
990
+ goal,
991
+ target: options.target,
992
+ budgetTokens: parsePositiveInt(options.budget, 8e3),
993
+ agent: options.agent,
994
+ contextPackId: options.contextPack
995
+ });
996
+ if (isJson()) {
997
+ emitJson(result);
998
+ return;
999
+ }
1000
+ log(result.task.id);
1001
+ log(`Saved task to ${result.markdownPath}`);
1002
+ });
1003
+ task.command("update").description("Append a note, decision, path, context pack, or status change to a task.").argument("<id>", "Task id").option("--note <text>", "Append a task note").option("--decision <text>", "Append a decision").option("--changed-path <path>", "Record a changed file or wiki path").option("--context-pack <id>", "Attach a context pack").option("--session <id>", "Attach a session id").option("--source <id>", "Attach a source id").option("--page <id>", "Attach a page id").option("--node <id>", "Attach a graph node id").option("--git-ref <ref>", "Attach a git ref").addOption(new Option("--status <status>", "Task status").choices(["active", "blocked", "completed", "archived"])).action(
1004
+ async (id, options) => {
1005
+ const result = await updateMemoryTask(process2.cwd(), id, {
1006
+ note: options.note,
1007
+ decision: options.decision,
1008
+ changedPath: options.changedPath,
1009
+ contextPackId: options.contextPack,
1010
+ sessionId: options.session,
1011
+ sourceId: options.source,
1012
+ pageId: options.page,
1013
+ nodeId: options.node,
1014
+ gitRef: options.gitRef,
1015
+ status: options.status
1016
+ });
1017
+ if (isJson()) {
1018
+ emitJson(result);
1019
+ return;
1020
+ }
1021
+ log(`Updated task ${result.task.id}.`);
1022
+ }
1023
+ );
1024
+ task.command("finish").description("Finish a task with an outcome and optional follow-up.").argument("<id>", "Task id").requiredOption("--outcome <text>", "Outcome to record").option("--follow-up <text>", "Follow-up to preserve for the next agent").action(async (id, options) => {
1025
+ const result = await finishMemoryTask(process2.cwd(), id, {
1026
+ outcome: options.outcome,
1027
+ followUp: options.followUp
1028
+ });
1029
+ if (isJson()) {
1030
+ emitJson(result);
1031
+ return;
1032
+ }
1033
+ log(`Finished task ${result.task.id}.`);
1034
+ });
1035
+ task.command("list").description("List saved agent tasks.").action(async () => {
1036
+ const tasks = await listMemoryTasks(process2.cwd());
1037
+ if (isJson()) {
1038
+ emitJson(tasks);
1039
+ return;
1040
+ }
1041
+ if (!tasks.length) {
1042
+ log("No tasks.");
1043
+ return;
1044
+ }
1045
+ for (const entry of tasks) {
1046
+ log(`${entry.id} \u2014 ${entry.status} \u2014 ${entry.goal}`);
1047
+ }
1048
+ });
1049
+ task.command("show").description("Print a saved agent task.").argument("<id>", "Task id").action(async (id) => {
1050
+ const entry = await readMemoryTask(process2.cwd(), id);
1051
+ if (!entry) {
1052
+ throw new Error(`Task not found: ${id}`);
1053
+ }
1054
+ if (isJson()) {
1055
+ emitJson(entry);
1056
+ return;
1057
+ }
1058
+ log(`Task: ${entry.title}`);
1059
+ log(`Status: ${entry.status}`);
1060
+ log(`Goal: ${entry.goal}`);
1061
+ if (entry.outcome) log(`Outcome: ${entry.outcome}`);
1062
+ if (entry.followUps.length) log(`Follow-ups: ${entry.followUps.join("; ")}`);
1063
+ log(`Markdown: ${entry.markdownPath}`);
1064
+ });
1065
+ task.command("resume").description("Render a task handoff for the next agent.").argument("<id>", "Task id").addOption(new Option("--format <format>", "Output format").choices(["markdown", "json", "llms"]).default("markdown")).action(async (id, options) => {
1066
+ const result = await resumeMemoryTask(process2.cwd(), id, { format: options.format });
1067
+ if (isJson() || options.format === "json") {
1068
+ emitJson(result);
1069
+ return;
1070
+ }
1071
+ log(result.rendered);
1072
+ });
1073
+ program.command("explore").description("Run a save-first multi-step exploration loop against the vault.").argument("<question>", "Root question to explore").option("--steps <n>", "Maximum number of exploration steps", "3").option("--gap-fill", "Pull external web-search evidence when the local wiki has gaps (requires webSearch.tasks.exploreProvider).").option("--task <id>", "Attach this exploration to an agent task").option("--memory <id>", "Compatibility alias for --task").addOption(
815
1074
  new Option("--format <format>", "Output format for step pages").choices(["markdown", "report", "slides", "chart", "image"]).default("markdown")
816
1075
  ).action(
817
1076
  async (question, options) => {
@@ -820,7 +1079,8 @@ program.command("explore").description("Run a save-first multi-step exploration
820
1079
  question,
821
1080
  steps: stepCount,
822
1081
  format: options.format,
823
- gapFill: options.gapFill ?? false
1082
+ gapFill: options.gapFill ?? false,
1083
+ memoryTaskId: options.task ?? options.memory
824
1084
  });
825
1085
  if (isJson()) {
826
1086
  emitJson(result);
@@ -976,9 +1236,10 @@ graph.command("export").description(
976
1236
  }
977
1237
  }
978
1238
  );
979
- graph.command("share").description("Print a shareable summary of the compiled graph.").option("--post", "Print only the short social post text", false).option("--svg [path]", "Write the visual SVG share card, defaulting to wiki/graph/share-card.svg").action(async (options) => {
980
- if (options.post && options.svg) {
981
- throw new Error("Choose either --post or --svg, not both.");
1239
+ graph.command("share").description("Print a shareable summary of the compiled graph.").option("--post", "Print only the short social post text", false).option("--svg [path]", "Write the visual SVG share card, defaulting to wiki/graph/share-card.svg").option("--bundle [dir]", "Write the portable share kit bundle, defaulting to wiki/graph/share-kit").action(async (options) => {
1240
+ const outputModeCount = [options.post, options.svg, options.bundle].filter(Boolean).length;
1241
+ if (outputModeCount > 1) {
1242
+ throw new Error("Choose one graph share output mode: --post, --svg, or --bundle.");
982
1243
  }
983
1244
  const { paths } = await loadVaultConfig(process2.cwd());
984
1245
  const raw = await readFile2(paths.graphPath, "utf-8");
@@ -1000,6 +1261,16 @@ graph.command("share").description("Print a shareable summary of the compiled gr
1000
1261
  log(`Wrote SVG share card to ${svgPath}`);
1001
1262
  return;
1002
1263
  }
1264
+ if (options.bundle) {
1265
+ const bundlePath = typeof options.bundle === "string" ? path2.resolve(process2.cwd(), options.bundle) : path2.join(paths.wikiDir, "graph", "share-kit");
1266
+ const bundleFiles = await writeShareBundle(bundlePath, renderGraphShareBundleFiles(artifact));
1267
+ if (isJson()) {
1268
+ emitJson({ ...artifact, bundlePath, bundleFiles });
1269
+ return;
1270
+ }
1271
+ log(`Wrote share kit to ${bundlePath}`);
1272
+ return;
1273
+ }
1003
1274
  if (isJson()) {
1004
1275
  emitJson(artifact);
1005
1276
  return;
@@ -1568,6 +1839,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1568
1839
  const { paths } = await loadVaultConfig(demoDir);
1569
1840
  const shareCardPath = path3.join(demoDir, "wiki", "graph", "share-card.md");
1570
1841
  const shareCardSvgPath = path3.join(demoDir, "wiki", "graph", "share-card.svg");
1842
+ const shareKitPath = path3.join(demoDir, "wiki", "graph", "share-kit");
1571
1843
  let graphStats = "";
1572
1844
  try {
1573
1845
  const raw = await readFile2(paths.graphPath, "utf-8");
@@ -1587,6 +1859,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1587
1859
  log(`Vault location: ${demoDir}`);
1588
1860
  log(`Share card: ${shareCardPath}`);
1589
1861
  log(`Visual card: ${shareCardSvgPath}`);
1862
+ log(`Share kit: ${shareKitPath}`);
1590
1863
  }
1591
1864
  if (options.serve !== false) {
1592
1865
  const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
@@ -1597,6 +1870,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1597
1870
  graphStats: graphStats.trim(),
1598
1871
  shareCardPath,
1599
1872
  shareCardSvgPath,
1873
+ shareKitPath,
1600
1874
  port: server.port,
1601
1875
  url: `http://localhost:${server.port}`
1602
1876
  });
@@ -1617,7 +1891,7 @@ program.command("demo").description("Try SwarmVault with a bundled sample vault
1617
1891
  process2.exit(0);
1618
1892
  });
1619
1893
  } else if (isJson()) {
1620
- emitJson({ demoDir, graphStats: graphStats.trim(), shareCardPath, shareCardSvgPath });
1894
+ emitJson({ demoDir, graphStats: graphStats.trim(), shareCardPath, shareCardSvgPath, shareKitPath });
1621
1895
  } else {
1622
1896
  log("");
1623
1897
  log("Try next:");
@@ -1739,6 +2013,49 @@ program.command("diff").description("Show what changed in the knowledge graph si
1739
2013
  }
1740
2014
  }
1741
2015
  });
2016
+ var retrieval = program.command("retrieval").description("Inspect and repair the local retrieval index.");
2017
+ retrieval.command("status").description("Show retrieval index health and configuration.").action(async () => {
2018
+ const status = await getRetrievalStatus(process2.cwd());
2019
+ if (isJson()) {
2020
+ emitJson(status);
2021
+ return;
2022
+ }
2023
+ log(`Retrieval backend: ${status.configured.backend}`);
2024
+ log(`Index: ${status.indexExists ? "present" : "missing"} (${status.indexPath})`);
2025
+ log(`Manifest: ${status.manifestExists ? "present" : "missing"} (${status.manifestPath})`);
2026
+ log(`Graph: ${status.graphExists ? "present" : "missing"}`);
2027
+ log(`Pages indexed: ${status.pageCount}`);
2028
+ log(`State: ${status.stale ? "stale" : "fresh"}`);
2029
+ for (const warning of status.warnings) {
2030
+ log(`Warning: ${warning}`);
2031
+ }
2032
+ });
2033
+ retrieval.command("rebuild").description("Rebuild the local retrieval index from the current graph.").action(async () => {
2034
+ const status = await rebuildRetrievalIndex(process2.cwd());
2035
+ if (isJson()) {
2036
+ emitJson(status);
2037
+ return;
2038
+ }
2039
+ log(`Rebuilt retrieval index at ${status.indexPath}`);
2040
+ log(`Pages indexed: ${status.pageCount}`);
2041
+ });
2042
+ retrieval.command("doctor").description("Diagnose retrieval index problems and optionally repair them.").option("--repair", "Rebuild stale or missing retrieval artifacts", false).action(async (options) => {
2043
+ const result = await doctorRetrieval(process2.cwd(), { repair: options.repair });
2044
+ if (isJson()) {
2045
+ emitJson(result);
2046
+ return;
2047
+ }
2048
+ log(`Retrieval health: ${result.ok ? "ok" : "needs attention"}`);
2049
+ if (result.repaired) {
2050
+ log("Repaired retrieval index.");
2051
+ }
2052
+ if (result.actions.length) {
2053
+ log(`Suggested action(s): ${result.actions.join(", ")}`);
2054
+ }
2055
+ for (const warning of result.status.warnings) {
2056
+ log(`Warning: ${warning}`);
2057
+ }
2058
+ });
1742
2059
  program.command("scan").description("Quick-start: initialize, ingest, compile, and serve a graph viewer in one command.").argument("<directory>", "Directory to scan").option("--port <port>", "Port for the graph viewer").option("--no-serve", "Skip launching the graph viewer after compile").action(async (directory, options) => {
1743
2060
  const rootDir = process2.cwd();
1744
2061
  await initVault(rootDir, {});
@@ -1752,17 +2069,27 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
1752
2069
  const compiled = await compileVault(rootDir, {});
1753
2070
  const shareCardPath = path2.join(rootDir, "wiki", "graph", "share-card.md");
1754
2071
  const shareCardSvgPath = path2.join(rootDir, "wiki", "graph", "share-card.svg");
2072
+ const shareKitPath = path2.join(rootDir, "wiki", "graph", "share-kit");
1755
2073
  if (!isJson()) {
1756
2074
  log(`Compiled ${compiled.sourceCount} source(s), ${compiled.pageCount} page(s).`);
1757
2075
  log(`Share card: ${shareCardPath}`);
1758
2076
  log(`Visual card: ${shareCardSvgPath}`);
2077
+ log(`Share kit: ${shareKitPath}`);
1759
2078
  log("Post text: swarmvault graph share --post");
1760
2079
  }
1761
2080
  if (options.serve !== false) {
1762
2081
  const port = options.port ? parsePositiveInt(options.port, 0) || void 0 : void 0;
1763
2082
  const server = await startGraphServer(rootDir, port, { full: false });
1764
2083
  if (isJson()) {
1765
- emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, port: server.port, url: `http://localhost:${server.port}` });
2084
+ emitJson({
2085
+ ...result,
2086
+ compiled,
2087
+ shareCardPath,
2088
+ shareCardSvgPath,
2089
+ shareKitPath,
2090
+ port: server.port,
2091
+ url: `http://localhost:${server.port}`
2092
+ });
1766
2093
  } else {
1767
2094
  log(`Graph viewer running at http://localhost:${server.port}`);
1768
2095
  }
@@ -1774,7 +2101,7 @@ program.command("scan").description("Quick-start: initialize, ingest, compile, a
1774
2101
  process2.exit(0);
1775
2102
  });
1776
2103
  } else if (isJson()) {
1777
- emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath });
2104
+ emitJson({ ...result, compiled, shareCardPath, shareCardSvgPath, shareKitPath });
1778
2105
  }
1779
2106
  });
1780
2107
  function enableStructuredJsonOnSubcommands(command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "1.3.0",
3
+ "version": "3.0.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": "1.3.0",
47
+ "@swarmvaultai/engine": "3.0.0",
48
48
  "commander": "^14.0.1"
49
49
  },
50
50
  "devDependencies": {