threadnote 1.5.0 → 1.6.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.
@@ -3447,7 +3447,7 @@ var DEFAULT_SEED_PATTERNS = [
3447
3447
  ];
3448
3448
 
3449
3449
  // src/hooks.ts
3450
- var import_promises8 = require("node:fs/promises");
3450
+ var import_promises9 = require("node:fs/promises");
3451
3451
  var import_node_path11 = require("node:path");
3452
3452
 
3453
3453
  // src/mcp.ts
@@ -3663,7 +3663,7 @@ function hasGlob(path2) {
3663
3663
  return path2.includes("*") || path2.includes("?");
3664
3664
  }
3665
3665
  function escapeRegExp(value) {
3666
- return value.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
3666
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3667
3667
  }
3668
3668
  async function requiredOpenVikingCli() {
3669
3669
  const command2 = await findOpenVikingCli();
@@ -7548,6 +7548,35 @@ async function inferProjectFromQuery(manifestPath, query) {
7548
7548
  return void 0;
7549
7549
  }
7550
7550
  }
7551
+ function resolveWorksetProjects(manifest, workset) {
7552
+ const byName = new Map(manifest.projects.map((project) => [project.name.toLowerCase(), project]));
7553
+ const projects = workset.projects.map((name) => byName.get(name.toLowerCase())).filter((project) => project !== void 0);
7554
+ return { name: workset.name, projects };
7555
+ }
7556
+ async function requireWorkset(manifestPath, worksetName) {
7557
+ const manifest = await readSeedManifest(manifestPath);
7558
+ const workset = manifest.worksets?.find((entry) => entry.name.toLowerCase() === worksetName.toLowerCase());
7559
+ if (!workset) {
7560
+ throw new Error(`No workset named "${worksetName}" in ${manifestPath}.`);
7561
+ }
7562
+ return resolveWorksetProjects(manifest, workset);
7563
+ }
7564
+ async function inferWorksetFromQuery(manifestPath, query) {
7565
+ try {
7566
+ const manifest = await readSeedManifest(manifestPath);
7567
+ if (!manifest.worksets || manifest.worksets.length === 0) {
7568
+ return void 0;
7569
+ }
7570
+ const workset = manifest.worksets.find((entry) => containsNameToken(query, entry.name));
7571
+ return workset ? resolveWorksetProjects(manifest, workset) : void 0;
7572
+ } catch {
7573
+ return void 0;
7574
+ }
7575
+ }
7576
+ function containsNameToken(query, name) {
7577
+ const escaped = escapeRegExp(name.toLowerCase());
7578
+ return new RegExp(`(^|[^a-z0-9])${escaped}($|[^a-z0-9])`).test(query.toLowerCase());
7579
+ }
7551
7580
  async function readSeedManifest(path2) {
7552
7581
  const raw = await (0, import_promises3.readFile)(path2, "utf8");
7553
7582
  const loaded = index_vite_proxy_tmp_default.load(raw);
@@ -7579,7 +7608,33 @@ async function readSeedManifest(path2) {
7579
7608
  uri: readString(loaded.future_monorepo, "uri")
7580
7609
  };
7581
7610
  }
7582
- return { futureMonorepo, projects, version };
7611
+ let worksets;
7612
+ if (loaded.worksets !== void 0) {
7613
+ if (!Array.isArray(loaded.worksets)) {
7614
+ throw new Error(`Manifest worksets must be an array: ${path2}`);
7615
+ }
7616
+ worksets = loaded.worksets.map((worksetValue) => {
7617
+ if (!isJsonObject(worksetValue)) {
7618
+ throw new Error(`Manifest workset must be an object: ${path2}`);
7619
+ }
7620
+ return {
7621
+ description: readOptionalString(worksetValue, "description"),
7622
+ name: readString(worksetValue, "name"),
7623
+ projects: readStringArray(worksetValue, "projects")
7624
+ };
7625
+ });
7626
+ }
7627
+ return { futureMonorepo, projects, version, worksets };
7628
+ }
7629
+ function readOptionalString(object, key) {
7630
+ const value = object[key];
7631
+ if (value === void 0) {
7632
+ return void 0;
7633
+ }
7634
+ if (typeof value !== "string") {
7635
+ throw new Error(`Expected string for ${key}`);
7636
+ }
7637
+ return value;
7583
7638
  }
7584
7639
  function readString(object, key) {
7585
7640
  const value = object[key];
@@ -7960,6 +8015,7 @@ function parseMemoryDocument(uri, content) {
7960
8015
  archivedFrom: headerValue(header, "archived_from"),
7961
8016
  kind,
7962
8017
  project: normalizeOptionalMetadata(headerValue(header, "project") ?? headerValue(header, "repo")),
8018
+ references: headerValues(header, "references"),
7963
8019
  sourceAgentClient: headerValue(header, "source_agent_client") ?? "unknown",
7964
8020
  status,
7965
8021
  supersedes: headerValue(header, "supersedes"),
@@ -8161,6 +8217,24 @@ function recallHygieneNudges(text, options) {
8161
8217
  }
8162
8218
  return [...new Set(nudges)];
8163
8219
  }
8220
+ function referencedUrisFromRecords(records, recallOutput) {
8221
+ const seen = /* @__PURE__ */ new Set();
8222
+ const result = [];
8223
+ for (const record of records) {
8224
+ for (const uri of record.metadata.references ?? []) {
8225
+ if (seen.has(uri) || recallOutput.includes(uri)) {
8226
+ continue;
8227
+ }
8228
+ seen.add(uri);
8229
+ result.push(uri);
8230
+ }
8231
+ }
8232
+ return result;
8233
+ }
8234
+ function referencedContextExcerpt(body, maxLines) {
8235
+ const lines = body.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(0, maxLines);
8236
+ return lines.map((line) => ` ${line}`).join("\n");
8237
+ }
8164
8238
  function activePersonalMemoryUrisFromText(text, user) {
8165
8239
  const userSegment = uriSegment(user);
8166
8240
  const matches = text.matchAll(/viking:\/\/[^\s)]+/g);
@@ -8311,7 +8385,8 @@ function formatMemoryDocument(title, metadata, body) {
8311
8385
  `source_agent_client: ${metadata.sourceAgentClient}`,
8312
8386
  `timestamp: ${metadata.timestamp}`,
8313
8387
  metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
8314
- metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
8388
+ metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0,
8389
+ ...(metadata.references ?? []).map((uri) => `references: ${uri}`)
8315
8390
  ].filter((line) => line !== void 0);
8316
8391
  return [...header, "", body.trim()].join("\n");
8317
8392
  }
@@ -8319,6 +8394,11 @@ function headerValue(header, key) {
8319
8394
  const prefix = `${key}:`;
8320
8395
  return header.split("\n").find((line) => line.startsWith(prefix))?.slice(prefix.length).trim();
8321
8396
  }
8397
+ function headerValues(header, key) {
8398
+ const prefix = `${key}:`;
8399
+ const values = header.split("\n").filter((line) => line.startsWith(prefix)).map((line) => line.slice(prefix.length).trim()).filter((value) => value.length > 0);
8400
+ return values.length > 0 ? values : void 0;
8401
+ }
8322
8402
  function parseOptionalMemoryKind(value) {
8323
8403
  if (!value) {
8324
8404
  return void 0;
@@ -10303,8 +10383,8 @@ function vikingUriToWorktreeRelative(config, uri, team) {
10303
10383
  }
10304
10384
  async function isRegularFileNoSymlink(path2) {
10305
10385
  try {
10306
- const stat5 = await (0, import_promises5.lstat)(path2);
10307
- return stat5.isFile();
10386
+ const stat6 = await (0, import_promises5.lstat)(path2);
10387
+ return stat6.isFile();
10308
10388
  } catch (_err) {
10309
10389
  return false;
10310
10390
  }
@@ -10863,7 +10943,7 @@ function stripPersonalProvenance(content) {
10863
10943
  }
10864
10944
  const cleaned = [];
10865
10945
  for (let index = 0; index < headerEnd; index += 1) {
10866
- if (/^(?:supersedes|archived_from):\s/.test(lines[index])) {
10946
+ if (/^(?:supersedes|archived_from|references):\s/.test(lines[index])) {
10867
10947
  continue;
10868
10948
  }
10869
10949
  cleaned.push(lines[index]);
@@ -11377,6 +11457,7 @@ async function runRecall(config, options) {
11377
11457
  const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
11378
11458
  const project = await inferProjectFromQuery(config.manifestPath, options.project ?? projectQuery);
11379
11459
  const nodeLimit = options.nodeLimit ? parsePositiveInteger(options.nodeLimit, "node limit") : void 0;
11460
+ const explicitWorkset = options.workset ? await requireWorkset(config.manifestPath, options.workset) : void 0;
11380
11461
  const searchArgs = (scopeUri) => [
11381
11462
  "search",
11382
11463
  query,
@@ -11402,6 +11483,15 @@ async function runRecall(config, options) {
11402
11483
  if (seededUri?.startsWith("viking://") && seededUri !== inferredUri && !options.uri && options.inferScope !== false) {
11403
11484
  passes.push(await recallSearchHits(config, ov, searchArgs(seededUri), { dryRun, includeArchived }));
11404
11485
  }
11486
+ const workset = !options.uri && explicitWorkset ? explicitWorkset : !options.uri && options.inferScope !== false ? await inferWorksetFromQuery(config.manifestPath, projectQuery) : void 0;
11487
+ if (workset && workset.projects.length > 0) {
11488
+ console.log(`Workset scope: ${workset.name} (${workset.projects.map((member) => member.name).join(", ")})`);
11489
+ const alreadyScoped = new Set([inferredUri, seededUri].filter((uri) => uri !== void 0));
11490
+ const worksetScopes = worksetScopeUris(config, workset).filter((uri) => !alreadyScoped.has(uri)).slice(0, MAX_WORKSET_PASSES);
11491
+ for (const scope of worksetScopes) {
11492
+ passes.push(await recallSearchHits(config, ov, searchArgs(scope), { dryRun, includeArchived }));
11493
+ }
11494
+ }
11405
11495
  const recallOutputs = [];
11406
11496
  const exactMatches = await collectExactMemoryMatches(config, ov, query, { dryRun, includeArchived, project });
11407
11497
  const { semanticSection, exactTail } = buildRecallSections(passes, exactMatches, nodeLimit ?? 12);
@@ -11415,8 +11505,45 @@ ${semanticSection}`);
11415
11505
  ${exactTail}`);
11416
11506
  recallOutputs.push(exactTail);
11417
11507
  }
11508
+ const referencedSection = await referencedContextSection(config, recallOutputs.join("\n"));
11509
+ if (referencedSection) {
11510
+ console.log(`
11511
+ ${referencedSection}`);
11512
+ recallOutputs.push(referencedSection);
11513
+ }
11418
11514
  await printRecallHygieneNudges(config, recallOutputs.join("\n"));
11419
11515
  }
11516
+ var MAX_REFERENCED_CONTEXT = 5;
11517
+ var REFERENCED_EXCERPT_LINES = 12;
11518
+ async function referencedContextSection(config, recallOutput) {
11519
+ const surfacedUris = activePersonalMemoryUrisFromText(recallOutput, config.user);
11520
+ if (surfacedUris.length === 0) {
11521
+ return void 0;
11522
+ }
11523
+ const surfaced = await readMemoryRecordsByUri(config, surfacedUris);
11524
+ const referenced = referencedUrisFromRecords(surfaced, recallOutput);
11525
+ if (referenced.length === 0) {
11526
+ return void 0;
11527
+ }
11528
+ const capped = referenced.slice(0, MAX_REFERENCED_CONTEXT);
11529
+ const records = await readMemoryRecordsByUri(config, capped);
11530
+ const byUri = new Map(records.map((record) => [record.uri, record]));
11531
+ const lines = ["Referenced read-only context (one-way pointers from surfaced memories):"];
11532
+ for (const uri of capped) {
11533
+ const record = byUri.get(uri);
11534
+ if (record) {
11535
+ lines.push(`- ${uri}`, referencedContextExcerpt(record.body, REFERENCED_EXCERPT_LINES));
11536
+ } else {
11537
+ lines.push(`- ${uri} [reference unavailable locally]`);
11538
+ }
11539
+ }
11540
+ if (referenced.length > capped.length) {
11541
+ lines.push(
11542
+ `- \u2026 ${referenced.length - capped.length} more referenced ${referenced.length - capped.length === 1 ? "memory" : "memories"} omitted`
11543
+ );
11544
+ }
11545
+ return lines.join("\n");
11546
+ }
11420
11547
  async function recallSearchHits(config, ov, args, options) {
11421
11548
  const jsonArgs = withIdentity(config, [...args, "--output", "json"]);
11422
11549
  console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, jsonArgs)}`);
@@ -11932,8 +12059,8 @@ async function removeVikingResourceWithRetry(ov, config, uri) {
11932
12059
  return false;
11933
12060
  }
11934
12061
  async function vikingResourceExists2(ov, config, uri) {
11935
- const stat5 = await runCommand(ov, withIdentity(config, ["stat", uri]), { allowFailure: true });
11936
- return stat5.exitCode === 0;
12062
+ const stat6 = await runCommand(ov, withIdentity(config, ["stat", uri]), { allowFailure: true });
12063
+ return stat6.exitCode === 0;
11937
12064
  }
11938
12065
  async function ensureDurableMemoryDirectory(ov, config) {
11939
12066
  await ensureMemoryDirectory(ov, config, durableMemoryDirectoryUri(config));
@@ -12000,6 +12127,18 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
12000
12127
  function lifecycleMigrationUri(config, metadata, hash) {
12001
12128
  return `${memoryDirectoryUri(config, metadata)}/legacy-${hash.slice(0, 16)}.md`;
12002
12129
  }
12130
+ var MAX_WORKSET_PASSES = 12;
12131
+ function worksetScopeUris(config, workset) {
12132
+ const scopes = [];
12133
+ for (const member of workset.projects) {
12134
+ scopes.push(`viking://user/${uriSegment(config.user)}/memories/durable/projects/${uriSegment(member.name)}`);
12135
+ const seeded = trimTrailingSlash(member.uri);
12136
+ if (seeded.startsWith("viking://")) {
12137
+ scopes.push(seeded);
12138
+ }
12139
+ }
12140
+ return [...new Set(scopes)];
12141
+ }
12003
12142
  function exactMemoryScopes(config, includeArchived, query, project) {
12004
12143
  return exactMemoryScopeUris({
12005
12144
  agentMemoriesUri: `viking://agent/${uriSegment(config.agentId)}/memories`,
@@ -12062,7 +12201,8 @@ function formatMemoryDocument2(title, metadata, body) {
12062
12201
  `source_agent_client: ${metadata.sourceAgentClient}`,
12063
12202
  `timestamp: ${metadata.timestamp}`,
12064
12203
  metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
12065
- metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
12204
+ metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0,
12205
+ ...(metadata.references ?? []).map((uri) => `references: ${uri}`)
12066
12206
  ].filter((line) => line !== void 0);
12067
12207
  return [...header, "", body.trim()].join("\n");
12068
12208
  }
@@ -12297,9 +12437,25 @@ function isResourceBusy(stderr, stdout2) {
12297
12437
  ${stdout2}`.toLowerCase();
12298
12438
  return output2.includes("resource is busy") || output2.includes("resource is being processed");
12299
12439
  }
12440
+ function normalizeReferenceUris(references) {
12441
+ if (!references || references.length === 0) {
12442
+ return void 0;
12443
+ }
12444
+ const seen = /* @__PURE__ */ new Set();
12445
+ for (const raw of references) {
12446
+ const uri = raw.trim();
12447
+ if (!uri || seen.has(uri)) {
12448
+ continue;
12449
+ }
12450
+ assertVikingUri(uri);
12451
+ seen.add(uri);
12452
+ }
12453
+ return seen.size > 0 ? [...seen] : void 0;
12454
+ }
12300
12455
  async function buildHandoff(options) {
12301
12456
  const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]) ?? getInvocationCwd();
12302
12457
  const branch = await gitValue(["branch", "--show-current"], repoRoot) ?? "unknown";
12458
+ const commit = await gitValue(["rev-parse", "HEAD"], repoRoot) ?? "unknown";
12303
12459
  const status = await gitValue(["status", "--short"], repoRoot) ?? "";
12304
12460
  const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
12305
12461
  const touchedFiles = await gitTouchedFiles(repoRoot);
@@ -12308,16 +12464,24 @@ async function buildHandoff(options) {
12308
12464
  const metadata = {
12309
12465
  kind: "handoff",
12310
12466
  project: normalizeOptionalMetadata2(options.project) ?? repoName,
12467
+ references: normalizeReferenceUris(options.references),
12311
12468
  sourceAgentClient: options.sourceAgentClient ?? "codex",
12312
12469
  status: "active",
12313
12470
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12314
12471
  topic: handoffTopicForBranch(topicBranch, { timestamped: options.timestamped, topic: options.topic })
12315
12472
  };
12473
+ const reviewState = [
12474
+ options.pr ? `pr: ${options.pr}` : void 0,
12475
+ options.issue ? `issue: ${options.issue}` : void 0,
12476
+ options.ci ? `ci: ${options.ci}` : void 0
12477
+ ].filter((line) => line !== void 0);
12316
12478
  const bodyText = [
12317
12479
  `repo: ${repoName}`,
12318
12480
  `repo_path: ${repoRoot}`,
12319
12481
  `branch: ${branch || "unknown"}`,
12482
+ `commit: ${commit}`,
12320
12483
  `task: ${options.task ?? "unspecified"}`,
12484
+ ...reviewState,
12321
12485
  "",
12322
12486
  "files_touched:",
12323
12487
  formatBlock(touchedFiles, "- none"),
@@ -12335,7 +12499,9 @@ async function buildHandoff(options) {
12335
12499
  options.blockers ?? "- none recorded",
12336
12500
  "",
12337
12501
  "next_step:",
12338
- options.nextStep ?? "- inspect the current repo state and continue from this handoff"
12502
+ options.nextStep ?? "- inspect the current repo state and continue from this handoff",
12503
+ ...options.sessionId ? ["", `session_id: ${options.sessionId}`] : [],
12504
+ ...options.trace ? ["", "trace (auto-captured, heuristic):", options.trace] : []
12339
12505
  ].join("\n");
12340
12506
  return { bodyText, metadata };
12341
12507
  }
@@ -12361,9 +12527,134 @@ function formatBlock(value, emptyValue) {
12361
12527
  return trimmed.split("\n").map((line) => `- ${line}`).join("\n");
12362
12528
  }
12363
12529
 
12530
+ // src/trace.ts
12531
+ var import_promises7 = require("node:fs/promises");
12532
+ var MAX_TRANSCRIPT_BYTES = 4 * 1024 * 1024;
12533
+ var MAX_INTENTS = 5;
12534
+ var MAX_INTENT_CHARS = 160;
12535
+ var MAX_TOOLS = 12;
12536
+ function isRecord(value) {
12537
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12538
+ }
12539
+ function extractText(content) {
12540
+ if (typeof content === "string") {
12541
+ return content.trim() || void 0;
12542
+ }
12543
+ if (!Array.isArray(content)) {
12544
+ return void 0;
12545
+ }
12546
+ const parts = [];
12547
+ for (const part of content) {
12548
+ if (typeof part === "string") {
12549
+ parts.push(part);
12550
+ } else if (isRecord(part) && part.type === "text" && typeof part.text === "string") {
12551
+ parts.push(part.text);
12552
+ }
12553
+ }
12554
+ const joined = parts.join(" ").trim();
12555
+ return joined || void 0;
12556
+ }
12557
+ function extractToolNames(content) {
12558
+ if (!Array.isArray(content)) {
12559
+ return [];
12560
+ }
12561
+ const names = [];
12562
+ for (const part of content) {
12563
+ if (isRecord(part) && part.type === "tool_use" && typeof part.name === "string") {
12564
+ names.push(part.name);
12565
+ }
12566
+ }
12567
+ return names;
12568
+ }
12569
+ function truncate(value, max) {
12570
+ const oneLine = value.replace(/\s+/g, " ").trim();
12571
+ return oneLine.length > max ? `${oneLine.slice(0, max - 1)}\u2026` : oneLine;
12572
+ }
12573
+ async function distillTrace(transcriptPath) {
12574
+ const raw = await readTranscriptTail(transcriptPath);
12575
+ if (raw === void 0) {
12576
+ return void 0;
12577
+ }
12578
+ if (!raw.trim()) {
12579
+ return void 0;
12580
+ }
12581
+ let events = 0;
12582
+ const tools = /* @__PURE__ */ new Set();
12583
+ const intents = [];
12584
+ for (const line of raw.split("\n")) {
12585
+ const trimmed = line.trim();
12586
+ if (!trimmed) {
12587
+ continue;
12588
+ }
12589
+ let parsed;
12590
+ try {
12591
+ parsed = JSON.parse(trimmed);
12592
+ } catch {
12593
+ continue;
12594
+ }
12595
+ if (!isRecord(parsed)) {
12596
+ continue;
12597
+ }
12598
+ events += 1;
12599
+ const message = isRecord(parsed.message) ? parsed.message : parsed;
12600
+ const role = typeof message.role === "string" ? message.role : typeof parsed.type === "string" ? parsed.type : void 0;
12601
+ if (role === "user") {
12602
+ const text = extractText(message.content);
12603
+ if (text) {
12604
+ const scrubbed = applyScrubber(text, { redact: true });
12605
+ if (scrubbed.blocker) {
12606
+ return void 0;
12607
+ }
12608
+ intents.push(truncate(scrubbed.cleaned, MAX_INTENT_CHARS));
12609
+ }
12610
+ } else if (role === "assistant") {
12611
+ for (const name of extractToolNames(message.content)) {
12612
+ tools.add(name);
12613
+ }
12614
+ }
12615
+ }
12616
+ if (events === 0) {
12617
+ return void 0;
12618
+ }
12619
+ const lines = [`- ${events} transcript events`];
12620
+ if (tools.size > 0) {
12621
+ lines.push(`- tools used: ${[...tools].slice(0, MAX_TOOLS).join(", ")}`);
12622
+ }
12623
+ const recentIntents = intents.slice(-MAX_INTENTS);
12624
+ if (recentIntents.length > 0) {
12625
+ lines.push("- recent intents:");
12626
+ for (const intent of recentIntents) {
12627
+ lines.push(` - ${intent}`);
12628
+ }
12629
+ }
12630
+ return lines.join("\n");
12631
+ }
12632
+ async function readTranscriptTail(transcriptPath) {
12633
+ try {
12634
+ const { size } = await (0, import_promises7.stat)(transcriptPath);
12635
+ if (size === 0) {
12636
+ return void 0;
12637
+ }
12638
+ if (size <= MAX_TRANSCRIPT_BYTES) {
12639
+ return await (0, import_promises7.readFile)(transcriptPath, "utf8");
12640
+ }
12641
+ const start = size - MAX_TRANSCRIPT_BYTES;
12642
+ const buffer = Buffer.alloc(MAX_TRANSCRIPT_BYTES);
12643
+ const file = await (0, import_promises7.open)(transcriptPath, "r");
12644
+ try {
12645
+ const { bytesRead } = await file.read(buffer, 0, MAX_TRANSCRIPT_BYTES, start);
12646
+ return buffer.subarray(0, bytesRead).toString("utf8").replace(/^[^\n]*(?:\n|$)/, "");
12647
+ } finally {
12648
+ await file.close();
12649
+ }
12650
+ } catch {
12651
+ return void 0;
12652
+ }
12653
+ }
12654
+
12364
12655
  // src/update-check.ts
12365
12656
  var import_node_child_process3 = require("node:child_process");
12366
- var import_promises7 = require("node:fs/promises");
12657
+ var import_promises8 = require("node:fs/promises");
12367
12658
  var import_node_path9 = require("node:path");
12368
12659
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
12369
12660
  var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
@@ -12417,7 +12708,7 @@ function isCacheFresh(cache) {
12417
12708
  }
12418
12709
  async function readUpdateCache(cachePath) {
12419
12710
  try {
12420
- const raw = await (0, import_promises7.readFile)(cachePath, "utf8");
12711
+ const raw = await (0, import_promises8.readFile)(cachePath, "utf8");
12421
12712
  const parsed = JSON.parse(raw);
12422
12713
  if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
12423
12714
  return void 0;
@@ -12429,8 +12720,8 @@ async function readUpdateCache(cachePath) {
12429
12720
  }
12430
12721
  async function writeUpdateCache(cachePath, contents) {
12431
12722
  try {
12432
- await (0, import_promises7.mkdir)((0, import_node_path9.dirname)(cachePath), { recursive: true });
12433
- await (0, import_promises7.writeFile)(cachePath, `${JSON.stringify(contents)}
12723
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(cachePath), { recursive: true });
12724
+ await (0, import_promises8.writeFile)(cachePath, `${JSON.stringify(contents)}
12434
12725
  `, { encoding: "utf8", mode: 384 });
12435
12726
  } catch {
12436
12727
  }
@@ -12502,7 +12793,7 @@ async function runHooksInstall(config, agent, options) {
12502
12793
  }
12503
12794
  async function runClaudeHooksInstall(options) {
12504
12795
  const path2 = expandPath(CLAUDE_SETTINGS_PATH);
12505
- const existingRaw = await exists(path2) ? await (0, import_promises8.readFile)(path2, "utf8") : "{}";
12796
+ const existingRaw = await exists(path2) ? await (0, import_promises9.readFile)(path2, "utf8") : "{}";
12506
12797
  const parsed = parseJsonConfigObject(existingRaw) ?? {};
12507
12798
  const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
12508
12799
  const before = JSON.stringify(parsed);
@@ -12519,11 +12810,11 @@ async function runClaudeHooksInstall(options) {
12519
12810
  console.log("\nRe-run with --apply to actually modify the file.");
12520
12811
  return;
12521
12812
  }
12522
- await (0, import_promises8.mkdir)((0, import_node_path11.dirname)(path2), { recursive: true });
12813
+ await (0, import_promises9.mkdir)((0, import_node_path11.dirname)(path2), { recursive: true });
12523
12814
  const serialized = `${JSON.stringify(next, void 0, 2)}
12524
12815
  `;
12525
- await (0, import_promises8.writeFile)(path2, serialized, { encoding: "utf8", mode: 384 });
12526
- await (0, import_promises8.chmod)(path2, 384);
12816
+ await (0, import_promises9.writeFile)(path2, serialized, { encoding: "utf8", mode: 384 });
12817
+ await (0, import_promises9.chmod)(path2, 384);
12527
12818
  console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
12528
12819
  }
12529
12820
  function withThreadnoteHooks(input2) {
@@ -12601,7 +12892,7 @@ async function hasManagedClaudeHooks() {
12601
12892
  if (!await exists(path2)) {
12602
12893
  return false;
12603
12894
  }
12604
- const raw = await (0, import_promises8.readFile)(path2, "utf8");
12895
+ const raw = await (0, import_promises9.readFile)(path2, "utf8");
12605
12896
  const parsed = parseJsonConfigObject(raw);
12606
12897
  if (!parsed || !isJsonObject(parsed.hooks)) {
12607
12898
  return false;
@@ -12619,15 +12910,18 @@ async function hasManagedClaudeHooks() {
12619
12910
  async function runPreCompactHook(config, options = {}) {
12620
12911
  try {
12621
12912
  const project = await resolveRepoName() ?? "general";
12913
+ const { sessionId, trace } = await captureTraceContext();
12622
12914
  await runHandoff(config, {
12623
12915
  blockers: "- none recorded",
12624
12916
  dryRun: options.dryRun === true,
12625
12917
  nextStep: "Continue from this auto-snapshot. A manual `threadnote handoff` will produce a richer write-up if you have more context.",
12626
12918
  project,
12919
+ sessionId,
12627
12920
  sourceAgentClient: "claude",
12628
12921
  task: "Auto-snapshot captured at Claude PreCompact (deterministic safety net before context compaction).",
12629
12922
  tests: "- not recorded (auto-snapshot)",
12630
- topic: HOOK_AUTO_PRECOMPACT_TOPIC
12923
+ topic: HOOK_AUTO_PRECOMPACT_TOPIC,
12924
+ trace
12631
12925
  });
12632
12926
  } catch (err) {
12633
12927
  process.stderr.write(
@@ -12660,6 +12954,76 @@ async function runSessionStartHook(config, options = {}) {
12660
12954
  );
12661
12955
  }
12662
12956
  }
12957
+ async function captureTraceContext() {
12958
+ try {
12959
+ const payload = await readHookPayload();
12960
+ if (!payload) {
12961
+ return {};
12962
+ }
12963
+ const rawTrace = payload.transcriptPath ? await distillTrace(payload.transcriptPath) : void 0;
12964
+ return { sessionId: payload.sessionId, trace: rawTrace ? scrubTrace(rawTrace) : void 0 };
12965
+ } catch {
12966
+ return {};
12967
+ }
12968
+ }
12969
+ function scrubTrace(trace) {
12970
+ const result = applyScrubber(trace, { redact: true });
12971
+ return result.blocker ? void 0 : result.cleaned;
12972
+ }
12973
+ async function readHookPayload() {
12974
+ if (process.stdin.isTTY) {
12975
+ return void 0;
12976
+ }
12977
+ const raw = await readStdinWithTimeout(1500, 512 * 1024);
12978
+ if (!raw.trim()) {
12979
+ return void 0;
12980
+ }
12981
+ let parsed;
12982
+ try {
12983
+ parsed = JSON.parse(raw);
12984
+ } catch {
12985
+ return void 0;
12986
+ }
12987
+ if (!isJsonObject(parsed)) {
12988
+ return void 0;
12989
+ }
12990
+ return {
12991
+ sessionId: typeof parsed.session_id === "string" ? parsed.session_id : void 0,
12992
+ transcriptPath: typeof parsed.transcript_path === "string" ? parsed.transcript_path : void 0
12993
+ };
12994
+ }
12995
+ function readStdinWithTimeout(timeoutMs, maxBytes) {
12996
+ return new Promise((resolve2) => {
12997
+ const stdin = process.stdin;
12998
+ let data = "";
12999
+ let settled = false;
13000
+ const finish = () => {
13001
+ if (settled) {
13002
+ return;
13003
+ }
13004
+ settled = true;
13005
+ stdin.off("data", onData);
13006
+ stdin.off("end", finish);
13007
+ stdin.off("error", finish);
13008
+ clearTimeout(timer);
13009
+ stdin.pause();
13010
+ resolve2(data);
13011
+ };
13012
+ const onData = (chunk) => {
13013
+ data += chunk;
13014
+ if (data.length >= maxBytes) {
13015
+ data = data.slice(0, maxBytes);
13016
+ finish();
13017
+ }
13018
+ };
13019
+ const timer = setTimeout(finish, timeoutMs);
13020
+ stdin.setEncoding("utf8");
13021
+ stdin.on("data", onData);
13022
+ stdin.on("end", finish);
13023
+ stdin.on("error", finish);
13024
+ stdin.resume();
13025
+ });
13026
+ }
12663
13027
  async function emitUpdateBannerIfOutdated(config) {
12664
13028
  try {
12665
13029
  const result = await checkForThreadnoteUpdate({
@@ -12688,8 +13052,144 @@ async function emitUpdateBannerIfOutdated(config) {
12688
13052
  }
12689
13053
 
12690
13054
  // src/seeding.ts
12691
- var import_promises9 = require("node:fs/promises");
13055
+ var import_promises11 = require("node:fs/promises");
13056
+ var import_node_path13 = require("node:path");
13057
+
13058
+ // src/graph.ts
13059
+ var import_promises10 = require("node:fs/promises");
12692
13060
  var import_node_path12 = require("node:path");
13061
+ async function readIfExists(path2) {
13062
+ try {
13063
+ return await (0, import_promises10.readFile)(path2, "utf8");
13064
+ } catch {
13065
+ return void 0;
13066
+ }
13067
+ }
13068
+ function parsePackageJson(content) {
13069
+ let parsed;
13070
+ try {
13071
+ parsed = JSON.parse(content);
13072
+ } catch {
13073
+ return void 0;
13074
+ }
13075
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
13076
+ return void 0;
13077
+ }
13078
+ const object = parsed;
13079
+ const dependencies = /* @__PURE__ */ new Set();
13080
+ for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
13081
+ const section = object[key];
13082
+ if (section && typeof section === "object" && !Array.isArray(section)) {
13083
+ for (const name of Object.keys(section)) {
13084
+ dependencies.add(name);
13085
+ }
13086
+ }
13087
+ }
13088
+ return { dependencies: [...dependencies], publishedName: typeof object.name === "string" ? object.name : void 0 };
13089
+ }
13090
+ function parseGoMod(content) {
13091
+ const dependencies = /* @__PURE__ */ new Set();
13092
+ let publishedName;
13093
+ let inRequireBlock = false;
13094
+ for (const rawLine of content.split("\n")) {
13095
+ const line = rawLine.replace(/\/\/.*$/, "").trim();
13096
+ if (!line) {
13097
+ continue;
13098
+ }
13099
+ const moduleMatch = /^module\s+(\S+)/.exec(line);
13100
+ if (moduleMatch) {
13101
+ publishedName = moduleMatch[1];
13102
+ continue;
13103
+ }
13104
+ if (/^require\s*\($/.test(line)) {
13105
+ inRequireBlock = true;
13106
+ continue;
13107
+ }
13108
+ if (inRequireBlock) {
13109
+ if (line === ")") {
13110
+ inRequireBlock = false;
13111
+ continue;
13112
+ }
13113
+ const blockMatch = /^(\S+)\s+v\S+/.exec(line);
13114
+ if (blockMatch) {
13115
+ dependencies.add(blockMatch[1]);
13116
+ }
13117
+ continue;
13118
+ }
13119
+ const singleMatch = /^require\s+(\S+)\s+v\S+/.exec(line);
13120
+ if (singleMatch) {
13121
+ dependencies.add(singleMatch[1]);
13122
+ }
13123
+ }
13124
+ return { dependencies: [...dependencies], publishedName };
13125
+ }
13126
+ async function extractDependencyFacts(projectRoot) {
13127
+ const dependencies = /* @__PURE__ */ new Set();
13128
+ const ecosystems = [];
13129
+ const manifestFiles = [];
13130
+ let publishedName;
13131
+ const packageJson = await readIfExists((0, import_node_path12.join)(projectRoot, "package.json"));
13132
+ if (packageJson !== void 0) {
13133
+ const parsed = parsePackageJson(packageJson);
13134
+ if (parsed) {
13135
+ manifestFiles.push("package.json");
13136
+ ecosystems.push("npm");
13137
+ publishedName = publishedName ?? parsed.publishedName;
13138
+ for (const dependency of parsed.dependencies) {
13139
+ dependencies.add(dependency);
13140
+ }
13141
+ }
13142
+ }
13143
+ const goMod = await readIfExists((0, import_node_path12.join)(projectRoot, "go.mod"));
13144
+ if (goMod !== void 0) {
13145
+ const parsed = parseGoMod(goMod);
13146
+ manifestFiles.push("go.mod");
13147
+ ecosystems.push("go");
13148
+ publishedName = publishedName ?? parsed.publishedName;
13149
+ for (const dependency of parsed.dependencies) {
13150
+ dependencies.add(dependency);
13151
+ }
13152
+ }
13153
+ return { dependencies: [...dependencies].sort(), ecosystems, manifestFiles, publishedName };
13154
+ }
13155
+ function buildGraphDocument(params) {
13156
+ const { externalCount, facts, internalEdges, projectName } = params;
13157
+ const lines = [
13158
+ `# ${projectName} \u2014 dependency facts`,
13159
+ "",
13160
+ "Auto-generated by `threadnote seed --graph` from declarative manifest files. A static snapshot of package metadata, not a live dependency graph.",
13161
+ "",
13162
+ `provides: ${facts.publishedName ?? "(none declared)"}`,
13163
+ `ecosystems: ${facts.ecosystems.length > 0 ? facts.ecosystems.join(", ") : "(none detected)"}`,
13164
+ "",
13165
+ "manifests:",
13166
+ ...facts.manifestFiles.length > 0 ? facts.manifestFiles.map((file) => `- ${file}`) : ["- (none)"],
13167
+ "",
13168
+ "depends_on (within this workspace):",
13169
+ ...internalEdges.length > 0 ? internalEdges.map((edge) => `- [[${edge.project}]] (via ${edge.dependency})`) : ["- (no in-workspace dependencies detected)"],
13170
+ "",
13171
+ `external dependencies: ${externalCount} declared`
13172
+ ];
13173
+ return `${lines.join("\n")}
13174
+ `;
13175
+ }
13176
+ function resolveGraphEdges(projectName, dependencies, projectByPublishedName) {
13177
+ const internalEdges = [];
13178
+ const seenTargets = /* @__PURE__ */ new Set();
13179
+ let externalCount = 0;
13180
+ for (const dependency of dependencies) {
13181
+ const target = projectByPublishedName.get(dependency.toLowerCase());
13182
+ if (target && target !== projectName && !seenTargets.has(target)) {
13183
+ seenTargets.add(target);
13184
+ internalEdges.push({ dependency, project: target });
13185
+ } else if (!target) {
13186
+ externalCount += 1;
13187
+ }
13188
+ }
13189
+ return { externalCount, internalEdges };
13190
+ }
13191
+
13192
+ // src/seeding.ts
12693
13193
  function parseSeedWatchIntervalMinutes(rawValue) {
12694
13194
  if (rawValue === void 0) {
12695
13195
  return void 0;
@@ -12708,7 +13208,7 @@ async function runSeed(config, options) {
12708
13208
  const ignorePatterns = await loadIgnorePatterns();
12709
13209
  const ov = await openVikingCliForMode(options.dryRun === true);
12710
13210
  const watchIntervalMinutes = parseSeedWatchIntervalMinutes(process.env[SEED_WATCH_INTERVAL_ENV]);
12711
- const statePath = (0, import_node_path12.join)(config.agentContextHome, SEED_STATE_FILE);
13211
+ const statePath = (0, import_node_path13.join)(config.agentContextHome, SEED_STATE_FILE);
12712
13212
  const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
12713
13213
  const projects = filterProjects(manifest.projects, options.only);
12714
13214
  let importedCount = 0;
@@ -12760,6 +13260,70 @@ async function runSeed(config, options) {
12760
13260
  console.log(
12761
13261
  `Seed complete: ${importedCount} candidate(s), ${unchangedCount} unchanged, ${skippedCount} skipped for safety.`
12762
13262
  );
13263
+ if (options.graph === true) {
13264
+ await seedDependencyGraphs(config, ov, manifest, projects, options.dryRun === true);
13265
+ }
13266
+ }
13267
+ async function seedDependencyGraphs(config, ov, manifest, targetProjects, dryRun) {
13268
+ const factsByProject = /* @__PURE__ */ new Map();
13269
+ for (const project of manifest.projects) {
13270
+ const projectRoot = expandPath(project.path);
13271
+ if (!await exists(projectRoot)) {
13272
+ continue;
13273
+ }
13274
+ factsByProject.set(project.name, await extractDependencyFacts(projectRoot));
13275
+ }
13276
+ const projectByPublishedName = /* @__PURE__ */ new Map();
13277
+ for (const [name, facts] of factsByProject) {
13278
+ if (facts.publishedName) {
13279
+ projectByPublishedName.set(facts.publishedName.toLowerCase(), name);
13280
+ }
13281
+ }
13282
+ let written = 0;
13283
+ let skipped = 0;
13284
+ for (const project of targetProjects) {
13285
+ const facts = factsByProject.get(project.name);
13286
+ if (!facts || facts.manifestFiles.length === 0) {
13287
+ continue;
13288
+ }
13289
+ const { externalCount, internalEdges } = resolveGraphEdges(project.name, facts.dependencies, projectByPublishedName);
13290
+ const document = buildGraphDocument({ externalCount, facts, internalEdges, projectName: project.name });
13291
+ const secretMatches = detectSecretMatches(document);
13292
+ if (secretMatches.length > 0) {
13293
+ skipped += 1;
13294
+ console.log(
13295
+ `SKIP ${project.name}/.graph.md: possible secret (${secretMatches.slice(0, MAX_SECRET_MATCHES_TO_PRINT).join(", ")})`
13296
+ );
13297
+ continue;
13298
+ }
13299
+ const destinationUri = `${trimTrailingSlash(project.uri)}/.graph.md`;
13300
+ if (dryRun) {
13301
+ console.log(`Would seed dependency facts: ${destinationUri} (${internalEdges.length} in-workspace edge(s))`);
13302
+ written += 1;
13303
+ continue;
13304
+ }
13305
+ const graphPath = (0, import_node_path13.join)(config.agentContextHome, "graph", graphCacheFileName(project.name));
13306
+ await ensureDirectory((0, import_node_path13.dirname)(graphPath), false);
13307
+ await (0, import_promises11.writeFile)(graphPath, document, { encoding: "utf8", mode: 384 });
13308
+ await (0, import_promises11.chmod)(graphPath, 384);
13309
+ await maybeRun(
13310
+ false,
13311
+ ov,
13312
+ withIdentity(config, [
13313
+ "add-resource",
13314
+ graphPath,
13315
+ "--to",
13316
+ destinationUri,
13317
+ "--reason",
13318
+ `Dependency facts for ${project.name}`,
13319
+ "--wait"
13320
+ ])
13321
+ );
13322
+ written += 1;
13323
+ }
13324
+ console.log(
13325
+ `Dependency graph seed complete: ${written} .graph.md resource(s)${skipped > 0 ? `, ${skipped} skipped for safety` : ""}.`
13326
+ );
12763
13327
  }
12764
13328
  function filterProjects(projects, only) {
12765
13329
  if (!only || only.length === 0) {
@@ -12776,7 +13340,7 @@ function filterProjects(projects, only) {
12776
13340
  }
12777
13341
  async function statSeedFile(path2) {
12778
13342
  try {
12779
- const result = await (0, import_promises9.stat)(path2);
13343
+ const result = await (0, import_promises11.stat)(path2);
12780
13344
  return { mtimeMs: result.mtimeMs, size: result.size };
12781
13345
  } catch (_err) {
12782
13346
  return void 0;
@@ -12784,7 +13348,7 @@ async function statSeedFile(path2) {
12784
13348
  }
12785
13349
  async function readSeedState(path2) {
12786
13350
  try {
12787
- const raw = await (0, import_promises9.readFile)(path2, "utf8");
13351
+ const raw = await (0, import_promises11.readFile)(path2, "utf8");
12788
13352
  const parsed = JSON.parse(raw);
12789
13353
  if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
12790
13354
  return { files: {}, version: 1 };
@@ -12801,13 +13365,13 @@ async function readSeedState(path2) {
12801
13365
  }
12802
13366
  }
12803
13367
  async function writeSeedState(path2, state) {
12804
- await ensureDirectory((0, import_node_path12.dirname)(path2), false);
12805
- await (0, import_promises9.writeFile)(path2, `${JSON.stringify(state, void 0, 2)}
13368
+ await ensureDirectory((0, import_node_path13.dirname)(path2), false);
13369
+ await (0, import_promises11.writeFile)(path2, `${JSON.stringify(state, void 0, 2)}
12806
13370
  `, { encoding: "utf8", mode: 384 });
12807
13371
  }
12808
13372
  async function runInitManifest(config, options) {
12809
13373
  const manifestPath = expandPath(
12810
- options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path12.join)(config.agentContextHome, USER_MANIFEST_NAME)
13374
+ options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path13.join)(config.agentContextHome, USER_MANIFEST_NAME)
12811
13375
  );
12812
13376
  const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
12813
13377
  const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
@@ -12844,20 +13408,58 @@ async function runInitManifest(config, options) {
12844
13408
  uri: existingManifest.futureMonorepo.uri
12845
13409
  };
12846
13410
  }
13411
+ if (existingManifest?.worksets) {
13412
+ outputManifest.worksets = existingManifest.worksets.map((workset) => ({
13413
+ name: workset.name,
13414
+ ...workset.description !== void 0 ? { description: workset.description } : {},
13415
+ projects: [...workset.projects]
13416
+ }));
13417
+ }
12847
13418
  const output2 = index_vite_proxy_tmp_default.dump(outputManifest, { lineWidth: 120, noRefs: true });
12848
13419
  if (options.dryRun === true) {
12849
13420
  console.log(`# Would write ${manifestPath}`);
12850
13421
  console.log(output2.trimEnd());
12851
13422
  return;
12852
13423
  }
12853
- await ensureDirectory((0, import_node_path12.dirname)(manifestPath), false);
12854
- await (0, import_promises9.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
12855
- await (0, import_promises9.chmod)(manifestPath, 384);
13424
+ await ensureDirectory((0, import_node_path13.dirname)(manifestPath), false);
13425
+ await (0, import_promises11.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
13426
+ await (0, import_promises11.chmod)(manifestPath, 384);
12856
13427
  console.log(`Wrote manifest: ${manifestPath}`);
12857
13428
  console.log("Seed with:");
12858
13429
  console.log(" threadnote seed --dry-run");
12859
13430
  console.log(" threadnote seed");
12860
13431
  }
13432
+ async function runWorksetList(config) {
13433
+ const manifest = await readSeedManifest(config.manifestPath);
13434
+ const worksets = manifest.worksets ?? [];
13435
+ if (worksets.length === 0) {
13436
+ console.log(
13437
+ "No worksets defined. Add a top-level `worksets:` list to the seed manifest to group related projects."
13438
+ );
13439
+ return;
13440
+ }
13441
+ console.log(`Worksets (${worksets.length}):`);
13442
+ for (const workset of worksets) {
13443
+ const summary = workset.description ? ` \u2014 ${workset.description}` : "";
13444
+ console.log(`- ${workset.name} (${workset.projects.length} project(s))${summary}`);
13445
+ }
13446
+ }
13447
+ async function runWorksetShow(config, name) {
13448
+ const manifest = await readSeedManifest(config.manifestPath);
13449
+ const workset = manifest.worksets?.find((entry) => entry.name.toLowerCase() === name.toLowerCase());
13450
+ if (!workset) {
13451
+ throw new Error(`No workset named "${name}" in ${config.manifestPath}.`);
13452
+ }
13453
+ console.log(`Workset: ${workset.name}`);
13454
+ if (workset.description) {
13455
+ console.log(workset.description);
13456
+ }
13457
+ console.log("Projects:");
13458
+ for (const memberName of workset.projects) {
13459
+ const project = manifest.projects.find((entry) => entry.name.toLowerCase() === memberName.toLowerCase());
13460
+ console.log(project ? `- ${project.name} (${project.uri})` : `- ${memberName} [not found in manifest projects]`);
13461
+ }
13462
+ }
12861
13463
  async function runSeedSkills(config, options) {
12862
13464
  const ov = await openVikingCliForMode(options.dryRun === true);
12863
13465
  const catalogItems = await collectSkillCandidates(config);
@@ -12898,13 +13500,13 @@ async function resolveRepoRoot(repoInput) {
12898
13500
  async function projectIdentity(path2) {
12899
13501
  const expanded = expandPath(path2);
12900
13502
  try {
12901
- return await (0, import_promises9.realpath)(expanded);
13503
+ return await (0, import_promises11.realpath)(expanded);
12902
13504
  } catch (_err) {
12903
13505
  return expanded;
12904
13506
  }
12905
13507
  }
12906
13508
  function projectManifestForRepo(repoRoot, existingProjects) {
12907
- const baseName = uriSegment((0, import_node_path12.basename)(repoRoot));
13509
+ const baseName = uriSegment((0, import_node_path13.basename)(repoRoot));
12908
13510
  const usedNames = new Set(existingProjects.map((project) => project.name));
12909
13511
  const usedUris = new Set(existingProjects.map((project) => project.uri));
12910
13512
  let name = baseName;
@@ -12926,7 +13528,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
12926
13528
  for (const pattern of project.seed) {
12927
13529
  const files = await resolveProjectPattern(projectRoot, pattern);
12928
13530
  for (const filePath of files) {
12929
- const relativePath = toPosixPath((0, import_node_path12.relative)(projectRoot, filePath));
13531
+ const relativePath = toPosixPath((0, import_node_path13.relative)(projectRoot, filePath));
12930
13532
  if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
12931
13533
  continue;
12932
13534
  }
@@ -12944,20 +13546,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
12944
13546
  async function resolveProjectPattern(projectRoot, pattern) {
12945
13547
  const normalizedPattern = toPosixPath(pattern);
12946
13548
  if (!hasGlob(normalizedPattern)) {
12947
- const filePath = (0, import_node_path12.join)(projectRoot, normalizedPattern);
13549
+ const filePath = (0, import_node_path13.join)(projectRoot, normalizedPattern);
12948
13550
  return await isFile(filePath) ? [filePath] : [];
12949
13551
  }
12950
13552
  const globBase = getGlobBase(normalizedPattern);
12951
- const basePath = (0, import_node_path12.join)(projectRoot, globBase);
13553
+ const basePath = (0, import_node_path13.join)(projectRoot, globBase);
12952
13554
  if (!await exists(basePath)) {
12953
13555
  return [];
12954
13556
  }
12955
13557
  const regex = globToRegExp(normalizedPattern);
12956
13558
  const files = await walkFiles(basePath);
12957
- return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path12.relative)(projectRoot, filePath))));
13559
+ return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path13.relative)(projectRoot, filePath))));
12958
13560
  }
12959
13561
  async function prepareSeedFile(config, candidate, dryRun) {
12960
- const content = await (0, import_promises9.readFile)(candidate.filePath, "utf8");
13562
+ const content = await (0, import_promises11.readFile)(candidate.filePath, "utf8");
12961
13563
  const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
12962
13564
  const secretMatches = detectSecretMatches(redactedContent);
12963
13565
  if (secretMatches.length > 0) {
@@ -12969,21 +13571,24 @@ async function prepareSeedFile(config, candidate, dryRun) {
12969
13571
  if (redactedContent === content) {
12970
13572
  return candidate.filePath;
12971
13573
  }
12972
- const redactedPath = (0, import_node_path12.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
13574
+ const redactedPath = (0, import_node_path13.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
12973
13575
  if (dryRun) {
12974
13576
  console.log(`Would write redacted copy: ${redactedPath}`);
12975
13577
  return redactedPath;
12976
13578
  }
12977
- await ensureDirectory((0, import_node_path12.dirname)(redactedPath), false);
12978
- await (0, import_promises9.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
12979
- await (0, import_promises9.chmod)(redactedPath, 384);
13579
+ await ensureDirectory((0, import_node_path13.dirname)(redactedPath), false);
13580
+ await (0, import_promises11.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
13581
+ await (0, import_promises11.chmod)(redactedPath, 384);
12980
13582
  return redactedPath;
12981
13583
  }
12982
13584
  function seedResourceReason(candidate) {
12983
13585
  return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
12984
13586
  }
13587
+ function graphCacheFileName(projectName) {
13588
+ return `${uriSegment(projectName)}-${sha256(projectName).slice(0, 8)}.graph.md`;
13589
+ }
12985
13590
  function skillResourceReason(skill) {
12986
- return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path12.basename)(
13591
+ return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path13.basename)(
12987
13592
  skill.filePath
12988
13593
  )}`;
12989
13594
  }
@@ -13016,7 +13621,7 @@ async function collectSkillCandidates(config) {
13016
13621
  for (const source of sources) {
13017
13622
  const files = await resolveAbsolutePattern(expandPath(source.pattern));
13018
13623
  for (const filePath of files) {
13019
- const content = await (0, import_promises9.readFile)(filePath, "utf8");
13624
+ const content = await (0, import_promises11.readFile)(filePath, "utf8");
13020
13625
  const matches = detectSecretMatches(content);
13021
13626
  if (matches.length > 0) {
13022
13627
  console.log(`SKIP skill with possible secret: ${filePath}`);
@@ -13050,16 +13655,16 @@ function skillResourceUri(skill) {
13050
13655
  return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
13051
13656
  }
13052
13657
  function skillResourceName(skill) {
13053
- const fileName = (0, import_node_path12.basename)(skill.filePath);
13658
+ const fileName = (0, import_node_path13.basename)(skill.filePath);
13054
13659
  if (fileName.toLowerCase() === "skill.md") {
13055
- return uriSegment((0, import_node_path12.basename)((0, import_node_path12.dirname)(skill.filePath)));
13660
+ return uriSegment((0, import_node_path13.basename)((0, import_node_path13.dirname)(skill.filePath)));
13056
13661
  }
13057
13662
  const extensionIndex = fileName.lastIndexOf(".");
13058
13663
  const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
13059
13664
  return uriSegment(stem);
13060
13665
  }
13061
13666
  async function loadIgnorePatterns() {
13062
- const raw = await (0, import_promises9.readFile)((0, import_node_path12.join)(toolRoot(), ".threadnoteignore"), "utf8");
13667
+ const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "utf8");
13063
13668
  return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
13064
13669
  }
13065
13670
  function matchesIgnore(relativePath, patterns) {
@@ -13132,18 +13737,18 @@ function detectSecretMatches(content) {
13132
13737
  // src/lifecycle.ts
13133
13738
  var import_node_child_process4 = require("node:child_process");
13134
13739
  var import_node_fs7 = require("node:fs");
13135
- var import_promises12 = require("node:fs/promises");
13740
+ var import_promises14 = require("node:fs/promises");
13136
13741
  var import_node_os6 = require("node:os");
13137
- var import_node_path14 = require("node:path");
13742
+ var import_node_path15 = require("node:path");
13138
13743
  var import_node_process4 = require("node:process");
13139
- var import_promises13 = require("node:readline/promises");
13744
+ var import_promises15 = require("node:readline/promises");
13140
13745
 
13141
13746
  // src/update.ts
13142
13747
  var import_node_fs6 = require("node:fs");
13143
- var import_promises10 = require("node:fs/promises");
13748
+ var import_promises12 = require("node:fs/promises");
13144
13749
  var import_node_os5 = require("node:os");
13145
- var import_node_path13 = require("node:path");
13146
- var import_promises11 = require("node:readline/promises");
13750
+ var import_node_path14 = require("node:path");
13751
+ var import_promises13 = require("node:readline/promises");
13147
13752
  var import_node_process3 = require("node:process");
13148
13753
 
13149
13754
  // src/release_notes.ts
@@ -13497,14 +14102,14 @@ async function waitForOpenVikingPortClosed(config, timeoutMs) {
13497
14102
  return false;
13498
14103
  }
13499
14104
  function launchAgentPlistPath() {
13500
- return (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
14105
+ return (0, import_node_path14.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
13501
14106
  }
13502
14107
  async function isLaunchAgentInstalled() {
13503
14108
  if (process.platform !== "darwin") {
13504
14109
  return false;
13505
14110
  }
13506
14111
  try {
13507
- await (0, import_promises10.access)(launchAgentPlistPath(), import_node_fs6.constants.F_OK);
14112
+ await (0, import_promises12.access)(launchAgentPlistPath(), import_node_fs6.constants.F_OK);
13508
14113
  return true;
13509
14114
  } catch (_err) {
13510
14115
  return false;
@@ -13575,11 +14180,11 @@ async function readFreshCache(config, registry) {
13575
14180
  }
13576
14181
  async function writeUpdateCache2(config, cache) {
13577
14182
  await ensureDirectory(config.agentContextHome, false);
13578
- await (0, import_promises10.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
14183
+ await (0, import_promises12.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
13579
14184
  `, { encoding: "utf8", mode: 384 });
13580
14185
  }
13581
14186
  function updateCachePath(config) {
13582
- return (0, import_node_path13.join)(config.agentContextHome, "update-check.json");
14187
+ return (0, import_node_path14.join)(config.agentContextHome, "update-check.json");
13583
14188
  }
13584
14189
  async function runApplicablePostUpdateMigrations(config, options) {
13585
14190
  const state = await readPostUpdateState(config);
@@ -13643,7 +14248,7 @@ async function applicablePostUpdateMigrations(config, options) {
13643
14248
  return applicable;
13644
14249
  }
13645
14250
  async function readPostUpdateMigrations() {
13646
- const raw = await readFileIfExists((0, import_node_path13.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
14251
+ const raw = await readFileIfExists((0, import_node_path14.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
13647
14252
  if (!raw) {
13648
14253
  return [];
13649
14254
  }
@@ -13682,7 +14287,7 @@ function printPostUpdateMigration(migration) {
13682
14287
  }
13683
14288
  }
13684
14289
  async function confirmPostUpdateMigration(prompt, defaultYes = false) {
13685
- const readline = (0, import_promises11.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
14290
+ const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
13686
14291
  try {
13687
14292
  const answer = (await readline.question(prompt)).trim().toLowerCase();
13688
14293
  if (answer === "") {
@@ -13717,11 +14322,11 @@ async function readPostUpdateState(config) {
13717
14322
  }
13718
14323
  async function writePostUpdateState(config, state) {
13719
14324
  await ensureDirectory(config.agentContextHome, false);
13720
- await (0, import_promises10.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
14325
+ await (0, import_promises12.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
13721
14326
  `, { encoding: "utf8", mode: 384 });
13722
14327
  }
13723
14328
  function postUpdateStatePath(config) {
13724
- return (0, import_node_path13.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
14329
+ return (0, import_node_path14.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
13725
14330
  }
13726
14331
  async function resolveUpdateRuntime(runtime) {
13727
14332
  if (runtime !== "auto") {
@@ -13751,14 +14356,14 @@ async function runtimeThreadnoteBin(runtime) {
13751
14356
  if (runtime === "npm") {
13752
14357
  const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
13753
14358
  const prefix = result.stdout.trim();
13754
- return prefix ? (0, import_node_path13.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
14359
+ return prefix ? (0, import_node_path14.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
13755
14360
  }
13756
14361
  if (runtime === "bun") {
13757
14362
  const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
13758
14363
  const binDir = result.stdout.trim();
13759
- return binDir ? (0, import_node_path13.join)(binDir, NPM_PACKAGE_NAME) : void 0;
14364
+ return binDir ? (0, import_node_path14.join)(binDir, NPM_PACKAGE_NAME) : void 0;
13760
14365
  }
13761
- return (0, import_node_path13.join)(process.env.DENO_INSTALL ?? (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
14366
+ return (0, import_node_path14.join)(process.env.DENO_INSTALL ?? (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
13762
14367
  }
13763
14368
  function updatePackageCommand(runtime, registry) {
13764
14369
  if (runtime === "npm") {
@@ -13831,9 +14436,9 @@ async function collectDoctorChecks(config, options = {}) {
13831
14436
  checks.push(await manifestCheck(config.manifestPath));
13832
14437
  checks.push(await recallIndexFreshnessCheck(config));
13833
14438
  checks.push(await memoryProjectConsistencyCheck(config));
13834
- checks.push(await fileCheck((0, import_node_path14.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
13835
- checks.push(await fileCheck((0, import_node_path14.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
13836
- checks.push(await fileCheck((0, import_node_path14.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
14439
+ checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
14440
+ checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
14441
+ checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
13837
14442
  checks.push(await healthCheck(config));
13838
14443
  return checks;
13839
14444
  }
@@ -13841,9 +14446,9 @@ async function runInstall(config, options) {
13841
14446
  const repairInvalidConfigs = options.repairInvalidConfigs === true;
13842
14447
  const dryRun = options.dryRun === true;
13843
14448
  await ensureDirectory(config.agentContextHome, dryRun);
13844
- await ensureDirectory((0, import_node_path14.join)(config.agentContextHome, "logs"), dryRun);
13845
- await ensureDirectory((0, import_node_path14.join)(config.agentContextHome, "redacted"), dryRun);
13846
- await ensureDirectory((0, import_node_path14.join)(config.agentContextHome, "mcp"), dryRun);
14449
+ await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "logs"), dryRun);
14450
+ await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "redacted"), dryRun);
14451
+ await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "mcp"), dryRun);
13847
14452
  await installCommandShim(dryRun);
13848
14453
  await installUserAgentInstructions(dryRun);
13849
14454
  const serverPath = await findOpenVikingServer();
@@ -13888,17 +14493,17 @@ async function runInstall(config, options) {
13888
14493
  }
13889
14494
  await writeTemplateIfMissing({
13890
14495
  config,
13891
- destinationPath: (0, import_node_path14.join)(config.agentContextHome, "ov.conf"),
14496
+ destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ov.conf"),
13892
14497
  dryRun,
13893
14498
  shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
13894
- templatePath: (0, import_node_path14.join)(toolRoot(), "config", "ov.conf.template.json")
14499
+ templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json")
13895
14500
  });
13896
14501
  await writeTemplateIfMissing({
13897
14502
  config,
13898
- destinationPath: (0, import_node_path14.join)(config.agentContextHome, "ovcli.conf"),
14503
+ destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ovcli.conf"),
13899
14504
  dryRun,
13900
14505
  shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
13901
- templatePath: (0, import_node_path14.join)(toolRoot(), "config", "ovcli.conf.template.json")
14506
+ templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json")
13902
14507
  });
13903
14508
  await configureOpenVikingCliLanguage(config, dryRun);
13904
14509
  if (options.start !== false) {
@@ -13955,7 +14560,7 @@ async function runUninstall(config, options) {
13955
14560
  }
13956
14561
  console.log("Uninstalling local Threadnote setup.");
13957
14562
  await runStop(config, { dryRun });
13958
- await removePathIfExists((0, import_node_path14.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
14563
+ await removePathIfExists((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
13959
14564
  await removeLaunchAgent(dryRun);
13960
14565
  await removeMcpConfigs(options.mcp ?? "available", dryRun);
13961
14566
  await removeMcpSnippets(config, dryRun);
@@ -14012,16 +14617,16 @@ async function repairManifest(config, dryRun) {
14012
14617
  console.log(output2.trimEnd());
14013
14618
  return;
14014
14619
  }
14015
- await ensureDirectory((0, import_node_path14.dirname)(config.manifestPath), false);
14620
+ await ensureDirectory((0, import_node_path15.dirname)(config.manifestPath), false);
14016
14621
  const currentContent = await readFileIfExists(config.manifestPath);
14017
14622
  if (currentContent !== void 0) {
14018
14623
  const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
14019
- await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
14020
- await (0, import_promises12.chmod)(backupPath, 384);
14624
+ await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
14625
+ await (0, import_promises14.chmod)(backupPath, 384);
14021
14626
  console.log(`Backup: ${backupPath}`);
14022
14627
  }
14023
- await (0, import_promises12.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
14024
- await (0, import_promises12.chmod)(config.manifestPath, 384);
14628
+ await (0, import_promises14.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
14629
+ await (0, import_promises14.chmod)(config.manifestPath, 384);
14025
14630
  console.log(`Wrote replacement manifest: ${config.manifestPath}`);
14026
14631
  }
14027
14632
  async function repairRecallIndex(config, dryRun) {
@@ -14096,7 +14701,7 @@ async function findOpenVikingServer() {
14096
14701
  return onPath;
14097
14702
  }
14098
14703
  for (const candidateDir of await openVikingServerCandidateDirs()) {
14099
- const candidate = (0, import_node_path14.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
14704
+ const candidate = (0, import_node_path15.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
14100
14705
  if (await isExecutable(candidate)) {
14101
14706
  return candidate;
14102
14707
  }
@@ -14142,7 +14747,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
14142
14747
  if (onPath) {
14143
14748
  return;
14144
14749
  }
14145
- const binDir = (0, import_node_path14.dirname)(serverPath);
14750
+ const binDir = (0, import_node_path15.dirname)(serverPath);
14146
14751
  const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
14147
14752
  console.log(
14148
14753
  `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.`
@@ -14186,7 +14791,7 @@ async function runStart(config, options) {
14186
14791
  );
14187
14792
  }
14188
14793
  const logPath = openVikingLogPath(config);
14189
- await ensureDirectory((0, import_node_path14.dirname)(logPath), false);
14794
+ await ensureDirectory((0, import_node_path15.dirname)(logPath), false);
14190
14795
  if (options.foreground === true) {
14191
14796
  const result = await runInteractive(server, args);
14192
14797
  process.exitCode = result;
@@ -14195,7 +14800,7 @@ async function runStart(config, options) {
14195
14800
  const logFd = (0, import_node_fs7.openSync)(logPath, "a");
14196
14801
  const child = spawnDetachedServerWithLog(server, args, logFd);
14197
14802
  child.unref();
14198
- await (0, import_promises12.writeFile)((0, import_node_path14.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
14803
+ await (0, import_promises14.writeFile)((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
14199
14804
  `, "utf8");
14200
14805
  const health = await waitForOpenVikingHealth(
14201
14806
  config,
@@ -14232,7 +14837,7 @@ async function runStop(config, options) {
14232
14837
  console.log(`No LaunchAgent found: ${launchAgentPath}`);
14233
14838
  }
14234
14839
  }
14235
- const pidPath = (0, import_node_path14.join)(config.agentContextHome, "openviking-server.pid");
14840
+ const pidPath = (0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid");
14236
14841
  const pidText = await readFileIfExists(pidPath);
14237
14842
  if (!pidText) {
14238
14843
  console.log("No pid file found for detached OpenViking server.");
@@ -14278,7 +14883,7 @@ async function openVikingServerCheck() {
14278
14883
  }
14279
14884
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
14280
14885
  const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
14281
- const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path14.dirname)(executable)} to PATH)`;
14886
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
14282
14887
  return {
14283
14888
  name,
14284
14889
  status: result.exitCode === 0 ? "ok" : "warn",
@@ -14319,7 +14924,7 @@ async function openVikingCliCheck() {
14319
14924
  }
14320
14925
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
14321
14926
  const onPath = await findExecutable(["ov", "openviking"]);
14322
- const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path14.dirname)(executable)} to PATH)`;
14927
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
14323
14928
  return {
14324
14929
  name: "openviking cli",
14325
14930
  status: result.exitCode === 0 ? "ok" : "warn",
@@ -14412,7 +15017,7 @@ async function pythonSystemCertificatesCheck() {
14412
15017
  };
14413
15018
  }
14414
15019
  async function commandShimCheck() {
14415
- const shimPath = (0, import_node_path14.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
15020
+ const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
14416
15021
  const content = await readFileIfExists(shimPath);
14417
15022
  if (content === void 0) {
14418
15023
  return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
@@ -14478,11 +15083,11 @@ async function hasPythonModule(serverPath, moduleName) {
14478
15083
  async function siblingPythonForExecutable(executablePath) {
14479
15084
  let resolvedPath;
14480
15085
  try {
14481
- resolvedPath = await (0, import_promises12.realpath)(executablePath);
15086
+ resolvedPath = await (0, import_promises14.realpath)(executablePath);
14482
15087
  } catch (_err) {
14483
15088
  return void 0;
14484
15089
  }
14485
- const pythonPath = (0, import_node_path14.join)((0, import_node_path14.dirname)(resolvedPath), "python");
15090
+ const pythonPath = (0, import_node_path15.join)((0, import_node_path15.dirname)(resolvedPath), "python");
14486
15091
  return await exists(pythonPath) ? pythonPath : void 0;
14487
15092
  }
14488
15093
  async function manifestCheck(path2) {
@@ -14525,7 +15130,7 @@ async function recallIndexFreshnessCheck(config) {
14525
15130
  }
14526
15131
  async function memoryProjectConsistencyCheck(config) {
14527
15132
  const name = "memory project consistency";
14528
- const memoriesRoot = (0, import_node_path14.join)(
15133
+ const memoriesRoot = (0, import_node_path15.join)(
14529
15134
  config.agentContextHome,
14530
15135
  "data",
14531
15136
  "viking",
@@ -14537,7 +15142,7 @@ async function memoryProjectConsistencyCheck(config) {
14537
15142
  try {
14538
15143
  let entries;
14539
15144
  try {
14540
- entries = await (0, import_promises12.readdir)(memoriesRoot, { recursive: true });
15145
+ entries = await (0, import_promises14.readdir)(memoriesRoot, { recursive: true });
14541
15146
  } catch {
14542
15147
  return { name, status: "ok", detail: "no memories directory yet" };
14543
15148
  }
@@ -14547,14 +15152,14 @@ async function memoryProjectConsistencyCheck(config) {
14547
15152
  if (!entry.endsWith(".md") || isSummarySidecarUri(entry)) {
14548
15153
  continue;
14549
15154
  }
14550
- const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(import_node_path14.sep).join("/")}`;
15155
+ const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(import_node_path15.sep).join("/")}`;
14551
15156
  const pathProject = memoryUriProjectSegment(uri);
14552
15157
  if (!pathProject) {
14553
15158
  continue;
14554
15159
  }
14555
15160
  let content;
14556
15161
  try {
14557
- content = await (0, import_promises12.readFile)((0, import_node_path14.join)(memoriesRoot, entry), "utf8");
15162
+ content = await (0, import_promises14.readFile)((0, import_node_path15.join)(memoriesRoot, entry), "utf8");
14558
15163
  } catch {
14559
15164
  continue;
14560
15165
  }
@@ -14809,7 +15414,7 @@ async function offerToInstallUv() {
14809
15414
  );
14810
15415
  return false;
14811
15416
  }
14812
- const readline = (0, import_promises13.createInterface)({ input: import_node_process4.stdin, output: import_node_process4.stdout });
15417
+ const readline = (0, import_promises15.createInterface)({ input: import_node_process4.stdin, output: import_node_process4.stdout });
14813
15418
  let answer;
14814
15419
  try {
14815
15420
  answer = (await readline.question(
@@ -14967,38 +15572,38 @@ function printInstallNextSteps(options) {
14967
15572
  }
14968
15573
  async function writeTemplateIfMissing(options) {
14969
15574
  if (await exists(options.destinationPath)) {
14970
- const currentContent = await (0, import_promises12.readFile)(options.destinationPath, "utf8");
15575
+ const currentContent = await (0, import_promises14.readFile)(options.destinationPath, "utf8");
14971
15576
  if (options.shouldRepair?.(currentContent) !== true) {
14972
15577
  console.log(`Already exists: ${options.destinationPath}`);
14973
15578
  return;
14974
15579
  }
14975
- const rendered2 = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
15580
+ const rendered2 = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
14976
15581
  if (options.dryRun) {
14977
15582
  console.log(`Would repair generated config: ${options.destinationPath}`);
14978
15583
  return;
14979
15584
  }
14980
15585
  const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
14981
- await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
14982
- await (0, import_promises12.chmod)(backupPath, 384);
14983
- await (0, import_promises12.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
14984
- await (0, import_promises12.chmod)(options.destinationPath, 384);
15586
+ await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
15587
+ await (0, import_promises14.chmod)(backupPath, 384);
15588
+ await (0, import_promises14.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
15589
+ await (0, import_promises14.chmod)(options.destinationPath, 384);
14985
15590
  console.log(`Repaired generated config: ${options.destinationPath}`);
14986
15591
  console.log(`Backup: ${backupPath}`);
14987
15592
  return;
14988
15593
  }
14989
- const rendered = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
15594
+ const rendered = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
14990
15595
  if (options.dryRun) {
14991
15596
  console.log(`Would write ${options.destinationPath}`);
14992
15597
  return;
14993
15598
  }
14994
- await ensureDirectory((0, import_node_path14.dirname)(options.destinationPath), false);
14995
- await (0, import_promises12.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
14996
- await (0, import_promises12.chmod)(options.destinationPath, 384);
15599
+ await ensureDirectory((0, import_node_path15.dirname)(options.destinationPath), false);
15600
+ await (0, import_promises14.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
15601
+ await (0, import_promises14.chmod)(options.destinationPath, 384);
14997
15602
  console.log(`Wrote ${options.destinationPath}`);
14998
15603
  }
14999
15604
  async function installCommandShim(dryRun) {
15000
15605
  const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
15001
- const shimPath = (0, import_node_path14.join)(binDir, "threadnote");
15606
+ const shimPath = (0, import_node_path15.join)(binDir, "threadnote");
15002
15607
  const existingContent = await readFileIfExists(shimPath);
15003
15608
  if (existingContent && !isManagedCommandShim(existingContent)) {
15004
15609
  console.log(`WARN not overwriting existing command shim: ${shimPath}`);
@@ -15014,12 +15619,12 @@ async function installCommandShim(dryRun) {
15014
15619
  return;
15015
15620
  }
15016
15621
  await ensureDirectory(binDir, false);
15017
- await (0, import_promises12.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
15018
- await (0, import_promises12.chmod)(shimPath, 493);
15622
+ await (0, import_promises14.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
15623
+ await (0, import_promises14.chmod)(shimPath, 493);
15019
15624
  console.log(`Wrote command shim: ${shimPath}`);
15020
15625
  }
15021
15626
  async function removeCommandShim(dryRun) {
15022
- const shimPath = (0, import_node_path14.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
15627
+ const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
15023
15628
  const content = await readFileIfExists(shimPath);
15024
15629
  if (content === void 0) {
15025
15630
  console.log(`Already absent: ${shimPath}`);
@@ -15053,8 +15658,8 @@ async function installUserAgentInstructions(dryRun) {
15053
15658
  console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
15054
15659
  continue;
15055
15660
  }
15056
- await ensureDirectory((0, import_node_path14.dirname)(targetPath), false);
15057
- await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15661
+ await ensureDirectory((0, import_node_path15.dirname)(targetPath), false);
15662
+ await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15058
15663
  console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
15059
15664
  }
15060
15665
  }
@@ -15091,7 +15696,7 @@ async function removeUserAgentInstructions(dryRun) {
15091
15696
  console.log(`Would update ${targetPath}`);
15092
15697
  continue;
15093
15698
  }
15094
- await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15699
+ await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15095
15700
  console.log(`Updated ${targetPath}`);
15096
15701
  }
15097
15702
  }
@@ -15111,7 +15716,7 @@ async function renderUserAgentInstructions(target) {
15111
15716
  ].join("\n");
15112
15717
  }
15113
15718
  async function renderUserAgentInstructionsBlock() {
15114
- const instructions = (await (0, import_promises12.readFile)((0, import_node_path14.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
15719
+ const instructions = (await (0, import_promises14.readFile)((0, import_node_path15.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
15115
15720
  return `${USER_INSTRUCTIONS_START_MARKER}
15116
15721
  ${instructions}
15117
15722
  ${USER_INSTRUCTIONS_END_MARKER}`;
@@ -15195,9 +15800,9 @@ async function installLaunchAgent(config, dryRun) {
15195
15800
  `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.`
15196
15801
  );
15197
15802
  }
15198
- const source = (0, import_node_path14.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
15803
+ const source = (0, import_node_path15.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
15199
15804
  const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
15200
- const rendered = renderTemplate(await (0, import_promises12.readFile)(source, "utf8"), config, {
15805
+ const rendered = renderTemplate(await (0, import_promises14.readFile)(source, "utf8"), config, {
15201
15806
  OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
15202
15807
  });
15203
15808
  if (dryRun) {
@@ -15209,9 +15814,9 @@ async function installLaunchAgent(config, dryRun) {
15209
15814
  console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
15210
15815
  return;
15211
15816
  }
15212
- await ensureDirectory((0, import_node_path14.dirname)(destination), false);
15213
- await ensureDirectory((0, import_node_path14.dirname)(openVikingLogPath(config)), false);
15214
- await (0, import_promises12.writeFile)(destination, rendered, "utf8");
15817
+ await ensureDirectory((0, import_node_path15.dirname)(destination), false);
15818
+ await ensureDirectory((0, import_node_path15.dirname)(openVikingLogPath(config)), false);
15819
+ await (0, import_promises14.writeFile)(destination, rendered, "utf8");
15215
15820
  await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
15216
15821
  await maybeRun(false, "launchctl", ["load", destination]);
15217
15822
  await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
@@ -15278,7 +15883,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
15278
15883
  if (typeof parsed.default_user !== "string") {
15279
15884
  return false;
15280
15885
  }
15281
- if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path14.join)(config.agentContextHome, "data")) {
15886
+ if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path15.join)(config.agentContextHome, "data")) {
15282
15887
  return false;
15283
15888
  }
15284
15889
  return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
@@ -15340,9 +15945,9 @@ function printWhatsNew(whatsNew) {
15340
15945
  // src/manager.ts
15341
15946
  var import_node_http2 = require("node:http");
15342
15947
  var import_node_crypto2 = require("node:crypto");
15343
- var import_promises14 = require("node:fs/promises");
15948
+ var import_promises16 = require("node:fs/promises");
15344
15949
  var import_node_os7 = require("node:os");
15345
- var import_node_path15 = require("node:path");
15950
+ var import_node_path16 = require("node:path");
15346
15951
  var STATIC_FILES = {
15347
15952
  "/": { contentType: "text/html; charset=utf-8", path: "index.html" },
15348
15953
  "/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
@@ -15413,18 +16018,18 @@ async function readManagedMemory(config, uri) {
15413
16018
  if (!path2) {
15414
16019
  throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
15415
16020
  }
15416
- const [content, pathStat] = await Promise.all([(0, import_promises14.readFile)(path2, "utf8"), (0, import_promises14.stat)(path2)]);
15417
- const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2).split(import_node_path15.sep).join("/");
16021
+ const [content, pathStat] = await Promise.all([(0, import_promises16.readFile)(path2, "utf8"), (0, import_promises16.stat)(path2)]);
16022
+ const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2).split(import_node_path16.sep).join("/");
15418
16023
  const record = parseMemoryDocument(uri, content);
15419
16024
  return {
15420
16025
  content,
15421
16026
  node: {
15422
16027
  isDir: false,
15423
16028
  isShared: isInSharedNamespace(config, uri),
15424
- isSystem: isSystemMemoryName(path2.split(import_node_path15.sep).at(-1) ?? ""),
16029
+ isSystem: isSystemMemoryName(path2.split(import_node_path16.sep).at(-1) ?? ""),
15425
16030
  metadata: record?.metadata,
15426
16031
  modTime: pathStat.mtime.toISOString(),
15427
- name: path2.split(import_node_path15.sep).at(-1) ?? uri,
16032
+ name: path2.split(import_node_path16.sep).at(-1) ?? uri,
15428
16033
  relativePath,
15429
16034
  sharedTeam: sharedTeamNameForUri(config, uri),
15430
16035
  size: pathStat.size,
@@ -15712,7 +16317,7 @@ async function handleRequest(context, request, response) {
15712
16317
  }
15713
16318
  async function serveStatic(context, url, response) {
15714
16319
  const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
15715
- const content = await (0, import_promises14.readFile)((0, import_node_path15.join)(toolRoot(), file.root ?? "manager", file.path));
16320
+ const content = await (0, import_promises16.readFile)((0, import_node_path16.join)(toolRoot(), file.root ?? "manager", file.path));
15716
16321
  const headers = { "content-type": file.contentType };
15717
16322
  if (file.root !== "docs") {
15718
16323
  headers["cache-control"] = "no-store";
@@ -15721,11 +16326,11 @@ async function serveStatic(context, url, response) {
15721
16326
  response.end(content);
15722
16327
  }
15723
16328
  async function readTree(config, path2, uri, relativePath, options = {}) {
15724
- const pathStat = await (0, import_promises14.stat)(path2);
16329
+ const pathStat = await (0, import_promises16.stat)(path2);
15725
16330
  const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
15726
16331
  const isDir = pathStat.isDirectory();
15727
16332
  if (!isDir) {
15728
- const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path2, "utf8").catch(() => ""));
16333
+ const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises16.readFile)(path2, "utf8").catch(() => ""));
15729
16334
  return {
15730
16335
  isDir: false,
15731
16336
  isShared: isInSharedNamespace(config, uri),
@@ -15739,13 +16344,13 @@ async function readTree(config, path2, uri, relativePath, options = {}) {
15739
16344
  uri
15740
16345
  };
15741
16346
  }
15742
- const entries = await (0, import_promises14.readdir)(path2, { withFileTypes: true });
16347
+ const entries = await (0, import_promises16.readdir)(path2, { withFileTypes: true });
15743
16348
  const children = await Promise.all(
15744
16349
  entries.sort(
15745
16350
  (left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
15746
16351
  ).map((entry) => {
15747
16352
  const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
15748
- return readTree(config, (0, import_node_path15.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
16353
+ return readTree(config, (0, import_node_path16.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
15749
16354
  })
15750
16355
  );
15751
16356
  return {
@@ -15919,27 +16524,27 @@ async function removeManagedFolder(config, uri) {
15919
16524
  if (!path2) {
15920
16525
  throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
15921
16526
  }
15922
- const pathStat = await (0, import_promises14.stat)(path2);
16527
+ const pathStat = await (0, import_promises16.stat)(path2);
15923
16528
  if (!pathStat.isDirectory()) {
15924
16529
  throw new Error(`Not a folder: ${uri}`);
15925
16530
  }
15926
- const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2);
15927
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path15.sep).includes("..")) {
16531
+ const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
16532
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
15928
16533
  throw new Error("Refusing to remove a folder outside the memories tree.");
15929
16534
  }
15930
16535
  const fileUris = await fileUrisUnderFolder(config, path2);
15931
16536
  for (const fileUri of fileUris) {
15932
16537
  await runForget(config, fileUri, {});
15933
16538
  }
15934
- await (0, import_promises14.rm)(path2, { force: true, recursive: true });
16539
+ await (0, import_promises16.rm)(path2, { force: true, recursive: true });
15935
16540
  console.log(`Removed folder: ${uri}`);
15936
16541
  console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
15937
16542
  }
15938
16543
  async function fileUrisUnderFolder(config, folderPath) {
15939
- const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
16544
+ const entries = await (0, import_promises16.readdir)(folderPath, { withFileTypes: true });
15940
16545
  const uris = [];
15941
16546
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
15942
- const path2 = (0, import_node_path15.join)(folderPath, entry.name);
16547
+ const path2 = (0, import_node_path16.join)(folderPath, entry.name);
15943
16548
  if (entry.isDirectory()) {
15944
16549
  uris.push(...await fileUrisUnderFolder(config, path2));
15945
16550
  } else if (entry.isFile()) {
@@ -16037,11 +16642,11 @@ async function runConsolidationAgent(agent, sources) {
16037
16642
  throw new Error(`${agent} executable was not found.`);
16038
16643
  }
16039
16644
  const prompt = consolidationPrompt(sources);
16040
- const stagingDir = await (0, import_promises14.mkdtemp)((0, import_node_path15.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
16041
- const promptPath = (0, import_node_path15.join)(stagingDir, "prompt.txt");
16645
+ const stagingDir = await (0, import_promises16.mkdtemp)((0, import_node_path16.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
16646
+ const promptPath = (0, import_node_path16.join)(stagingDir, "prompt.txt");
16042
16647
  try {
16043
- await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
16044
- await (0, import_promises14.chmod)(promptPath, 384);
16648
+ await (0, import_promises16.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
16649
+ await (0, import_promises16.chmod)(promptPath, 384);
16045
16650
  const script = consolidationAgentScript(agent, executable);
16046
16651
  const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
16047
16652
  allowFailure: true,
@@ -16057,7 +16662,7 @@ async function runConsolidationAgent(agent, sources) {
16057
16662
  }
16058
16663
  return draft;
16059
16664
  } finally {
16060
- await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
16665
+ await (0, import_promises16.rm)(stagingDir, { force: true, recursive: true });
16061
16666
  }
16062
16667
  }
16063
16668
  function consolidationAgentScript(agent, executable) {
@@ -16195,10 +16800,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
16195
16800
  }
16196
16801
  }
16197
16802
  function localMemoriesRoot(config) {
16198
- return (0, import_node_path15.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
16803
+ return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
16199
16804
  }
16200
16805
  function localResourcesRoot(config) {
16201
- return (0, import_node_path15.join)(config.agentContextHome, "data", "viking", config.account, "resources");
16806
+ return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "resources");
16202
16807
  }
16203
16808
  function localPathForMemoryUri(config, uri) {
16204
16809
  const prefix = `viking://user/${uriSegment(config.user)}/memories`;
@@ -16210,17 +16815,17 @@ function localPathForMemoryUri(config, uri) {
16210
16815
  if (segments.some((segment) => segment === "." || segment === "..")) {
16211
16816
  return void 0;
16212
16817
  }
16213
- return (0, import_node_path15.join)(localMemoriesRoot(config), ...segments);
16818
+ return (0, import_node_path16.join)(localMemoriesRoot(config), ...segments);
16214
16819
  }
16215
16820
  function isMissingPathError(err) {
16216
16821
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
16217
16822
  }
16218
16823
  function localPathToMemoryUri(config, path2) {
16219
- const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2);
16220
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path15.sep).includes("..")) {
16824
+ const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
16825
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
16221
16826
  throw new Error(`Path is outside the memories tree: ${path2}`);
16222
16827
  }
16223
- return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path15.sep).join("/")}`;
16828
+ return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path16.sep).join("/")}`;
16224
16829
  }
16225
16830
  async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
16226
16831
  const prefix = "viking://";
@@ -16414,7 +17019,10 @@ async function main() {
16414
17019
  ).option("--preserve-memories", "Preserve THREADNOTE_HOME and OpenViking memories (default)").option("--erase-memories", "Delete THREADNOTE_HOME, including all OpenViking memories").action(async (options) => {
16415
17020
  await runUninstall(getRuntimeConfig(program2), options);
16416
17021
  });
16417
- program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option("--manifest <path>", "Manifest path for this seed run").option(
17022
+ program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option(
17023
+ "--graph",
17024
+ "Also seed a per-project .graph.md dependency-facts resource (package.json/go.mod), with [[project]] cross-repo edges"
17025
+ ).option("--manifest <path>", "Manifest path for this seed run").option(
16418
17026
  "--only <project>",
16419
17027
  "Restrict seeding to one or more manifest projects by name; repeat for multiple",
16420
17028
  collectOption,
@@ -16460,9 +17068,19 @@ async function main() {
16460
17068
  program2.command("migrate-lifecycle").description("Move clear legacy handoff memories into lifecycle-aware archive paths").option("--apply", "Perform the migration; without this, prints a dry run").option("--dry-run", "Print migration actions without writing or removing memories").option("--limit <count>", "Maximum number of legacy handoffs to migrate").action(async (options) => {
16461
17069
  await runMigrateLifecycle(getRuntimeConfig(program2), options);
16462
17070
  });
16463
- program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
17071
+ program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").option(
17072
+ "--workset <name>",
17073
+ "Recall across a named seed-manifest workset (a set of related repos) as one working set"
17074
+ ).action(async (options) => {
16464
17075
  await runRecall(getRuntimeConfig(program2), options);
16465
17076
  });
17077
+ const workset = program2.command("workset").description("Inspect seed-manifest worksets (named sets of related repos recalled as one working set)");
17078
+ workset.command("list").description("List worksets defined in the seed manifest").action(async () => {
17079
+ await runWorksetList(getRuntimeConfig(program2));
17080
+ });
17081
+ workset.command("show").description("Show the member projects of a workset").argument("<name>", "Workset name").action(async (name) => {
17082
+ await runWorksetShow(getRuntimeConfig(program2), name);
17083
+ });
16466
17084
  program2.command("compact").description("Plan or apply scoped memory hygiene for active personal memories").requiredOption("--project <name>", "Project/repo namespace to inspect").option("--apply", "Apply the compact plan; without this, prints a dry run").option("--dry-run", "Print the compact plan without changing anything").option("--kind <kind>", "durable, handoff, or incident", parseCompactKind).option("--topic <name>", "Stable topic name to inspect").action(async (options) => {
16467
17085
  await runCompact(getRuntimeConfig(program2), options);
16468
17086
  });
@@ -16472,8 +17090,13 @@ async function main() {
16472
17090
  program2.command("list").alias("ls").description("List a viking:// directory").argument("[uri]", "viking:// URI to list", "viking://").option("-a, --all", "Show hidden files such as .abstract.md and .overview.md").option("--dry-run", "Print ov command without listing").option("-n, --node-limit <count>", "Maximum number of nodes to list").option("-r, --recursive", "List subdirectories recursively").option("-s, --simple", "Print only paths").action(async (uri, options) => {
16473
17091
  await runList(getRuntimeConfig(program2), uri, options);
16474
17092
  });
16475
- program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--dry-run", "Print handoff without storing").option("--next-step <text>", "Suggested next step").option("--project <name>", "Project/repo namespace; defaults to current repo basename").option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").option("--timestamped", "Store a historical timestamped handoff instead of updating the current branch handoff").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
16476
- await runHandoff(getRuntimeConfig(program2), options);
17093
+ program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--ci <text>", "Captured CI status snapshot (free text; not a live status board)").option("--dry-run", "Print handoff without storing").option("--issue <text>", "Related issue reference (number or URL)").option("--next-step <text>", "Suggested next step").option("--pr <text>", "Related pull request reference (number or URL)").option("--project <name>", "Project/repo namespace; defaults to current repo basename").option(
17094
+ "--reference <uri>",
17095
+ "viking:// memory to record as one-way read-only prior context; repeat for multiple",
17096
+ collectOption,
17097
+ []
17098
+ ).option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").option("--timestamped", "Store a historical timestamped handoff instead of updating the current branch handoff").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
17099
+ await runHandoff(getRuntimeConfig(program2), { ...options, references: options.reference });
16477
17100
  });
16478
17101
  program2.command("archive").description("Move a memory into the archived lifecycle tree, then remove the original after the archive is stored").argument("<uri>", "viking:// memory URI to archive").option("--dry-run", "Print archive content and ov commands without changing anything").option("--kind <kind>", "durable, handoff, incident, preference, or smoke", parseMemoryKind).option("--project <name>", "Override inferred project/repo namespace").option("--topic <name>", "Override inferred topic").action(async (uri, options) => {
16479
17102
  await runArchive(getRuntimeConfig(program2), uri, options);