threadnote 0.6.0 → 0.6.2

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.
@@ -3522,8 +3522,8 @@ var DEFAULT_SEED_PATTERNS = [
3522
3522
  ];
3523
3523
 
3524
3524
  // src/hooks.ts
3525
- var import_promises5 = require("node:fs/promises");
3526
- var import_node_path5 = require("node:path");
3525
+ var import_promises6 = require("node:fs/promises");
3526
+ var import_node_path7 = require("node:path");
3527
3527
 
3528
3528
  // src/mcp.ts
3529
3529
  var import_node_fs2 = require("node:fs");
@@ -7118,6 +7118,15 @@ function uriSegment(value) {
7118
7118
  const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
7119
7119
  return normalized.length > 0 ? normalized : "unknown";
7120
7120
  }
7121
+ async function inferProjectFromQuery(manifestPath, query) {
7122
+ try {
7123
+ const manifest = await readSeedManifest(manifestPath);
7124
+ const normalized = query.toLowerCase();
7125
+ return manifest.projects.find((project) => normalized.includes(project.name.toLowerCase()));
7126
+ } catch {
7127
+ return void 0;
7128
+ }
7129
+ }
7121
7130
  async function readSeedManifest(path) {
7122
7131
  const raw = await (0, import_promises3.readFile)(path, "utf8");
7123
7132
  const loaded = jsYaml.load(raw);
@@ -7388,11 +7397,32 @@ async function runRecall(config, options) {
7388
7397
  args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
7389
7398
  }
7390
7399
  await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
7400
+ await augmentRecallWithSeededResources(config, ov, options, inferredUri);
7391
7401
  await printExactMemoryMatches(config, ov, options.query, {
7392
7402
  dryRun: options.dryRun === true,
7393
7403
  includeArchived: options.includeArchived === true
7394
7404
  });
7395
7405
  }
7406
+ async function augmentRecallWithSeededResources(config, ov, options, inferredUri) {
7407
+ if (options.uri || options.inferScope === false) {
7408
+ return;
7409
+ }
7410
+ const project = await inferProjectFromQuery(config.manifestPath, options.query);
7411
+ if (!project) {
7412
+ return;
7413
+ }
7414
+ const projectResourceUri = trimTrailingSlash(project.uri);
7415
+ if (!projectResourceUri.startsWith("viking://") || projectResourceUri === inferredUri) {
7416
+ return;
7417
+ }
7418
+ const args = ["search", options.query, "--uri", projectResourceUri];
7419
+ if (options.nodeLimit) {
7420
+ args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
7421
+ }
7422
+ console.log(`
7423
+ Also searching seeded resources: ${projectResourceUri}`);
7424
+ await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
7425
+ }
7396
7426
  async function runRead(config, uri, options) {
7397
7427
  assertVikingUri(uri);
7398
7428
  const ov = await openVikingCliForMode(options.dryRun === true);
@@ -7511,21 +7541,11 @@ async function runImportPack(config, options) {
7511
7541
  );
7512
7542
  }
7513
7543
  async function inferRecallUri(config, query) {
7514
- const normalizedQuery = query.toLowerCase();
7515
- if (/\bskills?\b/.test(normalizedQuery)) {
7516
- const project2 = await inferProjectFromQuery(config, normalizedQuery);
7517
- return project2 ? `viking://resources/agent-skills/repo-local-${uriSegment(project2.name)}` : "viking://resources/agent-skills";
7518
- }
7519
- const project = await inferProjectFromQuery(config, normalizedQuery);
7520
- return project ? trimTrailingSlash(project.uri) : void 0;
7521
- }
7522
- async function inferProjectFromQuery(config, normalizedQuery) {
7523
- try {
7524
- const manifest = await readSeedManifest(config.manifestPath);
7525
- return manifest.projects.find((project) => normalizedQuery.includes(project.name.toLowerCase()));
7526
- } catch (_err) {
7544
+ if (!/\bskills?\b/.test(query.toLowerCase())) {
7527
7545
  return void 0;
7528
7546
  }
7547
+ const project = await inferProjectFromQuery(config.manifestPath, query);
7548
+ return project ? `viking://resources/agent-skills/repo-local-${uriSegment(project.name)}` : "viking://resources/agent-skills";
7529
7549
  }
7530
7550
  async function printExactMemoryMatches(config, ov, query, options) {
7531
7551
  const terms = exactRecallTerms(query);
@@ -7755,7 +7775,11 @@ function exactMemoryScopes(config, includeArchived) {
7755
7775
  `${userBase}/incidents/active`,
7756
7776
  `${userBase}/events`,
7757
7777
  `${userBase}/shared`,
7758
- `viking://agent/${uriSegment(config.agentId)}/memories`
7778
+ `viking://agent/${uriSegment(config.agentId)}/memories`,
7779
+ // Seeded project resources (READMEs, AGENTS.md, SKILL.md, docs/**) live
7780
+ // under viking://resources/repos/<project>. Include them so an exact-term
7781
+ // grep in a recall surfaces matches in seeded guidance, not only memories.
7782
+ "viking://resources/repos"
7759
7783
  ];
7760
7784
  return includeArchived ? [...scopes, `${userBase}/durable/archived`, `${userBase}/handoffs/archived`, `${userBase}/incidents/archived`] : scopes;
7761
7785
  }
@@ -8109,6 +8133,128 @@ function formatBlock(value, emptyValue) {
8109
8133
  return trimmed.split("\n").map((line) => `- ${line}`).join("\n");
8110
8134
  }
8111
8135
 
8136
+ // src/update-check.ts
8137
+ var import_node_child_process2 = require("node:child_process");
8138
+ var import_promises5 = require("node:fs/promises");
8139
+ var import_node_path5 = require("node:path");
8140
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
8141
+ var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
8142
+ var FETCH_TIMEOUT_MS = 3e3;
8143
+ async function checkForThreadnoteUpdate(args) {
8144
+ if (args.currentVersion === "unknown") {
8145
+ return void 0;
8146
+ }
8147
+ const cached = await readUpdateCache(args.cachePath);
8148
+ if (cached && isCacheFresh(cached)) {
8149
+ return compareVersions(args.currentVersion, cached.latestVersion);
8150
+ }
8151
+ const fresh = await fetchLatestVersion();
8152
+ if (fresh) {
8153
+ await writeUpdateCache(args.cachePath, {
8154
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
8155
+ latestVersion: fresh,
8156
+ version: 1
8157
+ });
8158
+ return compareVersions(args.currentVersion, fresh);
8159
+ }
8160
+ if (cached) {
8161
+ return compareVersions(args.currentVersion, cached.latestVersion);
8162
+ }
8163
+ return void 0;
8164
+ }
8165
+ function spawnDetachedAutoUpdate() {
8166
+ try {
8167
+ const entry = process.argv[1];
8168
+ if (typeof entry !== "string" || entry.length === 0) {
8169
+ return;
8170
+ }
8171
+ const child = (0, import_node_child_process2.spawn)(process.execPath, [entry, "update", "--yes"], {
8172
+ detached: true,
8173
+ stdio: "ignore"
8174
+ });
8175
+ child.unref();
8176
+ } catch {
8177
+ }
8178
+ }
8179
+ function compareVersions(currentVersion, latestVersion) {
8180
+ return {
8181
+ currentVersion,
8182
+ latestVersion,
8183
+ outdated: isNewerVersion(latestVersion, currentVersion)
8184
+ };
8185
+ }
8186
+ function isNewerVersion(candidate, baseline) {
8187
+ const candidateParts = parseVersion(candidate);
8188
+ const baselineParts = parseVersion(baseline);
8189
+ for (let index = 0; index < 3; index += 1) {
8190
+ if (candidateParts[index] !== baselineParts[index]) {
8191
+ return candidateParts[index] > baselineParts[index];
8192
+ }
8193
+ }
8194
+ return false;
8195
+ }
8196
+ function parseVersion(value) {
8197
+ const parts = value.split(".").map((part) => parseInt(part, 10));
8198
+ return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
8199
+ }
8200
+ function isCacheFresh(cache) {
8201
+ const checkedAt = new Date(cache.checkedAt).getTime();
8202
+ return Number.isFinite(checkedAt) && Date.now() - checkedAt < CACHE_TTL_MS;
8203
+ }
8204
+ async function readUpdateCache(cachePath) {
8205
+ try {
8206
+ const raw = await (0, import_promises5.readFile)(cachePath, "utf8");
8207
+ const parsed = JSON.parse(raw);
8208
+ if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
8209
+ return void 0;
8210
+ }
8211
+ return { checkedAt: parsed.checkedAt, latestVersion: parsed.latestVersion, version: 1 };
8212
+ } catch {
8213
+ return void 0;
8214
+ }
8215
+ }
8216
+ async function writeUpdateCache(cachePath, contents) {
8217
+ try {
8218
+ await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(cachePath), { recursive: true });
8219
+ await (0, import_promises5.writeFile)(cachePath, `${JSON.stringify(contents)}
8220
+ `, { encoding: "utf8", mode: 384 });
8221
+ } catch {
8222
+ }
8223
+ }
8224
+ async function fetchLatestVersion() {
8225
+ try {
8226
+ const response = await fetch(NPM_LATEST_URL, {
8227
+ headers: { accept: "application/json" },
8228
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
8229
+ });
8230
+ if (!response.ok) {
8231
+ return void 0;
8232
+ }
8233
+ const data = await response.json();
8234
+ return typeof data.version === "string" && data.version.length > 0 ? data.version : void 0;
8235
+ } catch {
8236
+ return void 0;
8237
+ }
8238
+ }
8239
+
8240
+ // src/version.ts
8241
+ var import_node_fs4 = require("node:fs");
8242
+ var import_node_path6 = require("node:path");
8243
+ var cachedVersion;
8244
+ function getThreadnoteVersion() {
8245
+ if (cachedVersion !== void 0) {
8246
+ return cachedVersion;
8247
+ }
8248
+ try {
8249
+ const packageJsonPath = (0, import_node_path6.join)(__dirname, "..", "package.json");
8250
+ const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
8251
+ cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
8252
+ } catch {
8253
+ cachedVersion = "unknown";
8254
+ }
8255
+ return cachedVersion;
8256
+ }
8257
+
8112
8258
  // src/hooks.ts
8113
8259
  var MANAGED_HOOKS = [
8114
8260
  {
@@ -8142,7 +8288,7 @@ async function runHooksInstall(config, agent, options) {
8142
8288
  }
8143
8289
  async function runClaudeHooksInstall(options) {
8144
8290
  const path = expandPath(CLAUDE_SETTINGS_PATH);
8145
- const existingRaw = await exists(path) ? await (0, import_promises5.readFile)(path, "utf8") : "{}";
8291
+ const existingRaw = await exists(path) ? await (0, import_promises6.readFile)(path, "utf8") : "{}";
8146
8292
  const parsed = parseJsonConfigObject(existingRaw) ?? {};
8147
8293
  const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
8148
8294
  const before = JSON.stringify(parsed);
@@ -8159,11 +8305,11 @@ async function runClaudeHooksInstall(options) {
8159
8305
  console.log("\nRe-run with --apply to actually modify the file.");
8160
8306
  return;
8161
8307
  }
8162
- await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
8308
+ await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(path), { recursive: true });
8163
8309
  const serialized = `${JSON.stringify(next, void 0, 2)}
8164
8310
  `;
8165
- await (0, import_promises5.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
8166
- await (0, import_promises5.chmod)(path, 384);
8311
+ await (0, import_promises6.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
8312
+ await (0, import_promises6.chmod)(path, 384);
8167
8313
  console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
8168
8314
  }
8169
8315
  function withThreadnoteHooks(input2) {
@@ -8241,7 +8387,7 @@ async function hasManagedClaudeHooks() {
8241
8387
  if (!await exists(path)) {
8242
8388
  return false;
8243
8389
  }
8244
- const raw = await (0, import_promises5.readFile)(path, "utf8");
8390
+ const raw = await (0, import_promises6.readFile)(path, "utf8");
8245
8391
  const parsed = parseJsonConfigObject(raw);
8246
8392
  if (!parsed || !isJsonObject(parsed.hooks)) {
8247
8393
  return false;
@@ -8259,7 +8405,7 @@ async function hasManagedClaudeHooks() {
8259
8405
  async function runPreCompactHook(config, options = {}) {
8260
8406
  try {
8261
8407
  const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
8262
- const project = repoRoot ? (0, import_node_path5.basename)(repoRoot) : "general";
8408
+ const project = repoRoot ? (0, import_node_path7.basename)(repoRoot) : "general";
8263
8409
  await runHandoff(config, {
8264
8410
  blockers: "- none recorded",
8265
8411
  dryRun: options.dryRun === true,
@@ -8283,7 +8429,8 @@ async function runSessionStartHook(config, options = {}) {
8283
8429
  if (!repoRoot) {
8284
8430
  return;
8285
8431
  }
8286
- const project = (0, import_node_path5.basename)(repoRoot);
8432
+ const project = (0, import_node_path7.basename)(repoRoot);
8433
+ await emitUpdateBannerIfOutdated(config);
8287
8434
  process.stdout.write(`## Threadnote \u2014 latest context for ${project}
8288
8435
 
8289
8436
  `);
@@ -8300,10 +8447,36 @@ async function runSessionStartHook(config, options = {}) {
8300
8447
  );
8301
8448
  }
8302
8449
  }
8450
+ async function emitUpdateBannerIfOutdated(config) {
8451
+ try {
8452
+ const result = await checkForThreadnoteUpdate({
8453
+ cachePath: (0, import_node_path7.join)(config.agentContextHome, ".update-state.json"),
8454
+ currentVersion: getThreadnoteVersion()
8455
+ });
8456
+ if (!result || !result.outdated) {
8457
+ return;
8458
+ }
8459
+ if (process.env.THREADNOTE_AUTO_UPDATE === "1") {
8460
+ process.stdout.write(
8461
+ `[threadnote] v${result.latestVersion} available (current v${result.currentVersion}). Auto-updating in the background; the new version takes effect next session.
8462
+
8463
+ `
8464
+ );
8465
+ spawnDetachedAutoUpdate();
8466
+ return;
8467
+ }
8468
+ process.stdout.write(
8469
+ `[threadnote] v${result.latestVersion} available (current v${result.currentVersion}). Run: threadnote update
8470
+
8471
+ `
8472
+ );
8473
+ } catch {
8474
+ }
8475
+ }
8303
8476
 
8304
8477
  // src/seeding.ts
8305
- var import_promises6 = require("node:fs/promises");
8306
- var import_node_path6 = require("node:path");
8478
+ var import_promises7 = require("node:fs/promises");
8479
+ var import_node_path8 = require("node:path");
8307
8480
  async function runSeed(config, options) {
8308
8481
  const manifest = await readSeedManifest(config.manifestPath);
8309
8482
  const ignorePatterns = await loadIgnorePatterns();
@@ -8332,7 +8505,7 @@ async function runSeed(config, options) {
8332
8505
  }
8333
8506
  async function runInitManifest(config, options) {
8334
8507
  const manifestPath = expandPath(
8335
- options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path6.join)(config.agentContextHome, USER_MANIFEST_NAME)
8508
+ options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path8.join)(config.agentContextHome, USER_MANIFEST_NAME)
8336
8509
  );
8337
8510
  const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
8338
8511
  const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
@@ -8375,9 +8548,9 @@ async function runInitManifest(config, options) {
8375
8548
  console.log(output2.trimEnd());
8376
8549
  return;
8377
8550
  }
8378
- await ensureDirectory((0, import_node_path6.dirname)(manifestPath), false);
8379
- await (0, import_promises6.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
8380
- await (0, import_promises6.chmod)(manifestPath, 384);
8551
+ await ensureDirectory((0, import_node_path8.dirname)(manifestPath), false);
8552
+ await (0, import_promises7.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
8553
+ await (0, import_promises7.chmod)(manifestPath, 384);
8381
8554
  console.log(`Wrote manifest: ${manifestPath}`);
8382
8555
  console.log("Seed with:");
8383
8556
  console.log(" threadnote seed --dry-run");
@@ -8407,13 +8580,13 @@ async function resolveRepoRoot(repoInput) {
8407
8580
  async function projectIdentity(path) {
8408
8581
  const expanded = expandPath(path);
8409
8582
  try {
8410
- return await (0, import_promises6.realpath)(expanded);
8583
+ return await (0, import_promises7.realpath)(expanded);
8411
8584
  } catch (_err) {
8412
8585
  return expanded;
8413
8586
  }
8414
8587
  }
8415
8588
  function projectManifestForRepo(repoRoot, existingProjects) {
8416
- const baseName = uriSegment((0, import_node_path6.basename)(repoRoot));
8589
+ const baseName = uriSegment((0, import_node_path8.basename)(repoRoot));
8417
8590
  const usedNames = new Set(existingProjects.map((project) => project.name));
8418
8591
  const usedUris = new Set(existingProjects.map((project) => project.uri));
8419
8592
  let name = baseName;
@@ -8435,7 +8608,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
8435
8608
  for (const pattern of project.seed) {
8436
8609
  const files = await resolveProjectPattern(projectRoot, pattern);
8437
8610
  for (const filePath of files) {
8438
- const relativePath = toPosixPath((0, import_node_path6.relative)(projectRoot, filePath));
8611
+ const relativePath = toPosixPath((0, import_node_path8.relative)(projectRoot, filePath));
8439
8612
  if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
8440
8613
  continue;
8441
8614
  }
@@ -8453,20 +8626,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
8453
8626
  async function resolveProjectPattern(projectRoot, pattern) {
8454
8627
  const normalizedPattern = toPosixPath(pattern);
8455
8628
  if (!hasGlob(normalizedPattern)) {
8456
- const filePath = (0, import_node_path6.join)(projectRoot, normalizedPattern);
8629
+ const filePath = (0, import_node_path8.join)(projectRoot, normalizedPattern);
8457
8630
  return await isFile(filePath) ? [filePath] : [];
8458
8631
  }
8459
8632
  const globBase = getGlobBase(normalizedPattern);
8460
- const basePath = (0, import_node_path6.join)(projectRoot, globBase);
8633
+ const basePath = (0, import_node_path8.join)(projectRoot, globBase);
8461
8634
  if (!await exists(basePath)) {
8462
8635
  return [];
8463
8636
  }
8464
8637
  const regex = globToRegExp(normalizedPattern);
8465
8638
  const files = await walkFiles(basePath);
8466
- return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path6.relative)(projectRoot, filePath))));
8639
+ return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path8.relative)(projectRoot, filePath))));
8467
8640
  }
8468
8641
  async function prepareSeedFile(config, candidate, dryRun) {
8469
- const content = await (0, import_promises6.readFile)(candidate.filePath, "utf8");
8642
+ const content = await (0, import_promises7.readFile)(candidate.filePath, "utf8");
8470
8643
  const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
8471
8644
  const secretMatches = detectSecretMatches(redactedContent);
8472
8645
  if (secretMatches.length > 0) {
@@ -8478,14 +8651,14 @@ async function prepareSeedFile(config, candidate, dryRun) {
8478
8651
  if (redactedContent === content) {
8479
8652
  return candidate.filePath;
8480
8653
  }
8481
- const redactedPath = (0, import_node_path6.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
8654
+ const redactedPath = (0, import_node_path8.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
8482
8655
  if (dryRun) {
8483
8656
  console.log(`Would write redacted copy: ${redactedPath}`);
8484
8657
  return redactedPath;
8485
8658
  }
8486
- await ensureDirectory((0, import_node_path6.dirname)(redactedPath), false);
8487
- await (0, import_promises6.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
8488
- await (0, import_promises6.chmod)(redactedPath, 384);
8659
+ await ensureDirectory((0, import_node_path8.dirname)(redactedPath), false);
8660
+ await (0, import_promises7.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
8661
+ await (0, import_promises7.chmod)(redactedPath, 384);
8489
8662
  return redactedPath;
8490
8663
  }
8491
8664
  async function collectSkillCandidates(config) {
@@ -8510,7 +8683,7 @@ async function collectSkillCandidates(config) {
8510
8683
  for (const source of sources) {
8511
8684
  const files = await resolveAbsolutePattern(expandPath(source.pattern));
8512
8685
  for (const filePath of files) {
8513
- const content = await (0, import_promises6.readFile)(filePath, "utf8");
8686
+ const content = await (0, import_promises7.readFile)(filePath, "utf8");
8514
8687
  const matches = detectSecretMatches(content);
8515
8688
  if (matches.length > 0) {
8516
8689
  console.log(`SKIP skill with possible secret: ${filePath}`);
@@ -8541,10 +8714,10 @@ async function resolveAbsolutePattern(pattern) {
8541
8714
  return files.filter((filePath) => regex.test(toPosixPath(filePath)));
8542
8715
  }
8543
8716
  function skillResourceUri(skill) {
8544
- return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0, import_node_path6.basename)((0, import_node_path6.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
8717
+ return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0, import_node_path8.basename)((0, import_node_path8.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
8545
8718
  }
8546
8719
  async function loadIgnorePatterns() {
8547
- const raw = await (0, import_promises6.readFile)((0, import_node_path6.join)(toolRoot(), ".threadnoteignore"), "utf8");
8720
+ const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(toolRoot(), ".threadnoteignore"), "utf8");
8548
8721
  return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
8549
8722
  }
8550
8723
  function matchesIgnore(relativePath, patterns) {
@@ -8615,9 +8788,9 @@ function detectSecretMatches(content) {
8615
8788
  }
8616
8789
 
8617
8790
  // src/share.ts
8618
- var import_promises7 = require("node:fs/promises");
8791
+ var import_promises8 = require("node:fs/promises");
8619
8792
  var import_node_os4 = require("node:os");
8620
- var import_node_path7 = require("node:path");
8793
+ var import_node_path9 = require("node:path");
8621
8794
  var TEAMS_FILE_VERSION = 1;
8622
8795
  var SHARED_SEGMENT = "shared";
8623
8796
  var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
@@ -8684,8 +8857,8 @@ async function runShareInit(config, remoteUrl, options) {
8684
8857
  if (await exists(gitdir)) {
8685
8858
  throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
8686
8859
  }
8687
- await ensureDirectory((0, import_node_path7.dirname)(worktree), dryRun);
8688
- await ensureDirectory((0, import_node_path7.dirname)(gitdir), dryRun);
8860
+ await ensureDirectory((0, import_node_path9.dirname)(worktree), dryRun);
8861
+ await ensureDirectory((0, import_node_path9.dirname)(gitdir), dryRun);
8689
8862
  const git = await requiredExecutable("git");
8690
8863
  await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
8691
8864
  const newConfig = {
@@ -8716,7 +8889,7 @@ async function runShareInit(config, remoteUrl, options) {
8716
8889
  var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
8717
8890
  var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
8718
8891
  async function ensureSharedGitignore(worktree, git, push) {
8719
- const gitignorePath = (0, import_node_path7.join)(worktree, ".gitignore");
8892
+ const gitignorePath = (0, import_node_path9.join)(worktree, ".gitignore");
8720
8893
  const existing = await readFileIfExists(gitignorePath) ?? "";
8721
8894
  const lines = existing.split("\n").map((line) => line.trim());
8722
8895
  const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
@@ -8735,7 +8908,7 @@ async function ensureSharedGitignore(worktree, git, push) {
8735
8908
  segments.push(SHARED_GITIGNORE_HEADER, "\n");
8736
8909
  }
8737
8910
  segments.push(missingPatterns.join("\n"), "\n");
8738
- await (0, import_promises7.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
8911
+ await (0, import_promises8.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
8739
8912
  console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
8740
8913
  await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
8741
8914
  const commitResult = await runCommand(
@@ -8800,7 +8973,7 @@ async function runShareSync(config, options) {
8800
8973
  if (dryRun) {
8801
8974
  console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
8802
8975
  } else if (pullResult && pullResult.exitCode !== 0) {
8803
- if (await exists((0, import_node_path7.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path7.join)(team.config.gitdir, "rebase-apply"))) {
8976
+ if (await exists((0, import_node_path9.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path9.join)(team.config.gitdir, "rebase-apply"))) {
8804
8977
  throw new Error(
8805
8978
  `git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
8806
8979
  Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
@@ -8976,10 +9149,10 @@ function normalizeTeamName(input2) {
8976
9149
  return candidate;
8977
9150
  }
8978
9151
  function teamsFilePath(config) {
8979
- return (0, import_node_path7.join)(config.agentContextHome, "share", "teams.json");
9152
+ return (0, import_node_path9.join)(config.agentContextHome, "share", "teams.json");
8980
9153
  }
8981
9154
  function teamWorktreePath(config, team) {
8982
- return (0, import_node_path7.join)(
9155
+ return (0, import_node_path9.join)(
8983
9156
  config.agentContextHome,
8984
9157
  "data",
8985
9158
  "viking",
@@ -8992,7 +9165,7 @@ function teamWorktreePath(config, team) {
8992
9165
  );
8993
9166
  }
8994
9167
  function teamGitdirPath(config, team) {
8995
- return (0, import_node_path7.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
9168
+ return (0, import_node_path9.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
8996
9169
  }
8997
9170
  async function readTeamsFile(config) {
8998
9171
  const path = teamsFilePath(config);
@@ -9035,13 +9208,13 @@ async function readTeamsFile(config) {
9035
9208
  }
9036
9209
  async function writeTeamsFile(config, contents) {
9037
9210
  const path = teamsFilePath(config);
9038
- await (0, import_promises7.mkdir)((0, import_node_path7.dirname)(path), { recursive: true });
9211
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(path), { recursive: true });
9039
9212
  const serializable = {
9040
9213
  defaultTeam: contents.defaultTeam,
9041
9214
  teams: contents.teams,
9042
9215
  version: contents.version
9043
9216
  };
9044
- await (0, import_promises7.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
9217
+ await (0, import_promises8.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
9045
9218
  `, { encoding: "utf8", mode: 384 });
9046
9219
  }
9047
9220
  async function resolveTeam(config, requested) {
@@ -9071,7 +9244,7 @@ async function assertWorktreeUsable(worktree) {
9071
9244
  if (!await isDirectory(worktree)) {
9072
9245
  throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
9073
9246
  }
9074
- const entries = await (0, import_promises7.readdir)(worktree);
9247
+ const entries = await (0, import_promises8.readdir)(worktree);
9075
9248
  if (entries.length > 0) {
9076
9249
  const preview = entries.slice(0, 5).join(", ");
9077
9250
  const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
@@ -9095,7 +9268,7 @@ async function walkMemoryFiles(root) {
9095
9268
  async function visit(path, depth) {
9096
9269
  let entries;
9097
9270
  try {
9098
- entries = await (0, import_promises7.readdir)(path, { withFileTypes: true });
9271
+ entries = await (0, import_promises8.readdir)(path, { withFileTypes: true });
9099
9272
  } catch (err) {
9100
9273
  console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
9101
9274
  return;
@@ -9104,7 +9277,7 @@ async function walkMemoryFiles(root) {
9104
9277
  if (entry.name === ".git") {
9105
9278
  continue;
9106
9279
  }
9107
- const full = (0, import_node_path7.join)(path, entry.name);
9280
+ const full = (0, import_node_path9.join)(path, entry.name);
9108
9281
  if (entry.isDirectory()) {
9109
9282
  if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
9110
9283
  continue;
@@ -9128,7 +9301,7 @@ async function walkMemoryFiles(root) {
9128
9301
  return out;
9129
9302
  }
9130
9303
  function workfileToVikingUri(config, team, filePath) {
9131
- const rel = (0, import_node_path7.relative)(team.worktree, filePath).split(import_node_path7.sep).join("/");
9304
+ const rel = (0, import_node_path9.relative)(team.worktree, filePath).split(import_node_path9.sep).join("/");
9132
9305
  return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
9133
9306
  }
9134
9307
  function isInSharedNamespace(config, uri) {
@@ -9240,13 +9413,13 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun) {
9240
9413
  console.log(`Would run: ${formatShellCommand(ov, args)}`);
9241
9414
  return;
9242
9415
  }
9243
- const stagingDir = await (0, import_promises7.mkdtemp)((0, import_node_path7.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
9244
- const tempPath = (0, import_node_path7.join)(stagingDir, "body.txt");
9416
+ const stagingDir = await (0, import_promises8.mkdtemp)((0, import_node_path9.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
9417
+ const tempPath = (0, import_node_path9.join)(stagingDir, "body.txt");
9245
9418
  try {
9246
- await (0, import_promises7.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
9419
+ await (0, import_promises8.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
9247
9420
  await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode);
9248
9421
  } finally {
9249
- await (0, import_promises7.rm)(stagingDir, { force: true, recursive: true });
9422
+ await (0, import_promises8.rm)(stagingDir, { force: true, recursive: true });
9250
9423
  }
9251
9424
  }
9252
9425
  async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode) {
@@ -9304,7 +9477,7 @@ ${stdout}`.toLowerCase();
9304
9477
  return output2.includes("resource is busy") || output2.includes("resource is being processed") || output2.includes("network error") || output2.includes("error sending request") || output2.includes("http request failed") || output2.includes("connection refused") || output2.includes("connection reset") || output2.includes("timed out");
9305
9478
  }
9306
9479
  async function ingestSingleFile(ov, config, uri, filePath, initialMode) {
9307
- const content = await (0, import_promises7.readFile)(filePath, "utf8");
9480
+ const content = await (0, import_promises8.readFile)(filePath, "utf8");
9308
9481
  await writeMemoryFile(config, ov, uri, content, initialMode, false);
9309
9482
  }
9310
9483
  async function removeWithRollback(config, ov, sourceUri, rollbackUri, worktree, dryRun, label) {
@@ -9346,7 +9519,7 @@ async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
9346
9519
  if (!relative3) {
9347
9520
  return;
9348
9521
  }
9349
- await (0, import_promises7.rm)((0, import_node_path7.join)(worktree, relative3), { force: true });
9522
+ await (0, import_promises8.rm)((0, import_node_path9.join)(worktree, relative3), { force: true });
9350
9523
  }
9351
9524
  async function removeMemoryUri(config, ov, uri, dryRun) {
9352
9525
  const args = withIdentity(config, ["rm", uri]);
@@ -9403,8 +9576,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
9403
9576
  const oldRel = entries[index + 1];
9404
9577
  const newRel = entries[index + 2];
9405
9578
  if (oldRel && newRel) {
9406
- changes.push({ path: (0, import_node_path7.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
9407
- changes.push({ path: (0, import_node_path7.join)(worktree, newRel), relativePath: newRel, status: "added" });
9579
+ changes.push({ path: (0, import_node_path9.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
9580
+ changes.push({ path: (0, import_node_path9.join)(worktree, newRel), relativePath: newRel, status: "added" });
9408
9581
  }
9409
9582
  index += 3;
9410
9583
  continue;
@@ -9412,7 +9585,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
9412
9585
  const rel = entries[index + 1];
9413
9586
  if (rel) {
9414
9587
  const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
9415
- changes.push({ path: (0, import_node_path7.join)(worktree, rel), relativePath: rel, status });
9588
+ changes.push({ path: (0, import_node_path9.join)(worktree, rel), relativePath: rel, status });
9416
9589
  }
9417
9590
  index += 2;
9418
9591
  }
@@ -9436,32 +9609,32 @@ async function applyChangesToOpenViking(config, team, changes) {
9436
9609
  if (!await isFile(change.path)) {
9437
9610
  continue;
9438
9611
  }
9439
- if (change.status === "modified" && await vikingResourceExists2(ov, config, uri)) {
9440
- console.warn(
9441
- `share sync: overwriting local ${uri} with the upstream version (local edits to the shared subtree are not preserved across sync).`
9442
- );
9612
+ const ovHasResource = await vikingResourceExists2(ov, config, uri);
9613
+ if (ovHasResource) {
9614
+ const reason = change.status === "modified" ? "overwriting local with upstream (local edits to the shared subtree are not preserved across sync)" : "aligning OV to upstream (resource pre-existed in OV, likely from an earlier local publish or sync)";
9615
+ console.warn(`share sync: ${uri}: ${reason}.`);
9443
9616
  }
9444
9617
  await ensureSharedDirectoryChain(config, ov, uri, false);
9445
- const writeMode = change.status === "modified" ? "replace" : "create";
9618
+ const writeMode = ovHasResource ? "replace" : "create";
9446
9619
  await ingestSingleFile(ov, config, uri, change.path, writeMode);
9447
9620
  }
9448
9621
  }
9449
9622
 
9450
9623
  // src/lifecycle.ts
9451
- var import_node_child_process2 = require("node:child_process");
9452
- var import_node_fs5 = require("node:fs");
9453
- var import_promises10 = require("node:fs/promises");
9624
+ var import_node_child_process3 = require("node:child_process");
9625
+ var import_node_fs6 = require("node:fs");
9626
+ var import_promises11 = require("node:fs/promises");
9454
9627
  var import_node_os6 = require("node:os");
9455
- var import_node_path9 = require("node:path");
9628
+ var import_node_path11 = require("node:path");
9456
9629
  var import_node_process2 = require("node:process");
9457
- var import_promises11 = require("node:readline/promises");
9630
+ var import_promises12 = require("node:readline/promises");
9458
9631
 
9459
9632
  // src/update.ts
9460
- var import_node_fs4 = require("node:fs");
9461
- var import_promises8 = require("node:fs/promises");
9633
+ var import_node_fs5 = require("node:fs");
9634
+ var import_promises9 = require("node:fs/promises");
9462
9635
  var import_node_os5 = require("node:os");
9463
- var import_node_path8 = require("node:path");
9464
- var import_promises9 = require("node:readline/promises");
9636
+ var import_node_path10 = require("node:path");
9637
+ var import_promises10 = require("node:readline/promises");
9465
9638
  var import_node_process = require("node:process");
9466
9639
  var NPM_PACKAGE_NAME = "threadnote";
9467
9640
  var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
@@ -9509,7 +9682,7 @@ async function runUpdate(config, options) {
9509
9682
  console.log(`Update available. Run: threadnote update`);
9510
9683
  } else {
9511
9684
  console.log(
9512
- compareVersions(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
9685
+ compareVersions2(info.currentVersion, info.latestVersion) > 0 ? "Current version is newer than npm latest." : "Threadnote is up to date."
9513
9686
  );
9514
9687
  }
9515
9688
  return;
@@ -9597,26 +9770,26 @@ async function maybeRunPostUpdateAfterRepair(config, options) {
9597
9770
  async function getUpdateInfo(config, options) {
9598
9771
  const currentVersion = await currentPackageVersion();
9599
9772
  const cached = options.preferFresh ? void 0 : await readFreshCache(config, options.registry);
9600
- const latestVersion = cached?.latestVersion ?? await fetchLatestVersion(options.registry);
9773
+ const latestVersion = cached?.latestVersion ?? await fetchLatestVersion2(options.registry);
9601
9774
  if (!cached && options.allowCacheWrite) {
9602
- await writeUpdateCache(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
9775
+ await writeUpdateCache2(config, { checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion, registry: options.registry });
9603
9776
  }
9604
9777
  return {
9605
9778
  currentVersion,
9606
- isUpdateAvailable: compareVersions(currentVersion, latestVersion) < 0,
9779
+ isUpdateAvailable: compareVersions2(currentVersion, latestVersion) < 0,
9607
9780
  latestVersion,
9608
9781
  registry: options.registry
9609
9782
  };
9610
9783
  }
9611
9784
  async function currentPackageVersion() {
9612
- const rawPackage = await (0, import_promises8.readFile)((0, import_node_path8.join)(toolRoot(), "package.json"), "utf8");
9785
+ const rawPackage = await (0, import_promises9.readFile)((0, import_node_path10.join)(toolRoot(), "package.json"), "utf8");
9613
9786
  const parsed = JSON.parse(rawPackage);
9614
9787
  if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
9615
9788
  throw new Error("Could not read current threadnote package version.");
9616
9789
  }
9617
9790
  return parsed.version;
9618
9791
  }
9619
- async function fetchLatestVersion(registry) {
9792
+ async function fetchLatestVersion2(registry) {
9620
9793
  const url = new URL(`${NPM_PACKAGE_NAME}/latest`, normalizeRegistry(registry));
9621
9794
  const controller = new AbortController();
9622
9795
  const timeout = setTimeout(() => {
@@ -9657,13 +9830,13 @@ async function readFreshCache(config, registry) {
9657
9830
  return void 0;
9658
9831
  }
9659
9832
  }
9660
- async function writeUpdateCache(config, cache) {
9833
+ async function writeUpdateCache2(config, cache) {
9661
9834
  await ensureDirectory(config.agentContextHome, false);
9662
- await (0, import_promises8.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
9835
+ await (0, import_promises9.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
9663
9836
  `, { encoding: "utf8", mode: 384 });
9664
9837
  }
9665
9838
  function updateCachePath(config) {
9666
- return (0, import_node_path8.join)(config.agentContextHome, "update-check.json");
9839
+ return (0, import_node_path10.join)(config.agentContextHome, "update-check.json");
9667
9840
  }
9668
9841
  async function runApplicablePostUpdateMigrations(config, options) {
9669
9842
  const state = await readPostUpdateState(config);
@@ -9713,10 +9886,10 @@ async function applicablePostUpdateMigrations(config, options) {
9713
9886
  if (handled.has(migration.id)) {
9714
9887
  continue;
9715
9888
  }
9716
- if (compareVersions(options.fromVersion, migration.introducedIn) >= 0) {
9889
+ if (compareVersions2(options.fromVersion, migration.introducedIn) >= 0) {
9717
9890
  continue;
9718
9891
  }
9719
- if (compareVersions(migration.introducedIn, options.toVersion) > 0) {
9892
+ if (compareVersions2(migration.introducedIn, options.toVersion) > 0) {
9720
9893
  continue;
9721
9894
  }
9722
9895
  if (migration.requiresLegacyHandoffs === true && !await hasLegacyLifecycleHandoffCandidates(config)) {
@@ -9727,7 +9900,7 @@ async function applicablePostUpdateMigrations(config, options) {
9727
9900
  return applicable;
9728
9901
  }
9729
9902
  async function readPostUpdateMigrations() {
9730
- const raw = await readFileIfExists((0, import_node_path8.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
9903
+ const raw = await readFileIfExists((0, import_node_path10.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
9731
9904
  if (!raw) {
9732
9905
  return [];
9733
9906
  }
@@ -9766,7 +9939,7 @@ function printPostUpdateMigration(migration) {
9766
9939
  }
9767
9940
  }
9768
9941
  async function confirmPostUpdateMigration(prompt) {
9769
- const readline = (0, import_promises9.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
9942
+ const readline = (0, import_promises10.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
9770
9943
  try {
9771
9944
  const answer = (await readline.question(prompt)).trim().toLowerCase();
9772
9945
  return answer === "y" || answer === "yes";
@@ -9798,11 +9971,11 @@ async function readPostUpdateState(config) {
9798
9971
  }
9799
9972
  async function writePostUpdateState(config, state) {
9800
9973
  await ensureDirectory(config.agentContextHome, false);
9801
- await (0, import_promises8.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
9974
+ await (0, import_promises9.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
9802
9975
  `, { encoding: "utf8", mode: 384 });
9803
9976
  }
9804
9977
  function postUpdateStatePath(config) {
9805
- return (0, import_node_path8.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
9978
+ return (0, import_node_path10.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
9806
9979
  }
9807
9980
  async function resolveUpdateRuntime(runtime) {
9808
9981
  if (runtime !== "auto") {
@@ -9832,18 +10005,18 @@ async function runtimeThreadnoteBin(runtime) {
9832
10005
  if (runtime === "npm") {
9833
10006
  const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
9834
10007
  const prefix = result.stdout.trim();
9835
- return prefix ? (0, import_node_path8.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
10008
+ return prefix ? (0, import_node_path10.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
9836
10009
  }
9837
10010
  if (runtime === "bun") {
9838
10011
  const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
9839
10012
  const binDir = result.stdout.trim();
9840
- return binDir ? (0, import_node_path8.join)(binDir, NPM_PACKAGE_NAME) : void 0;
10013
+ return binDir ? (0, import_node_path10.join)(binDir, NPM_PACKAGE_NAME) : void 0;
9841
10014
  }
9842
- return (0, import_node_path8.join)(process.env.DENO_INSTALL ?? (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
10015
+ return (0, import_node_path10.join)(process.env.DENO_INSTALL ?? (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
9843
10016
  }
9844
10017
  async function isExecutable(path) {
9845
10018
  try {
9846
- await (0, import_promises8.access)(path, import_node_fs4.constants.X_OK);
10019
+ await (0, import_promises9.access)(path, import_node_fs5.constants.X_OK);
9847
10020
  return true;
9848
10021
  } catch (_err) {
9849
10022
  return false;
@@ -9884,9 +10057,9 @@ function updateRegistry() {
9884
10057
  function isUpdateNotificationDisabled() {
9885
10058
  return process.env.CI !== void 0 || process.env.NO_UPDATE_NOTIFIER !== void 0 || process.env.THREADNOTE_NO_UPDATE_CHECK !== void 0;
9886
10059
  }
9887
- function compareVersions(currentVersion, latestVersion) {
9888
- const current = parseVersion(currentVersion);
9889
- const latest = parseVersion(latestVersion);
10060
+ function compareVersions2(currentVersion, latestVersion) {
10061
+ const current = parseVersion2(currentVersion);
10062
+ const latest = parseVersion2(latestVersion);
9890
10063
  for (let index = 0; index < 3; index += 1) {
9891
10064
  const difference = current.numbers[index] - latest.numbers[index];
9892
10065
  if (difference !== 0) {
@@ -9904,7 +10077,7 @@ function compareVersions(currentVersion, latestVersion) {
9904
10077
  }
9905
10078
  return current.prerelease.localeCompare(latest.prerelease);
9906
10079
  }
9907
- function parseVersion(version) {
10080
+ function parseVersion2(version) {
9908
10081
  const normalized = version.trim().replace(/^v/, "");
9909
10082
  const [core2, prerelease] = normalized.split("-", 2);
9910
10083
  const parts = core2.split(".").map((part) => Number(part));
@@ -9934,9 +10107,9 @@ async function runDoctor(config, options) {
9934
10107
  checks.push(await commandShimCheck());
9935
10108
  checks.push(...await userAgentInstructionsChecks());
9936
10109
  checks.push(await manifestCheck(config.manifestPath));
9937
- checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
9938
- checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
9939
- checks.push(await fileCheck((0, import_node_path9.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
10110
+ checks.push(await fileCheck((0, import_node_path11.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
10111
+ checks.push(await fileCheck((0, import_node_path11.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
10112
+ checks.push(await fileCheck((0, import_node_path11.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
9940
10113
  checks.push(await healthCheck(config));
9941
10114
  for (const check of checks) {
9942
10115
  console.log(`${formatStatus(check.status)} ${check.name}: ${check.detail}`);
@@ -9953,9 +10126,9 @@ async function runInstall(config, options) {
9953
10126
  const repairInvalidConfigs = options.repairInvalidConfigs === true;
9954
10127
  const dryRun = options.dryRun === true;
9955
10128
  await ensureDirectory(config.agentContextHome, dryRun);
9956
- await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "logs"), dryRun);
9957
- await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "redacted"), dryRun);
9958
- await ensureDirectory((0, import_node_path9.join)(config.agentContextHome, "mcp"), dryRun);
10129
+ await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "logs"), dryRun);
10130
+ await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "redacted"), dryRun);
10131
+ await ensureDirectory((0, import_node_path11.join)(config.agentContextHome, "mcp"), dryRun);
9959
10132
  await installCommandShim(dryRun);
9960
10133
  await installUserAgentInstructions(dryRun);
9961
10134
  const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
@@ -9981,17 +10154,17 @@ async function runInstall(config, options) {
9981
10154
  }
9982
10155
  await writeTemplateIfMissing({
9983
10156
  config,
9984
- destinationPath: (0, import_node_path9.join)(config.agentContextHome, "ov.conf"),
10157
+ destinationPath: (0, import_node_path11.join)(config.agentContextHome, "ov.conf"),
9985
10158
  dryRun,
9986
10159
  shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
9987
- templatePath: (0, import_node_path9.join)(toolRoot(), "config", "ov.conf.template.json")
10160
+ templatePath: (0, import_node_path11.join)(toolRoot(), "config", "ov.conf.template.json")
9988
10161
  });
9989
10162
  await writeTemplateIfMissing({
9990
10163
  config,
9991
- destinationPath: (0, import_node_path9.join)(config.agentContextHome, "ovcli.conf"),
10164
+ destinationPath: (0, import_node_path11.join)(config.agentContextHome, "ovcli.conf"),
9992
10165
  dryRun,
9993
10166
  shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
9994
- templatePath: (0, import_node_path9.join)(toolRoot(), "config", "ovcli.conf.template.json")
10167
+ templatePath: (0, import_node_path11.join)(toolRoot(), "config", "ovcli.conf.template.json")
9995
10168
  });
9996
10169
  if (options.start !== false) {
9997
10170
  const healthy = await repairServerHealth(config, dryRun);
@@ -10045,7 +10218,7 @@ async function runUninstall(config, options) {
10045
10218
  }
10046
10219
  console.log("Uninstalling local Threadnote setup.");
10047
10220
  await runStop(config, { dryRun });
10048
- await removePathIfExists((0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
10221
+ await removePathIfExists((0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
10049
10222
  await removeLaunchAgent(dryRun);
10050
10223
  await removeMcpConfigs(options.mcp ?? "available", dryRun);
10051
10224
  await removeMcpSnippets(config, dryRun);
@@ -10102,16 +10275,16 @@ async function repairManifest(config, dryRun) {
10102
10275
  console.log(output2.trimEnd());
10103
10276
  return;
10104
10277
  }
10105
- await ensureDirectory((0, import_node_path9.dirname)(config.manifestPath), false);
10278
+ await ensureDirectory((0, import_node_path11.dirname)(config.manifestPath), false);
10106
10279
  const currentContent = await readFileIfExists(config.manifestPath);
10107
10280
  if (currentContent !== void 0) {
10108
10281
  const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
10109
- await (0, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
10110
- await (0, import_promises10.chmod)(backupPath, 384);
10282
+ await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
10283
+ await (0, import_promises11.chmod)(backupPath, 384);
10111
10284
  console.log(`Backup: ${backupPath}`);
10112
10285
  }
10113
- await (0, import_promises10.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
10114
- await (0, import_promises10.chmod)(config.manifestPath, 384);
10286
+ await (0, import_promises11.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
10287
+ await (0, import_promises11.chmod)(config.manifestPath, 384);
10115
10288
  console.log(`Wrote replacement manifest: ${config.manifestPath}`);
10116
10289
  }
10117
10290
  async function repairServerHealth(config, dryRun) {
@@ -10152,16 +10325,16 @@ async function runStart(config, options) {
10152
10325
  );
10153
10326
  }
10154
10327
  const logPath = openVikingLogPath(config);
10155
- await ensureDirectory((0, import_node_path9.dirname)(logPath), false);
10328
+ await ensureDirectory((0, import_node_path11.dirname)(logPath), false);
10156
10329
  if (options.foreground === true) {
10157
10330
  const result = await runInteractive(server, args);
10158
10331
  process.exitCode = result;
10159
10332
  return;
10160
10333
  }
10161
- const logFd = (0, import_node_fs5.openSync)(logPath, "a");
10334
+ const logFd = (0, import_node_fs6.openSync)(logPath, "a");
10162
10335
  const child = spawnDetachedServerWithLog(server, args, logFd);
10163
10336
  child.unref();
10164
- await (0, import_promises10.writeFile)((0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
10337
+ await (0, import_promises11.writeFile)((0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
10165
10338
  `, "utf8");
10166
10339
  const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
10167
10340
  if (health) {
@@ -10176,11 +10349,11 @@ function spawnDetachedServerWithLog(server, args, logFd) {
10176
10349
  try {
10177
10350
  return spawnDetachedServer(server, args, logFd);
10178
10351
  } finally {
10179
- (0, import_node_fs5.closeSync)(logFd);
10352
+ (0, import_node_fs6.closeSync)(logFd);
10180
10353
  }
10181
10354
  }
10182
10355
  function spawnDetachedServer(server, args, logFd) {
10183
- return (0, import_node_child_process2.spawn)(server, args, {
10356
+ return (0, import_node_child_process3.spawn)(server, args, {
10184
10357
  detached: true,
10185
10358
  stdio: ["ignore", logFd, logFd]
10186
10359
  });
@@ -10194,7 +10367,7 @@ async function runStop(config, options) {
10194
10367
  console.log(`No LaunchAgent found: ${launchAgentPath}`);
10195
10368
  }
10196
10369
  }
10197
- const pidPath = (0, import_node_path9.join)(config.agentContextHome, "openviking-server.pid");
10370
+ const pidPath = (0, import_node_path11.join)(config.agentContextHome, "openviking-server.pid");
10198
10371
  const pidText = await readFileIfExists(pidPath);
10199
10372
  if (!pidText) {
10200
10373
  console.log("No pid file found for detached OpenViking server.");
@@ -10290,7 +10463,7 @@ async function pythonSystemCertificatesCheck() {
10290
10463
  };
10291
10464
  }
10292
10465
  async function commandShimCheck() {
10293
- const shimPath = (0, import_node_path9.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
10466
+ const shimPath = (0, import_node_path11.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
10294
10467
  const content = await readFileIfExists(shimPath);
10295
10468
  if (content === void 0) {
10296
10469
  return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
@@ -10356,11 +10529,11 @@ async function hasPythonModule(serverPath, moduleName) {
10356
10529
  async function siblingPythonForExecutable(executablePath) {
10357
10530
  let resolvedPath;
10358
10531
  try {
10359
- resolvedPath = await (0, import_promises10.realpath)(executablePath);
10532
+ resolvedPath = await (0, import_promises11.realpath)(executablePath);
10360
10533
  } catch (_err) {
10361
10534
  return void 0;
10362
10535
  }
10363
- const pythonPath = (0, import_node_path9.join)((0, import_node_path9.dirname)(resolvedPath), "python");
10536
+ const pythonPath = (0, import_node_path11.join)((0, import_node_path11.dirname)(resolvedPath), "python");
10364
10537
  return await exists(pythonPath) ? pythonPath : void 0;
10365
10538
  }
10366
10539
  async function manifestCheck(path) {
@@ -10431,7 +10604,7 @@ async function offerToInstallUv() {
10431
10604
  );
10432
10605
  return false;
10433
10606
  }
10434
- const readline = (0, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
10607
+ const readline = (0, import_promises12.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
10435
10608
  let answer;
10436
10609
  try {
10437
10610
  answer = (await readline.question(
@@ -10554,38 +10727,38 @@ function printInstallNextSteps(options) {
10554
10727
  }
10555
10728
  async function writeTemplateIfMissing(options) {
10556
10729
  if (await exists(options.destinationPath)) {
10557
- const currentContent = await (0, import_promises10.readFile)(options.destinationPath, "utf8");
10730
+ const currentContent = await (0, import_promises11.readFile)(options.destinationPath, "utf8");
10558
10731
  if (options.shouldRepair?.(currentContent) !== true) {
10559
10732
  console.log(`Already exists: ${options.destinationPath}`);
10560
10733
  return;
10561
10734
  }
10562
- const rendered2 = renderTemplate(await (0, import_promises10.readFile)(options.templatePath, "utf8"), options.config);
10735
+ const rendered2 = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
10563
10736
  if (options.dryRun) {
10564
10737
  console.log(`Would repair generated config: ${options.destinationPath}`);
10565
10738
  return;
10566
10739
  }
10567
10740
  const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
10568
- await (0, import_promises10.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
10569
- await (0, import_promises10.chmod)(backupPath, 384);
10570
- await (0, import_promises10.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
10571
- await (0, import_promises10.chmod)(options.destinationPath, 384);
10741
+ await (0, import_promises11.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
10742
+ await (0, import_promises11.chmod)(backupPath, 384);
10743
+ await (0, import_promises11.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
10744
+ await (0, import_promises11.chmod)(options.destinationPath, 384);
10572
10745
  console.log(`Repaired generated config: ${options.destinationPath}`);
10573
10746
  console.log(`Backup: ${backupPath}`);
10574
10747
  return;
10575
10748
  }
10576
- const rendered = renderTemplate(await (0, import_promises10.readFile)(options.templatePath, "utf8"), options.config);
10749
+ const rendered = renderTemplate(await (0, import_promises11.readFile)(options.templatePath, "utf8"), options.config);
10577
10750
  if (options.dryRun) {
10578
10751
  console.log(`Would write ${options.destinationPath}`);
10579
10752
  return;
10580
10753
  }
10581
- await ensureDirectory((0, import_node_path9.dirname)(options.destinationPath), false);
10582
- await (0, import_promises10.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
10583
- await (0, import_promises10.chmod)(options.destinationPath, 384);
10754
+ await ensureDirectory((0, import_node_path11.dirname)(options.destinationPath), false);
10755
+ await (0, import_promises11.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
10756
+ await (0, import_promises11.chmod)(options.destinationPath, 384);
10584
10757
  console.log(`Wrote ${options.destinationPath}`);
10585
10758
  }
10586
10759
  async function installCommandShim(dryRun) {
10587
10760
  const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
10588
- const shimPath = (0, import_node_path9.join)(binDir, "threadnote");
10761
+ const shimPath = (0, import_node_path11.join)(binDir, "threadnote");
10589
10762
  const existingContent = await readFileIfExists(shimPath);
10590
10763
  if (existingContent && !isManagedCommandShim(existingContent)) {
10591
10764
  console.log(`WARN not overwriting existing command shim: ${shimPath}`);
@@ -10601,12 +10774,12 @@ async function installCommandShim(dryRun) {
10601
10774
  return;
10602
10775
  }
10603
10776
  await ensureDirectory(binDir, false);
10604
- await (0, import_promises10.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
10605
- await (0, import_promises10.chmod)(shimPath, 493);
10777
+ await (0, import_promises11.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
10778
+ await (0, import_promises11.chmod)(shimPath, 493);
10606
10779
  console.log(`Wrote command shim: ${shimPath}`);
10607
10780
  }
10608
10781
  async function removeCommandShim(dryRun) {
10609
- const shimPath = (0, import_node_path9.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
10782
+ const shimPath = (0, import_node_path11.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
10610
10783
  const content = await readFileIfExists(shimPath);
10611
10784
  if (content === void 0) {
10612
10785
  console.log(`Already absent: ${shimPath}`);
@@ -10640,8 +10813,8 @@ async function installUserAgentInstructions(dryRun) {
10640
10813
  console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
10641
10814
  continue;
10642
10815
  }
10643
- await ensureDirectory((0, import_node_path9.dirname)(targetPath), false);
10644
- await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
10816
+ await ensureDirectory((0, import_node_path11.dirname)(targetPath), false);
10817
+ await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
10645
10818
  console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
10646
10819
  }
10647
10820
  }
@@ -10678,7 +10851,7 @@ async function removeUserAgentInstructions(dryRun) {
10678
10851
  console.log(`Would update ${targetPath}`);
10679
10852
  continue;
10680
10853
  }
10681
- await (0, import_promises10.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
10854
+ await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
10682
10855
  console.log(`Updated ${targetPath}`);
10683
10856
  }
10684
10857
  }
@@ -10698,7 +10871,7 @@ async function renderUserAgentInstructions(target) {
10698
10871
  ].join("\n");
10699
10872
  }
10700
10873
  async function renderUserAgentInstructionsBlock() {
10701
- const instructions = (await (0, import_promises10.readFile)((0, import_node_path9.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
10874
+ const instructions = (await (0, import_promises11.readFile)((0, import_node_path11.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
10702
10875
  return `${USER_INSTRUCTIONS_START_MARKER}
10703
10876
  ${instructions}
10704
10877
  ${USER_INSTRUCTIONS_END_MARKER}`;
@@ -10776,9 +10949,9 @@ async function installLaunchAgent(config, dryRun) {
10776
10949
  if ((0, import_node_os6.platform)() !== "darwin") {
10777
10950
  throw new Error("launchd autostart is only supported on macOS.");
10778
10951
  }
10779
- const source = (0, import_node_path9.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
10952
+ const source = (0, import_node_path11.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
10780
10953
  const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
10781
- const rendered = renderTemplate(await (0, import_promises10.readFile)(source, "utf8"), config);
10954
+ const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config);
10782
10955
  if (dryRun) {
10783
10956
  console.log(`Would write ${destination}`);
10784
10957
  console.log(`Would run: launchctl unload ${destination}`);
@@ -10786,9 +10959,9 @@ async function installLaunchAgent(config, dryRun) {
10786
10959
  console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
10787
10960
  return;
10788
10961
  }
10789
- await ensureDirectory((0, import_node_path9.dirname)(destination), false);
10790
- await ensureDirectory((0, import_node_path9.dirname)(openVikingLogPath(config)), false);
10791
- await (0, import_promises10.writeFile)(destination, rendered, "utf8");
10962
+ await ensureDirectory((0, import_node_path11.dirname)(destination), false);
10963
+ await ensureDirectory((0, import_node_path11.dirname)(openVikingLogPath(config)), false);
10964
+ await (0, import_promises11.writeFile)(destination, rendered, "utf8");
10792
10965
  await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
10793
10966
  await maybeRun(false, "launchctl", ["load", destination]);
10794
10967
  await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
@@ -10851,7 +11024,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
10851
11024
  if (typeof parsed.default_user !== "string") {
10852
11025
  return false;
10853
11026
  }
10854
- if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path9.join)(config.agentContextHome, "data")) {
11027
+ if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path11.join)(config.agentContextHome, "data")) {
10855
11028
  return false;
10856
11029
  }
10857
11030
  return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);