threadnote 1.5.0 → 1.6.1

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.
@@ -3394,7 +3394,7 @@ var program = new Command();
3394
3394
  // src/constants.ts
3395
3395
  var DEFAULT_HOST = "127.0.0.1";
3396
3396
  var DEFAULT_PORT = 1933;
3397
- var DEFAULT_OPENVIKING_VERSION = "0.4.5";
3397
+ var DEFAULT_OPENVIKING_VERSION = "0.4.7";
3398
3398
  var OPENVIKING_TOOL_PYTHON = "3.12";
3399
3399
  var DEFAULT_ACCOUNT = "local";
3400
3400
  var DEFAULT_AGENT_ID = "threadnote";
@@ -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;
@@ -12738,8 +13238,6 @@ async function runSeed(config, options) {
12738
13238
  importPath,
12739
13239
  "--to",
12740
13240
  candidate.destinationUri,
12741
- "--reason",
12742
- seedResourceReason(candidate),
12743
13241
  ...seedWatchArgs({
12744
13242
  watchIntervalMinutes,
12745
13243
  importedOriginal: importPath === candidate.filePath,
@@ -12760,6 +13258,58 @@ async function runSeed(config, options) {
12760
13258
  console.log(
12761
13259
  `Seed complete: ${importedCount} candidate(s), ${unchangedCount} unchanged, ${skippedCount} skipped for safety.`
12762
13260
  );
13261
+ if (options.graph === true) {
13262
+ await seedDependencyGraphs(config, ov, manifest, projects, options.dryRun === true);
13263
+ }
13264
+ }
13265
+ async function seedDependencyGraphs(config, ov, manifest, targetProjects, dryRun) {
13266
+ const factsByProject = /* @__PURE__ */ new Map();
13267
+ for (const project of manifest.projects) {
13268
+ const projectRoot = expandPath(project.path);
13269
+ if (!await exists(projectRoot)) {
13270
+ continue;
13271
+ }
13272
+ factsByProject.set(project.name, await extractDependencyFacts(projectRoot));
13273
+ }
13274
+ const projectByPublishedName = /* @__PURE__ */ new Map();
13275
+ for (const [name, facts] of factsByProject) {
13276
+ if (facts.publishedName) {
13277
+ projectByPublishedName.set(facts.publishedName.toLowerCase(), name);
13278
+ }
13279
+ }
13280
+ let written = 0;
13281
+ let skipped = 0;
13282
+ for (const project of targetProjects) {
13283
+ const facts = factsByProject.get(project.name);
13284
+ if (!facts || facts.manifestFiles.length === 0) {
13285
+ continue;
13286
+ }
13287
+ const { externalCount, internalEdges } = resolveGraphEdges(project.name, facts.dependencies, projectByPublishedName);
13288
+ const document = buildGraphDocument({ externalCount, facts, internalEdges, projectName: project.name });
13289
+ const secretMatches = detectSecretMatches(document);
13290
+ if (secretMatches.length > 0) {
13291
+ skipped += 1;
13292
+ console.log(
13293
+ `SKIP ${project.name}/.graph.md: possible secret (${secretMatches.slice(0, MAX_SECRET_MATCHES_TO_PRINT).join(", ")})`
13294
+ );
13295
+ continue;
13296
+ }
13297
+ const destinationUri = `${trimTrailingSlash(project.uri)}/.graph.md`;
13298
+ if (dryRun) {
13299
+ console.log(`Would seed dependency facts: ${destinationUri} (${internalEdges.length} in-workspace edge(s))`);
13300
+ written += 1;
13301
+ continue;
13302
+ }
13303
+ const graphPath = (0, import_node_path13.join)(config.agentContextHome, "graph", graphCacheFileName(project.name));
13304
+ await ensureDirectory((0, import_node_path13.dirname)(graphPath), false);
13305
+ await (0, import_promises11.writeFile)(graphPath, document, { encoding: "utf8", mode: 384 });
13306
+ await (0, import_promises11.chmod)(graphPath, 384);
13307
+ await maybeRun(false, ov, withIdentity(config, ["add-resource", graphPath, "--to", destinationUri, "--wait"]));
13308
+ written += 1;
13309
+ }
13310
+ console.log(
13311
+ `Dependency graph seed complete: ${written} .graph.md resource(s)${skipped > 0 ? `, ${skipped} skipped for safety` : ""}.`
13312
+ );
12763
13313
  }
12764
13314
  function filterProjects(projects, only) {
12765
13315
  if (!only || only.length === 0) {
@@ -12776,7 +13326,7 @@ function filterProjects(projects, only) {
12776
13326
  }
12777
13327
  async function statSeedFile(path2) {
12778
13328
  try {
12779
- const result = await (0, import_promises9.stat)(path2);
13329
+ const result = await (0, import_promises11.stat)(path2);
12780
13330
  return { mtimeMs: result.mtimeMs, size: result.size };
12781
13331
  } catch (_err) {
12782
13332
  return void 0;
@@ -12784,7 +13334,7 @@ async function statSeedFile(path2) {
12784
13334
  }
12785
13335
  async function readSeedState(path2) {
12786
13336
  try {
12787
- const raw = await (0, import_promises9.readFile)(path2, "utf8");
13337
+ const raw = await (0, import_promises11.readFile)(path2, "utf8");
12788
13338
  const parsed = JSON.parse(raw);
12789
13339
  if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
12790
13340
  return { files: {}, version: 1 };
@@ -12801,13 +13351,13 @@ async function readSeedState(path2) {
12801
13351
  }
12802
13352
  }
12803
13353
  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)}
13354
+ await ensureDirectory((0, import_node_path13.dirname)(path2), false);
13355
+ await (0, import_promises11.writeFile)(path2, `${JSON.stringify(state, void 0, 2)}
12806
13356
  `, { encoding: "utf8", mode: 384 });
12807
13357
  }
12808
13358
  async function runInitManifest(config, options) {
12809
13359
  const manifestPath = expandPath(
12810
- options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path12.join)(config.agentContextHome, USER_MANIFEST_NAME)
13360
+ options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path13.join)(config.agentContextHome, USER_MANIFEST_NAME)
12811
13361
  );
12812
13362
  const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
12813
13363
  const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
@@ -12844,20 +13394,58 @@ async function runInitManifest(config, options) {
12844
13394
  uri: existingManifest.futureMonorepo.uri
12845
13395
  };
12846
13396
  }
13397
+ if (existingManifest?.worksets) {
13398
+ outputManifest.worksets = existingManifest.worksets.map((workset) => ({
13399
+ name: workset.name,
13400
+ ...workset.description !== void 0 ? { description: workset.description } : {},
13401
+ projects: [...workset.projects]
13402
+ }));
13403
+ }
12847
13404
  const output2 = index_vite_proxy_tmp_default.dump(outputManifest, { lineWidth: 120, noRefs: true });
12848
13405
  if (options.dryRun === true) {
12849
13406
  console.log(`# Would write ${manifestPath}`);
12850
13407
  console.log(output2.trimEnd());
12851
13408
  return;
12852
13409
  }
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);
13410
+ await ensureDirectory((0, import_node_path13.dirname)(manifestPath), false);
13411
+ await (0, import_promises11.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
13412
+ await (0, import_promises11.chmod)(manifestPath, 384);
12856
13413
  console.log(`Wrote manifest: ${manifestPath}`);
12857
13414
  console.log("Seed with:");
12858
13415
  console.log(" threadnote seed --dry-run");
12859
13416
  console.log(" threadnote seed");
12860
13417
  }
13418
+ async function runWorksetList(config) {
13419
+ const manifest = await readSeedManifest(config.manifestPath);
13420
+ const worksets = manifest.worksets ?? [];
13421
+ if (worksets.length === 0) {
13422
+ console.log(
13423
+ "No worksets defined. Add a top-level `worksets:` list to the seed manifest to group related projects."
13424
+ );
13425
+ return;
13426
+ }
13427
+ console.log(`Worksets (${worksets.length}):`);
13428
+ for (const workset of worksets) {
13429
+ const summary = workset.description ? ` \u2014 ${workset.description}` : "";
13430
+ console.log(`- ${workset.name} (${workset.projects.length} project(s))${summary}`);
13431
+ }
13432
+ }
13433
+ async function runWorksetShow(config, name) {
13434
+ const manifest = await readSeedManifest(config.manifestPath);
13435
+ const workset = manifest.worksets?.find((entry) => entry.name.toLowerCase() === name.toLowerCase());
13436
+ if (!workset) {
13437
+ throw new Error(`No workset named "${name}" in ${config.manifestPath}.`);
13438
+ }
13439
+ console.log(`Workset: ${workset.name}`);
13440
+ if (workset.description) {
13441
+ console.log(workset.description);
13442
+ }
13443
+ console.log("Projects:");
13444
+ for (const memberName of workset.projects) {
13445
+ const project = manifest.projects.find((entry) => entry.name.toLowerCase() === memberName.toLowerCase());
13446
+ console.log(project ? `- ${project.name} (${project.uri})` : `- ${memberName} [not found in manifest projects]`);
13447
+ }
13448
+ }
12861
13449
  async function runSeedSkills(config, options) {
12862
13450
  const ov = await openVikingCliForMode(options.dryRun === true);
12863
13451
  const catalogItems = await collectSkillCandidates(config);
@@ -12873,15 +13461,7 @@ async function runSeedSkills(config, options) {
12873
13461
  console.log(`SKIP command in native skill mode: ${skill.filePath}`);
12874
13462
  continue;
12875
13463
  }
12876
- const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : [
12877
- "add-resource",
12878
- skill.filePath,
12879
- "--to",
12880
- skillResourceUri(skill),
12881
- "--reason",
12882
- skillResourceReason(skill),
12883
- "--wait"
12884
- ];
13464
+ const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : ["add-resource", skill.filePath, "--to", skillResourceUri(skill), "--wait"];
12885
13465
  await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
12886
13466
  }
12887
13467
  console.log(
@@ -12898,13 +13478,13 @@ async function resolveRepoRoot(repoInput) {
12898
13478
  async function projectIdentity(path2) {
12899
13479
  const expanded = expandPath(path2);
12900
13480
  try {
12901
- return await (0, import_promises9.realpath)(expanded);
13481
+ return await (0, import_promises11.realpath)(expanded);
12902
13482
  } catch (_err) {
12903
13483
  return expanded;
12904
13484
  }
12905
13485
  }
12906
13486
  function projectManifestForRepo(repoRoot, existingProjects) {
12907
- const baseName = uriSegment((0, import_node_path12.basename)(repoRoot));
13487
+ const baseName = uriSegment((0, import_node_path13.basename)(repoRoot));
12908
13488
  const usedNames = new Set(existingProjects.map((project) => project.name));
12909
13489
  const usedUris = new Set(existingProjects.map((project) => project.uri));
12910
13490
  let name = baseName;
@@ -12926,7 +13506,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
12926
13506
  for (const pattern of project.seed) {
12927
13507
  const files = await resolveProjectPattern(projectRoot, pattern);
12928
13508
  for (const filePath of files) {
12929
- const relativePath = toPosixPath((0, import_node_path12.relative)(projectRoot, filePath));
13509
+ const relativePath = toPosixPath((0, import_node_path13.relative)(projectRoot, filePath));
12930
13510
  if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
12931
13511
  continue;
12932
13512
  }
@@ -12944,20 +13524,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
12944
13524
  async function resolveProjectPattern(projectRoot, pattern) {
12945
13525
  const normalizedPattern = toPosixPath(pattern);
12946
13526
  if (!hasGlob(normalizedPattern)) {
12947
- const filePath = (0, import_node_path12.join)(projectRoot, normalizedPattern);
13527
+ const filePath = (0, import_node_path13.join)(projectRoot, normalizedPattern);
12948
13528
  return await isFile(filePath) ? [filePath] : [];
12949
13529
  }
12950
13530
  const globBase = getGlobBase(normalizedPattern);
12951
- const basePath = (0, import_node_path12.join)(projectRoot, globBase);
13531
+ const basePath = (0, import_node_path13.join)(projectRoot, globBase);
12952
13532
  if (!await exists(basePath)) {
12953
13533
  return [];
12954
13534
  }
12955
13535
  const regex = globToRegExp(normalizedPattern);
12956
13536
  const files = await walkFiles(basePath);
12957
- return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path12.relative)(projectRoot, filePath))));
13537
+ return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path13.relative)(projectRoot, filePath))));
12958
13538
  }
12959
13539
  async function prepareSeedFile(config, candidate, dryRun) {
12960
- const content = await (0, import_promises9.readFile)(candidate.filePath, "utf8");
13540
+ const content = await (0, import_promises11.readFile)(candidate.filePath, "utf8");
12961
13541
  const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
12962
13542
  const secretMatches = detectSecretMatches(redactedContent);
12963
13543
  if (secretMatches.length > 0) {
@@ -12969,23 +13549,18 @@ async function prepareSeedFile(config, candidate, dryRun) {
12969
13549
  if (redactedContent === content) {
12970
13550
  return candidate.filePath;
12971
13551
  }
12972
- const redactedPath = (0, import_node_path12.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
13552
+ const redactedPath = (0, import_node_path13.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
12973
13553
  if (dryRun) {
12974
13554
  console.log(`Would write redacted copy: ${redactedPath}`);
12975
13555
  return redactedPath;
12976
13556
  }
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);
13557
+ await ensureDirectory((0, import_node_path13.dirname)(redactedPath), false);
13558
+ await (0, import_promises11.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
13559
+ await (0, import_promises11.chmod)(redactedPath, 384);
12980
13560
  return redactedPath;
12981
13561
  }
12982
- function seedResourceReason(candidate) {
12983
- return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
12984
- }
12985
- function skillResourceReason(skill) {
12986
- return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path12.basename)(
12987
- skill.filePath
12988
- )}`;
13562
+ function graphCacheFileName(projectName) {
13563
+ return `${uriSegment(projectName)}-${sha256(projectName).slice(0, 8)}.graph.md`;
12989
13564
  }
12990
13565
  async function collectSkillCandidates(config) {
12991
13566
  const sources = [
@@ -13016,7 +13591,7 @@ async function collectSkillCandidates(config) {
13016
13591
  for (const source of sources) {
13017
13592
  const files = await resolveAbsolutePattern(expandPath(source.pattern));
13018
13593
  for (const filePath of files) {
13019
- const content = await (0, import_promises9.readFile)(filePath, "utf8");
13594
+ const content = await (0, import_promises11.readFile)(filePath, "utf8");
13020
13595
  const matches = detectSecretMatches(content);
13021
13596
  if (matches.length > 0) {
13022
13597
  console.log(`SKIP skill with possible secret: ${filePath}`);
@@ -13050,16 +13625,16 @@ function skillResourceUri(skill) {
13050
13625
  return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
13051
13626
  }
13052
13627
  function skillResourceName(skill) {
13053
- const fileName = (0, import_node_path12.basename)(skill.filePath);
13628
+ const fileName = (0, import_node_path13.basename)(skill.filePath);
13054
13629
  if (fileName.toLowerCase() === "skill.md") {
13055
- return uriSegment((0, import_node_path12.basename)((0, import_node_path12.dirname)(skill.filePath)));
13630
+ return uriSegment((0, import_node_path13.basename)((0, import_node_path13.dirname)(skill.filePath)));
13056
13631
  }
13057
13632
  const extensionIndex = fileName.lastIndexOf(".");
13058
13633
  const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
13059
13634
  return uriSegment(stem);
13060
13635
  }
13061
13636
  async function loadIgnorePatterns() {
13062
- const raw = await (0, import_promises9.readFile)((0, import_node_path12.join)(toolRoot(), ".threadnoteignore"), "utf8");
13637
+ const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "utf8");
13063
13638
  return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
13064
13639
  }
13065
13640
  function matchesIgnore(relativePath, patterns) {
@@ -13132,18 +13707,18 @@ function detectSecretMatches(content) {
13132
13707
  // src/lifecycle.ts
13133
13708
  var import_node_child_process4 = require("node:child_process");
13134
13709
  var import_node_fs7 = require("node:fs");
13135
- var import_promises12 = require("node:fs/promises");
13710
+ var import_promises14 = require("node:fs/promises");
13136
13711
  var import_node_os6 = require("node:os");
13137
- var import_node_path14 = require("node:path");
13712
+ var import_node_path15 = require("node:path");
13138
13713
  var import_node_process4 = require("node:process");
13139
- var import_promises13 = require("node:readline/promises");
13714
+ var import_promises15 = require("node:readline/promises");
13140
13715
 
13141
13716
  // src/update.ts
13142
13717
  var import_node_fs6 = require("node:fs");
13143
- var import_promises10 = require("node:fs/promises");
13718
+ var import_promises12 = require("node:fs/promises");
13144
13719
  var import_node_os5 = require("node:os");
13145
- var import_node_path13 = require("node:path");
13146
- var import_promises11 = require("node:readline/promises");
13720
+ var import_node_path14 = require("node:path");
13721
+ var import_promises13 = require("node:readline/promises");
13147
13722
  var import_node_process3 = require("node:process");
13148
13723
 
13149
13724
  // src/release_notes.ts
@@ -13497,14 +14072,14 @@ async function waitForOpenVikingPortClosed(config, timeoutMs) {
13497
14072
  return false;
13498
14073
  }
13499
14074
  function launchAgentPlistPath() {
13500
- return (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
14075
+ return (0, import_node_path14.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
13501
14076
  }
13502
14077
  async function isLaunchAgentInstalled() {
13503
14078
  if (process.platform !== "darwin") {
13504
14079
  return false;
13505
14080
  }
13506
14081
  try {
13507
- await (0, import_promises10.access)(launchAgentPlistPath(), import_node_fs6.constants.F_OK);
14082
+ await (0, import_promises12.access)(launchAgentPlistPath(), import_node_fs6.constants.F_OK);
13508
14083
  return true;
13509
14084
  } catch (_err) {
13510
14085
  return false;
@@ -13575,11 +14150,11 @@ async function readFreshCache(config, registry) {
13575
14150
  }
13576
14151
  async function writeUpdateCache2(config, cache) {
13577
14152
  await ensureDirectory(config.agentContextHome, false);
13578
- await (0, import_promises10.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
14153
+ await (0, import_promises12.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
13579
14154
  `, { encoding: "utf8", mode: 384 });
13580
14155
  }
13581
14156
  function updateCachePath(config) {
13582
- return (0, import_node_path13.join)(config.agentContextHome, "update-check.json");
14157
+ return (0, import_node_path14.join)(config.agentContextHome, "update-check.json");
13583
14158
  }
13584
14159
  async function runApplicablePostUpdateMigrations(config, options) {
13585
14160
  const state = await readPostUpdateState(config);
@@ -13643,7 +14218,7 @@ async function applicablePostUpdateMigrations(config, options) {
13643
14218
  return applicable;
13644
14219
  }
13645
14220
  async function readPostUpdateMigrations() {
13646
- const raw = await readFileIfExists((0, import_node_path13.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
14221
+ const raw = await readFileIfExists((0, import_node_path14.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
13647
14222
  if (!raw) {
13648
14223
  return [];
13649
14224
  }
@@ -13682,7 +14257,7 @@ function printPostUpdateMigration(migration) {
13682
14257
  }
13683
14258
  }
13684
14259
  async function confirmPostUpdateMigration(prompt, defaultYes = false) {
13685
- const readline = (0, import_promises11.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
14260
+ const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
13686
14261
  try {
13687
14262
  const answer = (await readline.question(prompt)).trim().toLowerCase();
13688
14263
  if (answer === "") {
@@ -13717,11 +14292,11 @@ async function readPostUpdateState(config) {
13717
14292
  }
13718
14293
  async function writePostUpdateState(config, state) {
13719
14294
  await ensureDirectory(config.agentContextHome, false);
13720
- await (0, import_promises10.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
14295
+ await (0, import_promises12.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
13721
14296
  `, { encoding: "utf8", mode: 384 });
13722
14297
  }
13723
14298
  function postUpdateStatePath(config) {
13724
- return (0, import_node_path13.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
14299
+ return (0, import_node_path14.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
13725
14300
  }
13726
14301
  async function resolveUpdateRuntime(runtime) {
13727
14302
  if (runtime !== "auto") {
@@ -13751,14 +14326,14 @@ async function runtimeThreadnoteBin(runtime) {
13751
14326
  if (runtime === "npm") {
13752
14327
  const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
13753
14328
  const prefix = result.stdout.trim();
13754
- return prefix ? (0, import_node_path13.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
14329
+ return prefix ? (0, import_node_path14.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
13755
14330
  }
13756
14331
  if (runtime === "bun") {
13757
14332
  const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
13758
14333
  const binDir = result.stdout.trim();
13759
- return binDir ? (0, import_node_path13.join)(binDir, NPM_PACKAGE_NAME) : void 0;
14334
+ return binDir ? (0, import_node_path14.join)(binDir, NPM_PACKAGE_NAME) : void 0;
13760
14335
  }
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);
14336
+ 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
14337
  }
13763
14338
  function updatePackageCommand(runtime, registry) {
13764
14339
  if (runtime === "npm") {
@@ -13831,9 +14406,9 @@ async function collectDoctorChecks(config, options = {}) {
13831
14406
  checks.push(await manifestCheck(config.manifestPath));
13832
14407
  checks.push(await recallIndexFreshnessCheck(config));
13833
14408
  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"));
14409
+ checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
14410
+ checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
14411
+ checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
13837
14412
  checks.push(await healthCheck(config));
13838
14413
  return checks;
13839
14414
  }
@@ -13841,9 +14416,9 @@ async function runInstall(config, options) {
13841
14416
  const repairInvalidConfigs = options.repairInvalidConfigs === true;
13842
14417
  const dryRun = options.dryRun === true;
13843
14418
  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);
14419
+ await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "logs"), dryRun);
14420
+ await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "redacted"), dryRun);
14421
+ await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "mcp"), dryRun);
13847
14422
  await installCommandShim(dryRun);
13848
14423
  await installUserAgentInstructions(dryRun);
13849
14424
  const serverPath = await findOpenVikingServer();
@@ -13888,17 +14463,17 @@ async function runInstall(config, options) {
13888
14463
  }
13889
14464
  await writeTemplateIfMissing({
13890
14465
  config,
13891
- destinationPath: (0, import_node_path14.join)(config.agentContextHome, "ov.conf"),
14466
+ destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ov.conf"),
13892
14467
  dryRun,
13893
14468
  shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
13894
- templatePath: (0, import_node_path14.join)(toolRoot(), "config", "ov.conf.template.json")
14469
+ templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json")
13895
14470
  });
13896
14471
  await writeTemplateIfMissing({
13897
14472
  config,
13898
- destinationPath: (0, import_node_path14.join)(config.agentContextHome, "ovcli.conf"),
14473
+ destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ovcli.conf"),
13899
14474
  dryRun,
13900
14475
  shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
13901
- templatePath: (0, import_node_path14.join)(toolRoot(), "config", "ovcli.conf.template.json")
14476
+ templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json")
13902
14477
  });
13903
14478
  await configureOpenVikingCliLanguage(config, dryRun);
13904
14479
  if (options.start !== false) {
@@ -13955,7 +14530,7 @@ async function runUninstall(config, options) {
13955
14530
  }
13956
14531
  console.log("Uninstalling local Threadnote setup.");
13957
14532
  await runStop(config, { dryRun });
13958
- await removePathIfExists((0, import_node_path14.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
14533
+ await removePathIfExists((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
13959
14534
  await removeLaunchAgent(dryRun);
13960
14535
  await removeMcpConfigs(options.mcp ?? "available", dryRun);
13961
14536
  await removeMcpSnippets(config, dryRun);
@@ -14012,16 +14587,16 @@ async function repairManifest(config, dryRun) {
14012
14587
  console.log(output2.trimEnd());
14013
14588
  return;
14014
14589
  }
14015
- await ensureDirectory((0, import_node_path14.dirname)(config.manifestPath), false);
14590
+ await ensureDirectory((0, import_node_path15.dirname)(config.manifestPath), false);
14016
14591
  const currentContent = await readFileIfExists(config.manifestPath);
14017
14592
  if (currentContent !== void 0) {
14018
14593
  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);
14594
+ await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
14595
+ await (0, import_promises14.chmod)(backupPath, 384);
14021
14596
  console.log(`Backup: ${backupPath}`);
14022
14597
  }
14023
- await (0, import_promises12.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
14024
- await (0, import_promises12.chmod)(config.manifestPath, 384);
14598
+ await (0, import_promises14.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
14599
+ await (0, import_promises14.chmod)(config.manifestPath, 384);
14025
14600
  console.log(`Wrote replacement manifest: ${config.manifestPath}`);
14026
14601
  }
14027
14602
  async function repairRecallIndex(config, dryRun) {
@@ -14096,7 +14671,7 @@ async function findOpenVikingServer() {
14096
14671
  return onPath;
14097
14672
  }
14098
14673
  for (const candidateDir of await openVikingServerCandidateDirs()) {
14099
- const candidate = (0, import_node_path14.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
14674
+ const candidate = (0, import_node_path15.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
14100
14675
  if (await isExecutable(candidate)) {
14101
14676
  return candidate;
14102
14677
  }
@@ -14142,7 +14717,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
14142
14717
  if (onPath) {
14143
14718
  return;
14144
14719
  }
14145
- const binDir = (0, import_node_path14.dirname)(serverPath);
14720
+ const binDir = (0, import_node_path15.dirname)(serverPath);
14146
14721
  const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
14147
14722
  console.log(
14148
14723
  `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 +14761,7 @@ async function runStart(config, options) {
14186
14761
  );
14187
14762
  }
14188
14763
  const logPath = openVikingLogPath(config);
14189
- await ensureDirectory((0, import_node_path14.dirname)(logPath), false);
14764
+ await ensureDirectory((0, import_node_path15.dirname)(logPath), false);
14190
14765
  if (options.foreground === true) {
14191
14766
  const result = await runInteractive(server, args);
14192
14767
  process.exitCode = result;
@@ -14195,7 +14770,7 @@ async function runStart(config, options) {
14195
14770
  const logFd = (0, import_node_fs7.openSync)(logPath, "a");
14196
14771
  const child = spawnDetachedServerWithLog(server, args, logFd);
14197
14772
  child.unref();
14198
- await (0, import_promises12.writeFile)((0, import_node_path14.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
14773
+ await (0, import_promises14.writeFile)((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
14199
14774
  `, "utf8");
14200
14775
  const health = await waitForOpenVikingHealth(
14201
14776
  config,
@@ -14232,7 +14807,7 @@ async function runStop(config, options) {
14232
14807
  console.log(`No LaunchAgent found: ${launchAgentPath}`);
14233
14808
  }
14234
14809
  }
14235
- const pidPath = (0, import_node_path14.join)(config.agentContextHome, "openviking-server.pid");
14810
+ const pidPath = (0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid");
14236
14811
  const pidText = await readFileIfExists(pidPath);
14237
14812
  if (!pidText) {
14238
14813
  console.log("No pid file found for detached OpenViking server.");
@@ -14278,7 +14853,7 @@ async function openVikingServerCheck() {
14278
14853
  }
14279
14854
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
14280
14855
  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)`;
14856
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
14282
14857
  return {
14283
14858
  name,
14284
14859
  status: result.exitCode === 0 ? "ok" : "warn",
@@ -14319,7 +14894,7 @@ async function openVikingCliCheck() {
14319
14894
  }
14320
14895
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
14321
14896
  const onPath = await findExecutable(["ov", "openviking"]);
14322
- const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path14.dirname)(executable)} to PATH)`;
14897
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
14323
14898
  return {
14324
14899
  name: "openviking cli",
14325
14900
  status: result.exitCode === 0 ? "ok" : "warn",
@@ -14412,7 +14987,7 @@ async function pythonSystemCertificatesCheck() {
14412
14987
  };
14413
14988
  }
14414
14989
  async function commandShimCheck() {
14415
- const shimPath = (0, import_node_path14.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
14990
+ const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
14416
14991
  const content = await readFileIfExists(shimPath);
14417
14992
  if (content === void 0) {
14418
14993
  return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
@@ -14478,11 +15053,11 @@ async function hasPythonModule(serverPath, moduleName) {
14478
15053
  async function siblingPythonForExecutable(executablePath) {
14479
15054
  let resolvedPath;
14480
15055
  try {
14481
- resolvedPath = await (0, import_promises12.realpath)(executablePath);
15056
+ resolvedPath = await (0, import_promises14.realpath)(executablePath);
14482
15057
  } catch (_err) {
14483
15058
  return void 0;
14484
15059
  }
14485
- const pythonPath = (0, import_node_path14.join)((0, import_node_path14.dirname)(resolvedPath), "python");
15060
+ const pythonPath = (0, import_node_path15.join)((0, import_node_path15.dirname)(resolvedPath), "python");
14486
15061
  return await exists(pythonPath) ? pythonPath : void 0;
14487
15062
  }
14488
15063
  async function manifestCheck(path2) {
@@ -14525,7 +15100,7 @@ async function recallIndexFreshnessCheck(config) {
14525
15100
  }
14526
15101
  async function memoryProjectConsistencyCheck(config) {
14527
15102
  const name = "memory project consistency";
14528
- const memoriesRoot = (0, import_node_path14.join)(
15103
+ const memoriesRoot = (0, import_node_path15.join)(
14529
15104
  config.agentContextHome,
14530
15105
  "data",
14531
15106
  "viking",
@@ -14537,7 +15112,7 @@ async function memoryProjectConsistencyCheck(config) {
14537
15112
  try {
14538
15113
  let entries;
14539
15114
  try {
14540
- entries = await (0, import_promises12.readdir)(memoriesRoot, { recursive: true });
15115
+ entries = await (0, import_promises14.readdir)(memoriesRoot, { recursive: true });
14541
15116
  } catch {
14542
15117
  return { name, status: "ok", detail: "no memories directory yet" };
14543
15118
  }
@@ -14547,14 +15122,14 @@ async function memoryProjectConsistencyCheck(config) {
14547
15122
  if (!entry.endsWith(".md") || isSummarySidecarUri(entry)) {
14548
15123
  continue;
14549
15124
  }
14550
- const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(import_node_path14.sep).join("/")}`;
15125
+ const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(import_node_path15.sep).join("/")}`;
14551
15126
  const pathProject = memoryUriProjectSegment(uri);
14552
15127
  if (!pathProject) {
14553
15128
  continue;
14554
15129
  }
14555
15130
  let content;
14556
15131
  try {
14557
- content = await (0, import_promises12.readFile)((0, import_node_path14.join)(memoriesRoot, entry), "utf8");
15132
+ content = await (0, import_promises14.readFile)((0, import_node_path15.join)(memoriesRoot, entry), "utf8");
14558
15133
  } catch {
14559
15134
  continue;
14560
15135
  }
@@ -14809,7 +15384,7 @@ async function offerToInstallUv() {
14809
15384
  );
14810
15385
  return false;
14811
15386
  }
14812
- const readline = (0, import_promises13.createInterface)({ input: import_node_process4.stdin, output: import_node_process4.stdout });
15387
+ const readline = (0, import_promises15.createInterface)({ input: import_node_process4.stdin, output: import_node_process4.stdout });
14813
15388
  let answer;
14814
15389
  try {
14815
15390
  answer = (await readline.question(
@@ -14967,38 +15542,38 @@ function printInstallNextSteps(options) {
14967
15542
  }
14968
15543
  async function writeTemplateIfMissing(options) {
14969
15544
  if (await exists(options.destinationPath)) {
14970
- const currentContent = await (0, import_promises12.readFile)(options.destinationPath, "utf8");
15545
+ const currentContent = await (0, import_promises14.readFile)(options.destinationPath, "utf8");
14971
15546
  if (options.shouldRepair?.(currentContent) !== true) {
14972
15547
  console.log(`Already exists: ${options.destinationPath}`);
14973
15548
  return;
14974
15549
  }
14975
- const rendered2 = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
15550
+ const rendered2 = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
14976
15551
  if (options.dryRun) {
14977
15552
  console.log(`Would repair generated config: ${options.destinationPath}`);
14978
15553
  return;
14979
15554
  }
14980
15555
  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);
15556
+ await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
15557
+ await (0, import_promises14.chmod)(backupPath, 384);
15558
+ await (0, import_promises14.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
15559
+ await (0, import_promises14.chmod)(options.destinationPath, 384);
14985
15560
  console.log(`Repaired generated config: ${options.destinationPath}`);
14986
15561
  console.log(`Backup: ${backupPath}`);
14987
15562
  return;
14988
15563
  }
14989
- const rendered = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
15564
+ const rendered = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
14990
15565
  if (options.dryRun) {
14991
15566
  console.log(`Would write ${options.destinationPath}`);
14992
15567
  return;
14993
15568
  }
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);
15569
+ await ensureDirectory((0, import_node_path15.dirname)(options.destinationPath), false);
15570
+ await (0, import_promises14.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
15571
+ await (0, import_promises14.chmod)(options.destinationPath, 384);
14997
15572
  console.log(`Wrote ${options.destinationPath}`);
14998
15573
  }
14999
15574
  async function installCommandShim(dryRun) {
15000
15575
  const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
15001
- const shimPath = (0, import_node_path14.join)(binDir, "threadnote");
15576
+ const shimPath = (0, import_node_path15.join)(binDir, "threadnote");
15002
15577
  const existingContent = await readFileIfExists(shimPath);
15003
15578
  if (existingContent && !isManagedCommandShim(existingContent)) {
15004
15579
  console.log(`WARN not overwriting existing command shim: ${shimPath}`);
@@ -15014,12 +15589,12 @@ async function installCommandShim(dryRun) {
15014
15589
  return;
15015
15590
  }
15016
15591
  await ensureDirectory(binDir, false);
15017
- await (0, import_promises12.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
15018
- await (0, import_promises12.chmod)(shimPath, 493);
15592
+ await (0, import_promises14.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
15593
+ await (0, import_promises14.chmod)(shimPath, 493);
15019
15594
  console.log(`Wrote command shim: ${shimPath}`);
15020
15595
  }
15021
15596
  async function removeCommandShim(dryRun) {
15022
- const shimPath = (0, import_node_path14.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
15597
+ const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
15023
15598
  const content = await readFileIfExists(shimPath);
15024
15599
  if (content === void 0) {
15025
15600
  console.log(`Already absent: ${shimPath}`);
@@ -15053,8 +15628,8 @@ async function installUserAgentInstructions(dryRun) {
15053
15628
  console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
15054
15629
  continue;
15055
15630
  }
15056
- await ensureDirectory((0, import_node_path14.dirname)(targetPath), false);
15057
- await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15631
+ await ensureDirectory((0, import_node_path15.dirname)(targetPath), false);
15632
+ await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15058
15633
  console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
15059
15634
  }
15060
15635
  }
@@ -15091,7 +15666,7 @@ async function removeUserAgentInstructions(dryRun) {
15091
15666
  console.log(`Would update ${targetPath}`);
15092
15667
  continue;
15093
15668
  }
15094
- await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15669
+ await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
15095
15670
  console.log(`Updated ${targetPath}`);
15096
15671
  }
15097
15672
  }
@@ -15111,7 +15686,7 @@ async function renderUserAgentInstructions(target) {
15111
15686
  ].join("\n");
15112
15687
  }
15113
15688
  async function renderUserAgentInstructionsBlock() {
15114
- const instructions = (await (0, import_promises12.readFile)((0, import_node_path14.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
15689
+ const instructions = (await (0, import_promises14.readFile)((0, import_node_path15.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
15115
15690
  return `${USER_INSTRUCTIONS_START_MARKER}
15116
15691
  ${instructions}
15117
15692
  ${USER_INSTRUCTIONS_END_MARKER}`;
@@ -15195,9 +15770,9 @@ async function installLaunchAgent(config, dryRun) {
15195
15770
  `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
15771
  );
15197
15772
  }
15198
- const source = (0, import_node_path14.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
15773
+ const source = (0, import_node_path15.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
15199
15774
  const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
15200
- const rendered = renderTemplate(await (0, import_promises12.readFile)(source, "utf8"), config, {
15775
+ const rendered = renderTemplate(await (0, import_promises14.readFile)(source, "utf8"), config, {
15201
15776
  OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
15202
15777
  });
15203
15778
  if (dryRun) {
@@ -15209,9 +15784,9 @@ async function installLaunchAgent(config, dryRun) {
15209
15784
  console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
15210
15785
  return;
15211
15786
  }
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");
15787
+ await ensureDirectory((0, import_node_path15.dirname)(destination), false);
15788
+ await ensureDirectory((0, import_node_path15.dirname)(openVikingLogPath(config)), false);
15789
+ await (0, import_promises14.writeFile)(destination, rendered, "utf8");
15215
15790
  await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
15216
15791
  await maybeRun(false, "launchctl", ["load", destination]);
15217
15792
  await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
@@ -15278,7 +15853,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
15278
15853
  if (typeof parsed.default_user !== "string") {
15279
15854
  return false;
15280
15855
  }
15281
- if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path14.join)(config.agentContextHome, "data")) {
15856
+ if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path15.join)(config.agentContextHome, "data")) {
15282
15857
  return false;
15283
15858
  }
15284
15859
  return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
@@ -15340,9 +15915,9 @@ function printWhatsNew(whatsNew) {
15340
15915
  // src/manager.ts
15341
15916
  var import_node_http2 = require("node:http");
15342
15917
  var import_node_crypto2 = require("node:crypto");
15343
- var import_promises14 = require("node:fs/promises");
15918
+ var import_promises16 = require("node:fs/promises");
15344
15919
  var import_node_os7 = require("node:os");
15345
- var import_node_path15 = require("node:path");
15920
+ var import_node_path16 = require("node:path");
15346
15921
  var STATIC_FILES = {
15347
15922
  "/": { contentType: "text/html; charset=utf-8", path: "index.html" },
15348
15923
  "/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
@@ -15413,18 +15988,18 @@ async function readManagedMemory(config, uri) {
15413
15988
  if (!path2) {
15414
15989
  throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
15415
15990
  }
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("/");
15991
+ const [content, pathStat] = await Promise.all([(0, import_promises16.readFile)(path2, "utf8"), (0, import_promises16.stat)(path2)]);
15992
+ const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2).split(import_node_path16.sep).join("/");
15418
15993
  const record = parseMemoryDocument(uri, content);
15419
15994
  return {
15420
15995
  content,
15421
15996
  node: {
15422
15997
  isDir: false,
15423
15998
  isShared: isInSharedNamespace(config, uri),
15424
- isSystem: isSystemMemoryName(path2.split(import_node_path15.sep).at(-1) ?? ""),
15999
+ isSystem: isSystemMemoryName(path2.split(import_node_path16.sep).at(-1) ?? ""),
15425
16000
  metadata: record?.metadata,
15426
16001
  modTime: pathStat.mtime.toISOString(),
15427
- name: path2.split(import_node_path15.sep).at(-1) ?? uri,
16002
+ name: path2.split(import_node_path16.sep).at(-1) ?? uri,
15428
16003
  relativePath,
15429
16004
  sharedTeam: sharedTeamNameForUri(config, uri),
15430
16005
  size: pathStat.size,
@@ -15712,7 +16287,7 @@ async function handleRequest(context, request, response) {
15712
16287
  }
15713
16288
  async function serveStatic(context, url, response) {
15714
16289
  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));
16290
+ const content = await (0, import_promises16.readFile)((0, import_node_path16.join)(toolRoot(), file.root ?? "manager", file.path));
15716
16291
  const headers = { "content-type": file.contentType };
15717
16292
  if (file.root !== "docs") {
15718
16293
  headers["cache-control"] = "no-store";
@@ -15721,11 +16296,11 @@ async function serveStatic(context, url, response) {
15721
16296
  response.end(content);
15722
16297
  }
15723
16298
  async function readTree(config, path2, uri, relativePath, options = {}) {
15724
- const pathStat = await (0, import_promises14.stat)(path2);
16299
+ const pathStat = await (0, import_promises16.stat)(path2);
15725
16300
  const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
15726
16301
  const isDir = pathStat.isDirectory();
15727
16302
  if (!isDir) {
15728
- const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path2, "utf8").catch(() => ""));
16303
+ const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises16.readFile)(path2, "utf8").catch(() => ""));
15729
16304
  return {
15730
16305
  isDir: false,
15731
16306
  isShared: isInSharedNamespace(config, uri),
@@ -15739,13 +16314,13 @@ async function readTree(config, path2, uri, relativePath, options = {}) {
15739
16314
  uri
15740
16315
  };
15741
16316
  }
15742
- const entries = await (0, import_promises14.readdir)(path2, { withFileTypes: true });
16317
+ const entries = await (0, import_promises16.readdir)(path2, { withFileTypes: true });
15743
16318
  const children = await Promise.all(
15744
16319
  entries.sort(
15745
16320
  (left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
15746
16321
  ).map((entry) => {
15747
16322
  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);
16323
+ return readTree(config, (0, import_node_path16.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
15749
16324
  })
15750
16325
  );
15751
16326
  return {
@@ -15919,27 +16494,27 @@ async function removeManagedFolder(config, uri) {
15919
16494
  if (!path2) {
15920
16495
  throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
15921
16496
  }
15922
- const pathStat = await (0, import_promises14.stat)(path2);
16497
+ const pathStat = await (0, import_promises16.stat)(path2);
15923
16498
  if (!pathStat.isDirectory()) {
15924
16499
  throw new Error(`Not a folder: ${uri}`);
15925
16500
  }
15926
- const relativePath = (0, import_node_path15.relative)(localMemoriesRoot(config), path2);
15927
- if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path15.sep).includes("..")) {
16501
+ const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
16502
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
15928
16503
  throw new Error("Refusing to remove a folder outside the memories tree.");
15929
16504
  }
15930
16505
  const fileUris = await fileUrisUnderFolder(config, path2);
15931
16506
  for (const fileUri of fileUris) {
15932
16507
  await runForget(config, fileUri, {});
15933
16508
  }
15934
- await (0, import_promises14.rm)(path2, { force: true, recursive: true });
16509
+ await (0, import_promises16.rm)(path2, { force: true, recursive: true });
15935
16510
  console.log(`Removed folder: ${uri}`);
15936
16511
  console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
15937
16512
  }
15938
16513
  async function fileUrisUnderFolder(config, folderPath) {
15939
- const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
16514
+ const entries = await (0, import_promises16.readdir)(folderPath, { withFileTypes: true });
15940
16515
  const uris = [];
15941
16516
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
15942
- const path2 = (0, import_node_path15.join)(folderPath, entry.name);
16517
+ const path2 = (0, import_node_path16.join)(folderPath, entry.name);
15943
16518
  if (entry.isDirectory()) {
15944
16519
  uris.push(...await fileUrisUnderFolder(config, path2));
15945
16520
  } else if (entry.isFile()) {
@@ -16037,11 +16612,11 @@ async function runConsolidationAgent(agent, sources) {
16037
16612
  throw new Error(`${agent} executable was not found.`);
16038
16613
  }
16039
16614
  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");
16615
+ const stagingDir = await (0, import_promises16.mkdtemp)((0, import_node_path16.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
16616
+ const promptPath = (0, import_node_path16.join)(stagingDir, "prompt.txt");
16042
16617
  try {
16043
- await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
16044
- await (0, import_promises14.chmod)(promptPath, 384);
16618
+ await (0, import_promises16.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
16619
+ await (0, import_promises16.chmod)(promptPath, 384);
16045
16620
  const script = consolidationAgentScript(agent, executable);
16046
16621
  const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
16047
16622
  allowFailure: true,
@@ -16057,7 +16632,7 @@ async function runConsolidationAgent(agent, sources) {
16057
16632
  }
16058
16633
  return draft;
16059
16634
  } finally {
16060
- await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
16635
+ await (0, import_promises16.rm)(stagingDir, { force: true, recursive: true });
16061
16636
  }
16062
16637
  }
16063
16638
  function consolidationAgentScript(agent, executable) {
@@ -16195,10 +16770,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
16195
16770
  }
16196
16771
  }
16197
16772
  function localMemoriesRoot(config) {
16198
- return (0, import_node_path15.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
16773
+ return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
16199
16774
  }
16200
16775
  function localResourcesRoot(config) {
16201
- return (0, import_node_path15.join)(config.agentContextHome, "data", "viking", config.account, "resources");
16776
+ return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "resources");
16202
16777
  }
16203
16778
  function localPathForMemoryUri(config, uri) {
16204
16779
  const prefix = `viking://user/${uriSegment(config.user)}/memories`;
@@ -16210,17 +16785,17 @@ function localPathForMemoryUri(config, uri) {
16210
16785
  if (segments.some((segment) => segment === "." || segment === "..")) {
16211
16786
  return void 0;
16212
16787
  }
16213
- return (0, import_node_path15.join)(localMemoriesRoot(config), ...segments);
16788
+ return (0, import_node_path16.join)(localMemoriesRoot(config), ...segments);
16214
16789
  }
16215
16790
  function isMissingPathError(err) {
16216
16791
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
16217
16792
  }
16218
16793
  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("..")) {
16794
+ const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
16795
+ if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
16221
16796
  throw new Error(`Path is outside the memories tree: ${path2}`);
16222
16797
  }
16223
- return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path15.sep).join("/")}`;
16798
+ return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path16.sep).join("/")}`;
16224
16799
  }
16225
16800
  async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
16226
16801
  const prefix = "viking://";
@@ -16414,7 +16989,10 @@ async function main() {
16414
16989
  ).option("--preserve-memories", "Preserve THREADNOTE_HOME and OpenViking memories (default)").option("--erase-memories", "Delete THREADNOTE_HOME, including all OpenViking memories").action(async (options) => {
16415
16990
  await runUninstall(getRuntimeConfig(program2), options);
16416
16991
  });
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(
16992
+ 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(
16993
+ "--graph",
16994
+ "Also seed a per-project .graph.md dependency-facts resource (package.json/go.mod), with [[project]] cross-repo edges"
16995
+ ).option("--manifest <path>", "Manifest path for this seed run").option(
16418
16996
  "--only <project>",
16419
16997
  "Restrict seeding to one or more manifest projects by name; repeat for multiple",
16420
16998
  collectOption,
@@ -16460,9 +17038,19 @@ async function main() {
16460
17038
  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
17039
  await runMigrateLifecycle(getRuntimeConfig(program2), options);
16462
17040
  });
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) => {
17041
+ 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(
17042
+ "--workset <name>",
17043
+ "Recall across a named seed-manifest workset (a set of related repos) as one working set"
17044
+ ).action(async (options) => {
16464
17045
  await runRecall(getRuntimeConfig(program2), options);
16465
17046
  });
17047
+ const workset = program2.command("workset").description("Inspect seed-manifest worksets (named sets of related repos recalled as one working set)");
17048
+ workset.command("list").description("List worksets defined in the seed manifest").action(async () => {
17049
+ await runWorksetList(getRuntimeConfig(program2));
17050
+ });
17051
+ workset.command("show").description("Show the member projects of a workset").argument("<name>", "Workset name").action(async (name) => {
17052
+ await runWorksetShow(getRuntimeConfig(program2), name);
17053
+ });
16466
17054
  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
17055
  await runCompact(getRuntimeConfig(program2), options);
16468
17056
  });
@@ -16472,8 +17060,13 @@ async function main() {
16472
17060
  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
17061
  await runList(getRuntimeConfig(program2), uri, options);
16474
17062
  });
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);
17063
+ 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(
17064
+ "--reference <uri>",
17065
+ "viking:// memory to record as one-way read-only prior context; repeat for multiple",
17066
+ collectOption,
17067
+ []
17068
+ ).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) => {
17069
+ await runHandoff(getRuntimeConfig(program2), { ...options, references: options.reference });
16477
17070
  });
16478
17071
  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
17072
  await runArchive(getRuntimeConfig(program2), uri, options);