threadnote 0.7.11 → 1.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.
@@ -3472,7 +3472,7 @@ var {
3472
3472
  // src/constants.ts
3473
3473
  var DEFAULT_HOST = "127.0.0.1";
3474
3474
  var DEFAULT_PORT = 1933;
3475
- var DEFAULT_OPENVIKING_VERSION = "0.3.21";
3475
+ var DEFAULT_OPENVIKING_VERSION = "0.3.24";
3476
3476
  var DEFAULT_ACCOUNT = "local";
3477
3477
  var DEFAULT_AGENT_ID = "threadnote";
3478
3478
  var OPENVIKING_PACKAGE_NAME = "openviking[local-embed]";
@@ -3523,8 +3523,8 @@ var DEFAULT_SEED_PATTERNS = [
3523
3523
  ];
3524
3524
 
3525
3525
  // src/hooks.ts
3526
- var import_promises7 = require("node:fs/promises");
3527
- var import_node_path9 = require("node:path");
3526
+ var import_promises8 = require("node:fs/promises");
3527
+ var import_node_path10 = require("node:path");
3528
3528
 
3529
3529
  // src/mcp.ts
3530
3530
  var import_node_fs2 = require("node:fs");
@@ -4773,8 +4773,8 @@ function existsSyncDirectory(path) {
4773
4773
  }
4774
4774
 
4775
4775
  // src/memory.ts
4776
- var import_promises5 = require("node:fs/promises");
4777
- var import_node_path6 = require("node:path");
4776
+ var import_promises6 = require("node:fs/promises");
4777
+ var import_node_path7 = require("node:path");
4778
4778
 
4779
4779
  // src/manifest.ts
4780
4780
  var import_promises3 = require("node:fs/promises");
@@ -7472,8 +7472,284 @@ function readStringArray(object, key) {
7472
7472
  return value;
7473
7473
  }
7474
7474
 
7475
- // src/memory_hygiene.ts
7475
+ // src/index_repair.ts
7476
+ var import_promises4 = require("node:fs/promises");
7477
+ var import_node_path4 = require("node:path");
7478
+
7479
+ // src/runtime.ts
7480
+ var import_node_fs3 = require("node:fs");
7481
+ var import_node_os3 = require("node:os");
7476
7482
  var import_node_path3 = require("node:path");
7483
+ function getRuntimeConfig(program2, manifestOverride) {
7484
+ const options = program2.opts();
7485
+ const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
7486
+ const manifestPath = expandPath(
7487
+ manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
7488
+ );
7489
+ return {
7490
+ account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
7491
+ agentContextHome: threadnoteHome,
7492
+ agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
7493
+ host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
7494
+ manifestPath,
7495
+ openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
7496
+ port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
7497
+ user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
7498
+ };
7499
+ }
7500
+ function defaultManifestPath(agentContextHome) {
7501
+ const userManifest = (0, import_node_path3.join)(agentContextHome, USER_MANIFEST_NAME);
7502
+ return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
7503
+ }
7504
+ function builtInExampleManifestPath() {
7505
+ return (0, import_node_path3.join)(toolRoot(), "config", "seed-manifest.example.yaml");
7506
+ }
7507
+ function openVikingHealthUrl(config) {
7508
+ return `http://${config.host}:${config.port}/health`;
7509
+ }
7510
+ function openVikingLogPath(config) {
7511
+ return (0, import_node_path3.join)(config.agentContextHome, "logs", "server.log");
7512
+ }
7513
+ function openVikingServerArgs(config) {
7514
+ return ["--config", (0, import_node_path3.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
7515
+ }
7516
+ function withIdentity(config, args) {
7517
+ return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
7518
+ }
7519
+ function renderTemplate(template, config, extras = {}) {
7520
+ let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
7521
+ for (const [key, value] of Object.entries(extras)) {
7522
+ rendered = rendered.replaceAll(`{{${key}}}`, () => value);
7523
+ }
7524
+ return rendered;
7525
+ }
7526
+
7527
+ // src/index_repair.ts
7528
+ var AUTO_REPAIR_STATE_FILE = "index-auto-repair.json";
7529
+ var AUTO_REPAIR_TTL_MS = 6 * 60 * 60 * 1e3;
7530
+ var MAX_SCAN_DEPTH = 5;
7531
+ var MAX_REPAIR_TARGETS = 4;
7532
+ var MAINTENANCE_MAX_REPAIR_TARGETS = 16;
7533
+ async function repairStaleRecallIndex(config, ov, options = {}) {
7534
+ const targets = await findStaleRecallIndexTargets(config, options);
7535
+ if (targets.length === 0) {
7536
+ return { repairedUris: [], skippedRecentUris: [], warnings: [] };
7537
+ }
7538
+ const state = options.dryRun === true ? { entries: {}, version: 1 } : await readAutoRepairState(config);
7539
+ const now = Date.now();
7540
+ const repairedUris = [];
7541
+ const skippedRecentUris = [];
7542
+ const warnings = [];
7543
+ for (const target of targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS)) {
7544
+ const previous = state.entries[target.uri];
7545
+ if (previous?.signature === target.signature && now - Date.parse(previous.repairedAt) < AUTO_REPAIR_TTL_MS && options.dryRun !== true && options.ignoreBackoff !== true) {
7546
+ skippedRecentUris.push(target.uri);
7547
+ continue;
7548
+ }
7549
+ if (options.dryRun === true) {
7550
+ repairedUris.push(target.uri);
7551
+ continue;
7552
+ }
7553
+ const result = await runCommand(
7554
+ ov,
7555
+ withIdentity(config, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
7556
+ { allowFailure: true }
7557
+ );
7558
+ if (result.exitCode === 0) {
7559
+ repairedUris.push(target.uri);
7560
+ state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
7561
+ } else {
7562
+ warnings.push(indexRepairWarning(target.uri, result));
7563
+ }
7564
+ }
7565
+ if (options.dryRun !== true && repairedUris.length > 0) {
7566
+ await writeAutoRepairState(config, state);
7567
+ }
7568
+ return { repairedUris, skippedRecentUris, warnings };
7569
+ }
7570
+ async function findStaleRecallIndexTargets(config, options = {}) {
7571
+ const roots = await scanRoots(config, options);
7572
+ const byUri = /* @__PURE__ */ new Map();
7573
+ for (const root of roots) {
7574
+ if (!await exists(root.path)) {
7575
+ continue;
7576
+ }
7577
+ const sidecars = await staleSidecars(root.path, root.uri);
7578
+ if (options.collapseToRoots === true) {
7579
+ if (sidecars.length === 0) {
7580
+ continue;
7581
+ }
7582
+ const parts = sidecars.map((sidecar) => `${sidecar.relativePath}
7583
+ ${sidecar.content}`);
7584
+ byUri.set(root.uri, {
7585
+ parts,
7586
+ staleCount: sidecars.length,
7587
+ uri: root.uri
7588
+ });
7589
+ continue;
7590
+ }
7591
+ for (const sidecar of sidecars) {
7592
+ const current = byUri.get(sidecar.uri) ?? { parts: [], staleCount: 0, uri: sidecar.uri };
7593
+ current.parts.push(`${sidecar.relativePath}
7594
+ ${sidecar.content}`);
7595
+ current.staleCount += 1;
7596
+ byUri.set(sidecar.uri, current);
7597
+ }
7598
+ }
7599
+ return [...byUri.values()].map((target) => ({
7600
+ signature: sha256([target.uri, ...target.parts.sort()].join("\n---\n")),
7601
+ staleCount: target.staleCount,
7602
+ uri: target.uri
7603
+ }));
7604
+ }
7605
+ function formatRecallIndexRepairMessages(result, options = {}) {
7606
+ const messages = [];
7607
+ for (const uri of result.repairedUris) {
7608
+ messages.push(
7609
+ `${options.dryRun === true ? "Would auto-reindex stale recall scope" : "Auto-reindexed stale recall scope"}: ${uri}`
7610
+ );
7611
+ }
7612
+ for (const warning2 of result.warnings) {
7613
+ messages.push(`Auto-index repair warning: ${warning2}`);
7614
+ }
7615
+ return messages;
7616
+ }
7617
+ function filterStaleRecallSummaryRows(output2) {
7618
+ return output2.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
7619
+ }
7620
+ function isStaleRecallSummaryRow(line) {
7621
+ return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
7622
+ }
7623
+ async function scanRoots(config, options) {
7624
+ const accountRoot = (0, import_node_path4.join)(config.agentContextHome, "data", "viking", config.account);
7625
+ const roots = [
7626
+ {
7627
+ path: (0, import_node_path4.join)(accountRoot, "user", uriSegment(config.user), "memories"),
7628
+ uri: `viking://user/${uriSegment(config.user)}/memories`
7629
+ }
7630
+ ];
7631
+ const query = options.query;
7632
+ if (query) {
7633
+ const project = await inferProjectFromQuery(config.manifestPath, query);
7634
+ const projectPath = project?.uri.startsWith("viking://resources/") ? (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")) : void 0;
7635
+ if (project && projectPath) {
7636
+ roots.push({ path: projectPath, uri: project.uri });
7637
+ }
7638
+ }
7639
+ if (options.includeManifestResources === true) {
7640
+ roots.push(...await manifestResourceRoots(config, accountRoot));
7641
+ }
7642
+ const scanAgentSkills = options.includeAgentSkills === true || (query ? /\bskills?\b/.test(query.toLowerCase()) : false);
7643
+ if (scanAgentSkills) {
7644
+ roots.push({ path: (0, import_node_path4.join)(accountRoot, "resources", "agent-skills"), uri: "viking://resources/agent-skills" });
7645
+ }
7646
+ return dedupeRoots(roots);
7647
+ }
7648
+ async function manifestResourceRoots(config, accountRoot) {
7649
+ try {
7650
+ const manifest = await readSeedManifest(config.manifestPath);
7651
+ return manifest.projects.filter((project) => project.uri.startsWith("viking://resources/")).map((project) => ({
7652
+ path: (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")),
7653
+ uri: project.uri
7654
+ }));
7655
+ } catch (_err) {
7656
+ return [];
7657
+ }
7658
+ }
7659
+ function dedupeRoots(roots) {
7660
+ const seen = /* @__PURE__ */ new Set();
7661
+ const deduped = [];
7662
+ for (const root of roots) {
7663
+ if (seen.has(root.uri)) {
7664
+ continue;
7665
+ }
7666
+ seen.add(root.uri);
7667
+ deduped.push(root);
7668
+ }
7669
+ return deduped;
7670
+ }
7671
+ async function staleSidecars(rootPath, rootUri) {
7672
+ const results = [];
7673
+ async function visit(path, depth) {
7674
+ let entries;
7675
+ try {
7676
+ entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
7677
+ } catch (_err) {
7678
+ return;
7679
+ }
7680
+ for (const entry of entries) {
7681
+ const childPath = (0, import_node_path4.join)(path, entry.name);
7682
+ if (entry.isFile() && isSummarySidecar(entry.name)) {
7683
+ let content;
7684
+ try {
7685
+ content = await (0, import_promises4.readFile)(childPath, "utf8");
7686
+ } catch (_err) {
7687
+ continue;
7688
+ }
7689
+ if (!isStaleSummary(content)) {
7690
+ continue;
7691
+ }
7692
+ const parentPath = (0, import_node_path4.dirname)(childPath);
7693
+ const parentRelative = toPosixPath((0, import_node_path4.relative)(rootPath, parentPath));
7694
+ const relativePath = toPosixPath((0, import_node_path4.relative)(rootPath, childPath));
7695
+ results.push({
7696
+ content: content.trim(),
7697
+ relativePath,
7698
+ uri: parentRelative ? `${trimLocalRootUri(rootUri)}/${parentRelative}` : trimLocalRootUri(rootUri)
7699
+ });
7700
+ } else if (entry.isDirectory() && depth < MAX_SCAN_DEPTH) {
7701
+ await visit(childPath, depth + 1);
7702
+ }
7703
+ }
7704
+ }
7705
+ await visit(rootPath, 0);
7706
+ return results;
7707
+ }
7708
+ function isSummarySidecar(name) {
7709
+ return name === ".abstract.md" || name === ".overview.md";
7710
+ }
7711
+ function isStaleSummary(content) {
7712
+ return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
7713
+ }
7714
+ function trimLocalRootUri(uri) {
7715
+ return uri.endsWith("/") ? uri.slice(0, -1) : uri;
7716
+ }
7717
+ async function readAutoRepairState(config) {
7718
+ const raw = await readFileIfExists(autoRepairStatePath(config));
7719
+ if (!raw) {
7720
+ return { entries: {}, version: 1 };
7721
+ }
7722
+ try {
7723
+ const parsed = JSON.parse(raw);
7724
+ if (!isJsonObject(parsed) || parsed.version !== 1 || !isJsonObject(parsed.entries)) {
7725
+ return { entries: {}, version: 1 };
7726
+ }
7727
+ const entries = {};
7728
+ for (const [uri, entry] of Object.entries(parsed.entries)) {
7729
+ if (isJsonObject(entry) && typeof entry.signature === "string" && typeof entry.repairedAt === "string") {
7730
+ entries[uri] = { repairedAt: entry.repairedAt, signature: entry.signature };
7731
+ }
7732
+ }
7733
+ return { entries, version: 1 };
7734
+ } catch (_err) {
7735
+ return { entries: {}, version: 1 };
7736
+ }
7737
+ }
7738
+ async function writeAutoRepairState(config, state) {
7739
+ const path = autoRepairStatePath(config);
7740
+ await ensureDirectory((0, import_node_path4.dirname)(path), false);
7741
+ await (0, import_promises4.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
7742
+ `, { encoding: "utf8", mode: 384 });
7743
+ }
7744
+ function autoRepairStatePath(config) {
7745
+ return (0, import_node_path4.join)(config.agentContextHome, AUTO_REPAIR_STATE_FILE);
7746
+ }
7747
+ function indexRepairWarning(uri, result) {
7748
+ return `${uri}: ${result.stderr.trim() || result.stdout.trim() || "reindex failed"}`;
7749
+ }
7750
+
7751
+ // src/memory_hygiene.ts
7752
+ var import_node_path5 = require("node:path");
7477
7753
  var HYGIENE_SOURCES_HEADING = "## Threadnote Hygiene Sources";
7478
7754
  var STALE_HANDOFF_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
7479
7755
  function parseMemoryDocument(uri, content) {
@@ -7766,7 +8042,7 @@ function branchFromBody(body) {
7766
8042
  return branch?.split(/\s+/)[0]?.replace(/[.,;:]+$/g, "");
7767
8043
  }
7768
8044
  function topicFromUri(uri) {
7769
- const name = (0, import_node_path3.basename)(uri).replace(/\.md$/, "");
8045
+ const name = (0, import_node_path5.basename)(uri).replace(/\.md$/, "");
7770
8046
  return name.startsWith("threadnote-") ? void 0 : name;
7771
8047
  }
7772
8048
  function parseProjectFromUri(uri) {
@@ -7796,7 +8072,7 @@ function preferredKeepRecord(records, topic) {
7796
8072
  }
7797
8073
  function isStableRecord(record, topic) {
7798
8074
  const recordTopic = topic ?? topicForRecord(record);
7799
- return recordTopic !== void 0 && (0, import_node_path3.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
8075
+ return recordTopic !== void 0 && (0, import_node_path5.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
7800
8076
  }
7801
8077
  function sortedNewestFirst(records) {
7802
8078
  return [...records].sort((left, right) => {
@@ -7907,58 +8183,10 @@ function formatPlanSection(title, lines) {
7907
8183
  return [`${title}:`, ...lines.map((line) => `- ${line}`)].join("\n");
7908
8184
  }
7909
8185
 
7910
- // src/runtime.ts
7911
- var import_node_fs3 = require("node:fs");
7912
- var import_node_os3 = require("node:os");
7913
- var import_node_path4 = require("node:path");
7914
- function getRuntimeConfig(program2, manifestOverride) {
7915
- const options = program2.opts();
7916
- const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
7917
- const manifestPath = expandPath(
7918
- manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
7919
- );
7920
- return {
7921
- account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
7922
- agentContextHome: threadnoteHome,
7923
- agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
7924
- host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
7925
- manifestPath,
7926
- openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
7927
- port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
7928
- user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
7929
- };
7930
- }
7931
- function defaultManifestPath(agentContextHome) {
7932
- const userManifest = (0, import_node_path4.join)(agentContextHome, USER_MANIFEST_NAME);
7933
- return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
7934
- }
7935
- function builtInExampleManifestPath() {
7936
- return (0, import_node_path4.join)(toolRoot(), "config", "seed-manifest.example.yaml");
7937
- }
7938
- function openVikingHealthUrl(config) {
7939
- return `http://${config.host}:${config.port}/health`;
7940
- }
7941
- function openVikingLogPath(config) {
7942
- return (0, import_node_path4.join)(config.agentContextHome, "logs", "server.log");
7943
- }
7944
- function openVikingServerArgs(config) {
7945
- return ["--config", (0, import_node_path4.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
7946
- }
7947
- function withIdentity(config, args) {
7948
- return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
7949
- }
7950
- function renderTemplate(template, config, extras = {}) {
7951
- let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
7952
- for (const [key, value] of Object.entries(extras)) {
7953
- rendered = rendered.replaceAll(`{{${key}}}`, () => value);
7954
- }
7955
- return rendered;
7956
- }
7957
-
7958
8186
  // src/share.ts
7959
- var import_promises4 = require("node:fs/promises");
8187
+ var import_promises5 = require("node:fs/promises");
7960
8188
  var import_node_os4 = require("node:os");
7961
- var import_node_path5 = require("node:path");
8189
+ var import_node_path6 = require("node:path");
7962
8190
  var TEAMS_FILE_VERSION = 1;
7963
8191
  var SHARED_SEGMENT = "shared";
7964
8192
  var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
@@ -8027,8 +8255,8 @@ async function runShareInit(config, remoteUrl, options) {
8027
8255
  if (await exists(gitdir)) {
8028
8256
  throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
8029
8257
  }
8030
- await ensureDirectory((0, import_node_path5.dirname)(worktree), dryRun);
8031
- await ensureDirectory((0, import_node_path5.dirname)(gitdir), dryRun);
8258
+ await ensureDirectory((0, import_node_path6.dirname)(worktree), dryRun);
8259
+ await ensureDirectory((0, import_node_path6.dirname)(gitdir), dryRun);
8032
8260
  const git = await requiredExecutable("git");
8033
8261
  await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
8034
8262
  const newConfig = {
@@ -8059,7 +8287,7 @@ async function runShareInit(config, remoteUrl, options) {
8059
8287
  var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
8060
8288
  var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
8061
8289
  async function ensureSharedGitignore(worktree, git, push) {
8062
- const gitignorePath = (0, import_node_path5.join)(worktree, ".gitignore");
8290
+ const gitignorePath = (0, import_node_path6.join)(worktree, ".gitignore");
8063
8291
  const existing = await readFileIfExists(gitignorePath) ?? "";
8064
8292
  const lines = existing.split("\n").map((line) => line.trim());
8065
8293
  const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
@@ -8078,7 +8306,7 @@ async function ensureSharedGitignore(worktree, git, push) {
8078
8306
  segments.push(SHARED_GITIGNORE_HEADER, "\n");
8079
8307
  }
8080
8308
  segments.push(missingPatterns.join("\n"), "\n");
8081
- await (0, import_promises4.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
8309
+ await (0, import_promises5.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
8082
8310
  console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
8083
8311
  await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
8084
8312
  const commitResult = await runCommand(
@@ -8160,7 +8388,7 @@ function autoShareState(config) {
8160
8388
  return state;
8161
8389
  }
8162
8390
  function pendingReindexesPath(config) {
8163
- return (0, import_node_path5.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
8391
+ return (0, import_node_path6.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
8164
8392
  }
8165
8393
  async function loadPendingReindexes(config, state) {
8166
8394
  const raw = await readFileIfExists(pendingReindexesPath(config));
@@ -8197,18 +8425,18 @@ async function loadPendingReindexes(config, state) {
8197
8425
  async function writePendingReindexes(config, state) {
8198
8426
  const path = pendingReindexesPath(config);
8199
8427
  if (state.pendingReindexes.size === 0) {
8200
- await (0, import_promises4.rm)(path, { force: true });
8428
+ await (0, import_promises5.rm)(path, { force: true });
8201
8429
  return;
8202
8430
  }
8203
8431
  const contents = {
8204
8432
  teams: Object.fromEntries(state.pendingReindexes),
8205
8433
  version: 1
8206
8434
  };
8207
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
8435
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
8208
8436
  const tempPath = `${path}.${process.pid}.tmp`;
8209
- await (0, import_promises4.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
8437
+ await (0, import_promises5.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
8210
8438
  `, { encoding: "utf8", mode: 384 });
8211
- await (0, import_promises4.rename)(tempPath, path);
8439
+ await (0, import_promises5.rename)(tempPath, path);
8212
8440
  }
8213
8441
  async function refreshShareUpdateStateLocked(config, state, options) {
8214
8442
  const now = Date.now();
@@ -8297,7 +8525,7 @@ async function runShareSync(config, options) {
8297
8525
  if (dryRun) {
8298
8526
  console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
8299
8527
  } else if (pullResult && pullResult.exitCode !== 0) {
8300
- if (await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-apply"))) {
8528
+ if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
8301
8529
  throw new Error(
8302
8530
  `git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
8303
8531
  Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
@@ -8359,7 +8587,7 @@ async function runShareSyncQuiet(config, state, options) {
8359
8587
  const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], false);
8360
8588
  const pullResult = await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
8361
8589
  if (pullResult.exitCode !== 0) {
8362
- if (await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-apply"))) {
8590
+ if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
8363
8591
  throw new Error(
8364
8592
  `Automatic share sync hit git conflicts in ${worktree}. Resolve them in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then rerun recall/read.`
8365
8593
  );
@@ -8598,8 +8826,8 @@ async function runShareRename(config, options) {
8598
8826
  console.log(`Would write teams file: ${teamsFilePath(config)}`);
8599
8827
  return;
8600
8828
  }
8601
- await (0, import_promises4.rename)(oldTeam.config.worktree, newWorktree);
8602
- await (0, import_promises4.rename)(oldTeam.config.gitdir, newGitdir);
8829
+ await (0, import_promises5.rename)(oldTeam.config.worktree, newWorktree);
8830
+ await (0, import_promises5.rename)(oldTeam.config.gitdir, newGitdir);
8603
8831
  await writeTeamsFile(config, updatedFile);
8604
8832
  const git = await requiredExecutable("git");
8605
8833
  await runCommand(git, ["-C", newWorktree, "config", "core.worktree", newWorktree]);
@@ -8681,12 +8909,12 @@ async function preserveSharedMemoriesLocally(config, team, dryRun) {
8681
8909
  const files = await walkMemoryFiles(team.worktree);
8682
8910
  let preserved = 0;
8683
8911
  for (const file of files) {
8684
- const rel = (0, import_node_path5.relative)(team.worktree, file).split(import_node_path5.sep).join("/");
8912
+ const rel = (0, import_node_path6.relative)(team.worktree, file).split(import_node_path6.sep).join("/");
8685
8913
  if (!rel.startsWith("durable/")) {
8686
8914
  continue;
8687
8915
  }
8688
8916
  const targetUri = `viking://user/${uriSegment(config.user)}/memories/${rel}`;
8689
- const content = await (0, import_promises4.readFile)(file, "utf8");
8917
+ const content = await (0, import_promises5.readFile)(file, "utf8");
8690
8918
  if (dryRun) {
8691
8919
  console.log(`Would preserve ${rel} -> ${targetUri}`);
8692
8920
  } else {
@@ -8725,10 +8953,10 @@ function normalizeTeamName(input2) {
8725
8953
  return candidate;
8726
8954
  }
8727
8955
  function teamsFilePath(config) {
8728
- return (0, import_node_path5.join)(config.agentContextHome, "share", "teams.json");
8956
+ return (0, import_node_path6.join)(config.agentContextHome, "share", "teams.json");
8729
8957
  }
8730
8958
  function teamWorktreePath(config, team) {
8731
- return (0, import_node_path5.join)(
8959
+ return (0, import_node_path6.join)(
8732
8960
  config.agentContextHome,
8733
8961
  "data",
8734
8962
  "viking",
@@ -8741,7 +8969,7 @@ function teamWorktreePath(config, team) {
8741
8969
  );
8742
8970
  }
8743
8971
  function teamGitdirPath(config, team) {
8744
- return (0, import_node_path5.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
8972
+ return (0, import_node_path6.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
8745
8973
  }
8746
8974
  async function readTeamsFile(config) {
8747
8975
  const path = teamsFilePath(config);
@@ -8784,13 +9012,13 @@ async function readTeamsFile(config) {
8784
9012
  }
8785
9013
  async function writeTeamsFile(config, contents) {
8786
9014
  const path = teamsFilePath(config);
8787
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
9015
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
8788
9016
  const serializable = {
8789
9017
  defaultTeam: contents.defaultTeam,
8790
9018
  teams: contents.teams,
8791
9019
  version: contents.version
8792
9020
  };
8793
- await (0, import_promises4.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
9021
+ await (0, import_promises5.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
8794
9022
  `, { encoding: "utf8", mode: 384 });
8795
9023
  }
8796
9024
  async function resolveTeam(config, requested) {
@@ -8820,7 +9048,7 @@ async function assertWorktreeUsable(worktree) {
8820
9048
  if (!await isDirectory(worktree)) {
8821
9049
  throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
8822
9050
  }
8823
- const entries = await (0, import_promises4.readdir)(worktree);
9051
+ const entries = await (0, import_promises5.readdir)(worktree);
8824
9052
  if (entries.length > 0) {
8825
9053
  const preview = entries.slice(0, 5).join(", ");
8826
9054
  const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
@@ -8844,7 +9072,7 @@ async function walkMemoryFiles(root) {
8844
9072
  async function visit(path, depth) {
8845
9073
  let entries;
8846
9074
  try {
8847
- entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
9075
+ entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
8848
9076
  } catch (err) {
8849
9077
  console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
8850
9078
  return;
@@ -8853,7 +9081,7 @@ async function walkMemoryFiles(root) {
8853
9081
  if (entry.name === ".git") {
8854
9082
  continue;
8855
9083
  }
8856
- const full = (0, import_node_path5.join)(path, entry.name);
9084
+ const full = (0, import_node_path6.join)(path, entry.name);
8857
9085
  if (entry.isDirectory()) {
8858
9086
  if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
8859
9087
  continue;
@@ -8877,7 +9105,7 @@ async function walkMemoryFiles(root) {
8877
9105
  return out;
8878
9106
  }
8879
9107
  function workfileToVikingUri(config, team, filePath) {
8880
- const rel = (0, import_node_path5.relative)(team.worktree, filePath).split(import_node_path5.sep).join("/");
9108
+ const rel = (0, import_node_path6.relative)(team.worktree, filePath).split(import_node_path6.sep).join("/");
8881
9109
  return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
8882
9110
  }
8883
9111
  function isInSharedNamespace(config, uri) {
@@ -9025,13 +9253,14 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun, op
9025
9253
  }
9026
9254
  return;
9027
9255
  }
9028
- const stagingDir = await (0, import_promises4.mkdtemp)((0, import_node_path5.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
9029
- const tempPath = (0, import_node_path5.join)(stagingDir, "body.txt");
9256
+ const stagingDir = await (0, import_promises5.mkdtemp)((0, import_node_path6.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
9257
+ const tempPath = (0, import_node_path6.join)(stagingDir, "body.txt");
9030
9258
  try {
9031
- await (0, import_promises4.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
9259
+ await (0, import_promises5.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
9032
9260
  await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode, options);
9261
+ await refreshMemoryIndex(config, ov, uri, options);
9033
9262
  } finally {
9034
- await (0, import_promises4.rm)(stagingDir, { force: true, recursive: true });
9263
+ await (0, import_promises5.rm)(stagingDir, { force: true, recursive: true });
9035
9264
  }
9036
9265
  }
9037
9266
  var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
@@ -9086,6 +9315,27 @@ async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode, opti
9086
9315
  }
9087
9316
  }
9088
9317
  }
9318
+ async function refreshMemoryIndex(config, ov, uri, options = {}) {
9319
+ const result = await runCommand(
9320
+ ov,
9321
+ withIdentity(config, ["reindex", uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
9322
+ { allowFailure: true }
9323
+ );
9324
+ if (result.exitCode === 0) {
9325
+ if (options.quiet !== true && result.stdout.trim()) {
9326
+ console.log(result.stdout.trim());
9327
+ }
9328
+ if (options.quiet !== true && result.stderr.trim()) {
9329
+ console.error(result.stderr.trim());
9330
+ }
9331
+ return;
9332
+ }
9333
+ if (options.quiet !== true) {
9334
+ console.error(
9335
+ `Memory stored, but index refresh failed for ${uri}: ${result.stderr.trim() || result.stdout.trim()}`
9336
+ );
9337
+ }
9338
+ }
9089
9339
  async function waitForOvQueue(ov, config, options = {}) {
9090
9340
  const result = await runCommand(ov, withIdentity(config, ["wait", "--timeout", "120"]), { allowFailure: true });
9091
9341
  if (options.quiet !== true && result.stdout.trim()) {
@@ -9106,7 +9356,7 @@ ${stdout2}`.toLowerCase();
9106
9356
  return output2.includes("resource is busy") || output2.includes("resource is being processed");
9107
9357
  }
9108
9358
  async function ingestSingleFile(ov, config, uri, filePath, initialMode, options = {}) {
9109
- const content = await (0, import_promises4.readFile)(filePath, "utf8");
9359
+ const content = await (0, import_promises5.readFile)(filePath, "utf8");
9110
9360
  await writeMemoryFile(config, ov, uri, content, initialMode, false, options);
9111
9361
  }
9112
9362
  async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
@@ -9171,8 +9421,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
9171
9421
  const oldRel = entries[index + 1];
9172
9422
  const newRel = entries[index + 2];
9173
9423
  if (oldRel && newRel) {
9174
- changes.push({ path: (0, import_node_path5.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
9175
- changes.push({ path: (0, import_node_path5.join)(worktree, newRel), relativePath: newRel, status: "added" });
9424
+ changes.push({ path: (0, import_node_path6.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
9425
+ changes.push({ path: (0, import_node_path6.join)(worktree, newRel), relativePath: newRel, status: "added" });
9176
9426
  }
9177
9427
  index += 3;
9178
9428
  continue;
@@ -9180,7 +9430,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
9180
9430
  const rel = entries[index + 1];
9181
9431
  if (rel) {
9182
9432
  const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
9183
- changes.push({ path: (0, import_node_path5.join)(worktree, rel), relativePath: rel, status });
9433
+ changes.push({ path: (0, import_node_path6.join)(worktree, rel), relativePath: rel, status });
9184
9434
  }
9185
9435
  index += 2;
9186
9436
  }
@@ -9302,7 +9552,7 @@ async function runMigrateMemories(config, options) {
9302
9552
  const candidates = await legacyMemoryCandidates(config, sourceAccounts);
9303
9553
  const existingHashes = await existingDurableMemoryHashes(config);
9304
9554
  const ov = await openVikingCliForMode(dryRun);
9305
- const migrationPath = (0, import_node_path6.join)(config.agentContextHome, "legacy-memory-migration.txt");
9555
+ const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "legacy-memory-migration.txt");
9306
9556
  let duplicateCount = 0;
9307
9557
  let migratedCount = 0;
9308
9558
  let sensitiveCount = 0;
@@ -9338,8 +9588,8 @@ async function runMigrateMemories(config, options) {
9338
9588
  }
9339
9589
  console.log(`${dryRun ? "Would migrate" : "Migrating"} ${legacySourceLabel(candidate)} -> ${memoryUri}`);
9340
9590
  if (!dryRun) {
9341
- await (0, import_promises5.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
9342
- await (0, import_promises5.chmod)(migrationPath, 384);
9591
+ await (0, import_promises6.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
9592
+ await (0, import_promises6.chmod)(migrationPath, 384);
9343
9593
  await writeDurableMemoryFile(ov, config, memoryUri, migrationPath, "create");
9344
9594
  existingHashes.add(candidate.hash);
9345
9595
  }
@@ -9347,7 +9597,7 @@ async function runMigrateMemories(config, options) {
9347
9597
  }
9348
9598
  } finally {
9349
9599
  if (!dryRun) {
9350
- await (0, import_promises5.rm)(migrationPath, { force: true });
9600
+ await (0, import_promises6.rm)(migrationPath, { force: true });
9351
9601
  }
9352
9602
  }
9353
9603
  console.log(
@@ -9365,7 +9615,7 @@ async function runMigrateLifecycle(config, options) {
9365
9615
  const limit = options.limit ? parsePositiveInteger(options.limit, "lifecycle migration limit") : void 0;
9366
9616
  const ov = await openVikingCliForMode(dryRun);
9367
9617
  const candidates = await legacyLifecycleHandoffCandidates(config);
9368
- const migrationPath = (0, import_node_path6.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
9618
+ const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
9369
9619
  let existingCount = 0;
9370
9620
  let migratedCount = 0;
9371
9621
  let skippedCount = 0;
@@ -9386,8 +9636,8 @@ async function runMigrateLifecycle(config, options) {
9386
9636
  existingCount += 1;
9387
9637
  console.log(`Archived copy already exists; cleaning up legacy source: ${candidate.sourceUri}`);
9388
9638
  } else {
9389
- await (0, import_promises5.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
9390
- await (0, import_promises5.chmod)(migrationPath, 384);
9639
+ await (0, import_promises6.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
9640
+ await (0, import_promises6.chmod)(migrationPath, 384);
9391
9641
  await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, candidate.metadata));
9392
9642
  await writeDurableMemoryFile(ov, config, destinationUri, migrationPath, "create");
9393
9643
  }
@@ -9403,7 +9653,7 @@ async function runMigrateLifecycle(config, options) {
9403
9653
  }
9404
9654
  } finally {
9405
9655
  if (!dryRun) {
9406
- await (0, import_promises5.rm)(migrationPath, { force: true });
9656
+ await (0, import_promises6.rm)(migrationPath, { force: true });
9407
9657
  }
9408
9658
  }
9409
9659
  console.log(
@@ -9423,6 +9673,19 @@ async function runRecall(config, options) {
9423
9673
  const ov = await openVikingCliForMode(options.dryRun === true);
9424
9674
  const query = await enrichRecallQueryWithWorkspaceContext(options.query);
9425
9675
  const projectQuery = await enrichRecallQueryWithWorkspaceProjectContext(options.query);
9676
+ let indexRepairMessages;
9677
+ try {
9678
+ const indexRepair = await repairStaleRecallIndex(config, ov, {
9679
+ dryRun: options.dryRun === true,
9680
+ query: projectQuery
9681
+ });
9682
+ indexRepairMessages = formatRecallIndexRepairMessages(indexRepair, { dryRun: options.dryRun === true });
9683
+ } catch (err) {
9684
+ indexRepairMessages = [`Auto-index repair warning: ${err instanceof Error ? err.message : String(err)}`];
9685
+ }
9686
+ for (const message of indexRepairMessages) {
9687
+ console.log(message);
9688
+ }
9426
9689
  const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
9427
9690
  const args = ["search", query];
9428
9691
  if (inferredUri) {
@@ -9433,9 +9696,9 @@ async function runRecall(config, options) {
9433
9696
  args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
9434
9697
  }
9435
9698
  const recallOutputs = [];
9436
- const baseResult = await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
9437
- if (baseResult) {
9438
- recallOutputs.push([baseResult.stdout.trim(), baseResult.stderr.trim()].filter(Boolean).join("\n"));
9699
+ const baseOutput = await runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
9700
+ if (baseOutput) {
9701
+ recallOutputs.push(baseOutput);
9439
9702
  }
9440
9703
  const seededOutput = await augmentRecallWithSeededResources(
9441
9704
  config,
@@ -9474,8 +9737,20 @@ async function augmentRecallWithSeededResources(config, ov, options, inferredUri
9474
9737
  }
9475
9738
  console.log(`
9476
9739
  Also searching seeded resources: ${projectResourceUri}`);
9477
- const result = await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
9478
- return result ? [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n") : void 0;
9740
+ return runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
9741
+ }
9742
+ async function runRecallSearch(config, ov, args, options) {
9743
+ const fullArgs = withIdentity(config, args);
9744
+ console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, fullArgs)}`);
9745
+ if (options.dryRun) {
9746
+ return void 0;
9747
+ }
9748
+ const result = await runCommand(ov, fullArgs);
9749
+ const output2 = filterStaleRecallSummaryRows([result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n"));
9750
+ if (output2) {
9751
+ console.log(output2);
9752
+ }
9753
+ return output2 || void 0;
9479
9754
  }
9480
9755
  async function runRead(config, uri, options) {
9481
9756
  assertVikingUri(uri);
@@ -9541,15 +9816,15 @@ async function runCompact(config, options) {
9541
9816
  return;
9542
9817
  }
9543
9818
  const ov = await openVikingCliForMode(false);
9544
- const updatePath = (0, import_node_path6.join)(config.agentContextHome, "compact-memory-update.txt");
9819
+ const updatePath = (0, import_node_path7.join)(config.agentContextHome, "compact-memory-update.txt");
9545
9820
  try {
9546
9821
  for (const action of plan.keepUpdates) {
9547
- await (0, import_promises5.writeFile)(updatePath, action.content, { encoding: "utf8", mode: 384 });
9548
- await (0, import_promises5.chmod)(updatePath, 384);
9822
+ await (0, import_promises6.writeFile)(updatePath, action.content, { encoding: "utf8", mode: 384 });
9823
+ await (0, import_promises6.chmod)(updatePath, 384);
9549
9824
  await writeDurableMemoryFile(ov, config, action.uri, updatePath, "replace");
9550
9825
  }
9551
9826
  } finally {
9552
- await (0, import_promises5.rm)(updatePath, { force: true });
9827
+ await (0, import_promises6.rm)(updatePath, { force: true });
9553
9828
  }
9554
9829
  for (const action of plan.archives) {
9555
9830
  await runArchive(config, action.uri, {
@@ -9605,7 +9880,7 @@ async function scopedCompactRecords(config, options) {
9605
9880
  const uriDirectory = memoryUriDirectoryForCompact(config, kind, options.project);
9606
9881
  let entries;
9607
9882
  try {
9608
- entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
9883
+ entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
9609
9884
  } catch (_err) {
9610
9885
  continue;
9611
9886
  }
@@ -9613,7 +9888,7 @@ async function scopedCompactRecords(config, options) {
9613
9888
  if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
9614
9889
  continue;
9615
9890
  }
9616
- const content = await readTextIfExists((0, import_node_path6.join)(directory, entry.name));
9891
+ const content = await readTextIfExists((0, import_node_path7.join)(directory, entry.name));
9617
9892
  if (!content) {
9618
9893
  continue;
9619
9894
  }
@@ -9651,11 +9926,11 @@ function localMemoryDirectoryForCompact(config, kind, project) {
9651
9926
  const projectSegment = uriSegment(project);
9652
9927
  switch (kind) {
9653
9928
  case "durable":
9654
- return (0, import_node_path6.join)(root, "durable", "projects", projectSegment);
9929
+ return (0, import_node_path7.join)(root, "durable", "projects", projectSegment);
9655
9930
  case "handoff":
9656
- return (0, import_node_path6.join)(root, "handoffs", "active", projectSegment);
9931
+ return (0, import_node_path7.join)(root, "handoffs", "active", projectSegment);
9657
9932
  case "incident":
9658
- return (0, import_node_path6.join)(root, "incidents", "active", projectSegment);
9933
+ return (0, import_node_path7.join)(root, "incidents", "active", projectSegment);
9659
9934
  }
9660
9935
  }
9661
9936
  function memoryUriDirectoryForCompact(config, kind, project) {
@@ -9675,11 +9950,11 @@ function localMemoryPathForUri(config, uri) {
9675
9950
  if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
9676
9951
  return void 0;
9677
9952
  }
9678
- const relative4 = uri.slice(prefix.length);
9679
- if (relative4.includes("..") || relative4.startsWith("/")) {
9953
+ const relative5 = uri.slice(prefix.length);
9954
+ if (relative5.includes("..") || relative5.startsWith("/")) {
9680
9955
  return void 0;
9681
9956
  }
9682
- return (0, import_node_path6.join)(localUserMemoriesRoot(config), ...relative4.split("/"));
9957
+ return (0, import_node_path7.join)(localUserMemoriesRoot(config), ...relative5.split("/"));
9683
9958
  }
9684
9959
  async function runList(config, uri, options) {
9685
9960
  assertVikingUri(uri);
@@ -9773,7 +10048,7 @@ async function runForget(config, uri, options) {
9773
10048
  }
9774
10049
  async function runExportPack(config, options) {
9775
10050
  const ov = await openVikingCliForMode(options.dryRun === true);
9776
- const defaultPath = (0, import_node_path6.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
10051
+ const defaultPath = (0, import_node_path7.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
9777
10052
  const outputPath = expandPath(options.path ?? defaultPath);
9778
10053
  await maybeRun(options.dryRun === true, ov, withIdentity(config, ["export", options.uri ?? "viking://", outputPath]));
9779
10054
  }
@@ -9789,12 +10064,25 @@ async function runImportPack(config, options) {
9789
10064
  );
9790
10065
  }
9791
10066
  async function inferRecallUri(config, query) {
9792
- if (!/\bskills?\b/.test(query.toLowerCase())) {
10067
+ if (!hasAgentSkillCatalogIntent(query)) {
9793
10068
  return void 0;
9794
10069
  }
9795
10070
  const project = await inferProjectFromQuery(config.manifestPath, query);
9796
10071
  return project ? `viking://resources/agent-skills/repo-local-${uriSegment(project.name)}` : "viking://resources/agent-skills";
9797
10072
  }
10073
+ function hasAgentSkillCatalogIntent(query) {
10074
+ const normalized = query.toLowerCase();
10075
+ if (!/\bskills?\b/.test(normalized)) {
10076
+ return false;
10077
+ }
10078
+ if (/\bseed[- ]skills?\b/.test(normalized) || /\bskills?\s+seed(?:ing)?\b/.test(normalized)) {
10079
+ return false;
10080
+ }
10081
+ if (/^\s*skills?\s*$/.test(normalized)) {
10082
+ return true;
10083
+ }
10084
+ return /\b(find|list|show|search|recall|use|choose|select)\b.{0,48}\bskills?\b/.test(normalized) || /\bskills?\b.{0,48}\b(for|to|that|which|about)\b/.test(normalized);
10085
+ }
9798
10086
  async function printExactMemoryMatches(config, ov, query, options) {
9799
10087
  const terms = exactRecallTerms(query);
9800
10088
  if (terms.length === 0) {
@@ -9833,7 +10121,7 @@ async function storeMemory(config, options) {
9833
10121
  await storeSharedMemoryReplacement(config, ov, options, options.replaceUri);
9834
10122
  return;
9835
10123
  }
9836
- const memoryPath = (0, import_node_path6.join)(config.agentContextHome, "last-memory.txt");
10124
+ const memoryPath = (0, import_node_path7.join)(config.agentContextHome, "last-memory.txt");
9837
10125
  const candidateMetadata = { ...options.metadata, supersedes: options.replaceUri };
9838
10126
  const candidateMemory = formatMemoryDocument2(options.title, candidateMetadata, options.bodyText);
9839
10127
  const memoryUri = memoryUriFor(config, candidateMemory, candidateMetadata);
@@ -9865,8 +10153,8 @@ async function storeMemory(config, options) {
9865
10153
  }
9866
10154
  return;
9867
10155
  }
9868
- await (0, import_promises5.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
9869
- await (0, import_promises5.chmod)(memoryPath, 384);
10156
+ await (0, import_promises6.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
10157
+ await (0, import_promises6.chmod)(memoryPath, 384);
9870
10158
  await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, finalMetadata));
9871
10159
  await writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode);
9872
10160
  console.log(`Stored memory: ${memoryUri}`);
@@ -9925,7 +10213,7 @@ async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
9925
10213
  console.log(`Updated shared memory: ${targetUri}`);
9926
10214
  }
9927
10215
  async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode) {
9928
- const content = await (0, import_promises5.readFile)(memoryPath, "utf8");
10216
+ const content = await (0, import_promises6.readFile)(memoryPath, "utf8");
9929
10217
  await writeMemoryFile(config, ov, memoryUri, content, writeMode, false);
9930
10218
  }
9931
10219
  async function removeVikingResourceWithRetry(ov, config, uri) {
@@ -9982,10 +10270,10 @@ async function hasLegacyLifecycleHandoffCandidates(config) {
9982
10270
  return (await legacyLifecycleHandoffCandidates(config, 1)).length > 0;
9983
10271
  }
9984
10272
  async function legacyLifecycleHandoffCandidates(config, limit) {
9985
- const eventsRoot = (0, import_node_path6.join)(localUserMemoriesRoot(config), "events");
10273
+ const eventsRoot = (0, import_node_path7.join)(localUserMemoriesRoot(config), "events");
9986
10274
  let entries;
9987
10275
  try {
9988
- entries = await (0, import_promises5.readdir)(eventsRoot, { withFileTypes: true });
10276
+ entries = await (0, import_promises6.readdir)(eventsRoot, { withFileTypes: true });
9989
10277
  } catch (_err) {
9990
10278
  return [];
9991
10279
  }
@@ -9994,7 +10282,7 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
9994
10282
  if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
9995
10283
  continue;
9996
10284
  }
9997
- const sourcePath = (0, import_node_path6.join)(eventsRoot, entry.name);
10285
+ const sourcePath = (0, import_node_path7.join)(eventsRoot, entry.name);
9998
10286
  const original = await readTextIfExists(sourcePath);
9999
10287
  if (!original || !isClearLegacyHandoffMemory(original) || sensitiveMemoryReason(original)) {
10000
10288
  continue;
@@ -10135,7 +10423,7 @@ function inferLegacyProject(memory) {
10135
10423
  return "general";
10136
10424
  }
10137
10425
  const trimmed = explicit.trim().replace(/[`.,;]+$/g, "");
10138
- return trimmed.includes("/") ? (0, import_node_path6.basename)(trimmed) : trimmed;
10426
+ return trimmed.includes("/") ? (0, import_node_path7.basename)(trimmed) : trimmed;
10139
10427
  }
10140
10428
  function parseOptionalMemoryKind2(value) {
10141
10429
  if (!value) {
@@ -10175,14 +10463,14 @@ async function legacySourceAccounts(config, options) {
10175
10463
  async function legacyMemoryCandidates(config, sourceAccounts) {
10176
10464
  const candidates = [];
10177
10465
  for (const sourceAccount of sourceAccounts) {
10178
- const sessionRoot = (0, import_node_path6.join)(localVikingDataRoot(config), sourceAccount, "session");
10466
+ const sessionRoot = (0, import_node_path7.join)(localVikingDataRoot(config), sourceAccount, "session");
10179
10467
  for (const sourceSession of await childDirectoryNames(sessionRoot)) {
10180
- const historyRoot = (0, import_node_path6.join)(sessionRoot, sourceSession, "history");
10468
+ const historyRoot = (0, import_node_path7.join)(sessionRoot, sourceSession, "history");
10181
10469
  for (const sourceArchive of await childDirectoryNames(historyRoot)) {
10182
10470
  if (!sourceArchive.startsWith("archive_")) {
10183
10471
  continue;
10184
10472
  }
10185
- const sourcePath = (0, import_node_path6.join)(historyRoot, sourceArchive, "messages.jsonl");
10473
+ const sourcePath = (0, import_node_path7.join)(historyRoot, sourceArchive, "messages.jsonl");
10186
10474
  for (const text of await legacyMemoryTexts(sourcePath)) {
10187
10475
  candidates.push({
10188
10476
  comparableHash: sha256(comparableMemoryText(text)),
@@ -10245,12 +10533,12 @@ async function existingDurableMemoryHashes(config) {
10245
10533
  async function collectDurableMemoryHashes(root, hashes) {
10246
10534
  let entries;
10247
10535
  try {
10248
- entries = await (0, import_promises5.readdir)(root, { withFileTypes: true });
10536
+ entries = await (0, import_promises6.readdir)(root, { withFileTypes: true });
10249
10537
  } catch (_err) {
10250
10538
  return;
10251
10539
  }
10252
10540
  for (const entry of entries) {
10253
- const path = (0, import_node_path6.join)(root, entry.name);
10541
+ const path = (0, import_node_path7.join)(root, entry.name);
10254
10542
  if (entry.isDirectory()) {
10255
10543
  await collectDurableMemoryHashes(path, hashes);
10256
10544
  continue;
@@ -10267,12 +10555,12 @@ async function collectDurableMemoryHashes(root, hashes) {
10267
10555
  }
10268
10556
  }
10269
10557
  function isDurableMemoryPath(path) {
10270
- return path.split(import_node_path6.sep).includes("memories");
10558
+ return path.split(import_node_path7.sep).includes("memories");
10271
10559
  }
10272
10560
  async function childDirectoryNames(path) {
10273
10561
  let entries;
10274
10562
  try {
10275
- entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
10563
+ entries = await (0, import_promises6.readdir)(path, { withFileTypes: true });
10276
10564
  } catch (_err) {
10277
10565
  return [];
10278
10566
  }
@@ -10280,11 +10568,11 @@ async function childDirectoryNames(path) {
10280
10568
  }
10281
10569
  async function readTextIfExists(path) {
10282
10570
  try {
10283
- const pathStat = await (0, import_promises5.stat)(path);
10571
+ const pathStat = await (0, import_promises6.stat)(path);
10284
10572
  if (!pathStat.isFile()) {
10285
10573
  return void 0;
10286
10574
  }
10287
- return await (0, import_promises5.readFile)(path, "utf8");
10575
+ return await (0, import_promises6.readFile)(path, "utf8");
10288
10576
  } catch (_err) {
10289
10577
  return void 0;
10290
10578
  }
@@ -10311,10 +10599,10 @@ function legacySourceLabel(candidate) {
10311
10599
  return `${candidate.sourceAccount}/${candidate.sourceSession}/${candidate.sourceArchive}`;
10312
10600
  }
10313
10601
  function localVikingDataRoot(config) {
10314
- return (0, import_node_path6.join)(config.agentContextHome, "data", "viking");
10602
+ return (0, import_node_path7.join)(config.agentContextHome, "data", "viking");
10315
10603
  }
10316
10604
  function localUserMemoriesRoot(config) {
10317
- return (0, import_node_path6.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
10605
+ return (0, import_node_path7.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
10318
10606
  }
10319
10607
  function uniqueStrings(values) {
10320
10608
  return [...new Set(values)].sort();
@@ -10330,7 +10618,7 @@ async function buildHandoff(options) {
10330
10618
  const status = await gitValue(["status", "--short"], repoRoot) ?? "";
10331
10619
  const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
10332
10620
  const touchedFiles = await gitTouchedFiles(repoRoot);
10333
- const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path6.basename)(repoRoot);
10621
+ const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path7.basename)(repoRoot);
10334
10622
  const topicBranch = branch && branch !== "unknown" ? branch : "current";
10335
10623
  const metadata = {
10336
10624
  kind: "handoff",
@@ -10390,8 +10678,8 @@ function formatBlock(value, emptyValue) {
10390
10678
 
10391
10679
  // src/update-check.ts
10392
10680
  var import_node_child_process2 = require("node:child_process");
10393
- var import_promises6 = require("node:fs/promises");
10394
- var import_node_path7 = require("node:path");
10681
+ var import_promises7 = require("node:fs/promises");
10682
+ var import_node_path8 = require("node:path");
10395
10683
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
10396
10684
  var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
10397
10685
  var FETCH_TIMEOUT_MS = 3e3;
@@ -10458,7 +10746,7 @@ function isCacheFresh(cache) {
10458
10746
  }
10459
10747
  async function readUpdateCache(cachePath) {
10460
10748
  try {
10461
- const raw = await (0, import_promises6.readFile)(cachePath, "utf8");
10749
+ const raw = await (0, import_promises7.readFile)(cachePath, "utf8");
10462
10750
  const parsed = JSON.parse(raw);
10463
10751
  if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
10464
10752
  return void 0;
@@ -10470,8 +10758,8 @@ async function readUpdateCache(cachePath) {
10470
10758
  }
10471
10759
  async function writeUpdateCache(cachePath, contents) {
10472
10760
  try {
10473
- await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(cachePath), { recursive: true });
10474
- await (0, import_promises6.writeFile)(cachePath, `${JSON.stringify(contents)}
10761
+ await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(cachePath), { recursive: true });
10762
+ await (0, import_promises7.writeFile)(cachePath, `${JSON.stringify(contents)}
10475
10763
  `, { encoding: "utf8", mode: 384 });
10476
10764
  } catch {
10477
10765
  }
@@ -10494,14 +10782,14 @@ async function fetchLatestVersion() {
10494
10782
 
10495
10783
  // src/version.ts
10496
10784
  var import_node_fs4 = require("node:fs");
10497
- var import_node_path8 = require("node:path");
10785
+ var import_node_path9 = require("node:path");
10498
10786
  var cachedVersion;
10499
10787
  function getThreadnoteVersion() {
10500
10788
  if (cachedVersion !== void 0) {
10501
10789
  return cachedVersion;
10502
10790
  }
10503
10791
  try {
10504
- const packageJsonPath = (0, import_node_path8.join)(__dirname, "..", "package.json");
10792
+ const packageJsonPath = (0, import_node_path9.join)(__dirname, "..", "package.json");
10505
10793
  const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
10506
10794
  cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
10507
10795
  } catch {
@@ -10543,7 +10831,7 @@ async function runHooksInstall(config, agent, options) {
10543
10831
  }
10544
10832
  async function runClaudeHooksInstall(options) {
10545
10833
  const path = expandPath(CLAUDE_SETTINGS_PATH);
10546
- const existingRaw = await exists(path) ? await (0, import_promises7.readFile)(path, "utf8") : "{}";
10834
+ const existingRaw = await exists(path) ? await (0, import_promises8.readFile)(path, "utf8") : "{}";
10547
10835
  const parsed = parseJsonConfigObject(existingRaw) ?? {};
10548
10836
  const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
10549
10837
  const before = JSON.stringify(parsed);
@@ -10560,11 +10848,11 @@ async function runClaudeHooksInstall(options) {
10560
10848
  console.log("\nRe-run with --apply to actually modify the file.");
10561
10849
  return;
10562
10850
  }
10563
- await (0, import_promises7.mkdir)((0, import_node_path9.dirname)(path), { recursive: true });
10851
+ await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(path), { recursive: true });
10564
10852
  const serialized = `${JSON.stringify(next, void 0, 2)}
10565
10853
  `;
10566
- await (0, import_promises7.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
10567
- await (0, import_promises7.chmod)(path, 384);
10854
+ await (0, import_promises8.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
10855
+ await (0, import_promises8.chmod)(path, 384);
10568
10856
  console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
10569
10857
  }
10570
10858
  function withThreadnoteHooks(input2) {
@@ -10642,7 +10930,7 @@ async function hasManagedClaudeHooks() {
10642
10930
  if (!await exists(path)) {
10643
10931
  return false;
10644
10932
  }
10645
- const raw = await (0, import_promises7.readFile)(path, "utf8");
10933
+ const raw = await (0, import_promises8.readFile)(path, "utf8");
10646
10934
  const parsed = parseJsonConfigObject(raw);
10647
10935
  if (!parsed || !isJsonObject(parsed.hooks)) {
10648
10936
  return false;
@@ -10704,7 +10992,7 @@ async function runSessionStartHook(config, options = {}) {
10704
10992
  async function emitUpdateBannerIfOutdated(config) {
10705
10993
  try {
10706
10994
  const result = await checkForThreadnoteUpdate({
10707
- cachePath: (0, import_node_path9.join)(config.agentContextHome, ".update-state.json"),
10995
+ cachePath: (0, import_node_path10.join)(config.agentContextHome, ".update-state.json"),
10708
10996
  currentVersion: getThreadnoteVersion()
10709
10997
  });
10710
10998
  if (!result || !result.outdated) {
@@ -10729,13 +11017,13 @@ async function emitUpdateBannerIfOutdated(config) {
10729
11017
  }
10730
11018
 
10731
11019
  // src/seeding.ts
10732
- var import_promises8 = require("node:fs/promises");
10733
- var import_node_path10 = require("node:path");
11020
+ var import_promises9 = require("node:fs/promises");
11021
+ var import_node_path11 = require("node:path");
10734
11022
  async function runSeed(config, options) {
10735
11023
  const manifest = await readSeedManifest(config.manifestPath);
10736
11024
  const ignorePatterns = await loadIgnorePatterns();
10737
11025
  const ov = await openVikingCliForMode(options.dryRun === true);
10738
- const statePath = (0, import_node_path10.join)(config.agentContextHome, SEED_STATE_FILE);
11026
+ const statePath = (0, import_node_path11.join)(config.agentContextHome, SEED_STATE_FILE);
10739
11027
  const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
10740
11028
  const projects = filterProjects(manifest.projects, options.only);
10741
11029
  let importedCount = 0;
@@ -10760,7 +11048,15 @@ async function runSeed(config, options) {
10760
11048
  skippedCount += 1;
10761
11049
  continue;
10762
11050
  }
10763
- const args = withIdentity(config, ["add-resource", importPath, "--to", candidate.destinationUri, "--wait"]);
11051
+ const args = withIdentity(config, [
11052
+ "add-resource",
11053
+ importPath,
11054
+ "--to",
11055
+ candidate.destinationUri,
11056
+ "--reason",
11057
+ seedResourceReason(candidate),
11058
+ "--wait"
11059
+ ]);
10764
11060
  await maybeRun(options.dryRun === true, ov, args);
10765
11061
  importedCount += 1;
10766
11062
  if (fileStat && options.dryRun !== true) {
@@ -10790,7 +11086,7 @@ function filterProjects(projects, only) {
10790
11086
  }
10791
11087
  async function statSeedFile(path) {
10792
11088
  try {
10793
- const result = await (0, import_promises8.stat)(path);
11089
+ const result = await (0, import_promises9.stat)(path);
10794
11090
  return { mtimeMs: result.mtimeMs, size: result.size };
10795
11091
  } catch (_err) {
10796
11092
  return void 0;
@@ -10798,7 +11094,7 @@ async function statSeedFile(path) {
10798
11094
  }
10799
11095
  async function readSeedState(path) {
10800
11096
  try {
10801
- const raw = await (0, import_promises8.readFile)(path, "utf8");
11097
+ const raw = await (0, import_promises9.readFile)(path, "utf8");
10802
11098
  const parsed = JSON.parse(raw);
10803
11099
  if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
10804
11100
  return { files: {}, version: 1 };
@@ -10815,13 +11111,13 @@ async function readSeedState(path) {
10815
11111
  }
10816
11112
  }
10817
11113
  async function writeSeedState(path, state) {
10818
- await ensureDirectory((0, import_node_path10.dirname)(path), false);
10819
- await (0, import_promises8.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
11114
+ await ensureDirectory((0, import_node_path11.dirname)(path), false);
11115
+ await (0, import_promises9.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
10820
11116
  `, { encoding: "utf8", mode: 384 });
10821
11117
  }
10822
11118
  async function runInitManifest(config, options) {
10823
11119
  const manifestPath = expandPath(
10824
- options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path10.join)(config.agentContextHome, USER_MANIFEST_NAME)
11120
+ options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path11.join)(config.agentContextHome, USER_MANIFEST_NAME)
10825
11121
  );
10826
11122
  const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
10827
11123
  const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
@@ -10864,9 +11160,9 @@ async function runInitManifest(config, options) {
10864
11160
  console.log(output2.trimEnd());
10865
11161
  return;
10866
11162
  }
10867
- await ensureDirectory((0, import_node_path10.dirname)(manifestPath), false);
10868
- await (0, import_promises8.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
10869
- await (0, import_promises8.chmod)(manifestPath, 384);
11163
+ await ensureDirectory((0, import_node_path11.dirname)(manifestPath), false);
11164
+ await (0, import_promises9.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
11165
+ await (0, import_promises9.chmod)(manifestPath, 384);
10870
11166
  console.log(`Wrote manifest: ${manifestPath}`);
10871
11167
  console.log("Seed with:");
10872
11168
  console.log(" threadnote seed --dry-run");
@@ -10874,17 +11170,33 @@ async function runInitManifest(config, options) {
10874
11170
  }
10875
11171
  async function runSeedSkills(config, options) {
10876
11172
  const ov = await openVikingCliForMode(options.dryRun === true);
10877
- const skills = await collectSkillCandidates(config);
11173
+ const catalogItems = await collectSkillCandidates(config);
10878
11174
  const nativeMode = options.native === true;
10879
11175
  console.log(
10880
11176
  nativeMode ? "Skill seed mode: native OpenViking skills. This requires a working VLM config." : "Skill seed mode: resource catalog. Use --native only after configuring a working VLM provider."
10881
11177
  );
10882
- for (const skill of skills) {
10883
- console.log(`Skill ${skill.source}: ${skill.filePath}`);
10884
- const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : ["add-resource", skill.filePath, "--to", skillResourceUri(skill), "--wait"];
11178
+ let skippedCount = 0;
11179
+ for (const skill of catalogItems) {
11180
+ console.log(`${skill.kind === "command" ? "Command" : "Skill"} ${skill.source}: ${skill.filePath}`);
11181
+ if (nativeMode && skill.kind === "command") {
11182
+ skippedCount += 1;
11183
+ console.log(`SKIP command in native skill mode: ${skill.filePath}`);
11184
+ continue;
11185
+ }
11186
+ const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : [
11187
+ "add-resource",
11188
+ skill.filePath,
11189
+ "--to",
11190
+ skillResourceUri(skill),
11191
+ "--reason",
11192
+ skillResourceReason(skill),
11193
+ "--wait"
11194
+ ];
10885
11195
  await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
10886
11196
  }
10887
- console.log(`Skill seed complete: ${skills.length} unique skill(s).`);
11197
+ console.log(
11198
+ `Skill seed complete: ${catalogItems.length - skippedCount} unique catalog item(s)${skippedCount > 0 ? `, ${skippedCount} skipped` : ""}.`
11199
+ );
10888
11200
  }
10889
11201
  async function resolveRepoRoot(repoInput) {
10890
11202
  const inputPath = expandPath(repoInput);
@@ -10896,13 +11208,13 @@ async function resolveRepoRoot(repoInput) {
10896
11208
  async function projectIdentity(path) {
10897
11209
  const expanded = expandPath(path);
10898
11210
  try {
10899
- return await (0, import_promises8.realpath)(expanded);
11211
+ return await (0, import_promises9.realpath)(expanded);
10900
11212
  } catch (_err) {
10901
11213
  return expanded;
10902
11214
  }
10903
11215
  }
10904
11216
  function projectManifestForRepo(repoRoot, existingProjects) {
10905
- const baseName = uriSegment((0, import_node_path10.basename)(repoRoot));
11217
+ const baseName = uriSegment((0, import_node_path11.basename)(repoRoot));
10906
11218
  const usedNames = new Set(existingProjects.map((project) => project.name));
10907
11219
  const usedUris = new Set(existingProjects.map((project) => project.uri));
10908
11220
  let name = baseName;
@@ -10924,7 +11236,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
10924
11236
  for (const pattern of project.seed) {
10925
11237
  const files = await resolveProjectPattern(projectRoot, pattern);
10926
11238
  for (const filePath of files) {
10927
- const relativePath = toPosixPath((0, import_node_path10.relative)(projectRoot, filePath));
11239
+ const relativePath = toPosixPath((0, import_node_path11.relative)(projectRoot, filePath));
10928
11240
  if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
10929
11241
  continue;
10930
11242
  }
@@ -10942,20 +11254,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
10942
11254
  async function resolveProjectPattern(projectRoot, pattern) {
10943
11255
  const normalizedPattern = toPosixPath(pattern);
10944
11256
  if (!hasGlob(normalizedPattern)) {
10945
- const filePath = (0, import_node_path10.join)(projectRoot, normalizedPattern);
11257
+ const filePath = (0, import_node_path11.join)(projectRoot, normalizedPattern);
10946
11258
  return await isFile(filePath) ? [filePath] : [];
10947
11259
  }
10948
11260
  const globBase = getGlobBase(normalizedPattern);
10949
- const basePath = (0, import_node_path10.join)(projectRoot, globBase);
11261
+ const basePath = (0, import_node_path11.join)(projectRoot, globBase);
10950
11262
  if (!await exists(basePath)) {
10951
11263
  return [];
10952
11264
  }
10953
11265
  const regex = globToRegExp(normalizedPattern);
10954
11266
  const files = await walkFiles(basePath);
10955
- return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path10.relative)(projectRoot, filePath))));
11267
+ return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path11.relative)(projectRoot, filePath))));
10956
11268
  }
10957
11269
  async function prepareSeedFile(config, candidate, dryRun) {
10958
- const content = await (0, import_promises8.readFile)(candidate.filePath, "utf8");
11270
+ const content = await (0, import_promises9.readFile)(candidate.filePath, "utf8");
10959
11271
  const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
10960
11272
  const secretMatches = detectSecretMatches(redactedContent);
10961
11273
  if (secretMatches.length > 0) {
@@ -10967,39 +11279,54 @@ async function prepareSeedFile(config, candidate, dryRun) {
10967
11279
  if (redactedContent === content) {
10968
11280
  return candidate.filePath;
10969
11281
  }
10970
- const redactedPath = (0, import_node_path10.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
11282
+ const redactedPath = (0, import_node_path11.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
10971
11283
  if (dryRun) {
10972
11284
  console.log(`Would write redacted copy: ${redactedPath}`);
10973
11285
  return redactedPath;
10974
11286
  }
10975
- await ensureDirectory((0, import_node_path10.dirname)(redactedPath), false);
10976
- await (0, import_promises8.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
10977
- await (0, import_promises8.chmod)(redactedPath, 384);
11287
+ await ensureDirectory((0, import_node_path11.dirname)(redactedPath), false);
11288
+ await (0, import_promises9.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
11289
+ await (0, import_promises9.chmod)(redactedPath, 384);
10978
11290
  return redactedPath;
10979
11291
  }
11292
+ function seedResourceReason(candidate) {
11293
+ return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
11294
+ }
11295
+ function skillResourceReason(skill) {
11296
+ return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path11.basename)(
11297
+ skill.filePath
11298
+ )}`;
11299
+ }
10980
11300
  async function collectSkillCandidates(config) {
10981
11301
  const sources = [
10982
- { pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
10983
- { pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
10984
- { pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" }
11302
+ { kind: "skill", pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
11303
+ { kind: "skill", pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
11304
+ { kind: "skill", pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" },
11305
+ { kind: "command", pattern: "~/.claude/commands/**/*.md", source: "claude-commands-global" }
10985
11306
  ];
10986
11307
  try {
10987
11308
  const manifest = await readSeedManifest(config.manifestPath);
10988
11309
  for (const project of manifest.projects) {
10989
11310
  sources.push({
11311
+ kind: "skill",
10990
11312
  pattern: `${project.path}/.claude/skills/**/SKILL.md`,
10991
11313
  source: `repo-local:${project.name}`
10992
11314
  });
11315
+ sources.push({
11316
+ kind: "command",
11317
+ pattern: `${project.path}/.claude/commands/**/*.md`,
11318
+ source: `repo-local:${project.name}:claude-commands`
11319
+ });
10993
11320
  }
10994
11321
  } catch (err) {
10995
- console.log(`WARN cannot read manifest for repo-local skill discovery: ${errorMessage(err)}`);
11322
+ console.log(`WARN cannot read manifest for repo-local skill/command discovery: ${errorMessage(err)}`);
10996
11323
  }
10997
11324
  const seenHashes = /* @__PURE__ */ new Set();
10998
11325
  const skills = [];
10999
11326
  for (const source of sources) {
11000
11327
  const files = await resolveAbsolutePattern(expandPath(source.pattern));
11001
11328
  for (const filePath of files) {
11002
- const content = await (0, import_promises8.readFile)(filePath, "utf8");
11329
+ const content = await (0, import_promises9.readFile)(filePath, "utf8");
11003
11330
  const matches = detectSecretMatches(content);
11004
11331
  if (matches.length > 0) {
11005
11332
  console.log(`SKIP skill with possible secret: ${filePath}`);
@@ -11010,7 +11337,7 @@ async function collectSkillCandidates(config) {
11010
11337
  continue;
11011
11338
  }
11012
11339
  seenHashes.add(hash);
11013
- skills.push({ filePath, hash, source: source.source });
11340
+ skills.push({ filePath, hash, kind: source.kind, source: source.source });
11014
11341
  }
11015
11342
  }
11016
11343
  return skills;
@@ -11030,10 +11357,19 @@ async function resolveAbsolutePattern(pattern) {
11030
11357
  return files.filter((filePath) => regex.test(toPosixPath(filePath)));
11031
11358
  }
11032
11359
  function skillResourceUri(skill) {
11033
- return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0, import_node_path10.basename)((0, import_node_path10.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
11360
+ return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
11361
+ }
11362
+ function skillResourceName(skill) {
11363
+ const fileName = (0, import_node_path11.basename)(skill.filePath);
11364
+ if (fileName.toLowerCase() === "skill.md") {
11365
+ return uriSegment((0, import_node_path11.basename)((0, import_node_path11.dirname)(skill.filePath)));
11366
+ }
11367
+ const extensionIndex = fileName.lastIndexOf(".");
11368
+ const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
11369
+ return uriSegment(stem);
11034
11370
  }
11035
11371
  async function loadIgnorePatterns() {
11036
- const raw = await (0, import_promises8.readFile)((0, import_node_path10.join)(toolRoot(), ".threadnoteignore"), "utf8");
11372
+ const raw = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), ".threadnoteignore"), "utf8");
11037
11373
  return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
11038
11374
  }
11039
11375
  function matchesIgnore(relativePath, patterns) {
@@ -11106,18 +11442,18 @@ function detectSecretMatches(content) {
11106
11442
  // src/lifecycle.ts
11107
11443
  var import_node_child_process3 = require("node:child_process");
11108
11444
  var import_node_fs6 = require("node:fs");
11109
- var import_promises11 = require("node:fs/promises");
11445
+ var import_promises12 = require("node:fs/promises");
11110
11446
  var import_node_os6 = require("node:os");
11111
- var import_node_path12 = require("node:path");
11447
+ var import_node_path13 = require("node:path");
11112
11448
  var import_node_process3 = require("node:process");
11113
- var import_promises12 = require("node:readline/promises");
11449
+ var import_promises13 = require("node:readline/promises");
11114
11450
 
11115
11451
  // src/update.ts
11116
11452
  var import_node_fs5 = require("node:fs");
11117
- var import_promises9 = require("node:fs/promises");
11453
+ var import_promises10 = require("node:fs/promises");
11118
11454
  var import_node_os5 = require("node:os");
11119
- var import_node_path11 = require("node:path");
11120
- var import_promises10 = require("node:readline/promises");
11455
+ var import_node_path12 = require("node:path");
11456
+ var import_promises11 = require("node:readline/promises");
11121
11457
  var import_node_process2 = require("node:process");
11122
11458
 
11123
11459
  // src/release_notes.ts
@@ -11388,7 +11724,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
11388
11724
  }
11389
11725
  console.log("");
11390
11726
  console.log(`Upgrading OpenViking ${installedVersion} -> ${pinned} (pinned by Threadnote).`);
11391
- console.log("Picks up upstream fixes for share-sync reliability (reindex lock acquisition, ov wait timeout).");
11727
+ console.log("Picks up upstream CLI, resource-ingestion, and index reliability fixes.");
11392
11728
  const wasRunning = await isOpenVikingHealthy(config);
11393
11729
  const usingLaunchd = await isLaunchAgentInstalled();
11394
11730
  const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
@@ -11449,14 +11785,14 @@ async function waitForOpenVikingHealthy(config, timeoutMs) {
11449
11785
  return isOpenVikingHealthy(config);
11450
11786
  }
11451
11787
  function launchAgentPlistPath() {
11452
- return (0, import_node_path11.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
11788
+ return (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
11453
11789
  }
11454
11790
  async function isLaunchAgentInstalled() {
11455
11791
  if (process.platform !== "darwin") {
11456
11792
  return false;
11457
11793
  }
11458
11794
  try {
11459
- await (0, import_promises9.access)(launchAgentPlistPath(), import_node_fs5.constants.F_OK);
11795
+ await (0, import_promises10.access)(launchAgentPlistPath(), import_node_fs5.constants.F_OK);
11460
11796
  return true;
11461
11797
  } catch (_err) {
11462
11798
  return false;
@@ -11485,7 +11821,7 @@ async function getUpdateInfo(config, options) {
11485
11821
  };
11486
11822
  }
11487
11823
  async function currentPackageVersion() {
11488
- const rawPackage = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), "package.json"), "utf8");
11824
+ const rawPackage = await (0, import_promises10.readFile)((0, import_node_path12.join)(toolRoot(), "package.json"), "utf8");
11489
11825
  const parsed = JSON.parse(rawPackage);
11490
11826
  if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
11491
11827
  throw new Error("Could not read current threadnote package version.");
@@ -11535,11 +11871,11 @@ async function readFreshCache(config, registry) {
11535
11871
  }
11536
11872
  async function writeUpdateCache2(config, cache) {
11537
11873
  await ensureDirectory(config.agentContextHome, false);
11538
- await (0, import_promises9.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
11874
+ await (0, import_promises10.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
11539
11875
  `, { encoding: "utf8", mode: 384 });
11540
11876
  }
11541
11877
  function updateCachePath(config) {
11542
- return (0, import_node_path11.join)(config.agentContextHome, "update-check.json");
11878
+ return (0, import_node_path12.join)(config.agentContextHome, "update-check.json");
11543
11879
  }
11544
11880
  async function runApplicablePostUpdateMigrations(config, options) {
11545
11881
  const state = await readPostUpdateState(config);
@@ -11603,7 +11939,7 @@ async function applicablePostUpdateMigrations(config, options) {
11603
11939
  return applicable;
11604
11940
  }
11605
11941
  async function readPostUpdateMigrations() {
11606
- const raw = await readFileIfExists((0, import_node_path11.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
11942
+ const raw = await readFileIfExists((0, import_node_path12.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
11607
11943
  if (!raw) {
11608
11944
  return [];
11609
11945
  }
@@ -11642,7 +11978,7 @@ function printPostUpdateMigration(migration) {
11642
11978
  }
11643
11979
  }
11644
11980
  async function confirmPostUpdateMigration(prompt, defaultYes = false) {
11645
- const readline = (0, import_promises10.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
11981
+ const readline = (0, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
11646
11982
  try {
11647
11983
  const answer = (await readline.question(prompt)).trim().toLowerCase();
11648
11984
  if (answer === "") {
@@ -11677,11 +12013,11 @@ async function readPostUpdateState(config) {
11677
12013
  }
11678
12014
  async function writePostUpdateState(config, state) {
11679
12015
  await ensureDirectory(config.agentContextHome, false);
11680
- await (0, import_promises9.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
12016
+ await (0, import_promises10.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
11681
12017
  `, { encoding: "utf8", mode: 384 });
11682
12018
  }
11683
12019
  function postUpdateStatePath(config) {
11684
- return (0, import_node_path11.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
12020
+ return (0, import_node_path12.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
11685
12021
  }
11686
12022
  async function resolveUpdateRuntime(runtime) {
11687
12023
  if (runtime !== "auto") {
@@ -11711,14 +12047,14 @@ async function runtimeThreadnoteBin(runtime) {
11711
12047
  if (runtime === "npm") {
11712
12048
  const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
11713
12049
  const prefix = result.stdout.trim();
11714
- return prefix ? (0, import_node_path11.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
12050
+ return prefix ? (0, import_node_path12.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
11715
12051
  }
11716
12052
  if (runtime === "bun") {
11717
12053
  const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
11718
12054
  const binDir = result.stdout.trim();
11719
- return binDir ? (0, import_node_path11.join)(binDir, NPM_PACKAGE_NAME) : void 0;
12055
+ return binDir ? (0, import_node_path12.join)(binDir, NPM_PACKAGE_NAME) : void 0;
11720
12056
  }
11721
- return (0, import_node_path11.join)(process.env.DENO_INSTALL ?? (0, import_node_path11.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
12057
+ return (0, import_node_path12.join)(process.env.DENO_INSTALL ?? (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
11722
12058
  }
11723
12059
  function updatePackageCommand(runtime, registry) {
11724
12060
  if (runtime === "npm") {
@@ -11786,9 +12122,10 @@ async function collectDoctorChecks(config, options = {}) {
11786
12122
  checks.push(await commandShimCheck());
11787
12123
  checks.push(...await userAgentInstructionsChecks());
11788
12124
  checks.push(await manifestCheck(config.manifestPath));
11789
- checks.push(await fileCheck((0, import_node_path12.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
11790
- checks.push(await fileCheck((0, import_node_path12.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
11791
- checks.push(await fileCheck((0, import_node_path12.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
12125
+ checks.push(await recallIndexFreshnessCheck(config));
12126
+ checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
12127
+ checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
12128
+ checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
11792
12129
  checks.push(await healthCheck(config));
11793
12130
  return checks;
11794
12131
  }
@@ -11796,9 +12133,9 @@ async function runInstall(config, options) {
11796
12133
  const repairInvalidConfigs = options.repairInvalidConfigs === true;
11797
12134
  const dryRun = options.dryRun === true;
11798
12135
  await ensureDirectory(config.agentContextHome, dryRun);
11799
- await ensureDirectory((0, import_node_path12.join)(config.agentContextHome, "logs"), dryRun);
11800
- await ensureDirectory((0, import_node_path12.join)(config.agentContextHome, "redacted"), dryRun);
11801
- await ensureDirectory((0, import_node_path12.join)(config.agentContextHome, "mcp"), dryRun);
12136
+ await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "logs"), dryRun);
12137
+ await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "redacted"), dryRun);
12138
+ await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "mcp"), dryRun);
11802
12139
  await installCommandShim(dryRun);
11803
12140
  await installUserAgentInstructions(dryRun);
11804
12141
  const serverPath = await findOpenVikingServer();
@@ -11835,18 +12172,19 @@ async function runInstall(config, options) {
11835
12172
  }
11836
12173
  await writeTemplateIfMissing({
11837
12174
  config,
11838
- destinationPath: (0, import_node_path12.join)(config.agentContextHome, "ov.conf"),
12175
+ destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ov.conf"),
11839
12176
  dryRun,
11840
12177
  shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
11841
- templatePath: (0, import_node_path12.join)(toolRoot(), "config", "ov.conf.template.json")
12178
+ templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json")
11842
12179
  });
11843
12180
  await writeTemplateIfMissing({
11844
12181
  config,
11845
- destinationPath: (0, import_node_path12.join)(config.agentContextHome, "ovcli.conf"),
12182
+ destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ovcli.conf"),
11846
12183
  dryRun,
11847
12184
  shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
11848
- templatePath: (0, import_node_path12.join)(toolRoot(), "config", "ovcli.conf.template.json")
12185
+ templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json")
11849
12186
  });
12187
+ await configureOpenVikingCliLanguage(config, dryRun);
11850
12188
  if (options.start !== false) {
11851
12189
  const healthy = await repairServerHealth(config, dryRun);
11852
12190
  if (!healthy && !dryRun) {
@@ -11873,6 +12211,7 @@ async function runRepair(config, options) {
11873
12211
  } else {
11874
12212
  console.log("Skipping server health repair because --no-start was provided.");
11875
12213
  }
12214
+ await repairRecallIndex(config, dryRun);
11876
12215
  const mcpClients = await resolveMcpClients(options.mcp ?? "available", "repair");
11877
12216
  if (mcpClients.length === 0) {
11878
12217
  console.log("Skipping MCP config repair.");
@@ -11899,7 +12238,7 @@ async function runUninstall(config, options) {
11899
12238
  }
11900
12239
  console.log("Uninstalling local Threadnote setup.");
11901
12240
  await runStop(config, { dryRun });
11902
- await removePathIfExists((0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
12241
+ await removePathIfExists((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
11903
12242
  await removeLaunchAgent(dryRun);
11904
12243
  await removeMcpConfigs(options.mcp ?? "available", dryRun);
11905
12244
  await removeMcpSnippets(config, dryRun);
@@ -11956,25 +12295,73 @@ async function repairManifest(config, dryRun) {
11956
12295
  console.log(output2.trimEnd());
11957
12296
  return;
11958
12297
  }
11959
- await ensureDirectory((0, import_node_path12.dirname)(config.manifestPath), false);
12298
+ await ensureDirectory((0, import_node_path13.dirname)(config.manifestPath), false);
11960
12299
  const currentContent = await readFileIfExists(config.manifestPath);
11961
12300
  if (currentContent !== void 0) {
11962
12301
  const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
11963
- await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
11964
- await (0, import_promises11.chmod)(backupPath, 384);
12302
+ await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
12303
+ await (0, import_promises12.chmod)(backupPath, 384);
11965
12304
  console.log(`Backup: ${backupPath}`);
11966
12305
  }
11967
- await (0, import_promises11.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
11968
- await (0, import_promises11.chmod)(config.manifestPath, 384);
12306
+ await (0, import_promises12.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
12307
+ await (0, import_promises12.chmod)(config.manifestPath, 384);
11969
12308
  console.log(`Wrote replacement manifest: ${config.manifestPath}`);
11970
12309
  }
12310
+ async function repairRecallIndex(config, dryRun) {
12311
+ console.log("\nRepairing recall index freshness.");
12312
+ const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
12313
+ if (!ov) {
12314
+ console.log("Skipping recall index repair: neither ov nor openviking was found in PATH.");
12315
+ return;
12316
+ }
12317
+ try {
12318
+ const result = await repairStaleRecallIndex(config, ov, {
12319
+ collapseToRoots: true,
12320
+ dryRun,
12321
+ ignoreBackoff: true,
12322
+ includeAgentSkills: true,
12323
+ includeManifestResources: true,
12324
+ maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS
12325
+ });
12326
+ const messages = formatRecallIndexRepairMessages(result, { dryRun });
12327
+ if (messages.length === 0) {
12328
+ console.log("Recall index freshness OK.");
12329
+ return;
12330
+ }
12331
+ for (const message of messages) {
12332
+ console.log(message);
12333
+ }
12334
+ } catch (err) {
12335
+ console.log(`WARN could not repair recall index freshness: ${errorMessage(err)}`);
12336
+ }
12337
+ }
12338
+ async function configureOpenVikingCliLanguage(config, dryRun) {
12339
+ const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
12340
+ if (!ov) {
12341
+ return;
12342
+ }
12343
+ const installedVersion = dryRun ? void 0 : await readOpenVikingCliVersion2(ov);
12344
+ const effectiveVersion = installedVersion ?? config.openVikingVersion;
12345
+ if (compareVersions(effectiveVersion, "0.3.23") < 0) {
12346
+ return;
12347
+ }
12348
+ await maybeRun(dryRun, ov, ["language", "en"], { allowFailure: true });
12349
+ }
12350
+ async function readOpenVikingCliVersion2(ov) {
12351
+ const result = await runCommand(ov, ["version"], { allowFailure: true });
12352
+ if (result.exitCode !== 0) {
12353
+ return void 0;
12354
+ }
12355
+ const match = result.stdout.match(/^\s*CLI:\s*(\S+)/m);
12356
+ return match ? match[1] : void 0;
12357
+ }
11971
12358
  async function findOpenVikingServer() {
11972
12359
  const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
11973
12360
  if (onPath) {
11974
12361
  return onPath;
11975
12362
  }
11976
12363
  for (const candidateDir of await openVikingServerCandidateDirs()) {
11977
- const candidate = (0, import_node_path12.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
12364
+ const candidate = (0, import_node_path13.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
11978
12365
  if (await isExecutable(candidate)) {
11979
12366
  return candidate;
11980
12367
  }
@@ -12020,7 +12407,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
12020
12407
  if (onPath) {
12021
12408
  return;
12022
12409
  }
12023
- const binDir = (0, import_node_path12.dirname)(serverPath);
12410
+ const binDir = (0, import_node_path13.dirname)(serverPath);
12024
12411
  const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
12025
12412
  console.log(
12026
12413
  `Note: ${serverPath} is installed but ${binDir} is not on this shell's PATH. Add \`export PATH="${binDir}:$PATH"\` to ${rcHint} so other tools can find openviking-server.`
@@ -12064,7 +12451,7 @@ async function runStart(config, options) {
12064
12451
  );
12065
12452
  }
12066
12453
  const logPath = openVikingLogPath(config);
12067
- await ensureDirectory((0, import_node_path12.dirname)(logPath), false);
12454
+ await ensureDirectory((0, import_node_path13.dirname)(logPath), false);
12068
12455
  if (options.foreground === true) {
12069
12456
  const result = await runInteractive(server, args);
12070
12457
  process.exitCode = result;
@@ -12073,7 +12460,7 @@ async function runStart(config, options) {
12073
12460
  const logFd = (0, import_node_fs6.openSync)(logPath, "a");
12074
12461
  const child = spawnDetachedServerWithLog(server, args, logFd);
12075
12462
  child.unref();
12076
- await (0, import_promises11.writeFile)((0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
12463
+ await (0, import_promises12.writeFile)((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
12077
12464
  `, "utf8");
12078
12465
  const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
12079
12466
  if (health) {
@@ -12106,7 +12493,7 @@ async function runStop(config, options) {
12106
12493
  console.log(`No LaunchAgent found: ${launchAgentPath}`);
12107
12494
  }
12108
12495
  }
12109
- const pidPath = (0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid");
12496
+ const pidPath = (0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid");
12110
12497
  const pidText = await readFileIfExists(pidPath);
12111
12498
  if (!pidText) {
12112
12499
  console.log("No pid file found for detached OpenViking server.");
@@ -12148,7 +12535,7 @@ async function openVikingServerCheck() {
12148
12535
  }
12149
12536
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
12150
12537
  const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
12151
- const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path12.dirname)(executable)} to PATH)`;
12538
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path13.dirname)(executable)} to PATH)`;
12152
12539
  return {
12153
12540
  name,
12154
12541
  status: result.exitCode === 0 ? "ok" : "warn",
@@ -12217,7 +12604,7 @@ async function pythonSystemCertificatesCheck() {
12217
12604
  };
12218
12605
  }
12219
12606
  async function commandShimCheck() {
12220
- const shimPath = (0, import_node_path12.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12607
+ const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12221
12608
  const content = await readFileIfExists(shimPath);
12222
12609
  if (content === void 0) {
12223
12610
  return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
@@ -12283,11 +12670,11 @@ async function hasPythonModule(serverPath, moduleName) {
12283
12670
  async function siblingPythonForExecutable(executablePath) {
12284
12671
  let resolvedPath;
12285
12672
  try {
12286
- resolvedPath = await (0, import_promises11.realpath)(executablePath);
12673
+ resolvedPath = await (0, import_promises12.realpath)(executablePath);
12287
12674
  } catch (_err) {
12288
12675
  return void 0;
12289
12676
  }
12290
- const pythonPath = (0, import_node_path12.join)((0, import_node_path12.dirname)(resolvedPath), "python");
12677
+ const pythonPath = (0, import_node_path13.join)((0, import_node_path13.dirname)(resolvedPath), "python");
12291
12678
  return await exists(pythonPath) ? pythonPath : void 0;
12292
12679
  }
12293
12680
  async function manifestCheck(path) {
@@ -12298,6 +12685,29 @@ async function manifestCheck(path) {
12298
12685
  return { name: "manifest", status: "fail", detail: errorMessage(err) };
12299
12686
  }
12300
12687
  }
12688
+ async function recallIndexFreshnessCheck(config) {
12689
+ try {
12690
+ const targets = await findStaleRecallIndexTargets(config, {
12691
+ collapseToRoots: true,
12692
+ includeAgentSkills: true,
12693
+ includeManifestResources: true
12694
+ });
12695
+ if (targets.length === 0) {
12696
+ return { name: "recall index freshness", status: "ok", detail: "no stale generated summaries found" };
12697
+ }
12698
+ const staleSummaryCount = targets.reduce((total, target) => total + target.staleCount, 0);
12699
+ const sampleUris = targets.slice(0, 3).map((target) => target.uri);
12700
+ const extraCount = targets.length - sampleUris.length;
12701
+ const sample = `${sampleUris.join(", ")}${extraCount > 0 ? `, +${extraCount} more` : ""}`;
12702
+ return {
12703
+ name: "recall index freshness",
12704
+ status: "warn",
12705
+ detail: `${staleSummaryCount} stale generated summary file(s) under ${targets.length} scope(s); run repair to reindex ${sample}`
12706
+ };
12707
+ } catch (err) {
12708
+ return { name: "recall index freshness", status: "warn", detail: errorMessage(err) };
12709
+ }
12710
+ }
12301
12711
  async function fileCheck(path, label) {
12302
12712
  return await exists(path) ? { name: label, status: "ok", detail: path } : { name: label, status: "fail", detail: `${path} missing` };
12303
12713
  }
@@ -12358,7 +12768,7 @@ async function offerToInstallUv() {
12358
12768
  );
12359
12769
  return false;
12360
12770
  }
12361
- const readline = (0, import_promises12.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
12771
+ const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
12362
12772
  let answer;
12363
12773
  try {
12364
12774
  answer = (await readline.question(
@@ -12483,38 +12893,38 @@ function printInstallNextSteps(options) {
12483
12893
  }
12484
12894
  async function writeTemplateIfMissing(options) {
12485
12895
  if (await exists(options.destinationPath)) {
12486
- const currentContent = await (0, import_promises11.readFile)(options.destinationPath, "utf8");
12896
+ const currentContent = await (0, import_promises12.readFile)(options.destinationPath, "utf8");
12487
12897
  if (options.shouldRepair?.(currentContent) !== true) {
12488
12898
  console.log(`Already exists: ${options.destinationPath}`);
12489
12899
  return;
12490
12900
  }
12491
- const rendered2 = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
12901
+ const rendered2 = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
12492
12902
  if (options.dryRun) {
12493
12903
  console.log(`Would repair generated config: ${options.destinationPath}`);
12494
12904
  return;
12495
12905
  }
12496
12906
  const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
12497
- await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
12498
- await (0, import_promises11.chmod)(backupPath, 384);
12499
- await (0, import_promises11.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
12500
- await (0, import_promises11.chmod)(options.destinationPath, 384);
12907
+ await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
12908
+ await (0, import_promises12.chmod)(backupPath, 384);
12909
+ await (0, import_promises12.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
12910
+ await (0, import_promises12.chmod)(options.destinationPath, 384);
12501
12911
  console.log(`Repaired generated config: ${options.destinationPath}`);
12502
12912
  console.log(`Backup: ${backupPath}`);
12503
12913
  return;
12504
12914
  }
12505
- const rendered = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
12915
+ const rendered = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
12506
12916
  if (options.dryRun) {
12507
12917
  console.log(`Would write ${options.destinationPath}`);
12508
12918
  return;
12509
12919
  }
12510
- await ensureDirectory((0, import_node_path12.dirname)(options.destinationPath), false);
12511
- await (0, import_promises11.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
12512
- await (0, import_promises11.chmod)(options.destinationPath, 384);
12920
+ await ensureDirectory((0, import_node_path13.dirname)(options.destinationPath), false);
12921
+ await (0, import_promises12.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
12922
+ await (0, import_promises12.chmod)(options.destinationPath, 384);
12513
12923
  console.log(`Wrote ${options.destinationPath}`);
12514
12924
  }
12515
12925
  async function installCommandShim(dryRun) {
12516
12926
  const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
12517
- const shimPath = (0, import_node_path12.join)(binDir, "threadnote");
12927
+ const shimPath = (0, import_node_path13.join)(binDir, "threadnote");
12518
12928
  const existingContent = await readFileIfExists(shimPath);
12519
12929
  if (existingContent && !isManagedCommandShim(existingContent)) {
12520
12930
  console.log(`WARN not overwriting existing command shim: ${shimPath}`);
@@ -12530,12 +12940,12 @@ async function installCommandShim(dryRun) {
12530
12940
  return;
12531
12941
  }
12532
12942
  await ensureDirectory(binDir, false);
12533
- await (0, import_promises11.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
12534
- await (0, import_promises11.chmod)(shimPath, 493);
12943
+ await (0, import_promises12.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
12944
+ await (0, import_promises12.chmod)(shimPath, 493);
12535
12945
  console.log(`Wrote command shim: ${shimPath}`);
12536
12946
  }
12537
12947
  async function removeCommandShim(dryRun) {
12538
- const shimPath = (0, import_node_path12.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12948
+ const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12539
12949
  const content = await readFileIfExists(shimPath);
12540
12950
  if (content === void 0) {
12541
12951
  console.log(`Already absent: ${shimPath}`);
@@ -12569,8 +12979,8 @@ async function installUserAgentInstructions(dryRun) {
12569
12979
  console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
12570
12980
  continue;
12571
12981
  }
12572
- await ensureDirectory((0, import_node_path12.dirname)(targetPath), false);
12573
- await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
12982
+ await ensureDirectory((0, import_node_path13.dirname)(targetPath), false);
12983
+ await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
12574
12984
  console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
12575
12985
  }
12576
12986
  }
@@ -12607,7 +13017,7 @@ async function removeUserAgentInstructions(dryRun) {
12607
13017
  console.log(`Would update ${targetPath}`);
12608
13018
  continue;
12609
13019
  }
12610
- await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
13020
+ await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
12611
13021
  console.log(`Updated ${targetPath}`);
12612
13022
  }
12613
13023
  }
@@ -12627,7 +13037,7 @@ async function renderUserAgentInstructions(target) {
12627
13037
  ].join("\n");
12628
13038
  }
12629
13039
  async function renderUserAgentInstructionsBlock() {
12630
- const instructions = (await (0, import_promises11.readFile)((0, import_node_path12.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
13040
+ const instructions = (await (0, import_promises12.readFile)((0, import_node_path13.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
12631
13041
  return `${USER_INSTRUCTIONS_START_MARKER}
12632
13042
  ${instructions}
12633
13043
  ${USER_INSTRUCTIONS_END_MARKER}`;
@@ -12711,9 +13121,9 @@ async function installLaunchAgent(config, dryRun) {
12711
13121
  `Cannot install LaunchAgent: ${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
12712
13122
  );
12713
13123
  }
12714
- const source = (0, import_node_path12.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
13124
+ const source = (0, import_node_path13.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
12715
13125
  const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
12716
- const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config, {
13126
+ const rendered = renderTemplate(await (0, import_promises12.readFile)(source, "utf8"), config, {
12717
13127
  OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
12718
13128
  });
12719
13129
  if (dryRun) {
@@ -12725,9 +13135,9 @@ async function installLaunchAgent(config, dryRun) {
12725
13135
  console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
12726
13136
  return;
12727
13137
  }
12728
- await ensureDirectory((0, import_node_path12.dirname)(destination), false);
12729
- await ensureDirectory((0, import_node_path12.dirname)(openVikingLogPath(config)), false);
12730
- await (0, import_promises11.writeFile)(destination, rendered, "utf8");
13138
+ await ensureDirectory((0, import_node_path13.dirname)(destination), false);
13139
+ await ensureDirectory((0, import_node_path13.dirname)(openVikingLogPath(config)), false);
13140
+ await (0, import_promises12.writeFile)(destination, rendered, "utf8");
12731
13141
  await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
12732
13142
  await maybeRun(false, "launchctl", ["load", destination]);
12733
13143
  await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
@@ -12790,7 +13200,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
12790
13200
  if (typeof parsed.default_user !== "string") {
12791
13201
  return false;
12792
13202
  }
12793
- if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path12.join)(config.agentContextHome, "data")) {
13203
+ if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path13.join)(config.agentContextHome, "data")) {
12794
13204
  return false;
12795
13205
  }
12796
13206
  return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
@@ -12852,9 +13262,9 @@ function printWhatsNew(whatsNew) {
12852
13262
  // src/manager.ts
12853
13263
  var import_node_http2 = require("node:http");
12854
13264
  var import_node_crypto2 = require("node:crypto");
12855
- var import_promises13 = require("node:fs/promises");
13265
+ var import_promises14 = require("node:fs/promises");
12856
13266
  var import_node_os7 = require("node:os");
12857
- var import_node_path13 = require("node:path");
13267
+ var import_node_path14 = require("node:path");
12858
13268
  var STATIC_FILES = {
12859
13269
  "/": { contentType: "text/html; charset=utf-8", path: "index.html" },
12860
13270
  "/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
@@ -12897,24 +13307,46 @@ async function memoryTree(config) {
12897
13307
  const root = localMemoriesRoot(config);
12898
13308
  return readTree(config, root, `viking://user/${uriSegment(config.user)}/memories`, "");
12899
13309
  }
13310
+ async function resourcesTree(config) {
13311
+ const root = localResourcesRoot(config);
13312
+ try {
13313
+ return await readTree(config, root, "viking://resources", "", {
13314
+ parseMemoryDocuments: false,
13315
+ rootName: "resources"
13316
+ });
13317
+ } catch (err) {
13318
+ if (isMissingPathError(err)) {
13319
+ return {
13320
+ children: [],
13321
+ isDir: true,
13322
+ isShared: false,
13323
+ isSystem: false,
13324
+ name: "resources",
13325
+ relativePath: "",
13326
+ uri: "viking://resources"
13327
+ };
13328
+ }
13329
+ throw err;
13330
+ }
13331
+ }
12900
13332
  async function readManagedMemory(config, uri) {
12901
13333
  assertVikingUri(uri);
12902
13334
  const path = localPathForMemoryUri(config, uri);
12903
13335
  if (!path) {
12904
13336
  throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
12905
13337
  }
12906
- const [content, pathStat] = await Promise.all([(0, import_promises13.readFile)(path, "utf8"), (0, import_promises13.stat)(path)]);
12907
- const relativePath = (0, import_node_path13.relative)(localMemoriesRoot(config), path).split(import_node_path13.sep).join("/");
13338
+ const [content, pathStat] = await Promise.all([(0, import_promises14.readFile)(path, "utf8"), (0, import_promises14.stat)(path)]);
13339
+ const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path).split(import_node_path14.sep).join("/");
12908
13340
  const record = parseMemoryDocument(uri, content);
12909
13341
  return {
12910
13342
  content,
12911
13343
  node: {
12912
13344
  isDir: false,
12913
13345
  isShared: isInSharedNamespace(config, uri),
12914
- isSystem: isSystemMemoryName(path.split(import_node_path13.sep).at(-1) ?? ""),
13346
+ isSystem: isSystemMemoryName(path.split(import_node_path14.sep).at(-1) ?? ""),
12915
13347
  metadata: record?.metadata,
12916
13348
  modTime: pathStat.mtime.toISOString(),
12917
- name: path.split(import_node_path13.sep).at(-1) ?? uri,
13349
+ name: path.split(import_node_path14.sep).at(-1) ?? uri,
12918
13350
  relativePath,
12919
13351
  sharedTeam: sharedTeamNameForUri(config, uri),
12920
13352
  size: pathStat.size,
@@ -12981,7 +13413,8 @@ async function handleRequest(context, request, response) {
12981
13413
  return;
12982
13414
  }
12983
13415
  if (request.method === "GET" && url.pathname === "/api/tree") {
12984
- writeJson(response, 200, { tree: await memoryTree(context.config) });
13416
+ const [tree, resourceTree] = await Promise.all([memoryTree(context.config), resourcesTree(context.config)]);
13417
+ writeJson(response, 200, { resourcesTree: resourceTree, tree });
12985
13418
  return;
12986
13419
  }
12987
13420
  if (request.method === "GET" && url.pathname === "/api/memory") {
@@ -13200,21 +13633,20 @@ async function handleRequest(context, request, response) {
13200
13633
  }
13201
13634
  async function serveStatic(context, url, response) {
13202
13635
  const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
13203
- const content = await (0, import_promises13.readFile)((0, import_node_path13.join)(toolRoot(), file.root ?? "manager", file.path));
13636
+ const content = await (0, import_promises14.readFile)((0, import_node_path14.join)(toolRoot(), file.root ?? "manager", file.path));
13204
13637
  const headers = { "content-type": file.contentType };
13205
- if (url.pathname === "/" || url.pathname === "/index.html") {
13638
+ if (file.root !== "docs") {
13206
13639
  headers["cache-control"] = "no-store";
13207
13640
  }
13208
13641
  response.writeHead(200, headers);
13209
13642
  response.end(content);
13210
13643
  }
13211
- async function readTree(config, path, uri, relativePath) {
13212
- const pathStat = await (0, import_promises13.stat)(path);
13213
- const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : "memories";
13644
+ async function readTree(config, path, uri, relativePath, options = {}) {
13645
+ const pathStat = await (0, import_promises14.stat)(path);
13646
+ const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
13214
13647
  const isDir = pathStat.isDirectory();
13215
13648
  if (!isDir) {
13216
- const content = await (0, import_promises13.readFile)(path, "utf8").catch(() => "");
13217
- const record = parseMemoryDocument(uri, content);
13649
+ const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path, "utf8").catch(() => ""));
13218
13650
  return {
13219
13651
  isDir: false,
13220
13652
  isShared: isInSharedNamespace(config, uri),
@@ -13228,13 +13660,13 @@ async function readTree(config, path, uri, relativePath) {
13228
13660
  uri
13229
13661
  };
13230
13662
  }
13231
- const entries = await (0, import_promises13.readdir)(path, { withFileTypes: true });
13663
+ const entries = await (0, import_promises14.readdir)(path, { withFileTypes: true });
13232
13664
  const children = await Promise.all(
13233
13665
  entries.sort(
13234
13666
  (left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
13235
13667
  ).map((entry) => {
13236
13668
  const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
13237
- return readTree(config, (0, import_node_path13.join)(path, entry.name), `${uri}/${entry.name}`, childRelative);
13669
+ return readTree(config, (0, import_node_path14.join)(path, entry.name), `${uri}/${entry.name}`, childRelative, options);
13238
13670
  })
13239
13671
  );
13240
13672
  return {
@@ -13408,27 +13840,27 @@ async function removeManagedFolder(config, uri) {
13408
13840
  if (!path) {
13409
13841
  throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
13410
13842
  }
13411
- const pathStat = await (0, import_promises13.stat)(path);
13843
+ const pathStat = await (0, import_promises14.stat)(path);
13412
13844
  if (!pathStat.isDirectory()) {
13413
13845
  throw new Error(`Not a folder: ${uri}`);
13414
13846
  }
13415
- const relativePath = (0, import_node_path13.relative)(localMemoriesRoot(config), path);
13416
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path13.sep).includes("..")) {
13847
+ const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
13848
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
13417
13849
  throw new Error("Refusing to remove a folder outside the memories tree.");
13418
13850
  }
13419
13851
  const fileUris = await fileUrisUnderFolder(config, path);
13420
13852
  for (const fileUri of fileUris) {
13421
13853
  await runForget(config, fileUri, {});
13422
13854
  }
13423
- await (0, import_promises13.rm)(path, { force: true, recursive: true });
13855
+ await (0, import_promises14.rm)(path, { force: true, recursive: true });
13424
13856
  console.log(`Removed folder: ${uri}`);
13425
13857
  console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
13426
13858
  }
13427
13859
  async function fileUrisUnderFolder(config, folderPath) {
13428
- const entries = await (0, import_promises13.readdir)(folderPath, { withFileTypes: true });
13860
+ const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
13429
13861
  const uris = [];
13430
13862
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
13431
- const path = (0, import_node_path13.join)(folderPath, entry.name);
13863
+ const path = (0, import_node_path14.join)(folderPath, entry.name);
13432
13864
  if (entry.isDirectory()) {
13433
13865
  uris.push(...await fileUrisUnderFolder(config, path));
13434
13866
  } else if (entry.isFile()) {
@@ -13526,11 +13958,11 @@ async function runConsolidationAgent(agent, sources) {
13526
13958
  throw new Error(`${agent} executable was not found.`);
13527
13959
  }
13528
13960
  const prompt = consolidationPrompt(sources);
13529
- const stagingDir = await (0, import_promises13.mkdtemp)((0, import_node_path13.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
13530
- const promptPath = (0, import_node_path13.join)(stagingDir, "prompt.txt");
13961
+ const stagingDir = await (0, import_promises14.mkdtemp)((0, import_node_path14.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
13962
+ const promptPath = (0, import_node_path14.join)(stagingDir, "prompt.txt");
13531
13963
  try {
13532
- await (0, import_promises13.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
13533
- await (0, import_promises13.chmod)(promptPath, 384);
13964
+ await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
13965
+ await (0, import_promises14.chmod)(promptPath, 384);
13534
13966
  const script = consolidationAgentScript(agent, executable);
13535
13967
  const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
13536
13968
  allowFailure: true,
@@ -13546,7 +13978,7 @@ async function runConsolidationAgent(agent, sources) {
13546
13978
  }
13547
13979
  return draft;
13548
13980
  } finally {
13549
- await (0, import_promises13.rm)(stagingDir, { force: true, recursive: true });
13981
+ await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
13550
13982
  }
13551
13983
  }
13552
13984
  function consolidationAgentScript(agent, executable) {
@@ -13684,7 +14116,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
13684
14116
  }
13685
14117
  }
13686
14118
  function localMemoriesRoot(config) {
13687
- return (0, import_node_path13.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
14119
+ return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
14120
+ }
14121
+ function localResourcesRoot(config) {
14122
+ return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "resources");
13688
14123
  }
13689
14124
  function localPathForMemoryUri(config, uri) {
13690
14125
  const prefix = `viking://user/${uriSegment(config.user)}/memories`;
@@ -13696,14 +14131,17 @@ function localPathForMemoryUri(config, uri) {
13696
14131
  if (segments.some((segment) => segment === "." || segment === "..")) {
13697
14132
  return void 0;
13698
14133
  }
13699
- return (0, import_node_path13.join)(localMemoriesRoot(config), ...segments);
14134
+ return (0, import_node_path14.join)(localMemoriesRoot(config), ...segments);
14135
+ }
14136
+ function isMissingPathError(err) {
14137
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
13700
14138
  }
13701
14139
  function localPathToMemoryUri(config, path) {
13702
- const relativePath = (0, import_node_path13.relative)(localMemoriesRoot(config), path);
13703
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path13.sep).includes("..")) {
14140
+ const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
14141
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
13704
14142
  throw new Error(`Path is outside the memories tree: ${path}`);
13705
14143
  }
13706
- return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path13.sep).join("/")}`;
14144
+ return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path14.sep).join("/")}`;
13707
14145
  }
13708
14146
  async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
13709
14147
  const prefix = "viking://";
@@ -13908,7 +14346,7 @@ async function main() {
13908
14346
  program2.command("init-manifest").description("Create or update a per-developer seed manifest from one or more repo roots").option("--dry-run", "Print the manifest without writing it").option("--path <path>", "Manifest path; defaults to THREADNOTE_MANIFEST or ~/.openviking/seed-manifest.yaml").option("--replace", "Replace the manifest instead of merging with existing projects").option("--repo <path>", "Repo root to include; repeat for multiple repos", collectOption, []).action(async (options) => {
13909
14347
  await runInitManifest(getRuntimeConfig(program2), options);
13910
14348
  });
13911
- program2.command("seed-skills").description("Seed Codex, Claude, and repo-local SKILL.md files as a searchable catalog").option("--dry-run", "Print skill files and ov commands without importing").option("--manifest <path>", "Manifest path for repo-local skill discovery").option("--native", "Use native OpenViking skill ingestion; requires a working VLM config").action(async (options) => {
14349
+ program2.command("seed-skills").description("Seed Codex/Claude skills and Claude command markdown files as a searchable catalog").option("--dry-run", "Print skill files and ov commands without importing").option("--manifest <path>", "Manifest path for repo-local skill discovery").option("--native", "Use native OpenViking skill ingestion; requires a working VLM config").action(async (options) => {
13912
14350
  await runSeedSkills(getRuntimeConfig(program2, options.manifest), options);
13913
14351
  });
13914
14352
  program2.command("mcp-install").description("Install OpenViking MCP config for a supported agent").argument("<agent>", "codex, claude, cursor, or copilot").option("--apply", "Actually modify the selected agent config").option("--name <name>", "MCP server name", OPENVIKING_MCP_NAME).option("--native-http", "Install OpenViking native HTTP MCP endpoint instead of the local stdio adapter").option("--scope <scope>", "Claude MCP config scope: user, local, or project", parseClaudeMcpScope, "user").option("--url <url>", "OpenViking native HTTP MCP URL").option("--bearer-token-env-var <name>", "Environment variable containing the local API key").action(async (agent, options) => {