threadnote 0.7.12 → 1.1.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");
@@ -3616,6 +3616,41 @@ async function withSpinner(message, action) {
3616
3616
  (0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
3617
3617
  }
3618
3618
  }
3619
+ function startProgress(message) {
3620
+ if (import_node_process.stdout.isTTY !== true || process.env.CI !== void 0 || process.env.THREADNOTE_NO_SPINNER !== void 0) {
3621
+ console.log(message);
3622
+ return {
3623
+ update(nextMessage) {
3624
+ console.log(nextMessage);
3625
+ },
3626
+ stop() {
3627
+ return;
3628
+ }
3629
+ };
3630
+ }
3631
+ const frames = ["-", "\\", "|", "/"];
3632
+ let frameIndex = 0;
3633
+ let currentMessage = message;
3634
+ const render = () => {
3635
+ (0, import_node_readline.clearLine)(import_node_process.stdout, 0);
3636
+ (0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
3637
+ import_node_process.stdout.write(`${muted(frames[frameIndex])} ${currentMessage}`);
3638
+ frameIndex = (frameIndex + 1) % frames.length;
3639
+ };
3640
+ const timer = setInterval(render, 100);
3641
+ render();
3642
+ return {
3643
+ update(nextMessage) {
3644
+ currentMessage = nextMessage;
3645
+ render();
3646
+ },
3647
+ stop() {
3648
+ clearInterval(timer);
3649
+ (0, import_node_readline.clearLine)(import_node_process.stdout, 0);
3650
+ (0, import_node_readline.cursorTo)(import_node_process.stdout, 0);
3651
+ }
3652
+ };
3653
+ }
3619
3654
 
3620
3655
  // src/utils.ts
3621
3656
  function isJsonObject(value) {
@@ -3753,15 +3788,11 @@ async function maybeRun(dryRun, executable, args, options = {}) {
3753
3788
  }
3754
3789
  async function runCommand(executable, args, options = {}) {
3755
3790
  return new Promise((resolvePromise, rejectPromise) => {
3756
- const child = (0, import_node_child_process.spawn)(executable, args, { cwd: options.cwd });
3757
- const stdoutChunks = [];
3758
- const stderrChunks = [];
3759
3791
  const maxOutputBytes = options.maxOutputBytes ?? defaultCommandMaxOutputBytes();
3760
- let stdoutBytes = 0;
3761
- let stderrBytes = 0;
3762
3792
  let finished = false;
3763
- let failureResult;
3764
3793
  let killTimer;
3794
+ let killEscalationTimer;
3795
+ let failureMessage;
3765
3796
  let sentTerminationSignal = false;
3766
3797
  const timeoutMs = options.timeoutMs ?? defaultCommandTimeoutMs();
3767
3798
  const finish = (result) => {
@@ -3772,22 +3803,47 @@ async function runCommand(executable, args, options = {}) {
3772
3803
  if (killTimer) {
3773
3804
  clearTimeout(killTimer);
3774
3805
  }
3806
+ if (killEscalationTimer) {
3807
+ clearTimeout(killEscalationTimer);
3808
+ }
3775
3809
  if (result.exitCode !== 0 && options.allowFailure !== true) {
3776
3810
  rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
3777
3811
  return;
3778
3812
  }
3779
3813
  resolvePromise(result);
3780
3814
  };
3815
+ const child = (0, import_node_child_process.execFile)(
3816
+ executable,
3817
+ [...args],
3818
+ {
3819
+ cwd: options.cwd,
3820
+ encoding: "utf8",
3821
+ maxBuffer: maxOutputBytes
3822
+ },
3823
+ (err, stdout2, stderr) => {
3824
+ finish(
3825
+ commandResultFromExecFileCallback({
3826
+ args,
3827
+ err,
3828
+ executable,
3829
+ failureMessage,
3830
+ maxOutputBytes,
3831
+ stderr,
3832
+ stdout: stdout2
3833
+ })
3834
+ );
3835
+ }
3836
+ );
3781
3837
  const failAndKill = (message) => {
3782
- if (failureResult) {
3838
+ if (failureMessage) {
3783
3839
  return;
3784
3840
  }
3785
- failureResult = { exitCode: 124, stderr: message, stdout: stdoutChunks.join("") };
3841
+ failureMessage = message;
3786
3842
  if (!sentTerminationSignal) {
3787
3843
  sentTerminationSignal = true;
3788
3844
  child.kill("SIGTERM");
3789
3845
  }
3790
- setTimeout(() => {
3846
+ killEscalationTimer = setTimeout(() => {
3791
3847
  if (!finished) {
3792
3848
  child.kill("SIGKILL");
3793
3849
  }
@@ -3799,54 +3855,20 @@ async function runCommand(executable, args, options = {}) {
3799
3855
  }, timeoutMs);
3800
3856
  killTimer.unref?.();
3801
3857
  }
3802
- child.stdout.on("data", (chunk) => {
3803
- if (failureResult) {
3804
- return;
3805
- }
3806
- const text = String(chunk);
3807
- stdoutBytes += Buffer.byteLength(text);
3808
- if (stdoutBytes + stderrBytes > maxOutputBytes) {
3809
- failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
3810
- return;
3811
- }
3812
- stdoutChunks.push(text);
3813
- });
3814
- child.stderr.on("data", (chunk) => {
3815
- if (failureResult) {
3816
- return;
3817
- }
3818
- const text = String(chunk);
3819
- stderrBytes += Buffer.byteLength(text);
3820
- if (stdoutBytes + stderrBytes > maxOutputBytes) {
3821
- failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
3822
- return;
3823
- }
3824
- stderrChunks.push(text);
3825
- });
3826
- child.on("error", (err) => {
3827
- if (finished) {
3828
- return;
3829
- }
3830
- if (killTimer) {
3831
- clearTimeout(killTimer);
3832
- }
3833
- if (options.allowFailure === true) {
3834
- finish({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
3835
- } else {
3836
- finished = true;
3837
- rejectPromise(err);
3838
- }
3839
- });
3840
- child.on("close", (code) => {
3841
- const result = failureResult ?? {
3842
- exitCode: code ?? 1,
3843
- stderr: stderrChunks.join(""),
3844
- stdout: stdoutChunks.join("")
3845
- };
3846
- finish(result);
3847
- });
3848
3858
  });
3849
3859
  }
3860
+ function commandResultFromExecFileCallback(params) {
3861
+ if (params.failureMessage) {
3862
+ return { exitCode: 124, stderr: params.failureMessage, stdout: params.stdout };
3863
+ }
3864
+ if (!params.err) {
3865
+ return { exitCode: 0, stderr: params.stderr, stdout: params.stdout };
3866
+ }
3867
+ const error = params.err;
3868
+ const exitCode = typeof error.code === "number" ? error.code : error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? 124 : 127;
3869
+ const stderr = error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? `${formatShellCommand(params.executable, params.args)} exceeded output limit of ${params.maxOutputBytes} bytes` : params.stderr || errorMessage(error);
3870
+ return { exitCode, stderr, stdout: params.stdout };
3871
+ }
3850
3872
  function defaultCommandTimeoutMs() {
3851
3873
  return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
3852
3874
  }
@@ -4773,8 +4795,8 @@ function existsSyncDirectory(path) {
4773
4795
  }
4774
4796
 
4775
4797
  // src/memory.ts
4776
- var import_promises5 = require("node:fs/promises");
4777
- var import_node_path6 = require("node:path");
4798
+ var import_promises6 = require("node:fs/promises");
4799
+ var import_node_path7 = require("node:path");
4778
4800
 
4779
4801
  // src/manifest.ts
4780
4802
  var import_promises3 = require("node:fs/promises");
@@ -7472,8 +7494,311 @@ function readStringArray(object, key) {
7472
7494
  return value;
7473
7495
  }
7474
7496
 
7475
- // src/memory_hygiene.ts
7497
+ // src/index_repair.ts
7498
+ var import_promises4 = require("node:fs/promises");
7499
+ var import_node_path4 = require("node:path");
7500
+
7501
+ // src/runtime.ts
7502
+ var import_node_fs3 = require("node:fs");
7503
+ var import_node_os3 = require("node:os");
7476
7504
  var import_node_path3 = require("node:path");
7505
+ function getRuntimeConfig(program2, manifestOverride) {
7506
+ const options = program2.opts();
7507
+ const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
7508
+ const manifestPath = expandPath(
7509
+ manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
7510
+ );
7511
+ return {
7512
+ account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
7513
+ agentContextHome: threadnoteHome,
7514
+ agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
7515
+ host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
7516
+ manifestPath,
7517
+ openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
7518
+ port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
7519
+ user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
7520
+ };
7521
+ }
7522
+ function defaultManifestPath(agentContextHome) {
7523
+ const userManifest = (0, import_node_path3.join)(agentContextHome, USER_MANIFEST_NAME);
7524
+ return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
7525
+ }
7526
+ function builtInExampleManifestPath() {
7527
+ return (0, import_node_path3.join)(toolRoot(), "config", "seed-manifest.example.yaml");
7528
+ }
7529
+ function openVikingHealthUrl(config) {
7530
+ return `http://${config.host}:${config.port}/health`;
7531
+ }
7532
+ function openVikingLogPath(config) {
7533
+ return (0, import_node_path3.join)(config.agentContextHome, "logs", "server.log");
7534
+ }
7535
+ function openVikingServerArgs(config) {
7536
+ return ["--config", (0, import_node_path3.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
7537
+ }
7538
+ function withIdentity(config, args) {
7539
+ return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
7540
+ }
7541
+ function renderTemplate(template, config, extras = {}) {
7542
+ 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);
7543
+ for (const [key, value] of Object.entries(extras)) {
7544
+ rendered = rendered.replaceAll(`{{${key}}}`, () => value);
7545
+ }
7546
+ return rendered;
7547
+ }
7548
+
7549
+ // src/index_repair.ts
7550
+ var AUTO_REPAIR_STATE_FILE = "index-auto-repair.json";
7551
+ var AUTO_REPAIR_TTL_MS = 6 * 60 * 60 * 1e3;
7552
+ var MAX_SCAN_DEPTH = 5;
7553
+ var MAX_REPAIR_TARGETS = 4;
7554
+ var MAINTENANCE_COLLAPSE_DEPTH = 3;
7555
+ var MAINTENANCE_MAX_REPAIR_TARGETS = 512;
7556
+ async function repairStaleRecallIndex(config, ov, options = {}) {
7557
+ options.onProgress?.({ type: "scan-start" });
7558
+ const targets = await findStaleRecallIndexTargets(config, options);
7559
+ const repairTargets = targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS);
7560
+ options.onProgress?.({
7561
+ repairTargetCount: repairTargets.length,
7562
+ totalTargets: targets.length,
7563
+ type: "scan-complete"
7564
+ });
7565
+ if (targets.length === 0) {
7566
+ return { repairedUris: [], skippedRecentUris: [], warnings: [] };
7567
+ }
7568
+ const state = options.dryRun === true ? { entries: {}, version: 1 } : await readAutoRepairState(config);
7569
+ const now = Date.now();
7570
+ const repairedUris = [];
7571
+ const skippedRecentUris = [];
7572
+ const warnings = [];
7573
+ for (const [targetIndex, target] of repairTargets.entries()) {
7574
+ const progressBase = { index: targetIndex + 1, target, total: repairTargets.length };
7575
+ const previous = state.entries[target.uri];
7576
+ if (previous?.signature === target.signature && now - Date.parse(previous.repairedAt) < AUTO_REPAIR_TTL_MS && options.dryRun !== true && options.ignoreBackoff !== true) {
7577
+ options.onProgress?.({ ...progressBase, type: "repair-skip-recent" });
7578
+ skippedRecentUris.push(target.uri);
7579
+ continue;
7580
+ }
7581
+ if (options.dryRun === true) {
7582
+ options.onProgress?.({ ...progressBase, type: "repair-dry-run" });
7583
+ repairedUris.push(target.uri);
7584
+ continue;
7585
+ }
7586
+ options.onProgress?.({ ...progressBase, type: "repair-start" });
7587
+ const result = await runCommand(
7588
+ ov,
7589
+ withIdentity(config, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
7590
+ { allowFailure: true }
7591
+ );
7592
+ if (result.exitCode === 0) {
7593
+ repairedUris.push(target.uri);
7594
+ state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
7595
+ } else {
7596
+ warnings.push(indexRepairWarning(target.uri, result));
7597
+ await options.onRepairFailure?.(target, result);
7598
+ }
7599
+ }
7600
+ if (options.dryRun !== true && repairedUris.length > 0) {
7601
+ await writeAutoRepairState(config, state);
7602
+ }
7603
+ return { repairedUris, skippedRecentUris, warnings };
7604
+ }
7605
+ async function findStaleRecallIndexTargets(config, options = {}) {
7606
+ const roots = await scanRoots(config, options);
7607
+ const byUri = /* @__PURE__ */ new Map();
7608
+ for (const root of roots) {
7609
+ if (!await exists(root.path)) {
7610
+ continue;
7611
+ }
7612
+ const sidecars = await staleSidecars(root.path, root.uri);
7613
+ if (options.collapseToRoots === true) {
7614
+ for (const sidecar of sidecars) {
7615
+ const uri = collapseStaleSidecarUri(root.uri, sidecar.relativePath, options.collapseDepth);
7616
+ const current = byUri.get(uri) ?? { parts: [], staleCount: 0, uri };
7617
+ current.parts.push(`${sidecar.relativePath}
7618
+ ${sidecar.content}`);
7619
+ current.staleCount += 1;
7620
+ byUri.set(uri, current);
7621
+ }
7622
+ continue;
7623
+ }
7624
+ for (const sidecar of sidecars) {
7625
+ const current = byUri.get(sidecar.uri) ?? { parts: [], staleCount: 0, uri: sidecar.uri };
7626
+ current.parts.push(`${sidecar.relativePath}
7627
+ ${sidecar.content}`);
7628
+ current.staleCount += 1;
7629
+ byUri.set(sidecar.uri, current);
7630
+ }
7631
+ }
7632
+ return [...byUri.values()].map((target) => ({
7633
+ signature: sha256([target.uri, ...target.parts.sort()].join("\n---\n")),
7634
+ staleCount: target.staleCount,
7635
+ uri: target.uri
7636
+ }));
7637
+ }
7638
+ function collapseStaleSidecarUri(rootUri, relativePath, depth = 0) {
7639
+ const normalizedRootUri = trimLocalRootUri(rootUri);
7640
+ if (depth <= 0) {
7641
+ return normalizedRootUri;
7642
+ }
7643
+ const parentParts = relativePath.split("/").slice(0, -1);
7644
+ const collapsedParts = parentParts.slice(0, depth);
7645
+ return collapsedParts.length === 0 ? normalizedRootUri : `${normalizedRootUri}/${collapsedParts.join("/")}`;
7646
+ }
7647
+ function formatRecallIndexRepairMessages(result, options = {}) {
7648
+ const messages = [];
7649
+ const maxUris = options.maxUris ?? result.repairedUris.length;
7650
+ for (const uri of result.repairedUris.slice(0, maxUris)) {
7651
+ messages.push(
7652
+ `${options.dryRun === true ? "Would auto-reindex stale recall scope" : "Auto-reindexed stale recall scope"}: ${uri}`
7653
+ );
7654
+ }
7655
+ const hiddenUriCount = result.repairedUris.length - maxUris;
7656
+ if (hiddenUriCount > 0) {
7657
+ messages.push(
7658
+ options.dryRun === true ? `Would auto-reindex ${hiddenUriCount} more stale recall scope(s).` : `Auto-reindexed ${hiddenUriCount} more stale recall scope(s).`
7659
+ );
7660
+ }
7661
+ for (const warning2 of result.warnings) {
7662
+ messages.push(`Auto-index repair warning: ${warning2}`);
7663
+ }
7664
+ return messages;
7665
+ }
7666
+ function filterStaleRecallSummaryRows(output2) {
7667
+ return output2.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
7668
+ }
7669
+ function isStaleRecallSummaryRow(line) {
7670
+ return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
7671
+ }
7672
+ async function scanRoots(config, options) {
7673
+ const accountRoot = (0, import_node_path4.join)(config.agentContextHome, "data", "viking", config.account);
7674
+ const roots = [
7675
+ {
7676
+ path: (0, import_node_path4.join)(accountRoot, "user", uriSegment(config.user), "memories"),
7677
+ uri: `viking://user/${uriSegment(config.user)}/memories`
7678
+ }
7679
+ ];
7680
+ const query = options.query;
7681
+ if (query) {
7682
+ const project = await inferProjectFromQuery(config.manifestPath, query);
7683
+ const projectPath = project?.uri.startsWith("viking://resources/") ? (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")) : void 0;
7684
+ if (project && projectPath) {
7685
+ roots.push({ path: projectPath, uri: project.uri });
7686
+ }
7687
+ }
7688
+ if (options.includeManifestResources === true) {
7689
+ roots.push(...await manifestResourceRoots(config, accountRoot));
7690
+ }
7691
+ const scanAgentSkills = options.includeAgentSkills === true || (query ? /\bskills?\b/.test(query.toLowerCase()) : false);
7692
+ if (scanAgentSkills) {
7693
+ roots.push({ path: (0, import_node_path4.join)(accountRoot, "resources", "agent-skills"), uri: "viking://resources/agent-skills" });
7694
+ }
7695
+ return dedupeRoots(roots);
7696
+ }
7697
+ async function manifestResourceRoots(config, accountRoot) {
7698
+ try {
7699
+ const manifest = await readSeedManifest(config.manifestPath);
7700
+ return manifest.projects.filter((project) => project.uri.startsWith("viking://resources/")).map((project) => ({
7701
+ path: (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")),
7702
+ uri: project.uri
7703
+ }));
7704
+ } catch (_err) {
7705
+ return [];
7706
+ }
7707
+ }
7708
+ function dedupeRoots(roots) {
7709
+ const seen = /* @__PURE__ */ new Set();
7710
+ const deduped = [];
7711
+ for (const root of roots) {
7712
+ if (seen.has(root.uri)) {
7713
+ continue;
7714
+ }
7715
+ seen.add(root.uri);
7716
+ deduped.push(root);
7717
+ }
7718
+ return deduped;
7719
+ }
7720
+ async function staleSidecars(rootPath, rootUri) {
7721
+ const results = [];
7722
+ async function visit(path, depth) {
7723
+ let entries;
7724
+ try {
7725
+ entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
7726
+ } catch (_err) {
7727
+ return;
7728
+ }
7729
+ for (const entry of entries) {
7730
+ const childPath = (0, import_node_path4.join)(path, entry.name);
7731
+ if (entry.isFile() && isSummarySidecar(entry.name)) {
7732
+ let content;
7733
+ try {
7734
+ content = await (0, import_promises4.readFile)(childPath, "utf8");
7735
+ } catch (_err) {
7736
+ continue;
7737
+ }
7738
+ if (!isStaleSummary(content)) {
7739
+ continue;
7740
+ }
7741
+ const parentPath = (0, import_node_path4.dirname)(childPath);
7742
+ const parentRelative = toPosixPath((0, import_node_path4.relative)(rootPath, parentPath));
7743
+ const relativePath = toPosixPath((0, import_node_path4.relative)(rootPath, childPath));
7744
+ results.push({
7745
+ content: content.trim(),
7746
+ relativePath,
7747
+ uri: parentRelative ? `${trimLocalRootUri(rootUri)}/${parentRelative}` : trimLocalRootUri(rootUri)
7748
+ });
7749
+ } else if (entry.isDirectory() && depth < MAX_SCAN_DEPTH) {
7750
+ await visit(childPath, depth + 1);
7751
+ }
7752
+ }
7753
+ }
7754
+ await visit(rootPath, 0);
7755
+ return results;
7756
+ }
7757
+ function isSummarySidecar(name) {
7758
+ return name === ".abstract.md" || name === ".overview.md";
7759
+ }
7760
+ function isStaleSummary(content) {
7761
+ return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
7762
+ }
7763
+ function trimLocalRootUri(uri) {
7764
+ return uri.endsWith("/") ? uri.slice(0, -1) : uri;
7765
+ }
7766
+ async function readAutoRepairState(config) {
7767
+ const raw = await readFileIfExists(autoRepairStatePath(config));
7768
+ if (!raw) {
7769
+ return { entries: {}, version: 1 };
7770
+ }
7771
+ try {
7772
+ const parsed = JSON.parse(raw);
7773
+ if (!isJsonObject(parsed) || parsed.version !== 1 || !isJsonObject(parsed.entries)) {
7774
+ return { entries: {}, version: 1 };
7775
+ }
7776
+ const entries = {};
7777
+ for (const [uri, entry] of Object.entries(parsed.entries)) {
7778
+ if (isJsonObject(entry) && typeof entry.signature === "string" && typeof entry.repairedAt === "string") {
7779
+ entries[uri] = { repairedAt: entry.repairedAt, signature: entry.signature };
7780
+ }
7781
+ }
7782
+ return { entries, version: 1 };
7783
+ } catch (_err) {
7784
+ return { entries: {}, version: 1 };
7785
+ }
7786
+ }
7787
+ async function writeAutoRepairState(config, state) {
7788
+ const path = autoRepairStatePath(config);
7789
+ await ensureDirectory((0, import_node_path4.dirname)(path), false);
7790
+ await (0, import_promises4.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
7791
+ `, { encoding: "utf8", mode: 384 });
7792
+ }
7793
+ function autoRepairStatePath(config) {
7794
+ return (0, import_node_path4.join)(config.agentContextHome, AUTO_REPAIR_STATE_FILE);
7795
+ }
7796
+ function indexRepairWarning(uri, result) {
7797
+ return `${uri}: ${result.stderr.trim() || result.stdout.trim() || "reindex failed"}`;
7798
+ }
7799
+
7800
+ // src/memory_hygiene.ts
7801
+ var import_node_path5 = require("node:path");
7477
7802
  var HYGIENE_SOURCES_HEADING = "## Threadnote Hygiene Sources";
7478
7803
  var STALE_HANDOFF_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
7479
7804
  function parseMemoryDocument(uri, content) {
@@ -7766,7 +8091,7 @@ function branchFromBody(body) {
7766
8091
  return branch?.split(/\s+/)[0]?.replace(/[.,;:]+$/g, "");
7767
8092
  }
7768
8093
  function topicFromUri(uri) {
7769
- const name = (0, import_node_path3.basename)(uri).replace(/\.md$/, "");
8094
+ const name = (0, import_node_path5.basename)(uri).replace(/\.md$/, "");
7770
8095
  return name.startsWith("threadnote-") ? void 0 : name;
7771
8096
  }
7772
8097
  function parseProjectFromUri(uri) {
@@ -7796,7 +8121,7 @@ function preferredKeepRecord(records, topic) {
7796
8121
  }
7797
8122
  function isStableRecord(record, topic) {
7798
8123
  const recordTopic = topic ?? topicForRecord(record);
7799
- return recordTopic !== void 0 && (0, import_node_path3.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
8124
+ return recordTopic !== void 0 && (0, import_node_path5.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
7800
8125
  }
7801
8126
  function sortedNewestFirst(records) {
7802
8127
  return [...records].sort((left, right) => {
@@ -7907,58 +8232,10 @@ function formatPlanSection(title, lines) {
7907
8232
  return [`${title}:`, ...lines.map((line) => `- ${line}`)].join("\n");
7908
8233
  }
7909
8234
 
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
8235
  // src/share.ts
7959
- var import_promises4 = require("node:fs/promises");
8236
+ var import_promises5 = require("node:fs/promises");
7960
8237
  var import_node_os4 = require("node:os");
7961
- var import_node_path5 = require("node:path");
8238
+ var import_node_path6 = require("node:path");
7962
8239
  var TEAMS_FILE_VERSION = 1;
7963
8240
  var SHARED_SEGMENT = "shared";
7964
8241
  var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
@@ -8027,8 +8304,8 @@ async function runShareInit(config, remoteUrl, options) {
8027
8304
  if (await exists(gitdir)) {
8028
8305
  throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
8029
8306
  }
8030
- await ensureDirectory((0, import_node_path5.dirname)(worktree), dryRun);
8031
- await ensureDirectory((0, import_node_path5.dirname)(gitdir), dryRun);
8307
+ await ensureDirectory((0, import_node_path6.dirname)(worktree), dryRun);
8308
+ await ensureDirectory((0, import_node_path6.dirname)(gitdir), dryRun);
8032
8309
  const git = await requiredExecutable("git");
8033
8310
  await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
8034
8311
  const newConfig = {
@@ -8059,7 +8336,7 @@ async function runShareInit(config, remoteUrl, options) {
8059
8336
  var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
8060
8337
  var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
8061
8338
  async function ensureSharedGitignore(worktree, git, push) {
8062
- const gitignorePath = (0, import_node_path5.join)(worktree, ".gitignore");
8339
+ const gitignorePath = (0, import_node_path6.join)(worktree, ".gitignore");
8063
8340
  const existing = await readFileIfExists(gitignorePath) ?? "";
8064
8341
  const lines = existing.split("\n").map((line) => line.trim());
8065
8342
  const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
@@ -8078,7 +8355,7 @@ async function ensureSharedGitignore(worktree, git, push) {
8078
8355
  segments.push(SHARED_GITIGNORE_HEADER, "\n");
8079
8356
  }
8080
8357
  segments.push(missingPatterns.join("\n"), "\n");
8081
- await (0, import_promises4.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
8358
+ await (0, import_promises5.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
8082
8359
  console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
8083
8360
  await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
8084
8361
  const commitResult = await runCommand(
@@ -8160,7 +8437,7 @@ function autoShareState(config) {
8160
8437
  return state;
8161
8438
  }
8162
8439
  function pendingReindexesPath(config) {
8163
- return (0, import_node_path5.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
8440
+ return (0, import_node_path6.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
8164
8441
  }
8165
8442
  async function loadPendingReindexes(config, state) {
8166
8443
  const raw = await readFileIfExists(pendingReindexesPath(config));
@@ -8197,18 +8474,18 @@ async function loadPendingReindexes(config, state) {
8197
8474
  async function writePendingReindexes(config, state) {
8198
8475
  const path = pendingReindexesPath(config);
8199
8476
  if (state.pendingReindexes.size === 0) {
8200
- await (0, import_promises4.rm)(path, { force: true });
8477
+ await (0, import_promises5.rm)(path, { force: true });
8201
8478
  return;
8202
8479
  }
8203
8480
  const contents = {
8204
8481
  teams: Object.fromEntries(state.pendingReindexes),
8205
8482
  version: 1
8206
8483
  };
8207
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
8484
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
8208
8485
  const tempPath = `${path}.${process.pid}.tmp`;
8209
- await (0, import_promises4.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
8486
+ await (0, import_promises5.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
8210
8487
  `, { encoding: "utf8", mode: 384 });
8211
- await (0, import_promises4.rename)(tempPath, path);
8488
+ await (0, import_promises5.rename)(tempPath, path);
8212
8489
  }
8213
8490
  async function refreshShareUpdateStateLocked(config, state, options) {
8214
8491
  const now = Date.now();
@@ -8297,7 +8574,7 @@ async function runShareSync(config, options) {
8297
8574
  if (dryRun) {
8298
8575
  console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
8299
8576
  } 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"))) {
8577
+ 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
8578
  throw new Error(
8302
8579
  `git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
8303
8580
  Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
@@ -8359,7 +8636,7 @@ async function runShareSyncQuiet(config, state, options) {
8359
8636
  const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], false);
8360
8637
  const pullResult = await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
8361
8638
  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"))) {
8639
+ 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
8640
  throw new Error(
8364
8641
  `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
8642
  );
@@ -8598,8 +8875,8 @@ async function runShareRename(config, options) {
8598
8875
  console.log(`Would write teams file: ${teamsFilePath(config)}`);
8599
8876
  return;
8600
8877
  }
8601
- await (0, import_promises4.rename)(oldTeam.config.worktree, newWorktree);
8602
- await (0, import_promises4.rename)(oldTeam.config.gitdir, newGitdir);
8878
+ await (0, import_promises5.rename)(oldTeam.config.worktree, newWorktree);
8879
+ await (0, import_promises5.rename)(oldTeam.config.gitdir, newGitdir);
8603
8880
  await writeTeamsFile(config, updatedFile);
8604
8881
  const git = await requiredExecutable("git");
8605
8882
  await runCommand(git, ["-C", newWorktree, "config", "core.worktree", newWorktree]);
@@ -8681,12 +8958,12 @@ async function preserveSharedMemoriesLocally(config, team, dryRun) {
8681
8958
  const files = await walkMemoryFiles(team.worktree);
8682
8959
  let preserved = 0;
8683
8960
  for (const file of files) {
8684
- const rel = (0, import_node_path5.relative)(team.worktree, file).split(import_node_path5.sep).join("/");
8961
+ const rel = (0, import_node_path6.relative)(team.worktree, file).split(import_node_path6.sep).join("/");
8685
8962
  if (!rel.startsWith("durable/")) {
8686
8963
  continue;
8687
8964
  }
8688
8965
  const targetUri = `viking://user/${uriSegment(config.user)}/memories/${rel}`;
8689
- const content = await (0, import_promises4.readFile)(file, "utf8");
8966
+ const content = await (0, import_promises5.readFile)(file, "utf8");
8690
8967
  if (dryRun) {
8691
8968
  console.log(`Would preserve ${rel} -> ${targetUri}`);
8692
8969
  } else {
@@ -8725,10 +9002,10 @@ function normalizeTeamName(input2) {
8725
9002
  return candidate;
8726
9003
  }
8727
9004
  function teamsFilePath(config) {
8728
- return (0, import_node_path5.join)(config.agentContextHome, "share", "teams.json");
9005
+ return (0, import_node_path6.join)(config.agentContextHome, "share", "teams.json");
8729
9006
  }
8730
9007
  function teamWorktreePath(config, team) {
8731
- return (0, import_node_path5.join)(
9008
+ return (0, import_node_path6.join)(
8732
9009
  config.agentContextHome,
8733
9010
  "data",
8734
9011
  "viking",
@@ -8741,7 +9018,7 @@ function teamWorktreePath(config, team) {
8741
9018
  );
8742
9019
  }
8743
9020
  function teamGitdirPath(config, team) {
8744
- return (0, import_node_path5.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
9021
+ return (0, import_node_path6.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
8745
9022
  }
8746
9023
  async function readTeamsFile(config) {
8747
9024
  const path = teamsFilePath(config);
@@ -8784,13 +9061,13 @@ async function readTeamsFile(config) {
8784
9061
  }
8785
9062
  async function writeTeamsFile(config, contents) {
8786
9063
  const path = teamsFilePath(config);
8787
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
9064
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
8788
9065
  const serializable = {
8789
9066
  defaultTeam: contents.defaultTeam,
8790
9067
  teams: contents.teams,
8791
9068
  version: contents.version
8792
9069
  };
8793
- await (0, import_promises4.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
9070
+ await (0, import_promises5.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
8794
9071
  `, { encoding: "utf8", mode: 384 });
8795
9072
  }
8796
9073
  async function resolveTeam(config, requested) {
@@ -8820,7 +9097,7 @@ async function assertWorktreeUsable(worktree) {
8820
9097
  if (!await isDirectory(worktree)) {
8821
9098
  throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
8822
9099
  }
8823
- const entries = await (0, import_promises4.readdir)(worktree);
9100
+ const entries = await (0, import_promises5.readdir)(worktree);
8824
9101
  if (entries.length > 0) {
8825
9102
  const preview = entries.slice(0, 5).join(", ");
8826
9103
  const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
@@ -8844,7 +9121,7 @@ async function walkMemoryFiles(root) {
8844
9121
  async function visit(path, depth) {
8845
9122
  let entries;
8846
9123
  try {
8847
- entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
9124
+ entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
8848
9125
  } catch (err) {
8849
9126
  console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
8850
9127
  return;
@@ -8853,7 +9130,7 @@ async function walkMemoryFiles(root) {
8853
9130
  if (entry.name === ".git") {
8854
9131
  continue;
8855
9132
  }
8856
- const full = (0, import_node_path5.join)(path, entry.name);
9133
+ const full = (0, import_node_path6.join)(path, entry.name);
8857
9134
  if (entry.isDirectory()) {
8858
9135
  if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
8859
9136
  continue;
@@ -8877,7 +9154,7 @@ async function walkMemoryFiles(root) {
8877
9154
  return out;
8878
9155
  }
8879
9156
  function workfileToVikingUri(config, team, filePath) {
8880
- const rel = (0, import_node_path5.relative)(team.worktree, filePath).split(import_node_path5.sep).join("/");
9157
+ const rel = (0, import_node_path6.relative)(team.worktree, filePath).split(import_node_path6.sep).join("/");
8881
9158
  return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
8882
9159
  }
8883
9160
  function isInSharedNamespace(config, uri) {
@@ -9025,13 +9302,14 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun, op
9025
9302
  }
9026
9303
  return;
9027
9304
  }
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");
9305
+ const stagingDir = await (0, import_promises5.mkdtemp)((0, import_node_path6.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
9306
+ const tempPath = (0, import_node_path6.join)(stagingDir, "body.txt");
9030
9307
  try {
9031
- await (0, import_promises4.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
9308
+ await (0, import_promises5.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
9032
9309
  await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode, options);
9310
+ await refreshMemoryIndex(config, ov, uri, options);
9033
9311
  } finally {
9034
- await (0, import_promises4.rm)(stagingDir, { force: true, recursive: true });
9312
+ await (0, import_promises5.rm)(stagingDir, { force: true, recursive: true });
9035
9313
  }
9036
9314
  }
9037
9315
  var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
@@ -9086,6 +9364,27 @@ async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode, opti
9086
9364
  }
9087
9365
  }
9088
9366
  }
9367
+ async function refreshMemoryIndex(config, ov, uri, options = {}) {
9368
+ const result = await runCommand(
9369
+ ov,
9370
+ withIdentity(config, ["reindex", uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
9371
+ { allowFailure: true }
9372
+ );
9373
+ if (result.exitCode === 0) {
9374
+ if (options.quiet !== true && result.stdout.trim()) {
9375
+ console.log(result.stdout.trim());
9376
+ }
9377
+ if (options.quiet !== true && result.stderr.trim()) {
9378
+ console.error(result.stderr.trim());
9379
+ }
9380
+ return;
9381
+ }
9382
+ if (options.quiet !== true) {
9383
+ console.error(
9384
+ `Memory stored, but index refresh failed for ${uri}: ${result.stderr.trim() || result.stdout.trim()}`
9385
+ );
9386
+ }
9387
+ }
9089
9388
  async function waitForOvQueue(ov, config, options = {}) {
9090
9389
  const result = await runCommand(ov, withIdentity(config, ["wait", "--timeout", "120"]), { allowFailure: true });
9091
9390
  if (options.quiet !== true && result.stdout.trim()) {
@@ -9106,7 +9405,7 @@ ${stdout2}`.toLowerCase();
9106
9405
  return output2.includes("resource is busy") || output2.includes("resource is being processed");
9107
9406
  }
9108
9407
  async function ingestSingleFile(ov, config, uri, filePath, initialMode, options = {}) {
9109
- const content = await (0, import_promises4.readFile)(filePath, "utf8");
9408
+ const content = await (0, import_promises5.readFile)(filePath, "utf8");
9110
9409
  await writeMemoryFile(config, ov, uri, content, initialMode, false, options);
9111
9410
  }
9112
9411
  async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
@@ -9171,8 +9470,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
9171
9470
  const oldRel = entries[index + 1];
9172
9471
  const newRel = entries[index + 2];
9173
9472
  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" });
9473
+ changes.push({ path: (0, import_node_path6.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
9474
+ changes.push({ path: (0, import_node_path6.join)(worktree, newRel), relativePath: newRel, status: "added" });
9176
9475
  }
9177
9476
  index += 3;
9178
9477
  continue;
@@ -9180,7 +9479,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
9180
9479
  const rel = entries[index + 1];
9181
9480
  if (rel) {
9182
9481
  const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
9183
- changes.push({ path: (0, import_node_path5.join)(worktree, rel), relativePath: rel, status });
9482
+ changes.push({ path: (0, import_node_path6.join)(worktree, rel), relativePath: rel, status });
9184
9483
  }
9185
9484
  index += 2;
9186
9485
  }
@@ -9302,7 +9601,7 @@ async function runMigrateMemories(config, options) {
9302
9601
  const candidates = await legacyMemoryCandidates(config, sourceAccounts);
9303
9602
  const existingHashes = await existingDurableMemoryHashes(config);
9304
9603
  const ov = await openVikingCliForMode(dryRun);
9305
- const migrationPath = (0, import_node_path6.join)(config.agentContextHome, "legacy-memory-migration.txt");
9604
+ const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "legacy-memory-migration.txt");
9306
9605
  let duplicateCount = 0;
9307
9606
  let migratedCount = 0;
9308
9607
  let sensitiveCount = 0;
@@ -9338,8 +9637,8 @@ async function runMigrateMemories(config, options) {
9338
9637
  }
9339
9638
  console.log(`${dryRun ? "Would migrate" : "Migrating"} ${legacySourceLabel(candidate)} -> ${memoryUri}`);
9340
9639
  if (!dryRun) {
9341
- await (0, import_promises5.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
9342
- await (0, import_promises5.chmod)(migrationPath, 384);
9640
+ await (0, import_promises6.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
9641
+ await (0, import_promises6.chmod)(migrationPath, 384);
9343
9642
  await writeDurableMemoryFile(ov, config, memoryUri, migrationPath, "create");
9344
9643
  existingHashes.add(candidate.hash);
9345
9644
  }
@@ -9347,7 +9646,7 @@ async function runMigrateMemories(config, options) {
9347
9646
  }
9348
9647
  } finally {
9349
9648
  if (!dryRun) {
9350
- await (0, import_promises5.rm)(migrationPath, { force: true });
9649
+ await (0, import_promises6.rm)(migrationPath, { force: true });
9351
9650
  }
9352
9651
  }
9353
9652
  console.log(
@@ -9365,7 +9664,7 @@ async function runMigrateLifecycle(config, options) {
9365
9664
  const limit = options.limit ? parsePositiveInteger(options.limit, "lifecycle migration limit") : void 0;
9366
9665
  const ov = await openVikingCliForMode(dryRun);
9367
9666
  const candidates = await legacyLifecycleHandoffCandidates(config);
9368
- const migrationPath = (0, import_node_path6.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
9667
+ const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
9369
9668
  let existingCount = 0;
9370
9669
  let migratedCount = 0;
9371
9670
  let skippedCount = 0;
@@ -9386,8 +9685,8 @@ async function runMigrateLifecycle(config, options) {
9386
9685
  existingCount += 1;
9387
9686
  console.log(`Archived copy already exists; cleaning up legacy source: ${candidate.sourceUri}`);
9388
9687
  } else {
9389
- await (0, import_promises5.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
9390
- await (0, import_promises5.chmod)(migrationPath, 384);
9688
+ await (0, import_promises6.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
9689
+ await (0, import_promises6.chmod)(migrationPath, 384);
9391
9690
  await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, candidate.metadata));
9392
9691
  await writeDurableMemoryFile(ov, config, destinationUri, migrationPath, "create");
9393
9692
  }
@@ -9403,7 +9702,7 @@ async function runMigrateLifecycle(config, options) {
9403
9702
  }
9404
9703
  } finally {
9405
9704
  if (!dryRun) {
9406
- await (0, import_promises5.rm)(migrationPath, { force: true });
9705
+ await (0, import_promises6.rm)(migrationPath, { force: true });
9407
9706
  }
9408
9707
  }
9409
9708
  console.log(
@@ -9423,6 +9722,19 @@ async function runRecall(config, options) {
9423
9722
  const ov = await openVikingCliForMode(options.dryRun === true);
9424
9723
  const query = await enrichRecallQueryWithWorkspaceContext(options.query);
9425
9724
  const projectQuery = await enrichRecallQueryWithWorkspaceProjectContext(options.query);
9725
+ let indexRepairMessages;
9726
+ try {
9727
+ const indexRepair = await repairStaleRecallIndex(config, ov, {
9728
+ dryRun: options.dryRun === true,
9729
+ query: projectQuery
9730
+ });
9731
+ indexRepairMessages = formatRecallIndexRepairMessages(indexRepair, { dryRun: options.dryRun === true });
9732
+ } catch (err) {
9733
+ indexRepairMessages = [`Auto-index repair warning: ${err instanceof Error ? err.message : String(err)}`];
9734
+ }
9735
+ for (const message of indexRepairMessages) {
9736
+ console.log(message);
9737
+ }
9426
9738
  const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
9427
9739
  const args = ["search", query];
9428
9740
  if (inferredUri) {
@@ -9433,9 +9745,9 @@ async function runRecall(config, options) {
9433
9745
  args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
9434
9746
  }
9435
9747
  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"));
9748
+ const baseOutput = await runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
9749
+ if (baseOutput) {
9750
+ recallOutputs.push(baseOutput);
9439
9751
  }
9440
9752
  const seededOutput = await augmentRecallWithSeededResources(
9441
9753
  config,
@@ -9474,8 +9786,20 @@ async function augmentRecallWithSeededResources(config, ov, options, inferredUri
9474
9786
  }
9475
9787
  console.log(`
9476
9788
  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;
9789
+ return runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
9790
+ }
9791
+ async function runRecallSearch(config, ov, args, options) {
9792
+ const fullArgs = withIdentity(config, args);
9793
+ console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, fullArgs)}`);
9794
+ if (options.dryRun) {
9795
+ return void 0;
9796
+ }
9797
+ const result = await runCommand(ov, fullArgs);
9798
+ const output2 = filterStaleRecallSummaryRows([result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n"));
9799
+ if (output2) {
9800
+ console.log(output2);
9801
+ }
9802
+ return output2 || void 0;
9479
9803
  }
9480
9804
  async function runRead(config, uri, options) {
9481
9805
  assertVikingUri(uri);
@@ -9541,15 +9865,15 @@ async function runCompact(config, options) {
9541
9865
  return;
9542
9866
  }
9543
9867
  const ov = await openVikingCliForMode(false);
9544
- const updatePath = (0, import_node_path6.join)(config.agentContextHome, "compact-memory-update.txt");
9868
+ const updatePath = (0, import_node_path7.join)(config.agentContextHome, "compact-memory-update.txt");
9545
9869
  try {
9546
9870
  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);
9871
+ await (0, import_promises6.writeFile)(updatePath, action.content, { encoding: "utf8", mode: 384 });
9872
+ await (0, import_promises6.chmod)(updatePath, 384);
9549
9873
  await writeDurableMemoryFile(ov, config, action.uri, updatePath, "replace");
9550
9874
  }
9551
9875
  } finally {
9552
- await (0, import_promises5.rm)(updatePath, { force: true });
9876
+ await (0, import_promises6.rm)(updatePath, { force: true });
9553
9877
  }
9554
9878
  for (const action of plan.archives) {
9555
9879
  await runArchive(config, action.uri, {
@@ -9605,7 +9929,7 @@ async function scopedCompactRecords(config, options) {
9605
9929
  const uriDirectory = memoryUriDirectoryForCompact(config, kind, options.project);
9606
9930
  let entries;
9607
9931
  try {
9608
- entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
9932
+ entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
9609
9933
  } catch (_err) {
9610
9934
  continue;
9611
9935
  }
@@ -9613,7 +9937,7 @@ async function scopedCompactRecords(config, options) {
9613
9937
  if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
9614
9938
  continue;
9615
9939
  }
9616
- const content = await readTextIfExists((0, import_node_path6.join)(directory, entry.name));
9940
+ const content = await readTextIfExists((0, import_node_path7.join)(directory, entry.name));
9617
9941
  if (!content) {
9618
9942
  continue;
9619
9943
  }
@@ -9651,11 +9975,11 @@ function localMemoryDirectoryForCompact(config, kind, project) {
9651
9975
  const projectSegment = uriSegment(project);
9652
9976
  switch (kind) {
9653
9977
  case "durable":
9654
- return (0, import_node_path6.join)(root, "durable", "projects", projectSegment);
9978
+ return (0, import_node_path7.join)(root, "durable", "projects", projectSegment);
9655
9979
  case "handoff":
9656
- return (0, import_node_path6.join)(root, "handoffs", "active", projectSegment);
9980
+ return (0, import_node_path7.join)(root, "handoffs", "active", projectSegment);
9657
9981
  case "incident":
9658
- return (0, import_node_path6.join)(root, "incidents", "active", projectSegment);
9982
+ return (0, import_node_path7.join)(root, "incidents", "active", projectSegment);
9659
9983
  }
9660
9984
  }
9661
9985
  function memoryUriDirectoryForCompact(config, kind, project) {
@@ -9675,11 +9999,11 @@ function localMemoryPathForUri(config, uri) {
9675
9999
  if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
9676
10000
  return void 0;
9677
10001
  }
9678
- const relative4 = uri.slice(prefix.length);
9679
- if (relative4.includes("..") || relative4.startsWith("/")) {
10002
+ const relative5 = uri.slice(prefix.length);
10003
+ if (relative5.includes("..") || relative5.startsWith("/")) {
9680
10004
  return void 0;
9681
10005
  }
9682
- return (0, import_node_path6.join)(localUserMemoriesRoot(config), ...relative4.split("/"));
10006
+ return (0, import_node_path7.join)(localUserMemoriesRoot(config), ...relative5.split("/"));
9683
10007
  }
9684
10008
  async function runList(config, uri, options) {
9685
10009
  assertVikingUri(uri);
@@ -9773,7 +10097,7 @@ async function runForget(config, uri, options) {
9773
10097
  }
9774
10098
  async function runExportPack(config, options) {
9775
10099
  const ov = await openVikingCliForMode(options.dryRun === true);
9776
- const defaultPath = (0, import_node_path6.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
10100
+ const defaultPath = (0, import_node_path7.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
9777
10101
  const outputPath = expandPath(options.path ?? defaultPath);
9778
10102
  await maybeRun(options.dryRun === true, ov, withIdentity(config, ["export", options.uri ?? "viking://", outputPath]));
9779
10103
  }
@@ -9789,12 +10113,25 @@ async function runImportPack(config, options) {
9789
10113
  );
9790
10114
  }
9791
10115
  async function inferRecallUri(config, query) {
9792
- if (!/\bskills?\b/.test(query.toLowerCase())) {
10116
+ if (!hasAgentSkillCatalogIntent(query)) {
9793
10117
  return void 0;
9794
10118
  }
9795
10119
  const project = await inferProjectFromQuery(config.manifestPath, query);
9796
10120
  return project ? `viking://resources/agent-skills/repo-local-${uriSegment(project.name)}` : "viking://resources/agent-skills";
9797
10121
  }
10122
+ function hasAgentSkillCatalogIntent(query) {
10123
+ const normalized = query.toLowerCase();
10124
+ if (!/\bskills?\b/.test(normalized)) {
10125
+ return false;
10126
+ }
10127
+ if (/\bseed[- ]skills?\b/.test(normalized) || /\bskills?\s+seed(?:ing)?\b/.test(normalized)) {
10128
+ return false;
10129
+ }
10130
+ if (/^\s*skills?\s*$/.test(normalized)) {
10131
+ return true;
10132
+ }
10133
+ 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);
10134
+ }
9798
10135
  async function printExactMemoryMatches(config, ov, query, options) {
9799
10136
  const terms = exactRecallTerms(query);
9800
10137
  if (terms.length === 0) {
@@ -9833,7 +10170,7 @@ async function storeMemory(config, options) {
9833
10170
  await storeSharedMemoryReplacement(config, ov, options, options.replaceUri);
9834
10171
  return;
9835
10172
  }
9836
- const memoryPath = (0, import_node_path6.join)(config.agentContextHome, "last-memory.txt");
10173
+ const memoryPath = (0, import_node_path7.join)(config.agentContextHome, "last-memory.txt");
9837
10174
  const candidateMetadata = { ...options.metadata, supersedes: options.replaceUri };
9838
10175
  const candidateMemory = formatMemoryDocument2(options.title, candidateMetadata, options.bodyText);
9839
10176
  const memoryUri = memoryUriFor(config, candidateMemory, candidateMetadata);
@@ -9865,8 +10202,8 @@ async function storeMemory(config, options) {
9865
10202
  }
9866
10203
  return;
9867
10204
  }
9868
- await (0, import_promises5.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
9869
- await (0, import_promises5.chmod)(memoryPath, 384);
10205
+ await (0, import_promises6.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
10206
+ await (0, import_promises6.chmod)(memoryPath, 384);
9870
10207
  await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, finalMetadata));
9871
10208
  await writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode);
9872
10209
  console.log(`Stored memory: ${memoryUri}`);
@@ -9925,7 +10262,7 @@ async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
9925
10262
  console.log(`Updated shared memory: ${targetUri}`);
9926
10263
  }
9927
10264
  async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode) {
9928
- const content = await (0, import_promises5.readFile)(memoryPath, "utf8");
10265
+ const content = await (0, import_promises6.readFile)(memoryPath, "utf8");
9929
10266
  await writeMemoryFile(config, ov, memoryUri, content, writeMode, false);
9930
10267
  }
9931
10268
  async function removeVikingResourceWithRetry(ov, config, uri) {
@@ -9982,10 +10319,10 @@ async function hasLegacyLifecycleHandoffCandidates(config) {
9982
10319
  return (await legacyLifecycleHandoffCandidates(config, 1)).length > 0;
9983
10320
  }
9984
10321
  async function legacyLifecycleHandoffCandidates(config, limit) {
9985
- const eventsRoot = (0, import_node_path6.join)(localUserMemoriesRoot(config), "events");
10322
+ const eventsRoot = (0, import_node_path7.join)(localUserMemoriesRoot(config), "events");
9986
10323
  let entries;
9987
10324
  try {
9988
- entries = await (0, import_promises5.readdir)(eventsRoot, { withFileTypes: true });
10325
+ entries = await (0, import_promises6.readdir)(eventsRoot, { withFileTypes: true });
9989
10326
  } catch (_err) {
9990
10327
  return [];
9991
10328
  }
@@ -9994,7 +10331,7 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
9994
10331
  if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
9995
10332
  continue;
9996
10333
  }
9997
- const sourcePath = (0, import_node_path6.join)(eventsRoot, entry.name);
10334
+ const sourcePath = (0, import_node_path7.join)(eventsRoot, entry.name);
9998
10335
  const original = await readTextIfExists(sourcePath);
9999
10336
  if (!original || !isClearLegacyHandoffMemory(original) || sensitiveMemoryReason(original)) {
10000
10337
  continue;
@@ -10135,7 +10472,7 @@ function inferLegacyProject(memory) {
10135
10472
  return "general";
10136
10473
  }
10137
10474
  const trimmed = explicit.trim().replace(/[`.,;]+$/g, "");
10138
- return trimmed.includes("/") ? (0, import_node_path6.basename)(trimmed) : trimmed;
10475
+ return trimmed.includes("/") ? (0, import_node_path7.basename)(trimmed) : trimmed;
10139
10476
  }
10140
10477
  function parseOptionalMemoryKind2(value) {
10141
10478
  if (!value) {
@@ -10175,14 +10512,14 @@ async function legacySourceAccounts(config, options) {
10175
10512
  async function legacyMemoryCandidates(config, sourceAccounts) {
10176
10513
  const candidates = [];
10177
10514
  for (const sourceAccount of sourceAccounts) {
10178
- const sessionRoot = (0, import_node_path6.join)(localVikingDataRoot(config), sourceAccount, "session");
10515
+ const sessionRoot = (0, import_node_path7.join)(localVikingDataRoot(config), sourceAccount, "session");
10179
10516
  for (const sourceSession of await childDirectoryNames(sessionRoot)) {
10180
- const historyRoot = (0, import_node_path6.join)(sessionRoot, sourceSession, "history");
10517
+ const historyRoot = (0, import_node_path7.join)(sessionRoot, sourceSession, "history");
10181
10518
  for (const sourceArchive of await childDirectoryNames(historyRoot)) {
10182
10519
  if (!sourceArchive.startsWith("archive_")) {
10183
10520
  continue;
10184
10521
  }
10185
- const sourcePath = (0, import_node_path6.join)(historyRoot, sourceArchive, "messages.jsonl");
10522
+ const sourcePath = (0, import_node_path7.join)(historyRoot, sourceArchive, "messages.jsonl");
10186
10523
  for (const text of await legacyMemoryTexts(sourcePath)) {
10187
10524
  candidates.push({
10188
10525
  comparableHash: sha256(comparableMemoryText(text)),
@@ -10245,12 +10582,12 @@ async function existingDurableMemoryHashes(config) {
10245
10582
  async function collectDurableMemoryHashes(root, hashes) {
10246
10583
  let entries;
10247
10584
  try {
10248
- entries = await (0, import_promises5.readdir)(root, { withFileTypes: true });
10585
+ entries = await (0, import_promises6.readdir)(root, { withFileTypes: true });
10249
10586
  } catch (_err) {
10250
10587
  return;
10251
10588
  }
10252
10589
  for (const entry of entries) {
10253
- const path = (0, import_node_path6.join)(root, entry.name);
10590
+ const path = (0, import_node_path7.join)(root, entry.name);
10254
10591
  if (entry.isDirectory()) {
10255
10592
  await collectDurableMemoryHashes(path, hashes);
10256
10593
  continue;
@@ -10267,12 +10604,12 @@ async function collectDurableMemoryHashes(root, hashes) {
10267
10604
  }
10268
10605
  }
10269
10606
  function isDurableMemoryPath(path) {
10270
- return path.split(import_node_path6.sep).includes("memories");
10607
+ return path.split(import_node_path7.sep).includes("memories");
10271
10608
  }
10272
10609
  async function childDirectoryNames(path) {
10273
10610
  let entries;
10274
10611
  try {
10275
- entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
10612
+ entries = await (0, import_promises6.readdir)(path, { withFileTypes: true });
10276
10613
  } catch (_err) {
10277
10614
  return [];
10278
10615
  }
@@ -10280,11 +10617,11 @@ async function childDirectoryNames(path) {
10280
10617
  }
10281
10618
  async function readTextIfExists(path) {
10282
10619
  try {
10283
- const pathStat = await (0, import_promises5.stat)(path);
10620
+ const pathStat = await (0, import_promises6.stat)(path);
10284
10621
  if (!pathStat.isFile()) {
10285
10622
  return void 0;
10286
10623
  }
10287
- return await (0, import_promises5.readFile)(path, "utf8");
10624
+ return await (0, import_promises6.readFile)(path, "utf8");
10288
10625
  } catch (_err) {
10289
10626
  return void 0;
10290
10627
  }
@@ -10311,10 +10648,10 @@ function legacySourceLabel(candidate) {
10311
10648
  return `${candidate.sourceAccount}/${candidate.sourceSession}/${candidate.sourceArchive}`;
10312
10649
  }
10313
10650
  function localVikingDataRoot(config) {
10314
- return (0, import_node_path6.join)(config.agentContextHome, "data", "viking");
10651
+ return (0, import_node_path7.join)(config.agentContextHome, "data", "viking");
10315
10652
  }
10316
10653
  function localUserMemoriesRoot(config) {
10317
- return (0, import_node_path6.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
10654
+ return (0, import_node_path7.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
10318
10655
  }
10319
10656
  function uniqueStrings(values) {
10320
10657
  return [...new Set(values)].sort();
@@ -10330,7 +10667,7 @@ async function buildHandoff(options) {
10330
10667
  const status = await gitValue(["status", "--short"], repoRoot) ?? "";
10331
10668
  const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
10332
10669
  const touchedFiles = await gitTouchedFiles(repoRoot);
10333
- const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path6.basename)(repoRoot);
10670
+ const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path7.basename)(repoRoot);
10334
10671
  const topicBranch = branch && branch !== "unknown" ? branch : "current";
10335
10672
  const metadata = {
10336
10673
  kind: "handoff",
@@ -10390,8 +10727,8 @@ function formatBlock(value, emptyValue) {
10390
10727
 
10391
10728
  // src/update-check.ts
10392
10729
  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");
10730
+ var import_promises7 = require("node:fs/promises");
10731
+ var import_node_path8 = require("node:path");
10395
10732
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
10396
10733
  var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
10397
10734
  var FETCH_TIMEOUT_MS = 3e3;
@@ -10458,7 +10795,7 @@ function isCacheFresh(cache) {
10458
10795
  }
10459
10796
  async function readUpdateCache(cachePath) {
10460
10797
  try {
10461
- const raw = await (0, import_promises6.readFile)(cachePath, "utf8");
10798
+ const raw = await (0, import_promises7.readFile)(cachePath, "utf8");
10462
10799
  const parsed = JSON.parse(raw);
10463
10800
  if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
10464
10801
  return void 0;
@@ -10470,8 +10807,8 @@ async function readUpdateCache(cachePath) {
10470
10807
  }
10471
10808
  async function writeUpdateCache(cachePath, contents) {
10472
10809
  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)}
10810
+ await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(cachePath), { recursive: true });
10811
+ await (0, import_promises7.writeFile)(cachePath, `${JSON.stringify(contents)}
10475
10812
  `, { encoding: "utf8", mode: 384 });
10476
10813
  } catch {
10477
10814
  }
@@ -10494,14 +10831,14 @@ async function fetchLatestVersion() {
10494
10831
 
10495
10832
  // src/version.ts
10496
10833
  var import_node_fs4 = require("node:fs");
10497
- var import_node_path8 = require("node:path");
10834
+ var import_node_path9 = require("node:path");
10498
10835
  var cachedVersion;
10499
10836
  function getThreadnoteVersion() {
10500
10837
  if (cachedVersion !== void 0) {
10501
10838
  return cachedVersion;
10502
10839
  }
10503
10840
  try {
10504
- const packageJsonPath = (0, import_node_path8.join)(__dirname, "..", "package.json");
10841
+ const packageJsonPath = (0, import_node_path9.join)(__dirname, "..", "package.json");
10505
10842
  const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
10506
10843
  cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
10507
10844
  } catch {
@@ -10543,7 +10880,7 @@ async function runHooksInstall(config, agent, options) {
10543
10880
  }
10544
10881
  async function runClaudeHooksInstall(options) {
10545
10882
  const path = expandPath(CLAUDE_SETTINGS_PATH);
10546
- const existingRaw = await exists(path) ? await (0, import_promises7.readFile)(path, "utf8") : "{}";
10883
+ const existingRaw = await exists(path) ? await (0, import_promises8.readFile)(path, "utf8") : "{}";
10547
10884
  const parsed = parseJsonConfigObject(existingRaw) ?? {};
10548
10885
  const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
10549
10886
  const before = JSON.stringify(parsed);
@@ -10560,11 +10897,11 @@ async function runClaudeHooksInstall(options) {
10560
10897
  console.log("\nRe-run with --apply to actually modify the file.");
10561
10898
  return;
10562
10899
  }
10563
- await (0, import_promises7.mkdir)((0, import_node_path9.dirname)(path), { recursive: true });
10900
+ await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(path), { recursive: true });
10564
10901
  const serialized = `${JSON.stringify(next, void 0, 2)}
10565
10902
  `;
10566
- await (0, import_promises7.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
10567
- await (0, import_promises7.chmod)(path, 384);
10903
+ await (0, import_promises8.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
10904
+ await (0, import_promises8.chmod)(path, 384);
10568
10905
  console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
10569
10906
  }
10570
10907
  function withThreadnoteHooks(input2) {
@@ -10642,7 +10979,7 @@ async function hasManagedClaudeHooks() {
10642
10979
  if (!await exists(path)) {
10643
10980
  return false;
10644
10981
  }
10645
- const raw = await (0, import_promises7.readFile)(path, "utf8");
10982
+ const raw = await (0, import_promises8.readFile)(path, "utf8");
10646
10983
  const parsed = parseJsonConfigObject(raw);
10647
10984
  if (!parsed || !isJsonObject(parsed.hooks)) {
10648
10985
  return false;
@@ -10704,7 +11041,7 @@ async function runSessionStartHook(config, options = {}) {
10704
11041
  async function emitUpdateBannerIfOutdated(config) {
10705
11042
  try {
10706
11043
  const result = await checkForThreadnoteUpdate({
10707
- cachePath: (0, import_node_path9.join)(config.agentContextHome, ".update-state.json"),
11044
+ cachePath: (0, import_node_path10.join)(config.agentContextHome, ".update-state.json"),
10708
11045
  currentVersion: getThreadnoteVersion()
10709
11046
  });
10710
11047
  if (!result || !result.outdated) {
@@ -10729,13 +11066,13 @@ async function emitUpdateBannerIfOutdated(config) {
10729
11066
  }
10730
11067
 
10731
11068
  // src/seeding.ts
10732
- var import_promises8 = require("node:fs/promises");
10733
- var import_node_path10 = require("node:path");
11069
+ var import_promises9 = require("node:fs/promises");
11070
+ var import_node_path11 = require("node:path");
10734
11071
  async function runSeed(config, options) {
10735
11072
  const manifest = await readSeedManifest(config.manifestPath);
10736
11073
  const ignorePatterns = await loadIgnorePatterns();
10737
11074
  const ov = await openVikingCliForMode(options.dryRun === true);
10738
- const statePath = (0, import_node_path10.join)(config.agentContextHome, SEED_STATE_FILE);
11075
+ const statePath = (0, import_node_path11.join)(config.agentContextHome, SEED_STATE_FILE);
10739
11076
  const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
10740
11077
  const projects = filterProjects(manifest.projects, options.only);
10741
11078
  let importedCount = 0;
@@ -10760,7 +11097,15 @@ async function runSeed(config, options) {
10760
11097
  skippedCount += 1;
10761
11098
  continue;
10762
11099
  }
10763
- const args = withIdentity(config, ["add-resource", importPath, "--to", candidate.destinationUri, "--wait"]);
11100
+ const args = withIdentity(config, [
11101
+ "add-resource",
11102
+ importPath,
11103
+ "--to",
11104
+ candidate.destinationUri,
11105
+ "--reason",
11106
+ seedResourceReason(candidate),
11107
+ "--wait"
11108
+ ]);
10764
11109
  await maybeRun(options.dryRun === true, ov, args);
10765
11110
  importedCount += 1;
10766
11111
  if (fileStat && options.dryRun !== true) {
@@ -10790,7 +11135,7 @@ function filterProjects(projects, only) {
10790
11135
  }
10791
11136
  async function statSeedFile(path) {
10792
11137
  try {
10793
- const result = await (0, import_promises8.stat)(path);
11138
+ const result = await (0, import_promises9.stat)(path);
10794
11139
  return { mtimeMs: result.mtimeMs, size: result.size };
10795
11140
  } catch (_err) {
10796
11141
  return void 0;
@@ -10798,7 +11143,7 @@ async function statSeedFile(path) {
10798
11143
  }
10799
11144
  async function readSeedState(path) {
10800
11145
  try {
10801
- const raw = await (0, import_promises8.readFile)(path, "utf8");
11146
+ const raw = await (0, import_promises9.readFile)(path, "utf8");
10802
11147
  const parsed = JSON.parse(raw);
10803
11148
  if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
10804
11149
  return { files: {}, version: 1 };
@@ -10815,13 +11160,13 @@ async function readSeedState(path) {
10815
11160
  }
10816
11161
  }
10817
11162
  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)}
11163
+ await ensureDirectory((0, import_node_path11.dirname)(path), false);
11164
+ await (0, import_promises9.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
10820
11165
  `, { encoding: "utf8", mode: 384 });
10821
11166
  }
10822
11167
  async function runInitManifest(config, options) {
10823
11168
  const manifestPath = expandPath(
10824
- options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path10.join)(config.agentContextHome, USER_MANIFEST_NAME)
11169
+ options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path11.join)(config.agentContextHome, USER_MANIFEST_NAME)
10825
11170
  );
10826
11171
  const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
10827
11172
  const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
@@ -10864,9 +11209,9 @@ async function runInitManifest(config, options) {
10864
11209
  console.log(output2.trimEnd());
10865
11210
  return;
10866
11211
  }
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);
11212
+ await ensureDirectory((0, import_node_path11.dirname)(manifestPath), false);
11213
+ await (0, import_promises9.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
11214
+ await (0, import_promises9.chmod)(manifestPath, 384);
10870
11215
  console.log(`Wrote manifest: ${manifestPath}`);
10871
11216
  console.log("Seed with:");
10872
11217
  console.log(" threadnote seed --dry-run");
@@ -10874,17 +11219,33 @@ async function runInitManifest(config, options) {
10874
11219
  }
10875
11220
  async function runSeedSkills(config, options) {
10876
11221
  const ov = await openVikingCliForMode(options.dryRun === true);
10877
- const skills = await collectSkillCandidates(config);
11222
+ const catalogItems = await collectSkillCandidates(config);
10878
11223
  const nativeMode = options.native === true;
10879
11224
  console.log(
10880
11225
  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
11226
  );
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"];
11227
+ let skippedCount = 0;
11228
+ for (const skill of catalogItems) {
11229
+ console.log(`${skill.kind === "command" ? "Command" : "Skill"} ${skill.source}: ${skill.filePath}`);
11230
+ if (nativeMode && skill.kind === "command") {
11231
+ skippedCount += 1;
11232
+ console.log(`SKIP command in native skill mode: ${skill.filePath}`);
11233
+ continue;
11234
+ }
11235
+ const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : [
11236
+ "add-resource",
11237
+ skill.filePath,
11238
+ "--to",
11239
+ skillResourceUri(skill),
11240
+ "--reason",
11241
+ skillResourceReason(skill),
11242
+ "--wait"
11243
+ ];
10885
11244
  await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
10886
11245
  }
10887
- console.log(`Skill seed complete: ${skills.length} unique skill(s).`);
11246
+ console.log(
11247
+ `Skill seed complete: ${catalogItems.length - skippedCount} unique catalog item(s)${skippedCount > 0 ? `, ${skippedCount} skipped` : ""}.`
11248
+ );
10888
11249
  }
10889
11250
  async function resolveRepoRoot(repoInput) {
10890
11251
  const inputPath = expandPath(repoInput);
@@ -10896,13 +11257,13 @@ async function resolveRepoRoot(repoInput) {
10896
11257
  async function projectIdentity(path) {
10897
11258
  const expanded = expandPath(path);
10898
11259
  try {
10899
- return await (0, import_promises8.realpath)(expanded);
11260
+ return await (0, import_promises9.realpath)(expanded);
10900
11261
  } catch (_err) {
10901
11262
  return expanded;
10902
11263
  }
10903
11264
  }
10904
11265
  function projectManifestForRepo(repoRoot, existingProjects) {
10905
- const baseName = uriSegment((0, import_node_path10.basename)(repoRoot));
11266
+ const baseName = uriSegment((0, import_node_path11.basename)(repoRoot));
10906
11267
  const usedNames = new Set(existingProjects.map((project) => project.name));
10907
11268
  const usedUris = new Set(existingProjects.map((project) => project.uri));
10908
11269
  let name = baseName;
@@ -10924,7 +11285,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
10924
11285
  for (const pattern of project.seed) {
10925
11286
  const files = await resolveProjectPattern(projectRoot, pattern);
10926
11287
  for (const filePath of files) {
10927
- const relativePath = toPosixPath((0, import_node_path10.relative)(projectRoot, filePath));
11288
+ const relativePath = toPosixPath((0, import_node_path11.relative)(projectRoot, filePath));
10928
11289
  if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
10929
11290
  continue;
10930
11291
  }
@@ -10942,20 +11303,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
10942
11303
  async function resolveProjectPattern(projectRoot, pattern) {
10943
11304
  const normalizedPattern = toPosixPath(pattern);
10944
11305
  if (!hasGlob(normalizedPattern)) {
10945
- const filePath = (0, import_node_path10.join)(projectRoot, normalizedPattern);
11306
+ const filePath = (0, import_node_path11.join)(projectRoot, normalizedPattern);
10946
11307
  return await isFile(filePath) ? [filePath] : [];
10947
11308
  }
10948
11309
  const globBase = getGlobBase(normalizedPattern);
10949
- const basePath = (0, import_node_path10.join)(projectRoot, globBase);
11310
+ const basePath = (0, import_node_path11.join)(projectRoot, globBase);
10950
11311
  if (!await exists(basePath)) {
10951
11312
  return [];
10952
11313
  }
10953
11314
  const regex = globToRegExp(normalizedPattern);
10954
11315
  const files = await walkFiles(basePath);
10955
- return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path10.relative)(projectRoot, filePath))));
11316
+ return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path11.relative)(projectRoot, filePath))));
10956
11317
  }
10957
11318
  async function prepareSeedFile(config, candidate, dryRun) {
10958
- const content = await (0, import_promises8.readFile)(candidate.filePath, "utf8");
11319
+ const content = await (0, import_promises9.readFile)(candidate.filePath, "utf8");
10959
11320
  const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
10960
11321
  const secretMatches = detectSecretMatches(redactedContent);
10961
11322
  if (secretMatches.length > 0) {
@@ -10967,39 +11328,54 @@ async function prepareSeedFile(config, candidate, dryRun) {
10967
11328
  if (redactedContent === content) {
10968
11329
  return candidate.filePath;
10969
11330
  }
10970
- const redactedPath = (0, import_node_path10.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
11331
+ const redactedPath = (0, import_node_path11.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
10971
11332
  if (dryRun) {
10972
11333
  console.log(`Would write redacted copy: ${redactedPath}`);
10973
11334
  return redactedPath;
10974
11335
  }
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);
11336
+ await ensureDirectory((0, import_node_path11.dirname)(redactedPath), false);
11337
+ await (0, import_promises9.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
11338
+ await (0, import_promises9.chmod)(redactedPath, 384);
10978
11339
  return redactedPath;
10979
11340
  }
11341
+ function seedResourceReason(candidate) {
11342
+ return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
11343
+ }
11344
+ function skillResourceReason(skill) {
11345
+ return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path11.basename)(
11346
+ skill.filePath
11347
+ )}`;
11348
+ }
10980
11349
  async function collectSkillCandidates(config) {
10981
11350
  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" }
11351
+ { kind: "skill", pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
11352
+ { kind: "skill", pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
11353
+ { kind: "skill", pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" },
11354
+ { kind: "command", pattern: "~/.claude/commands/**/*.md", source: "claude-commands-global" }
10985
11355
  ];
10986
11356
  try {
10987
11357
  const manifest = await readSeedManifest(config.manifestPath);
10988
11358
  for (const project of manifest.projects) {
10989
11359
  sources.push({
11360
+ kind: "skill",
10990
11361
  pattern: `${project.path}/.claude/skills/**/SKILL.md`,
10991
11362
  source: `repo-local:${project.name}`
10992
11363
  });
11364
+ sources.push({
11365
+ kind: "command",
11366
+ pattern: `${project.path}/.claude/commands/**/*.md`,
11367
+ source: `repo-local:${project.name}:claude-commands`
11368
+ });
10993
11369
  }
10994
11370
  } catch (err) {
10995
- console.log(`WARN cannot read manifest for repo-local skill discovery: ${errorMessage(err)}`);
11371
+ console.log(`WARN cannot read manifest for repo-local skill/command discovery: ${errorMessage(err)}`);
10996
11372
  }
10997
11373
  const seenHashes = /* @__PURE__ */ new Set();
10998
11374
  const skills = [];
10999
11375
  for (const source of sources) {
11000
11376
  const files = await resolveAbsolutePattern(expandPath(source.pattern));
11001
11377
  for (const filePath of files) {
11002
- const content = await (0, import_promises8.readFile)(filePath, "utf8");
11378
+ const content = await (0, import_promises9.readFile)(filePath, "utf8");
11003
11379
  const matches = detectSecretMatches(content);
11004
11380
  if (matches.length > 0) {
11005
11381
  console.log(`SKIP skill with possible secret: ${filePath}`);
@@ -11010,7 +11386,7 @@ async function collectSkillCandidates(config) {
11010
11386
  continue;
11011
11387
  }
11012
11388
  seenHashes.add(hash);
11013
- skills.push({ filePath, hash, source: source.source });
11389
+ skills.push({ filePath, hash, kind: source.kind, source: source.source });
11014
11390
  }
11015
11391
  }
11016
11392
  return skills;
@@ -11030,10 +11406,19 @@ async function resolveAbsolutePattern(pattern) {
11030
11406
  return files.filter((filePath) => regex.test(toPosixPath(filePath)));
11031
11407
  }
11032
11408
  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`;
11409
+ return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
11410
+ }
11411
+ function skillResourceName(skill) {
11412
+ const fileName = (0, import_node_path11.basename)(skill.filePath);
11413
+ if (fileName.toLowerCase() === "skill.md") {
11414
+ return uriSegment((0, import_node_path11.basename)((0, import_node_path11.dirname)(skill.filePath)));
11415
+ }
11416
+ const extensionIndex = fileName.lastIndexOf(".");
11417
+ const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
11418
+ return uriSegment(stem);
11034
11419
  }
11035
11420
  async function loadIgnorePatterns() {
11036
- const raw = await (0, import_promises8.readFile)((0, import_node_path10.join)(toolRoot(), ".threadnoteignore"), "utf8");
11421
+ const raw = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), ".threadnoteignore"), "utf8");
11037
11422
  return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
11038
11423
  }
11039
11424
  function matchesIgnore(relativePath, patterns) {
@@ -11106,18 +11491,18 @@ function detectSecretMatches(content) {
11106
11491
  // src/lifecycle.ts
11107
11492
  var import_node_child_process3 = require("node:child_process");
11108
11493
  var import_node_fs6 = require("node:fs");
11109
- var import_promises11 = require("node:fs/promises");
11494
+ var import_promises12 = require("node:fs/promises");
11110
11495
  var import_node_os6 = require("node:os");
11111
- var import_node_path12 = require("node:path");
11496
+ var import_node_path13 = require("node:path");
11112
11497
  var import_node_process3 = require("node:process");
11113
- var import_promises12 = require("node:readline/promises");
11498
+ var import_promises13 = require("node:readline/promises");
11114
11499
 
11115
11500
  // src/update.ts
11116
11501
  var import_node_fs5 = require("node:fs");
11117
- var import_promises9 = require("node:fs/promises");
11502
+ var import_promises10 = require("node:fs/promises");
11118
11503
  var import_node_os5 = require("node:os");
11119
- var import_node_path11 = require("node:path");
11120
- var import_promises10 = require("node:readline/promises");
11504
+ var import_node_path12 = require("node:path");
11505
+ var import_promises11 = require("node:readline/promises");
11121
11506
  var import_node_process2 = require("node:process");
11122
11507
 
11123
11508
  // src/release_notes.ts
@@ -11286,7 +11671,9 @@ async function runUpdate(config, options) {
11286
11671
  return;
11287
11672
  }
11288
11673
  const threadnoteCommand = await installedThreadnoteCommand(runtime);
11289
- await maybeRun(options.dryRun === true, threadnoteCommand, ["repair", "--no-post-update"]);
11674
+ console.log("");
11675
+ console.log("Repairing local Threadnote setup after package update.");
11676
+ await runThreadnoteSubcommand(options.dryRun === true, threadnoteCommand, ["repair", "--no-post-update"]);
11290
11677
  if (options.postUpdate !== false) {
11291
11678
  const postUpdateArgs = [
11292
11679
  "post-update",
@@ -11296,15 +11683,7 @@ async function runUpdate(config, options) {
11296
11683
  info2.latestVersion,
11297
11684
  ...options.yes === true ? ["--yes"] : []
11298
11685
  ];
11299
- if (options.dryRun === true) {
11300
- await maybeRun(true, threadnoteCommand, postUpdateArgs);
11301
- } else {
11302
- console.log(`Running: ${formatShellCommand(threadnoteCommand, postUpdateArgs)}`);
11303
- const postUpdateExitCode = await runInteractive(threadnoteCommand, postUpdateArgs);
11304
- if (postUpdateExitCode !== 0) {
11305
- throw new Error(`${formatShellCommand(threadnoteCommand, postUpdateArgs)} exited with ${postUpdateExitCode}.`);
11306
- }
11307
- }
11686
+ await runThreadnoteSubcommand(options.dryRun === true, threadnoteCommand, postUpdateArgs);
11308
11687
  } else {
11309
11688
  console.log("Skipping post-update migration prompts because --no-post-update was provided.");
11310
11689
  }
@@ -11388,7 +11767,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
11388
11767
  }
11389
11768
  console.log("");
11390
11769
  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).");
11770
+ console.log("Picks up upstream CLI, resource-ingestion, and index reliability fixes.");
11392
11771
  const wasRunning = await isOpenVikingHealthy(config);
11393
11772
  const usingLaunchd = await isLaunchAgentInstalled();
11394
11773
  const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
@@ -11406,9 +11785,11 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
11406
11785
  if (usingLaunchd) {
11407
11786
  const launchAgentPath = launchAgentPlistPath();
11408
11787
  await runCommand("launchctl", ["unload", launchAgentPath], { allowFailure: true });
11788
+ await waitForOpenVikingPortClosed(config, 15e3);
11409
11789
  await runCommand("launchctl", ["load", launchAgentPath], { allowFailure: true });
11410
11790
  } else {
11411
11791
  await maybeRun(false, threadnoteCommand, ["stop"]);
11792
+ await waitForOpenVikingPortClosed(config, 15e3);
11412
11793
  await maybeRun(false, threadnoteCommand, ["start"]);
11413
11794
  }
11414
11795
  const healthyAfter = await waitForOpenVikingHealthy(config, 1e4);
@@ -11419,6 +11800,17 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
11419
11800
  console.log("Check the server log or run: threadnote start");
11420
11801
  }
11421
11802
  }
11803
+ async function runThreadnoteSubcommand(dryRun, executable, args) {
11804
+ if (dryRun) {
11805
+ await maybeRun(true, executable, args);
11806
+ return;
11807
+ }
11808
+ console.log(`Running: ${formatShellCommand(executable, args)}`);
11809
+ const exitCode = await runInteractive(executable, args);
11810
+ if (exitCode !== 0) {
11811
+ throw new Error(`${formatShellCommand(executable, args)} exited with ${exitCode}.`);
11812
+ }
11813
+ }
11422
11814
  function openVikingHealthEndpoint(config) {
11423
11815
  return `http://${config.host}:${config.port}/health`;
11424
11816
  }
@@ -11442,21 +11834,36 @@ async function waitForOpenVikingHealthy(config, timeoutMs) {
11442
11834
  if (await isOpenVikingHealthy(config)) {
11443
11835
  return true;
11444
11836
  }
11445
- await new Promise((resolvePromise) => {
11446
- setTimeout(resolvePromise, 500);
11447
- });
11837
+ await sleep(500);
11448
11838
  }
11449
11839
  return isOpenVikingHealthy(config);
11450
11840
  }
11841
+ async function waitForOpenVikingPortClosed(config, timeoutMs) {
11842
+ console.log(`Waiting for OpenViking port ${config.host}:${config.port} to close before restart.`);
11843
+ const deadline = Date.now() + timeoutMs;
11844
+ while (Date.now() < deadline) {
11845
+ if (!await isTcpPortOpen(config.host, config.port, 300)) {
11846
+ return true;
11847
+ }
11848
+ await sleep(300);
11849
+ }
11850
+ if (!await isTcpPortOpen(config.host, config.port, 300)) {
11851
+ return true;
11852
+ }
11853
+ console.log(
11854
+ `Warning: OpenViking port ${config.host}:${config.port} is still in use after ${timeoutMs / 1e3}s; start may fail.`
11855
+ );
11856
+ return false;
11857
+ }
11451
11858
  function launchAgentPlistPath() {
11452
- return (0, import_node_path11.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
11859
+ return (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
11453
11860
  }
11454
11861
  async function isLaunchAgentInstalled() {
11455
11862
  if (process.platform !== "darwin") {
11456
11863
  return false;
11457
11864
  }
11458
11865
  try {
11459
- await (0, import_promises9.access)(launchAgentPlistPath(), import_node_fs5.constants.F_OK);
11866
+ await (0, import_promises10.access)(launchAgentPlistPath(), import_node_fs5.constants.F_OK);
11460
11867
  return true;
11461
11868
  } catch (_err) {
11462
11869
  return false;
@@ -11485,7 +11892,7 @@ async function getUpdateInfo(config, options) {
11485
11892
  };
11486
11893
  }
11487
11894
  async function currentPackageVersion() {
11488
- const rawPackage = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), "package.json"), "utf8");
11895
+ const rawPackage = await (0, import_promises10.readFile)((0, import_node_path12.join)(toolRoot(), "package.json"), "utf8");
11489
11896
  const parsed = JSON.parse(rawPackage);
11490
11897
  if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
11491
11898
  throw new Error("Could not read current threadnote package version.");
@@ -11535,11 +11942,11 @@ async function readFreshCache(config, registry) {
11535
11942
  }
11536
11943
  async function writeUpdateCache2(config, cache) {
11537
11944
  await ensureDirectory(config.agentContextHome, false);
11538
- await (0, import_promises9.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
11945
+ await (0, import_promises10.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
11539
11946
  `, { encoding: "utf8", mode: 384 });
11540
11947
  }
11541
11948
  function updateCachePath(config) {
11542
- return (0, import_node_path11.join)(config.agentContextHome, "update-check.json");
11949
+ return (0, import_node_path12.join)(config.agentContextHome, "update-check.json");
11543
11950
  }
11544
11951
  async function runApplicablePostUpdateMigrations(config, options) {
11545
11952
  const state = await readPostUpdateState(config);
@@ -11603,7 +12010,7 @@ async function applicablePostUpdateMigrations(config, options) {
11603
12010
  return applicable;
11604
12011
  }
11605
12012
  async function readPostUpdateMigrations() {
11606
- const raw = await readFileIfExists((0, import_node_path11.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
12013
+ const raw = await readFileIfExists((0, import_node_path12.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
11607
12014
  if (!raw) {
11608
12015
  return [];
11609
12016
  }
@@ -11642,7 +12049,7 @@ function printPostUpdateMigration(migration) {
11642
12049
  }
11643
12050
  }
11644
12051
  async function confirmPostUpdateMigration(prompt, defaultYes = false) {
11645
- const readline = (0, import_promises10.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
12052
+ const readline = (0, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
11646
12053
  try {
11647
12054
  const answer = (await readline.question(prompt)).trim().toLowerCase();
11648
12055
  if (answer === "") {
@@ -11677,11 +12084,11 @@ async function readPostUpdateState(config) {
11677
12084
  }
11678
12085
  async function writePostUpdateState(config, state) {
11679
12086
  await ensureDirectory(config.agentContextHome, false);
11680
- await (0, import_promises9.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
12087
+ await (0, import_promises10.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
11681
12088
  `, { encoding: "utf8", mode: 384 });
11682
12089
  }
11683
12090
  function postUpdateStatePath(config) {
11684
- return (0, import_node_path11.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
12091
+ return (0, import_node_path12.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
11685
12092
  }
11686
12093
  async function resolveUpdateRuntime(runtime) {
11687
12094
  if (runtime !== "auto") {
@@ -11711,14 +12118,14 @@ async function runtimeThreadnoteBin(runtime) {
11711
12118
  if (runtime === "npm") {
11712
12119
  const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
11713
12120
  const prefix = result.stdout.trim();
11714
- return prefix ? (0, import_node_path11.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
12121
+ return prefix ? (0, import_node_path12.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
11715
12122
  }
11716
12123
  if (runtime === "bun") {
11717
12124
  const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
11718
12125
  const binDir = result.stdout.trim();
11719
- return binDir ? (0, import_node_path11.join)(binDir, NPM_PACKAGE_NAME) : void 0;
12126
+ return binDir ? (0, import_node_path12.join)(binDir, NPM_PACKAGE_NAME) : void 0;
11720
12127
  }
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);
12128
+ 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
12129
  }
11723
12130
  function updatePackageCommand(runtime, registry) {
11724
12131
  if (runtime === "npm") {
@@ -11786,9 +12193,10 @@ async function collectDoctorChecks(config, options = {}) {
11786
12193
  checks.push(await commandShimCheck());
11787
12194
  checks.push(...await userAgentInstructionsChecks());
11788
12195
  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"));
12196
+ checks.push(await recallIndexFreshnessCheck(config));
12197
+ checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
12198
+ checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
12199
+ checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
11792
12200
  checks.push(await healthCheck(config));
11793
12201
  return checks;
11794
12202
  }
@@ -11796,9 +12204,9 @@ async function runInstall(config, options) {
11796
12204
  const repairInvalidConfigs = options.repairInvalidConfigs === true;
11797
12205
  const dryRun = options.dryRun === true;
11798
12206
  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);
12207
+ await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "logs"), dryRun);
12208
+ await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "redacted"), dryRun);
12209
+ await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "mcp"), dryRun);
11802
12210
  await installCommandShim(dryRun);
11803
12211
  await installUserAgentInstructions(dryRun);
11804
12212
  const serverPath = await findOpenVikingServer();
@@ -11835,18 +12243,19 @@ async function runInstall(config, options) {
11835
12243
  }
11836
12244
  await writeTemplateIfMissing({
11837
12245
  config,
11838
- destinationPath: (0, import_node_path12.join)(config.agentContextHome, "ov.conf"),
12246
+ destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ov.conf"),
11839
12247
  dryRun,
11840
12248
  shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
11841
- templatePath: (0, import_node_path12.join)(toolRoot(), "config", "ov.conf.template.json")
12249
+ templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json")
11842
12250
  });
11843
12251
  await writeTemplateIfMissing({
11844
12252
  config,
11845
- destinationPath: (0, import_node_path12.join)(config.agentContextHome, "ovcli.conf"),
12253
+ destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ovcli.conf"),
11846
12254
  dryRun,
11847
12255
  shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
11848
- templatePath: (0, import_node_path12.join)(toolRoot(), "config", "ovcli.conf.template.json")
12256
+ templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json")
11849
12257
  });
12258
+ await configureOpenVikingCliLanguage(config, dryRun);
11850
12259
  if (options.start !== false) {
11851
12260
  const healthy = await repairServerHealth(config, dryRun);
11852
12261
  if (!healthy && !dryRun) {
@@ -11873,6 +12282,7 @@ async function runRepair(config, options) {
11873
12282
  } else {
11874
12283
  console.log("Skipping server health repair because --no-start was provided.");
11875
12284
  }
12285
+ await repairRecallIndex(config, dryRun);
11876
12286
  const mcpClients = await resolveMcpClients(options.mcp ?? "available", "repair");
11877
12287
  if (mcpClients.length === 0) {
11878
12288
  console.log("Skipping MCP config repair.");
@@ -11899,7 +12309,7 @@ async function runUninstall(config, options) {
11899
12309
  }
11900
12310
  console.log("Uninstalling local Threadnote setup.");
11901
12311
  await runStop(config, { dryRun });
11902
- await removePathIfExists((0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
12312
+ await removePathIfExists((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
11903
12313
  await removeLaunchAgent(dryRun);
11904
12314
  await removeMcpConfigs(options.mcp ?? "available", dryRun);
11905
12315
  await removeMcpSnippets(config, dryRun);
@@ -11956,25 +12366,102 @@ async function repairManifest(config, dryRun) {
11956
12366
  console.log(output2.trimEnd());
11957
12367
  return;
11958
12368
  }
11959
- await ensureDirectory((0, import_node_path12.dirname)(config.manifestPath), false);
12369
+ await ensureDirectory((0, import_node_path13.dirname)(config.manifestPath), false);
11960
12370
  const currentContent = await readFileIfExists(config.manifestPath);
11961
12371
  if (currentContent !== void 0) {
11962
12372
  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);
12373
+ await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
12374
+ await (0, import_promises12.chmod)(backupPath, 384);
11965
12375
  console.log(`Backup: ${backupPath}`);
11966
12376
  }
11967
- await (0, import_promises11.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
11968
- await (0, import_promises11.chmod)(config.manifestPath, 384);
12377
+ await (0, import_promises12.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
12378
+ await (0, import_promises12.chmod)(config.manifestPath, 384);
11969
12379
  console.log(`Wrote replacement manifest: ${config.manifestPath}`);
11970
12380
  }
12381
+ async function repairRecallIndex(config, dryRun) {
12382
+ console.log("\nRepairing recall index freshness.");
12383
+ const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
12384
+ if (!ov) {
12385
+ console.log("Skipping recall index repair: neither ov nor openviking was found in PATH.");
12386
+ return;
12387
+ }
12388
+ const progress = startProgress("Scanning recall index freshness across memories and seeded resources.");
12389
+ try {
12390
+ const result = await repairStaleRecallIndex(config, ov, {
12391
+ collapseDepth: MAINTENANCE_COLLAPSE_DEPTH,
12392
+ collapseToRoots: true,
12393
+ dryRun,
12394
+ ignoreBackoff: true,
12395
+ includeAgentSkills: true,
12396
+ includeManifestResources: true,
12397
+ maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS,
12398
+ onRepairFailure: async () => {
12399
+ progress.update("A recall index reindex failed; checking OpenViking health before continuing.");
12400
+ await repairServerHealth(config, dryRun);
12401
+ },
12402
+ onProgress: (event) => {
12403
+ if (event.type === "scan-complete") {
12404
+ if (event.totalTargets === 0) {
12405
+ progress.update("No stale recall index scopes found.");
12406
+ } else {
12407
+ progress.update(
12408
+ `Found ${event.totalTargets} stale recall index scope(s); repairing ${event.repairTargetCount}.`
12409
+ );
12410
+ }
12411
+ } else if (event.type === "repair-start") {
12412
+ progress.update(
12413
+ `Reindexing ${event.index}/${event.total}: ${event.target.uri} (${event.target.staleCount} stale summaries).`
12414
+ );
12415
+ } else if (event.type === "repair-dry-run") {
12416
+ progress.update(
12417
+ `Planning reindex ${event.index}/${event.total}: ${event.target.uri} (${event.target.staleCount} stale summaries).`
12418
+ );
12419
+ } else if (event.type === "repair-skip-recent") {
12420
+ progress.update(`Skipping recently repaired scope ${event.index}/${event.total}: ${event.target.uri}.`);
12421
+ }
12422
+ }
12423
+ });
12424
+ progress.stop();
12425
+ const messages = formatRecallIndexRepairMessages(result, { dryRun, maxUris: 20 });
12426
+ if (messages.length === 0) {
12427
+ console.log("Recall index freshness OK.");
12428
+ return;
12429
+ }
12430
+ for (const message of messages) {
12431
+ console.log(message);
12432
+ }
12433
+ } catch (err) {
12434
+ progress.stop();
12435
+ console.log(`WARN could not repair recall index freshness: ${errorMessage(err)}`);
12436
+ }
12437
+ }
12438
+ async function configureOpenVikingCliLanguage(config, dryRun) {
12439
+ const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
12440
+ if (!ov) {
12441
+ return;
12442
+ }
12443
+ const installedVersion = dryRun ? void 0 : await readOpenVikingCliVersion2(ov);
12444
+ const effectiveVersion = installedVersion ?? config.openVikingVersion;
12445
+ if (compareVersions(effectiveVersion, "0.3.23") < 0) {
12446
+ return;
12447
+ }
12448
+ await maybeRun(dryRun, ov, ["language", "en"], { allowFailure: true });
12449
+ }
12450
+ async function readOpenVikingCliVersion2(ov) {
12451
+ const result = await runCommand(ov, ["version"], { allowFailure: true });
12452
+ if (result.exitCode !== 0) {
12453
+ return void 0;
12454
+ }
12455
+ const match = result.stdout.match(/^\s*CLI:\s*(\S+)/m);
12456
+ return match ? match[1] : void 0;
12457
+ }
11971
12458
  async function findOpenVikingServer() {
11972
12459
  const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
11973
12460
  if (onPath) {
11974
12461
  return onPath;
11975
12462
  }
11976
12463
  for (const candidateDir of await openVikingServerCandidateDirs()) {
11977
- const candidate = (0, import_node_path12.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
12464
+ const candidate = (0, import_node_path13.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
11978
12465
  if (await isExecutable(candidate)) {
11979
12466
  return candidate;
11980
12467
  }
@@ -12020,7 +12507,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
12020
12507
  if (onPath) {
12021
12508
  return;
12022
12509
  }
12023
- const binDir = (0, import_node_path12.dirname)(serverPath);
12510
+ const binDir = (0, import_node_path13.dirname)(serverPath);
12024
12511
  const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
12025
12512
  console.log(
12026
12513
  `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 +12551,7 @@ async function runStart(config, options) {
12064
12551
  );
12065
12552
  }
12066
12553
  const logPath = openVikingLogPath(config);
12067
- await ensureDirectory((0, import_node_path12.dirname)(logPath), false);
12554
+ await ensureDirectory((0, import_node_path13.dirname)(logPath), false);
12068
12555
  if (options.foreground === true) {
12069
12556
  const result = await runInteractive(server, args);
12070
12557
  process.exitCode = result;
@@ -12073,7 +12560,7 @@ async function runStart(config, options) {
12073
12560
  const logFd = (0, import_node_fs6.openSync)(logPath, "a");
12074
12561
  const child = spawnDetachedServerWithLog(server, args, logFd);
12075
12562
  child.unref();
12076
- await (0, import_promises11.writeFile)((0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
12563
+ await (0, import_promises12.writeFile)((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
12077
12564
  `, "utf8");
12078
12565
  const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
12079
12566
  if (health) {
@@ -12106,7 +12593,7 @@ async function runStop(config, options) {
12106
12593
  console.log(`No LaunchAgent found: ${launchAgentPath}`);
12107
12594
  }
12108
12595
  }
12109
- const pidPath = (0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid");
12596
+ const pidPath = (0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid");
12110
12597
  const pidText = await readFileIfExists(pidPath);
12111
12598
  if (!pidText) {
12112
12599
  console.log("No pid file found for detached OpenViking server.");
@@ -12148,7 +12635,7 @@ async function openVikingServerCheck() {
12148
12635
  }
12149
12636
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
12150
12637
  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)`;
12638
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path13.dirname)(executable)} to PATH)`;
12152
12639
  return {
12153
12640
  name,
12154
12641
  status: result.exitCode === 0 ? "ok" : "warn",
@@ -12217,7 +12704,7 @@ async function pythonSystemCertificatesCheck() {
12217
12704
  };
12218
12705
  }
12219
12706
  async function commandShimCheck() {
12220
- const shimPath = (0, import_node_path12.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12707
+ const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12221
12708
  const content = await readFileIfExists(shimPath);
12222
12709
  if (content === void 0) {
12223
12710
  return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
@@ -12283,11 +12770,11 @@ async function hasPythonModule(serverPath, moduleName) {
12283
12770
  async function siblingPythonForExecutable(executablePath) {
12284
12771
  let resolvedPath;
12285
12772
  try {
12286
- resolvedPath = await (0, import_promises11.realpath)(executablePath);
12773
+ resolvedPath = await (0, import_promises12.realpath)(executablePath);
12287
12774
  } catch (_err) {
12288
12775
  return void 0;
12289
12776
  }
12290
- const pythonPath = (0, import_node_path12.join)((0, import_node_path12.dirname)(resolvedPath), "python");
12777
+ const pythonPath = (0, import_node_path13.join)((0, import_node_path13.dirname)(resolvedPath), "python");
12291
12778
  return await exists(pythonPath) ? pythonPath : void 0;
12292
12779
  }
12293
12780
  async function manifestCheck(path) {
@@ -12298,6 +12785,29 @@ async function manifestCheck(path) {
12298
12785
  return { name: "manifest", status: "fail", detail: errorMessage(err) };
12299
12786
  }
12300
12787
  }
12788
+ async function recallIndexFreshnessCheck(config) {
12789
+ try {
12790
+ const targets = await findStaleRecallIndexTargets(config, {
12791
+ collapseToRoots: true,
12792
+ includeAgentSkills: true,
12793
+ includeManifestResources: true
12794
+ });
12795
+ if (targets.length === 0) {
12796
+ return { name: "recall index freshness", status: "ok", detail: "no stale generated summaries found" };
12797
+ }
12798
+ const staleSummaryCount = targets.reduce((total, target) => total + target.staleCount, 0);
12799
+ const sampleUris = targets.slice(0, 3).map((target) => target.uri);
12800
+ const extraCount = targets.length - sampleUris.length;
12801
+ const sample = `${sampleUris.join(", ")}${extraCount > 0 ? `, +${extraCount} more` : ""}`;
12802
+ return {
12803
+ name: "recall index freshness",
12804
+ status: "warn",
12805
+ detail: `${staleSummaryCount} stale generated summary file(s) under ${targets.length} scope(s); run repair to reindex ${sample}`
12806
+ };
12807
+ } catch (err) {
12808
+ return { name: "recall index freshness", status: "warn", detail: errorMessage(err) };
12809
+ }
12810
+ }
12301
12811
  async function fileCheck(path, label) {
12302
12812
  return await exists(path) ? { name: label, status: "ok", detail: path } : { name: label, status: "fail", detail: `${path} missing` };
12303
12813
  }
@@ -12358,7 +12868,7 @@ async function offerToInstallUv() {
12358
12868
  );
12359
12869
  return false;
12360
12870
  }
12361
- const readline = (0, import_promises12.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
12871
+ const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
12362
12872
  let answer;
12363
12873
  try {
12364
12874
  answer = (await readline.question(
@@ -12483,38 +12993,38 @@ function printInstallNextSteps(options) {
12483
12993
  }
12484
12994
  async function writeTemplateIfMissing(options) {
12485
12995
  if (await exists(options.destinationPath)) {
12486
- const currentContent = await (0, import_promises11.readFile)(options.destinationPath, "utf8");
12996
+ const currentContent = await (0, import_promises12.readFile)(options.destinationPath, "utf8");
12487
12997
  if (options.shouldRepair?.(currentContent) !== true) {
12488
12998
  console.log(`Already exists: ${options.destinationPath}`);
12489
12999
  return;
12490
13000
  }
12491
- const rendered2 = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
13001
+ const rendered2 = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
12492
13002
  if (options.dryRun) {
12493
13003
  console.log(`Would repair generated config: ${options.destinationPath}`);
12494
13004
  return;
12495
13005
  }
12496
13006
  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);
13007
+ await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
13008
+ await (0, import_promises12.chmod)(backupPath, 384);
13009
+ await (0, import_promises12.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
13010
+ await (0, import_promises12.chmod)(options.destinationPath, 384);
12501
13011
  console.log(`Repaired generated config: ${options.destinationPath}`);
12502
13012
  console.log(`Backup: ${backupPath}`);
12503
13013
  return;
12504
13014
  }
12505
- const rendered = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
13015
+ const rendered = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
12506
13016
  if (options.dryRun) {
12507
13017
  console.log(`Would write ${options.destinationPath}`);
12508
13018
  return;
12509
13019
  }
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);
13020
+ await ensureDirectory((0, import_node_path13.dirname)(options.destinationPath), false);
13021
+ await (0, import_promises12.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
13022
+ await (0, import_promises12.chmod)(options.destinationPath, 384);
12513
13023
  console.log(`Wrote ${options.destinationPath}`);
12514
13024
  }
12515
13025
  async function installCommandShim(dryRun) {
12516
13026
  const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
12517
- const shimPath = (0, import_node_path12.join)(binDir, "threadnote");
13027
+ const shimPath = (0, import_node_path13.join)(binDir, "threadnote");
12518
13028
  const existingContent = await readFileIfExists(shimPath);
12519
13029
  if (existingContent && !isManagedCommandShim(existingContent)) {
12520
13030
  console.log(`WARN not overwriting existing command shim: ${shimPath}`);
@@ -12530,12 +13040,12 @@ async function installCommandShim(dryRun) {
12530
13040
  return;
12531
13041
  }
12532
13042
  await ensureDirectory(binDir, false);
12533
- await (0, import_promises11.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
12534
- await (0, import_promises11.chmod)(shimPath, 493);
13043
+ await (0, import_promises12.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
13044
+ await (0, import_promises12.chmod)(shimPath, 493);
12535
13045
  console.log(`Wrote command shim: ${shimPath}`);
12536
13046
  }
12537
13047
  async function removeCommandShim(dryRun) {
12538
- const shimPath = (0, import_node_path12.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
13048
+ const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
12539
13049
  const content = await readFileIfExists(shimPath);
12540
13050
  if (content === void 0) {
12541
13051
  console.log(`Already absent: ${shimPath}`);
@@ -12569,8 +13079,8 @@ async function installUserAgentInstructions(dryRun) {
12569
13079
  console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
12570
13080
  continue;
12571
13081
  }
12572
- await ensureDirectory((0, import_node_path12.dirname)(targetPath), false);
12573
- await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
13082
+ await ensureDirectory((0, import_node_path13.dirname)(targetPath), false);
13083
+ await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
12574
13084
  console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
12575
13085
  }
12576
13086
  }
@@ -12607,7 +13117,7 @@ async function removeUserAgentInstructions(dryRun) {
12607
13117
  console.log(`Would update ${targetPath}`);
12608
13118
  continue;
12609
13119
  }
12610
- await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
13120
+ await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
12611
13121
  console.log(`Updated ${targetPath}`);
12612
13122
  }
12613
13123
  }
@@ -12627,7 +13137,7 @@ async function renderUserAgentInstructions(target) {
12627
13137
  ].join("\n");
12628
13138
  }
12629
13139
  async function renderUserAgentInstructionsBlock() {
12630
- const instructions = (await (0, import_promises11.readFile)((0, import_node_path12.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
13140
+ const instructions = (await (0, import_promises12.readFile)((0, import_node_path13.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
12631
13141
  return `${USER_INSTRUCTIONS_START_MARKER}
12632
13142
  ${instructions}
12633
13143
  ${USER_INSTRUCTIONS_END_MARKER}`;
@@ -12711,9 +13221,9 @@ async function installLaunchAgent(config, dryRun) {
12711
13221
  `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
13222
  );
12713
13223
  }
12714
- const source = (0, import_node_path12.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
13224
+ const source = (0, import_node_path13.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
12715
13225
  const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
12716
- const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config, {
13226
+ const rendered = renderTemplate(await (0, import_promises12.readFile)(source, "utf8"), config, {
12717
13227
  OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
12718
13228
  });
12719
13229
  if (dryRun) {
@@ -12725,9 +13235,9 @@ async function installLaunchAgent(config, dryRun) {
12725
13235
  console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
12726
13236
  return;
12727
13237
  }
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");
13238
+ await ensureDirectory((0, import_node_path13.dirname)(destination), false);
13239
+ await ensureDirectory((0, import_node_path13.dirname)(openVikingLogPath(config)), false);
13240
+ await (0, import_promises12.writeFile)(destination, rendered, "utf8");
12731
13241
  await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
12732
13242
  await maybeRun(false, "launchctl", ["load", destination]);
12733
13243
  await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
@@ -12790,7 +13300,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
12790
13300
  if (typeof parsed.default_user !== "string") {
12791
13301
  return false;
12792
13302
  }
12793
- if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path12.join)(config.agentContextHome, "data")) {
13303
+ if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path13.join)(config.agentContextHome, "data")) {
12794
13304
  return false;
12795
13305
  }
12796
13306
  return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
@@ -12852,9 +13362,9 @@ function printWhatsNew(whatsNew) {
12852
13362
  // src/manager.ts
12853
13363
  var import_node_http2 = require("node:http");
12854
13364
  var import_node_crypto2 = require("node:crypto");
12855
- var import_promises13 = require("node:fs/promises");
13365
+ var import_promises14 = require("node:fs/promises");
12856
13366
  var import_node_os7 = require("node:os");
12857
- var import_node_path13 = require("node:path");
13367
+ var import_node_path14 = require("node:path");
12858
13368
  var STATIC_FILES = {
12859
13369
  "/": { contentType: "text/html; charset=utf-8", path: "index.html" },
12860
13370
  "/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
@@ -12897,24 +13407,46 @@ async function memoryTree(config) {
12897
13407
  const root = localMemoriesRoot(config);
12898
13408
  return readTree(config, root, `viking://user/${uriSegment(config.user)}/memories`, "");
12899
13409
  }
13410
+ async function resourcesTree(config) {
13411
+ const root = localResourcesRoot(config);
13412
+ try {
13413
+ return await readTree(config, root, "viking://resources", "", {
13414
+ parseMemoryDocuments: false,
13415
+ rootName: "resources"
13416
+ });
13417
+ } catch (err) {
13418
+ if (isMissingPathError(err)) {
13419
+ return {
13420
+ children: [],
13421
+ isDir: true,
13422
+ isShared: false,
13423
+ isSystem: false,
13424
+ name: "resources",
13425
+ relativePath: "",
13426
+ uri: "viking://resources"
13427
+ };
13428
+ }
13429
+ throw err;
13430
+ }
13431
+ }
12900
13432
  async function readManagedMemory(config, uri) {
12901
13433
  assertVikingUri(uri);
12902
13434
  const path = localPathForMemoryUri(config, uri);
12903
13435
  if (!path) {
12904
13436
  throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
12905
13437
  }
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("/");
13438
+ const [content, pathStat] = await Promise.all([(0, import_promises14.readFile)(path, "utf8"), (0, import_promises14.stat)(path)]);
13439
+ const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path).split(import_node_path14.sep).join("/");
12908
13440
  const record = parseMemoryDocument(uri, content);
12909
13441
  return {
12910
13442
  content,
12911
13443
  node: {
12912
13444
  isDir: false,
12913
13445
  isShared: isInSharedNamespace(config, uri),
12914
- isSystem: isSystemMemoryName(path.split(import_node_path13.sep).at(-1) ?? ""),
13446
+ isSystem: isSystemMemoryName(path.split(import_node_path14.sep).at(-1) ?? ""),
12915
13447
  metadata: record?.metadata,
12916
13448
  modTime: pathStat.mtime.toISOString(),
12917
- name: path.split(import_node_path13.sep).at(-1) ?? uri,
13449
+ name: path.split(import_node_path14.sep).at(-1) ?? uri,
12918
13450
  relativePath,
12919
13451
  sharedTeam: sharedTeamNameForUri(config, uri),
12920
13452
  size: pathStat.size,
@@ -12981,7 +13513,8 @@ async function handleRequest(context, request, response) {
12981
13513
  return;
12982
13514
  }
12983
13515
  if (request.method === "GET" && url.pathname === "/api/tree") {
12984
- writeJson(response, 200, { tree: await memoryTree(context.config) });
13516
+ const [tree, resourceTree] = await Promise.all([memoryTree(context.config), resourcesTree(context.config)]);
13517
+ writeJson(response, 200, { resourcesTree: resourceTree, tree });
12985
13518
  return;
12986
13519
  }
12987
13520
  if (request.method === "GET" && url.pathname === "/api/memory") {
@@ -13200,21 +13733,20 @@ async function handleRequest(context, request, response) {
13200
13733
  }
13201
13734
  async function serveStatic(context, url, response) {
13202
13735
  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));
13736
+ const content = await (0, import_promises14.readFile)((0, import_node_path14.join)(toolRoot(), file.root ?? "manager", file.path));
13204
13737
  const headers = { "content-type": file.contentType };
13205
- if (url.pathname === "/" || url.pathname === "/index.html") {
13738
+ if (file.root !== "docs") {
13206
13739
  headers["cache-control"] = "no-store";
13207
13740
  }
13208
13741
  response.writeHead(200, headers);
13209
13742
  response.end(content);
13210
13743
  }
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";
13744
+ async function readTree(config, path, uri, relativePath, options = {}) {
13745
+ const pathStat = await (0, import_promises14.stat)(path);
13746
+ const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
13214
13747
  const isDir = pathStat.isDirectory();
13215
13748
  if (!isDir) {
13216
- const content = await (0, import_promises13.readFile)(path, "utf8").catch(() => "");
13217
- const record = parseMemoryDocument(uri, content);
13749
+ const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path, "utf8").catch(() => ""));
13218
13750
  return {
13219
13751
  isDir: false,
13220
13752
  isShared: isInSharedNamespace(config, uri),
@@ -13228,13 +13760,13 @@ async function readTree(config, path, uri, relativePath) {
13228
13760
  uri
13229
13761
  };
13230
13762
  }
13231
- const entries = await (0, import_promises13.readdir)(path, { withFileTypes: true });
13763
+ const entries = await (0, import_promises14.readdir)(path, { withFileTypes: true });
13232
13764
  const children = await Promise.all(
13233
13765
  entries.sort(
13234
13766
  (left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
13235
13767
  ).map((entry) => {
13236
13768
  const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
13237
- return readTree(config, (0, import_node_path13.join)(path, entry.name), `${uri}/${entry.name}`, childRelative);
13769
+ return readTree(config, (0, import_node_path14.join)(path, entry.name), `${uri}/${entry.name}`, childRelative, options);
13238
13770
  })
13239
13771
  );
13240
13772
  return {
@@ -13408,27 +13940,27 @@ async function removeManagedFolder(config, uri) {
13408
13940
  if (!path) {
13409
13941
  throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
13410
13942
  }
13411
- const pathStat = await (0, import_promises13.stat)(path);
13943
+ const pathStat = await (0, import_promises14.stat)(path);
13412
13944
  if (!pathStat.isDirectory()) {
13413
13945
  throw new Error(`Not a folder: ${uri}`);
13414
13946
  }
13415
- const relativePath = (0, import_node_path13.relative)(localMemoriesRoot(config), path);
13416
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path13.sep).includes("..")) {
13947
+ const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
13948
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
13417
13949
  throw new Error("Refusing to remove a folder outside the memories tree.");
13418
13950
  }
13419
13951
  const fileUris = await fileUrisUnderFolder(config, path);
13420
13952
  for (const fileUri of fileUris) {
13421
13953
  await runForget(config, fileUri, {});
13422
13954
  }
13423
- await (0, import_promises13.rm)(path, { force: true, recursive: true });
13955
+ await (0, import_promises14.rm)(path, { force: true, recursive: true });
13424
13956
  console.log(`Removed folder: ${uri}`);
13425
13957
  console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
13426
13958
  }
13427
13959
  async function fileUrisUnderFolder(config, folderPath) {
13428
- const entries = await (0, import_promises13.readdir)(folderPath, { withFileTypes: true });
13960
+ const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
13429
13961
  const uris = [];
13430
13962
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
13431
- const path = (0, import_node_path13.join)(folderPath, entry.name);
13963
+ const path = (0, import_node_path14.join)(folderPath, entry.name);
13432
13964
  if (entry.isDirectory()) {
13433
13965
  uris.push(...await fileUrisUnderFolder(config, path));
13434
13966
  } else if (entry.isFile()) {
@@ -13526,11 +14058,11 @@ async function runConsolidationAgent(agent, sources) {
13526
14058
  throw new Error(`${agent} executable was not found.`);
13527
14059
  }
13528
14060
  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");
14061
+ const stagingDir = await (0, import_promises14.mkdtemp)((0, import_node_path14.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
14062
+ const promptPath = (0, import_node_path14.join)(stagingDir, "prompt.txt");
13531
14063
  try {
13532
- await (0, import_promises13.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
13533
- await (0, import_promises13.chmod)(promptPath, 384);
14064
+ await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
14065
+ await (0, import_promises14.chmod)(promptPath, 384);
13534
14066
  const script = consolidationAgentScript(agent, executable);
13535
14067
  const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
13536
14068
  allowFailure: true,
@@ -13546,7 +14078,7 @@ async function runConsolidationAgent(agent, sources) {
13546
14078
  }
13547
14079
  return draft;
13548
14080
  } finally {
13549
- await (0, import_promises13.rm)(stagingDir, { force: true, recursive: true });
14081
+ await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
13550
14082
  }
13551
14083
  }
13552
14084
  function consolidationAgentScript(agent, executable) {
@@ -13684,7 +14216,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
13684
14216
  }
13685
14217
  }
13686
14218
  function localMemoriesRoot(config) {
13687
- return (0, import_node_path13.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
14219
+ return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
14220
+ }
14221
+ function localResourcesRoot(config) {
14222
+ return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "resources");
13688
14223
  }
13689
14224
  function localPathForMemoryUri(config, uri) {
13690
14225
  const prefix = `viking://user/${uriSegment(config.user)}/memories`;
@@ -13696,14 +14231,17 @@ function localPathForMemoryUri(config, uri) {
13696
14231
  if (segments.some((segment) => segment === "." || segment === "..")) {
13697
14232
  return void 0;
13698
14233
  }
13699
- return (0, import_node_path13.join)(localMemoriesRoot(config), ...segments);
14234
+ return (0, import_node_path14.join)(localMemoriesRoot(config), ...segments);
14235
+ }
14236
+ function isMissingPathError(err) {
14237
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
13700
14238
  }
13701
14239
  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("..")) {
14240
+ const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
14241
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
13704
14242
  throw new Error(`Path is outside the memories tree: ${path}`);
13705
14243
  }
13706
- return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path13.sep).join("/")}`;
14244
+ return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path14.sep).join("/")}`;
13707
14245
  }
13708
14246
  async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
13709
14247
  const prefix = "viking://";
@@ -13908,7 +14446,7 @@ async function main() {
13908
14446
  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
14447
  await runInitManifest(getRuntimeConfig(program2), options);
13910
14448
  });
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) => {
14449
+ 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
14450
  await runSeedSkills(getRuntimeConfig(program2, options.manifest), options);
13913
14451
  });
13914
14452
  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) => {