threadnote 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/seed-manifest.example.yaml +9 -0
- package/dist/mcp_server.cjs +180 -8
- package/dist/threadnote.cjs +774 -151
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3447,7 +3447,7 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3447
3447
|
];
|
|
3448
3448
|
|
|
3449
3449
|
// src/hooks.ts
|
|
3450
|
-
var
|
|
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(/[
|
|
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
|
-
|
|
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
|
|
10307
|
-
return
|
|
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
|
|
11936
|
-
return
|
|
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
|
|
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,
|
|
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,
|
|
12433
|
-
await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
12526
|
-
await (0,
|
|
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,
|
|
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
|
|
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,
|
|
13211
|
+
const statePath = (0, import_node_path13.join)(config.agentContextHome, SEED_STATE_FILE);
|
|
12712
13212
|
const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
|
|
12713
13213
|
const projects = filterProjects(manifest.projects, options.only);
|
|
12714
13214
|
let importedCount = 0;
|
|
@@ -12760,6 +13260,70 @@ async function runSeed(config, options) {
|
|
|
12760
13260
|
console.log(
|
|
12761
13261
|
`Seed complete: ${importedCount} candidate(s), ${unchangedCount} unchanged, ${skippedCount} skipped for safety.`
|
|
12762
13262
|
);
|
|
13263
|
+
if (options.graph === true) {
|
|
13264
|
+
await seedDependencyGraphs(config, ov, manifest, projects, options.dryRun === true);
|
|
13265
|
+
}
|
|
13266
|
+
}
|
|
13267
|
+
async function seedDependencyGraphs(config, ov, manifest, targetProjects, dryRun) {
|
|
13268
|
+
const factsByProject = /* @__PURE__ */ new Map();
|
|
13269
|
+
for (const project of manifest.projects) {
|
|
13270
|
+
const projectRoot = expandPath(project.path);
|
|
13271
|
+
if (!await exists(projectRoot)) {
|
|
13272
|
+
continue;
|
|
13273
|
+
}
|
|
13274
|
+
factsByProject.set(project.name, await extractDependencyFacts(projectRoot));
|
|
13275
|
+
}
|
|
13276
|
+
const projectByPublishedName = /* @__PURE__ */ new Map();
|
|
13277
|
+
for (const [name, facts] of factsByProject) {
|
|
13278
|
+
if (facts.publishedName) {
|
|
13279
|
+
projectByPublishedName.set(facts.publishedName.toLowerCase(), name);
|
|
13280
|
+
}
|
|
13281
|
+
}
|
|
13282
|
+
let written = 0;
|
|
13283
|
+
let skipped = 0;
|
|
13284
|
+
for (const project of targetProjects) {
|
|
13285
|
+
const facts = factsByProject.get(project.name);
|
|
13286
|
+
if (!facts || facts.manifestFiles.length === 0) {
|
|
13287
|
+
continue;
|
|
13288
|
+
}
|
|
13289
|
+
const { externalCount, internalEdges } = resolveGraphEdges(project.name, facts.dependencies, projectByPublishedName);
|
|
13290
|
+
const document = buildGraphDocument({ externalCount, facts, internalEdges, projectName: project.name });
|
|
13291
|
+
const secretMatches = detectSecretMatches(document);
|
|
13292
|
+
if (secretMatches.length > 0) {
|
|
13293
|
+
skipped += 1;
|
|
13294
|
+
console.log(
|
|
13295
|
+
`SKIP ${project.name}/.graph.md: possible secret (${secretMatches.slice(0, MAX_SECRET_MATCHES_TO_PRINT).join(", ")})`
|
|
13296
|
+
);
|
|
13297
|
+
continue;
|
|
13298
|
+
}
|
|
13299
|
+
const destinationUri = `${trimTrailingSlash(project.uri)}/.graph.md`;
|
|
13300
|
+
if (dryRun) {
|
|
13301
|
+
console.log(`Would seed dependency facts: ${destinationUri} (${internalEdges.length} in-workspace edge(s))`);
|
|
13302
|
+
written += 1;
|
|
13303
|
+
continue;
|
|
13304
|
+
}
|
|
13305
|
+
const graphPath = (0, import_node_path13.join)(config.agentContextHome, "graph", graphCacheFileName(project.name));
|
|
13306
|
+
await ensureDirectory((0, import_node_path13.dirname)(graphPath), false);
|
|
13307
|
+
await (0, import_promises11.writeFile)(graphPath, document, { encoding: "utf8", mode: 384 });
|
|
13308
|
+
await (0, import_promises11.chmod)(graphPath, 384);
|
|
13309
|
+
await maybeRun(
|
|
13310
|
+
false,
|
|
13311
|
+
ov,
|
|
13312
|
+
withIdentity(config, [
|
|
13313
|
+
"add-resource",
|
|
13314
|
+
graphPath,
|
|
13315
|
+
"--to",
|
|
13316
|
+
destinationUri,
|
|
13317
|
+
"--reason",
|
|
13318
|
+
`Dependency facts for ${project.name}`,
|
|
13319
|
+
"--wait"
|
|
13320
|
+
])
|
|
13321
|
+
);
|
|
13322
|
+
written += 1;
|
|
13323
|
+
}
|
|
13324
|
+
console.log(
|
|
13325
|
+
`Dependency graph seed complete: ${written} .graph.md resource(s)${skipped > 0 ? `, ${skipped} skipped for safety` : ""}.`
|
|
13326
|
+
);
|
|
12763
13327
|
}
|
|
12764
13328
|
function filterProjects(projects, only) {
|
|
12765
13329
|
if (!only || only.length === 0) {
|
|
@@ -12776,7 +13340,7 @@ function filterProjects(projects, only) {
|
|
|
12776
13340
|
}
|
|
12777
13341
|
async function statSeedFile(path2) {
|
|
12778
13342
|
try {
|
|
12779
|
-
const result = await (0,
|
|
13343
|
+
const result = await (0, import_promises11.stat)(path2);
|
|
12780
13344
|
return { mtimeMs: result.mtimeMs, size: result.size };
|
|
12781
13345
|
} catch (_err) {
|
|
12782
13346
|
return void 0;
|
|
@@ -12784,7 +13348,7 @@ async function statSeedFile(path2) {
|
|
|
12784
13348
|
}
|
|
12785
13349
|
async function readSeedState(path2) {
|
|
12786
13350
|
try {
|
|
12787
|
-
const raw = await (0,
|
|
13351
|
+
const raw = await (0, import_promises11.readFile)(path2, "utf8");
|
|
12788
13352
|
const parsed = JSON.parse(raw);
|
|
12789
13353
|
if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
|
|
12790
13354
|
return { files: {}, version: 1 };
|
|
@@ -12801,13 +13365,13 @@ async function readSeedState(path2) {
|
|
|
12801
13365
|
}
|
|
12802
13366
|
}
|
|
12803
13367
|
async function writeSeedState(path2, state) {
|
|
12804
|
-
await ensureDirectory((0,
|
|
12805
|
-
await (0,
|
|
13368
|
+
await ensureDirectory((0, import_node_path13.dirname)(path2), false);
|
|
13369
|
+
await (0, import_promises11.writeFile)(path2, `${JSON.stringify(state, void 0, 2)}
|
|
12806
13370
|
`, { encoding: "utf8", mode: 384 });
|
|
12807
13371
|
}
|
|
12808
13372
|
async function runInitManifest(config, options) {
|
|
12809
13373
|
const manifestPath = expandPath(
|
|
12810
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
13374
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path13.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
12811
13375
|
);
|
|
12812
13376
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
12813
13377
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -12844,20 +13408,58 @@ async function runInitManifest(config, options) {
|
|
|
12844
13408
|
uri: existingManifest.futureMonorepo.uri
|
|
12845
13409
|
};
|
|
12846
13410
|
}
|
|
13411
|
+
if (existingManifest?.worksets) {
|
|
13412
|
+
outputManifest.worksets = existingManifest.worksets.map((workset) => ({
|
|
13413
|
+
name: workset.name,
|
|
13414
|
+
...workset.description !== void 0 ? { description: workset.description } : {},
|
|
13415
|
+
projects: [...workset.projects]
|
|
13416
|
+
}));
|
|
13417
|
+
}
|
|
12847
13418
|
const output2 = index_vite_proxy_tmp_default.dump(outputManifest, { lineWidth: 120, noRefs: true });
|
|
12848
13419
|
if (options.dryRun === true) {
|
|
12849
13420
|
console.log(`# Would write ${manifestPath}`);
|
|
12850
13421
|
console.log(output2.trimEnd());
|
|
12851
13422
|
return;
|
|
12852
13423
|
}
|
|
12853
|
-
await ensureDirectory((0,
|
|
12854
|
-
await (0,
|
|
12855
|
-
await (0,
|
|
13424
|
+
await ensureDirectory((0, import_node_path13.dirname)(manifestPath), false);
|
|
13425
|
+
await (0, import_promises11.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
13426
|
+
await (0, import_promises11.chmod)(manifestPath, 384);
|
|
12856
13427
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
12857
13428
|
console.log("Seed with:");
|
|
12858
13429
|
console.log(" threadnote seed --dry-run");
|
|
12859
13430
|
console.log(" threadnote seed");
|
|
12860
13431
|
}
|
|
13432
|
+
async function runWorksetList(config) {
|
|
13433
|
+
const manifest = await readSeedManifest(config.manifestPath);
|
|
13434
|
+
const worksets = manifest.worksets ?? [];
|
|
13435
|
+
if (worksets.length === 0) {
|
|
13436
|
+
console.log(
|
|
13437
|
+
"No worksets defined. Add a top-level `worksets:` list to the seed manifest to group related projects."
|
|
13438
|
+
);
|
|
13439
|
+
return;
|
|
13440
|
+
}
|
|
13441
|
+
console.log(`Worksets (${worksets.length}):`);
|
|
13442
|
+
for (const workset of worksets) {
|
|
13443
|
+
const summary = workset.description ? ` \u2014 ${workset.description}` : "";
|
|
13444
|
+
console.log(`- ${workset.name} (${workset.projects.length} project(s))${summary}`);
|
|
13445
|
+
}
|
|
13446
|
+
}
|
|
13447
|
+
async function runWorksetShow(config, name) {
|
|
13448
|
+
const manifest = await readSeedManifest(config.manifestPath);
|
|
13449
|
+
const workset = manifest.worksets?.find((entry) => entry.name.toLowerCase() === name.toLowerCase());
|
|
13450
|
+
if (!workset) {
|
|
13451
|
+
throw new Error(`No workset named "${name}" in ${config.manifestPath}.`);
|
|
13452
|
+
}
|
|
13453
|
+
console.log(`Workset: ${workset.name}`);
|
|
13454
|
+
if (workset.description) {
|
|
13455
|
+
console.log(workset.description);
|
|
13456
|
+
}
|
|
13457
|
+
console.log("Projects:");
|
|
13458
|
+
for (const memberName of workset.projects) {
|
|
13459
|
+
const project = manifest.projects.find((entry) => entry.name.toLowerCase() === memberName.toLowerCase());
|
|
13460
|
+
console.log(project ? `- ${project.name} (${project.uri})` : `- ${memberName} [not found in manifest projects]`);
|
|
13461
|
+
}
|
|
13462
|
+
}
|
|
12861
13463
|
async function runSeedSkills(config, options) {
|
|
12862
13464
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
12863
13465
|
const catalogItems = await collectSkillCandidates(config);
|
|
@@ -12898,13 +13500,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
12898
13500
|
async function projectIdentity(path2) {
|
|
12899
13501
|
const expanded = expandPath(path2);
|
|
12900
13502
|
try {
|
|
12901
|
-
return await (0,
|
|
13503
|
+
return await (0, import_promises11.realpath)(expanded);
|
|
12902
13504
|
} catch (_err) {
|
|
12903
13505
|
return expanded;
|
|
12904
13506
|
}
|
|
12905
13507
|
}
|
|
12906
13508
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
12907
|
-
const baseName = uriSegment((0,
|
|
13509
|
+
const baseName = uriSegment((0, import_node_path13.basename)(repoRoot));
|
|
12908
13510
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
12909
13511
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
12910
13512
|
let name = baseName;
|
|
@@ -12926,7 +13528,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
12926
13528
|
for (const pattern of project.seed) {
|
|
12927
13529
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
12928
13530
|
for (const filePath of files) {
|
|
12929
|
-
const relativePath = toPosixPath((0,
|
|
13531
|
+
const relativePath = toPosixPath((0, import_node_path13.relative)(projectRoot, filePath));
|
|
12930
13532
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
12931
13533
|
continue;
|
|
12932
13534
|
}
|
|
@@ -12944,20 +13546,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
12944
13546
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
12945
13547
|
const normalizedPattern = toPosixPath(pattern);
|
|
12946
13548
|
if (!hasGlob(normalizedPattern)) {
|
|
12947
|
-
const filePath = (0,
|
|
13549
|
+
const filePath = (0, import_node_path13.join)(projectRoot, normalizedPattern);
|
|
12948
13550
|
return await isFile(filePath) ? [filePath] : [];
|
|
12949
13551
|
}
|
|
12950
13552
|
const globBase = getGlobBase(normalizedPattern);
|
|
12951
|
-
const basePath = (0,
|
|
13553
|
+
const basePath = (0, import_node_path13.join)(projectRoot, globBase);
|
|
12952
13554
|
if (!await exists(basePath)) {
|
|
12953
13555
|
return [];
|
|
12954
13556
|
}
|
|
12955
13557
|
const regex = globToRegExp(normalizedPattern);
|
|
12956
13558
|
const files = await walkFiles(basePath);
|
|
12957
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
13559
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path13.relative)(projectRoot, filePath))));
|
|
12958
13560
|
}
|
|
12959
13561
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
12960
|
-
const content = await (0,
|
|
13562
|
+
const content = await (0, import_promises11.readFile)(candidate.filePath, "utf8");
|
|
12961
13563
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
12962
13564
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
12963
13565
|
if (secretMatches.length > 0) {
|
|
@@ -12969,21 +13571,24 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
12969
13571
|
if (redactedContent === content) {
|
|
12970
13572
|
return candidate.filePath;
|
|
12971
13573
|
}
|
|
12972
|
-
const redactedPath = (0,
|
|
13574
|
+
const redactedPath = (0, import_node_path13.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
12973
13575
|
if (dryRun) {
|
|
12974
13576
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
12975
13577
|
return redactedPath;
|
|
12976
13578
|
}
|
|
12977
|
-
await ensureDirectory((0,
|
|
12978
|
-
await (0,
|
|
12979
|
-
await (0,
|
|
13579
|
+
await ensureDirectory((0, import_node_path13.dirname)(redactedPath), false);
|
|
13580
|
+
await (0, import_promises11.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
13581
|
+
await (0, import_promises11.chmod)(redactedPath, 384);
|
|
12980
13582
|
return redactedPath;
|
|
12981
13583
|
}
|
|
12982
13584
|
function seedResourceReason(candidate) {
|
|
12983
13585
|
return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
|
|
12984
13586
|
}
|
|
13587
|
+
function graphCacheFileName(projectName) {
|
|
13588
|
+
return `${uriSegment(projectName)}-${sha256(projectName).slice(0, 8)}.graph.md`;
|
|
13589
|
+
}
|
|
12985
13590
|
function skillResourceReason(skill) {
|
|
12986
|
-
return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0,
|
|
13591
|
+
return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path13.basename)(
|
|
12987
13592
|
skill.filePath
|
|
12988
13593
|
)}`;
|
|
12989
13594
|
}
|
|
@@ -13016,7 +13621,7 @@ async function collectSkillCandidates(config) {
|
|
|
13016
13621
|
for (const source of sources) {
|
|
13017
13622
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
13018
13623
|
for (const filePath of files) {
|
|
13019
|
-
const content = await (0,
|
|
13624
|
+
const content = await (0, import_promises11.readFile)(filePath, "utf8");
|
|
13020
13625
|
const matches = detectSecretMatches(content);
|
|
13021
13626
|
if (matches.length > 0) {
|
|
13022
13627
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -13050,16 +13655,16 @@ function skillResourceUri(skill) {
|
|
|
13050
13655
|
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
|
|
13051
13656
|
}
|
|
13052
13657
|
function skillResourceName(skill) {
|
|
13053
|
-
const fileName = (0,
|
|
13658
|
+
const fileName = (0, import_node_path13.basename)(skill.filePath);
|
|
13054
13659
|
if (fileName.toLowerCase() === "skill.md") {
|
|
13055
|
-
return uriSegment((0,
|
|
13660
|
+
return uriSegment((0, import_node_path13.basename)((0, import_node_path13.dirname)(skill.filePath)));
|
|
13056
13661
|
}
|
|
13057
13662
|
const extensionIndex = fileName.lastIndexOf(".");
|
|
13058
13663
|
const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
|
|
13059
13664
|
return uriSegment(stem);
|
|
13060
13665
|
}
|
|
13061
13666
|
async function loadIgnorePatterns() {
|
|
13062
|
-
const raw = await (0,
|
|
13667
|
+
const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
13063
13668
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
13064
13669
|
}
|
|
13065
13670
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -13132,18 +13737,18 @@ function detectSecretMatches(content) {
|
|
|
13132
13737
|
// src/lifecycle.ts
|
|
13133
13738
|
var import_node_child_process4 = require("node:child_process");
|
|
13134
13739
|
var import_node_fs7 = require("node:fs");
|
|
13135
|
-
var
|
|
13740
|
+
var import_promises14 = require("node:fs/promises");
|
|
13136
13741
|
var import_node_os6 = require("node:os");
|
|
13137
|
-
var
|
|
13742
|
+
var import_node_path15 = require("node:path");
|
|
13138
13743
|
var import_node_process4 = require("node:process");
|
|
13139
|
-
var
|
|
13744
|
+
var import_promises15 = require("node:readline/promises");
|
|
13140
13745
|
|
|
13141
13746
|
// src/update.ts
|
|
13142
13747
|
var import_node_fs6 = require("node:fs");
|
|
13143
|
-
var
|
|
13748
|
+
var import_promises12 = require("node:fs/promises");
|
|
13144
13749
|
var import_node_os5 = require("node:os");
|
|
13145
|
-
var
|
|
13146
|
-
var
|
|
13750
|
+
var import_node_path14 = require("node:path");
|
|
13751
|
+
var import_promises13 = require("node:readline/promises");
|
|
13147
13752
|
var import_node_process3 = require("node:process");
|
|
13148
13753
|
|
|
13149
13754
|
// src/release_notes.ts
|
|
@@ -13497,14 +14102,14 @@ async function waitForOpenVikingPortClosed(config, timeoutMs) {
|
|
|
13497
14102
|
return false;
|
|
13498
14103
|
}
|
|
13499
14104
|
function launchAgentPlistPath() {
|
|
13500
|
-
return (0,
|
|
14105
|
+
return (0, import_node_path14.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
|
|
13501
14106
|
}
|
|
13502
14107
|
async function isLaunchAgentInstalled() {
|
|
13503
14108
|
if (process.platform !== "darwin") {
|
|
13504
14109
|
return false;
|
|
13505
14110
|
}
|
|
13506
14111
|
try {
|
|
13507
|
-
await (0,
|
|
14112
|
+
await (0, import_promises12.access)(launchAgentPlistPath(), import_node_fs6.constants.F_OK);
|
|
13508
14113
|
return true;
|
|
13509
14114
|
} catch (_err) {
|
|
13510
14115
|
return false;
|
|
@@ -13575,11 +14180,11 @@ async function readFreshCache(config, registry) {
|
|
|
13575
14180
|
}
|
|
13576
14181
|
async function writeUpdateCache2(config, cache) {
|
|
13577
14182
|
await ensureDirectory(config.agentContextHome, false);
|
|
13578
|
-
await (0,
|
|
14183
|
+
await (0, import_promises12.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
13579
14184
|
`, { encoding: "utf8", mode: 384 });
|
|
13580
14185
|
}
|
|
13581
14186
|
function updateCachePath(config) {
|
|
13582
|
-
return (0,
|
|
14187
|
+
return (0, import_node_path14.join)(config.agentContextHome, "update-check.json");
|
|
13583
14188
|
}
|
|
13584
14189
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
13585
14190
|
const state = await readPostUpdateState(config);
|
|
@@ -13643,7 +14248,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
13643
14248
|
return applicable;
|
|
13644
14249
|
}
|
|
13645
14250
|
async function readPostUpdateMigrations() {
|
|
13646
|
-
const raw = await readFileIfExists((0,
|
|
14251
|
+
const raw = await readFileIfExists((0, import_node_path14.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
13647
14252
|
if (!raw) {
|
|
13648
14253
|
return [];
|
|
13649
14254
|
}
|
|
@@ -13682,7 +14287,7 @@ function printPostUpdateMigration(migration) {
|
|
|
13682
14287
|
}
|
|
13683
14288
|
}
|
|
13684
14289
|
async function confirmPostUpdateMigration(prompt, defaultYes = false) {
|
|
13685
|
-
const readline = (0,
|
|
14290
|
+
const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
|
|
13686
14291
|
try {
|
|
13687
14292
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
13688
14293
|
if (answer === "") {
|
|
@@ -13717,11 +14322,11 @@ async function readPostUpdateState(config) {
|
|
|
13717
14322
|
}
|
|
13718
14323
|
async function writePostUpdateState(config, state) {
|
|
13719
14324
|
await ensureDirectory(config.agentContextHome, false);
|
|
13720
|
-
await (0,
|
|
14325
|
+
await (0, import_promises12.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
13721
14326
|
`, { encoding: "utf8", mode: 384 });
|
|
13722
14327
|
}
|
|
13723
14328
|
function postUpdateStatePath(config) {
|
|
13724
|
-
return (0,
|
|
14329
|
+
return (0, import_node_path14.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
13725
14330
|
}
|
|
13726
14331
|
async function resolveUpdateRuntime(runtime) {
|
|
13727
14332
|
if (runtime !== "auto") {
|
|
@@ -13751,14 +14356,14 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
13751
14356
|
if (runtime === "npm") {
|
|
13752
14357
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
13753
14358
|
const prefix = result.stdout.trim();
|
|
13754
|
-
return prefix ? (0,
|
|
14359
|
+
return prefix ? (0, import_node_path14.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
13755
14360
|
}
|
|
13756
14361
|
if (runtime === "bun") {
|
|
13757
14362
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
13758
14363
|
const binDir = result.stdout.trim();
|
|
13759
|
-
return binDir ? (0,
|
|
14364
|
+
return binDir ? (0, import_node_path14.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
13760
14365
|
}
|
|
13761
|
-
return (0,
|
|
14366
|
+
return (0, import_node_path14.join)(process.env.DENO_INSTALL ?? (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
13762
14367
|
}
|
|
13763
14368
|
function updatePackageCommand(runtime, registry) {
|
|
13764
14369
|
if (runtime === "npm") {
|
|
@@ -13831,9 +14436,9 @@ async function collectDoctorChecks(config, options = {}) {
|
|
|
13831
14436
|
checks.push(await manifestCheck(config.manifestPath));
|
|
13832
14437
|
checks.push(await recallIndexFreshnessCheck(config));
|
|
13833
14438
|
checks.push(await memoryProjectConsistencyCheck(config));
|
|
13834
|
-
checks.push(await fileCheck((0,
|
|
13835
|
-
checks.push(await fileCheck((0,
|
|
13836
|
-
checks.push(await fileCheck((0,
|
|
14439
|
+
checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
14440
|
+
checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
14441
|
+
checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
13837
14442
|
checks.push(await healthCheck(config));
|
|
13838
14443
|
return checks;
|
|
13839
14444
|
}
|
|
@@ -13841,9 +14446,9 @@ async function runInstall(config, options) {
|
|
|
13841
14446
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
13842
14447
|
const dryRun = options.dryRun === true;
|
|
13843
14448
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
13844
|
-
await ensureDirectory((0,
|
|
13845
|
-
await ensureDirectory((0,
|
|
13846
|
-
await ensureDirectory((0,
|
|
14449
|
+
await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "logs"), dryRun);
|
|
14450
|
+
await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "redacted"), dryRun);
|
|
14451
|
+
await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "mcp"), dryRun);
|
|
13847
14452
|
await installCommandShim(dryRun);
|
|
13848
14453
|
await installUserAgentInstructions(dryRun);
|
|
13849
14454
|
const serverPath = await findOpenVikingServer();
|
|
@@ -13888,17 +14493,17 @@ async function runInstall(config, options) {
|
|
|
13888
14493
|
}
|
|
13889
14494
|
await writeTemplateIfMissing({
|
|
13890
14495
|
config,
|
|
13891
|
-
destinationPath: (0,
|
|
14496
|
+
destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ov.conf"),
|
|
13892
14497
|
dryRun,
|
|
13893
14498
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
13894
|
-
templatePath: (0,
|
|
14499
|
+
templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
13895
14500
|
});
|
|
13896
14501
|
await writeTemplateIfMissing({
|
|
13897
14502
|
config,
|
|
13898
|
-
destinationPath: (0,
|
|
14503
|
+
destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ovcli.conf"),
|
|
13899
14504
|
dryRun,
|
|
13900
14505
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
13901
|
-
templatePath: (0,
|
|
14506
|
+
templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
13902
14507
|
});
|
|
13903
14508
|
await configureOpenVikingCliLanguage(config, dryRun);
|
|
13904
14509
|
if (options.start !== false) {
|
|
@@ -13955,7 +14560,7 @@ async function runUninstall(config, options) {
|
|
|
13955
14560
|
}
|
|
13956
14561
|
console.log("Uninstalling local Threadnote setup.");
|
|
13957
14562
|
await runStop(config, { dryRun });
|
|
13958
|
-
await removePathIfExists((0,
|
|
14563
|
+
await removePathIfExists((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
13959
14564
|
await removeLaunchAgent(dryRun);
|
|
13960
14565
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
13961
14566
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -14012,16 +14617,16 @@ async function repairManifest(config, dryRun) {
|
|
|
14012
14617
|
console.log(output2.trimEnd());
|
|
14013
14618
|
return;
|
|
14014
14619
|
}
|
|
14015
|
-
await ensureDirectory((0,
|
|
14620
|
+
await ensureDirectory((0, import_node_path15.dirname)(config.manifestPath), false);
|
|
14016
14621
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
14017
14622
|
if (currentContent !== void 0) {
|
|
14018
14623
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
14019
|
-
await (0,
|
|
14020
|
-
await (0,
|
|
14624
|
+
await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
14625
|
+
await (0, import_promises14.chmod)(backupPath, 384);
|
|
14021
14626
|
console.log(`Backup: ${backupPath}`);
|
|
14022
14627
|
}
|
|
14023
|
-
await (0,
|
|
14024
|
-
await (0,
|
|
14628
|
+
await (0, import_promises14.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
14629
|
+
await (0, import_promises14.chmod)(config.manifestPath, 384);
|
|
14025
14630
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
14026
14631
|
}
|
|
14027
14632
|
async function repairRecallIndex(config, dryRun) {
|
|
@@ -14096,7 +14701,7 @@ async function findOpenVikingServer() {
|
|
|
14096
14701
|
return onPath;
|
|
14097
14702
|
}
|
|
14098
14703
|
for (const candidateDir of await openVikingServerCandidateDirs()) {
|
|
14099
|
-
const candidate = (0,
|
|
14704
|
+
const candidate = (0, import_node_path15.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
|
|
14100
14705
|
if (await isExecutable(candidate)) {
|
|
14101
14706
|
return candidate;
|
|
14102
14707
|
}
|
|
@@ -14142,7 +14747,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
|
|
|
14142
14747
|
if (onPath) {
|
|
14143
14748
|
return;
|
|
14144
14749
|
}
|
|
14145
|
-
const binDir = (0,
|
|
14750
|
+
const binDir = (0, import_node_path15.dirname)(serverPath);
|
|
14146
14751
|
const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
|
|
14147
14752
|
console.log(
|
|
14148
14753
|
`Note: ${serverPath} is installed but ${binDir} is not on this shell's PATH. Add \`export PATH="${binDir}:$PATH"\` to ${rcHint} so other tools can find openviking-server.`
|
|
@@ -14186,7 +14791,7 @@ async function runStart(config, options) {
|
|
|
14186
14791
|
);
|
|
14187
14792
|
}
|
|
14188
14793
|
const logPath = openVikingLogPath(config);
|
|
14189
|
-
await ensureDirectory((0,
|
|
14794
|
+
await ensureDirectory((0, import_node_path15.dirname)(logPath), false);
|
|
14190
14795
|
if (options.foreground === true) {
|
|
14191
14796
|
const result = await runInteractive(server, args);
|
|
14192
14797
|
process.exitCode = result;
|
|
@@ -14195,7 +14800,7 @@ async function runStart(config, options) {
|
|
|
14195
14800
|
const logFd = (0, import_node_fs7.openSync)(logPath, "a");
|
|
14196
14801
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
14197
14802
|
child.unref();
|
|
14198
|
-
await (0,
|
|
14803
|
+
await (0, import_promises14.writeFile)((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
14199
14804
|
`, "utf8");
|
|
14200
14805
|
const health = await waitForOpenVikingHealth(
|
|
14201
14806
|
config,
|
|
@@ -14232,7 +14837,7 @@ async function runStop(config, options) {
|
|
|
14232
14837
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
14233
14838
|
}
|
|
14234
14839
|
}
|
|
14235
|
-
const pidPath = (0,
|
|
14840
|
+
const pidPath = (0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid");
|
|
14236
14841
|
const pidText = await readFileIfExists(pidPath);
|
|
14237
14842
|
if (!pidText) {
|
|
14238
14843
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -14278,7 +14883,7 @@ async function openVikingServerCheck() {
|
|
|
14278
14883
|
}
|
|
14279
14884
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
14280
14885
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
14281
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
14886
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
|
|
14282
14887
|
return {
|
|
14283
14888
|
name,
|
|
14284
14889
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -14319,7 +14924,7 @@ async function openVikingCliCheck() {
|
|
|
14319
14924
|
}
|
|
14320
14925
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
14321
14926
|
const onPath = await findExecutable(["ov", "openviking"]);
|
|
14322
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
14927
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
|
|
14323
14928
|
return {
|
|
14324
14929
|
name: "openviking cli",
|
|
14325
14930
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -14412,7 +15017,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
14412
15017
|
};
|
|
14413
15018
|
}
|
|
14414
15019
|
async function commandShimCheck() {
|
|
14415
|
-
const shimPath = (0,
|
|
15020
|
+
const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
14416
15021
|
const content = await readFileIfExists(shimPath);
|
|
14417
15022
|
if (content === void 0) {
|
|
14418
15023
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -14478,11 +15083,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
14478
15083
|
async function siblingPythonForExecutable(executablePath) {
|
|
14479
15084
|
let resolvedPath;
|
|
14480
15085
|
try {
|
|
14481
|
-
resolvedPath = await (0,
|
|
15086
|
+
resolvedPath = await (0, import_promises14.realpath)(executablePath);
|
|
14482
15087
|
} catch (_err) {
|
|
14483
15088
|
return void 0;
|
|
14484
15089
|
}
|
|
14485
|
-
const pythonPath = (0,
|
|
15090
|
+
const pythonPath = (0, import_node_path15.join)((0, import_node_path15.dirname)(resolvedPath), "python");
|
|
14486
15091
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
14487
15092
|
}
|
|
14488
15093
|
async function manifestCheck(path2) {
|
|
@@ -14525,7 +15130,7 @@ async function recallIndexFreshnessCheck(config) {
|
|
|
14525
15130
|
}
|
|
14526
15131
|
async function memoryProjectConsistencyCheck(config) {
|
|
14527
15132
|
const name = "memory project consistency";
|
|
14528
|
-
const memoriesRoot = (0,
|
|
15133
|
+
const memoriesRoot = (0, import_node_path15.join)(
|
|
14529
15134
|
config.agentContextHome,
|
|
14530
15135
|
"data",
|
|
14531
15136
|
"viking",
|
|
@@ -14537,7 +15142,7 @@ async function memoryProjectConsistencyCheck(config) {
|
|
|
14537
15142
|
try {
|
|
14538
15143
|
let entries;
|
|
14539
15144
|
try {
|
|
14540
|
-
entries = await (0,
|
|
15145
|
+
entries = await (0, import_promises14.readdir)(memoriesRoot, { recursive: true });
|
|
14541
15146
|
} catch {
|
|
14542
15147
|
return { name, status: "ok", detail: "no memories directory yet" };
|
|
14543
15148
|
}
|
|
@@ -14547,14 +15152,14 @@ async function memoryProjectConsistencyCheck(config) {
|
|
|
14547
15152
|
if (!entry.endsWith(".md") || isSummarySidecarUri(entry)) {
|
|
14548
15153
|
continue;
|
|
14549
15154
|
}
|
|
14550
|
-
const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(
|
|
15155
|
+
const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(import_node_path15.sep).join("/")}`;
|
|
14551
15156
|
const pathProject = memoryUriProjectSegment(uri);
|
|
14552
15157
|
if (!pathProject) {
|
|
14553
15158
|
continue;
|
|
14554
15159
|
}
|
|
14555
15160
|
let content;
|
|
14556
15161
|
try {
|
|
14557
|
-
content = await (0,
|
|
15162
|
+
content = await (0, import_promises14.readFile)((0, import_node_path15.join)(memoriesRoot, entry), "utf8");
|
|
14558
15163
|
} catch {
|
|
14559
15164
|
continue;
|
|
14560
15165
|
}
|
|
@@ -14809,7 +15414,7 @@ async function offerToInstallUv() {
|
|
|
14809
15414
|
);
|
|
14810
15415
|
return false;
|
|
14811
15416
|
}
|
|
14812
|
-
const readline = (0,
|
|
15417
|
+
const readline = (0, import_promises15.createInterface)({ input: import_node_process4.stdin, output: import_node_process4.stdout });
|
|
14813
15418
|
let answer;
|
|
14814
15419
|
try {
|
|
14815
15420
|
answer = (await readline.question(
|
|
@@ -14967,38 +15572,38 @@ function printInstallNextSteps(options) {
|
|
|
14967
15572
|
}
|
|
14968
15573
|
async function writeTemplateIfMissing(options) {
|
|
14969
15574
|
if (await exists(options.destinationPath)) {
|
|
14970
|
-
const currentContent = await (0,
|
|
15575
|
+
const currentContent = await (0, import_promises14.readFile)(options.destinationPath, "utf8");
|
|
14971
15576
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
14972
15577
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
14973
15578
|
return;
|
|
14974
15579
|
}
|
|
14975
|
-
const rendered2 = renderTemplate(await (0,
|
|
15580
|
+
const rendered2 = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
|
|
14976
15581
|
if (options.dryRun) {
|
|
14977
15582
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
14978
15583
|
return;
|
|
14979
15584
|
}
|
|
14980
15585
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
14981
|
-
await (0,
|
|
14982
|
-
await (0,
|
|
14983
|
-
await (0,
|
|
14984
|
-
await (0,
|
|
15586
|
+
await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
15587
|
+
await (0, import_promises14.chmod)(backupPath, 384);
|
|
15588
|
+
await (0, import_promises14.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
15589
|
+
await (0, import_promises14.chmod)(options.destinationPath, 384);
|
|
14985
15590
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
14986
15591
|
console.log(`Backup: ${backupPath}`);
|
|
14987
15592
|
return;
|
|
14988
15593
|
}
|
|
14989
|
-
const rendered = renderTemplate(await (0,
|
|
15594
|
+
const rendered = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
|
|
14990
15595
|
if (options.dryRun) {
|
|
14991
15596
|
console.log(`Would write ${options.destinationPath}`);
|
|
14992
15597
|
return;
|
|
14993
15598
|
}
|
|
14994
|
-
await ensureDirectory((0,
|
|
14995
|
-
await (0,
|
|
14996
|
-
await (0,
|
|
15599
|
+
await ensureDirectory((0, import_node_path15.dirname)(options.destinationPath), false);
|
|
15600
|
+
await (0, import_promises14.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
15601
|
+
await (0, import_promises14.chmod)(options.destinationPath, 384);
|
|
14997
15602
|
console.log(`Wrote ${options.destinationPath}`);
|
|
14998
15603
|
}
|
|
14999
15604
|
async function installCommandShim(dryRun) {
|
|
15000
15605
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
15001
|
-
const shimPath = (0,
|
|
15606
|
+
const shimPath = (0, import_node_path15.join)(binDir, "threadnote");
|
|
15002
15607
|
const existingContent = await readFileIfExists(shimPath);
|
|
15003
15608
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
15004
15609
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -15014,12 +15619,12 @@ async function installCommandShim(dryRun) {
|
|
|
15014
15619
|
return;
|
|
15015
15620
|
}
|
|
15016
15621
|
await ensureDirectory(binDir, false);
|
|
15017
|
-
await (0,
|
|
15018
|
-
await (0,
|
|
15622
|
+
await (0, import_promises14.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
15623
|
+
await (0, import_promises14.chmod)(shimPath, 493);
|
|
15019
15624
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
15020
15625
|
}
|
|
15021
15626
|
async function removeCommandShim(dryRun) {
|
|
15022
|
-
const shimPath = (0,
|
|
15627
|
+
const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
15023
15628
|
const content = await readFileIfExists(shimPath);
|
|
15024
15629
|
if (content === void 0) {
|
|
15025
15630
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -15053,8 +15658,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
15053
15658
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
15054
15659
|
continue;
|
|
15055
15660
|
}
|
|
15056
|
-
await ensureDirectory((0,
|
|
15057
|
-
await (0,
|
|
15661
|
+
await ensureDirectory((0, import_node_path15.dirname)(targetPath), false);
|
|
15662
|
+
await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
15058
15663
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
15059
15664
|
}
|
|
15060
15665
|
}
|
|
@@ -15091,7 +15696,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
15091
15696
|
console.log(`Would update ${targetPath}`);
|
|
15092
15697
|
continue;
|
|
15093
15698
|
}
|
|
15094
|
-
await (0,
|
|
15699
|
+
await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
15095
15700
|
console.log(`Updated ${targetPath}`);
|
|
15096
15701
|
}
|
|
15097
15702
|
}
|
|
@@ -15111,7 +15716,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
15111
15716
|
].join("\n");
|
|
15112
15717
|
}
|
|
15113
15718
|
async function renderUserAgentInstructionsBlock() {
|
|
15114
|
-
const instructions = (await (0,
|
|
15719
|
+
const instructions = (await (0, import_promises14.readFile)((0, import_node_path15.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
15115
15720
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
15116
15721
|
${instructions}
|
|
15117
15722
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -15195,9 +15800,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
15195
15800
|
`Cannot install LaunchAgent: ${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
|
|
15196
15801
|
);
|
|
15197
15802
|
}
|
|
15198
|
-
const source = (0,
|
|
15803
|
+
const source = (0, import_node_path15.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
15199
15804
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
15200
|
-
const rendered = renderTemplate(await (0,
|
|
15805
|
+
const rendered = renderTemplate(await (0, import_promises14.readFile)(source, "utf8"), config, {
|
|
15201
15806
|
OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
|
|
15202
15807
|
});
|
|
15203
15808
|
if (dryRun) {
|
|
@@ -15209,9 +15814,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
15209
15814
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
15210
15815
|
return;
|
|
15211
15816
|
}
|
|
15212
|
-
await ensureDirectory((0,
|
|
15213
|
-
await ensureDirectory((0,
|
|
15214
|
-
await (0,
|
|
15817
|
+
await ensureDirectory((0, import_node_path15.dirname)(destination), false);
|
|
15818
|
+
await ensureDirectory((0, import_node_path15.dirname)(openVikingLogPath(config)), false);
|
|
15819
|
+
await (0, import_promises14.writeFile)(destination, rendered, "utf8");
|
|
15215
15820
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
15216
15821
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
15217
15822
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -15278,7 +15883,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
15278
15883
|
if (typeof parsed.default_user !== "string") {
|
|
15279
15884
|
return false;
|
|
15280
15885
|
}
|
|
15281
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
15886
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path15.join)(config.agentContextHome, "data")) {
|
|
15282
15887
|
return false;
|
|
15283
15888
|
}
|
|
15284
15889
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -15340,9 +15945,9 @@ function printWhatsNew(whatsNew) {
|
|
|
15340
15945
|
// src/manager.ts
|
|
15341
15946
|
var import_node_http2 = require("node:http");
|
|
15342
15947
|
var import_node_crypto2 = require("node:crypto");
|
|
15343
|
-
var
|
|
15948
|
+
var import_promises16 = require("node:fs/promises");
|
|
15344
15949
|
var import_node_os7 = require("node:os");
|
|
15345
|
-
var
|
|
15950
|
+
var import_node_path16 = require("node:path");
|
|
15346
15951
|
var STATIC_FILES = {
|
|
15347
15952
|
"/": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
15348
15953
|
"/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
@@ -15413,18 +16018,18 @@ async function readManagedMemory(config, uri) {
|
|
|
15413
16018
|
if (!path2) {
|
|
15414
16019
|
throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
|
|
15415
16020
|
}
|
|
15416
|
-
const [content, pathStat] = await Promise.all([(0,
|
|
15417
|
-
const relativePath = (0,
|
|
16021
|
+
const [content, pathStat] = await Promise.all([(0, import_promises16.readFile)(path2, "utf8"), (0, import_promises16.stat)(path2)]);
|
|
16022
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2).split(import_node_path16.sep).join("/");
|
|
15418
16023
|
const record = parseMemoryDocument(uri, content);
|
|
15419
16024
|
return {
|
|
15420
16025
|
content,
|
|
15421
16026
|
node: {
|
|
15422
16027
|
isDir: false,
|
|
15423
16028
|
isShared: isInSharedNamespace(config, uri),
|
|
15424
|
-
isSystem: isSystemMemoryName(path2.split(
|
|
16029
|
+
isSystem: isSystemMemoryName(path2.split(import_node_path16.sep).at(-1) ?? ""),
|
|
15425
16030
|
metadata: record?.metadata,
|
|
15426
16031
|
modTime: pathStat.mtime.toISOString(),
|
|
15427
|
-
name: path2.split(
|
|
16032
|
+
name: path2.split(import_node_path16.sep).at(-1) ?? uri,
|
|
15428
16033
|
relativePath,
|
|
15429
16034
|
sharedTeam: sharedTeamNameForUri(config, uri),
|
|
15430
16035
|
size: pathStat.size,
|
|
@@ -15712,7 +16317,7 @@ async function handleRequest(context, request, response) {
|
|
|
15712
16317
|
}
|
|
15713
16318
|
async function serveStatic(context, url, response) {
|
|
15714
16319
|
const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
|
|
15715
|
-
const content = await (0,
|
|
16320
|
+
const content = await (0, import_promises16.readFile)((0, import_node_path16.join)(toolRoot(), file.root ?? "manager", file.path));
|
|
15716
16321
|
const headers = { "content-type": file.contentType };
|
|
15717
16322
|
if (file.root !== "docs") {
|
|
15718
16323
|
headers["cache-control"] = "no-store";
|
|
@@ -15721,11 +16326,11 @@ async function serveStatic(context, url, response) {
|
|
|
15721
16326
|
response.end(content);
|
|
15722
16327
|
}
|
|
15723
16328
|
async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
15724
|
-
const pathStat = await (0,
|
|
16329
|
+
const pathStat = await (0, import_promises16.stat)(path2);
|
|
15725
16330
|
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
|
|
15726
16331
|
const isDir = pathStat.isDirectory();
|
|
15727
16332
|
if (!isDir) {
|
|
15728
|
-
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0,
|
|
16333
|
+
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises16.readFile)(path2, "utf8").catch(() => ""));
|
|
15729
16334
|
return {
|
|
15730
16335
|
isDir: false,
|
|
15731
16336
|
isShared: isInSharedNamespace(config, uri),
|
|
@@ -15739,13 +16344,13 @@ async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
|
15739
16344
|
uri
|
|
15740
16345
|
};
|
|
15741
16346
|
}
|
|
15742
|
-
const entries = await (0,
|
|
16347
|
+
const entries = await (0, import_promises16.readdir)(path2, { withFileTypes: true });
|
|
15743
16348
|
const children = await Promise.all(
|
|
15744
16349
|
entries.sort(
|
|
15745
16350
|
(left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
|
|
15746
16351
|
).map((entry) => {
|
|
15747
16352
|
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
15748
|
-
return readTree(config, (0,
|
|
16353
|
+
return readTree(config, (0, import_node_path16.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
|
|
15749
16354
|
})
|
|
15750
16355
|
);
|
|
15751
16356
|
return {
|
|
@@ -15919,27 +16524,27 @@ async function removeManagedFolder(config, uri) {
|
|
|
15919
16524
|
if (!path2) {
|
|
15920
16525
|
throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
|
|
15921
16526
|
}
|
|
15922
|
-
const pathStat = await (0,
|
|
16527
|
+
const pathStat = await (0, import_promises16.stat)(path2);
|
|
15923
16528
|
if (!pathStat.isDirectory()) {
|
|
15924
16529
|
throw new Error(`Not a folder: ${uri}`);
|
|
15925
16530
|
}
|
|
15926
|
-
const relativePath = (0,
|
|
15927
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
16531
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
|
|
16532
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
|
|
15928
16533
|
throw new Error("Refusing to remove a folder outside the memories tree.");
|
|
15929
16534
|
}
|
|
15930
16535
|
const fileUris = await fileUrisUnderFolder(config, path2);
|
|
15931
16536
|
for (const fileUri of fileUris) {
|
|
15932
16537
|
await runForget(config, fileUri, {});
|
|
15933
16538
|
}
|
|
15934
|
-
await (0,
|
|
16539
|
+
await (0, import_promises16.rm)(path2, { force: true, recursive: true });
|
|
15935
16540
|
console.log(`Removed folder: ${uri}`);
|
|
15936
16541
|
console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
|
|
15937
16542
|
}
|
|
15938
16543
|
async function fileUrisUnderFolder(config, folderPath) {
|
|
15939
|
-
const entries = await (0,
|
|
16544
|
+
const entries = await (0, import_promises16.readdir)(folderPath, { withFileTypes: true });
|
|
15940
16545
|
const uris = [];
|
|
15941
16546
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
15942
|
-
const path2 = (0,
|
|
16547
|
+
const path2 = (0, import_node_path16.join)(folderPath, entry.name);
|
|
15943
16548
|
if (entry.isDirectory()) {
|
|
15944
16549
|
uris.push(...await fileUrisUnderFolder(config, path2));
|
|
15945
16550
|
} else if (entry.isFile()) {
|
|
@@ -16037,11 +16642,11 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
16037
16642
|
throw new Error(`${agent} executable was not found.`);
|
|
16038
16643
|
}
|
|
16039
16644
|
const prompt = consolidationPrompt(sources);
|
|
16040
|
-
const stagingDir = await (0,
|
|
16041
|
-
const promptPath = (0,
|
|
16645
|
+
const stagingDir = await (0, import_promises16.mkdtemp)((0, import_node_path16.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
|
|
16646
|
+
const promptPath = (0, import_node_path16.join)(stagingDir, "prompt.txt");
|
|
16042
16647
|
try {
|
|
16043
|
-
await (0,
|
|
16044
|
-
await (0,
|
|
16648
|
+
await (0, import_promises16.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
|
|
16649
|
+
await (0, import_promises16.chmod)(promptPath, 384);
|
|
16045
16650
|
const script = consolidationAgentScript(agent, executable);
|
|
16046
16651
|
const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
|
|
16047
16652
|
allowFailure: true,
|
|
@@ -16057,7 +16662,7 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
16057
16662
|
}
|
|
16058
16663
|
return draft;
|
|
16059
16664
|
} finally {
|
|
16060
|
-
await (0,
|
|
16665
|
+
await (0, import_promises16.rm)(stagingDir, { force: true, recursive: true });
|
|
16061
16666
|
}
|
|
16062
16667
|
}
|
|
16063
16668
|
function consolidationAgentScript(agent, executable) {
|
|
@@ -16195,10 +16800,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
|
|
|
16195
16800
|
}
|
|
16196
16801
|
}
|
|
16197
16802
|
function localMemoriesRoot(config) {
|
|
16198
|
-
return (0,
|
|
16803
|
+
return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
|
|
16199
16804
|
}
|
|
16200
16805
|
function localResourcesRoot(config) {
|
|
16201
|
-
return (0,
|
|
16806
|
+
return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "resources");
|
|
16202
16807
|
}
|
|
16203
16808
|
function localPathForMemoryUri(config, uri) {
|
|
16204
16809
|
const prefix = `viking://user/${uriSegment(config.user)}/memories`;
|
|
@@ -16210,17 +16815,17 @@ function localPathForMemoryUri(config, uri) {
|
|
|
16210
16815
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
16211
16816
|
return void 0;
|
|
16212
16817
|
}
|
|
16213
|
-
return (0,
|
|
16818
|
+
return (0, import_node_path16.join)(localMemoriesRoot(config), ...segments);
|
|
16214
16819
|
}
|
|
16215
16820
|
function isMissingPathError(err) {
|
|
16216
16821
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
16217
16822
|
}
|
|
16218
16823
|
function localPathToMemoryUri(config, path2) {
|
|
16219
|
-
const relativePath = (0,
|
|
16220
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
16824
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
|
|
16825
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
|
|
16221
16826
|
throw new Error(`Path is outside the memories tree: ${path2}`);
|
|
16222
16827
|
}
|
|
16223
|
-
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(
|
|
16828
|
+
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path16.sep).join("/")}`;
|
|
16224
16829
|
}
|
|
16225
16830
|
async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
|
|
16226
16831
|
const prefix = "viking://";
|
|
@@ -16414,7 +17019,10 @@ async function main() {
|
|
|
16414
17019
|
).option("--preserve-memories", "Preserve THREADNOTE_HOME and OpenViking memories (default)").option("--erase-memories", "Delete THREADNOTE_HOME, including all OpenViking memories").action(async (options) => {
|
|
16415
17020
|
await runUninstall(getRuntimeConfig(program2), options);
|
|
16416
17021
|
});
|
|
16417
|
-
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option(
|
|
17022
|
+
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option(
|
|
17023
|
+
"--graph",
|
|
17024
|
+
"Also seed a per-project .graph.md dependency-facts resource (package.json/go.mod), with [[project]] cross-repo edges"
|
|
17025
|
+
).option("--manifest <path>", "Manifest path for this seed run").option(
|
|
16418
17026
|
"--only <project>",
|
|
16419
17027
|
"Restrict seeding to one or more manifest projects by name; repeat for multiple",
|
|
16420
17028
|
collectOption,
|
|
@@ -16460,9 +17068,19 @@ async function main() {
|
|
|
16460
17068
|
program2.command("migrate-lifecycle").description("Move clear legacy handoff memories into lifecycle-aware archive paths").option("--apply", "Perform the migration; without this, prints a dry run").option("--dry-run", "Print migration actions without writing or removing memories").option("--limit <count>", "Maximum number of legacy handoffs to migrate").action(async (options) => {
|
|
16461
17069
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
16462
17070
|
});
|
|
16463
|
-
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").
|
|
17071
|
+
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").option(
|
|
17072
|
+
"--workset <name>",
|
|
17073
|
+
"Recall across a named seed-manifest workset (a set of related repos) as one working set"
|
|
17074
|
+
).action(async (options) => {
|
|
16464
17075
|
await runRecall(getRuntimeConfig(program2), options);
|
|
16465
17076
|
});
|
|
17077
|
+
const workset = program2.command("workset").description("Inspect seed-manifest worksets (named sets of related repos recalled as one working set)");
|
|
17078
|
+
workset.command("list").description("List worksets defined in the seed manifest").action(async () => {
|
|
17079
|
+
await runWorksetList(getRuntimeConfig(program2));
|
|
17080
|
+
});
|
|
17081
|
+
workset.command("show").description("Show the member projects of a workset").argument("<name>", "Workset name").action(async (name) => {
|
|
17082
|
+
await runWorksetShow(getRuntimeConfig(program2), name);
|
|
17083
|
+
});
|
|
16466
17084
|
program2.command("compact").description("Plan or apply scoped memory hygiene for active personal memories").requiredOption("--project <name>", "Project/repo namespace to inspect").option("--apply", "Apply the compact plan; without this, prints a dry run").option("--dry-run", "Print the compact plan without changing anything").option("--kind <kind>", "durable, handoff, or incident", parseCompactKind).option("--topic <name>", "Stable topic name to inspect").action(async (options) => {
|
|
16467
17085
|
await runCompact(getRuntimeConfig(program2), options);
|
|
16468
17086
|
});
|
|
@@ -16472,8 +17090,13 @@ async function main() {
|
|
|
16472
17090
|
program2.command("list").alias("ls").description("List a viking:// directory").argument("[uri]", "viking:// URI to list", "viking://").option("-a, --all", "Show hidden files such as .abstract.md and .overview.md").option("--dry-run", "Print ov command without listing").option("-n, --node-limit <count>", "Maximum number of nodes to list").option("-r, --recursive", "List subdirectories recursively").option("-s, --simple", "Print only paths").action(async (uri, options) => {
|
|
16473
17091
|
await runList(getRuntimeConfig(program2), uri, options);
|
|
16474
17092
|
});
|
|
16475
|
-
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--
|
|
16476
|
-
|
|
17093
|
+
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--ci <text>", "Captured CI status snapshot (free text; not a live status board)").option("--dry-run", "Print handoff without storing").option("--issue <text>", "Related issue reference (number or URL)").option("--next-step <text>", "Suggested next step").option("--pr <text>", "Related pull request reference (number or URL)").option("--project <name>", "Project/repo namespace; defaults to current repo basename").option(
|
|
17094
|
+
"--reference <uri>",
|
|
17095
|
+
"viking:// memory to record as one-way read-only prior context; repeat for multiple",
|
|
17096
|
+
collectOption,
|
|
17097
|
+
[]
|
|
17098
|
+
).option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").option("--timestamped", "Store a historical timestamped handoff instead of updating the current branch handoff").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
|
|
17099
|
+
await runHandoff(getRuntimeConfig(program2), { ...options, references: options.reference });
|
|
16477
17100
|
});
|
|
16478
17101
|
program2.command("archive").description("Move a memory into the archived lifecycle tree, then remove the original after the archive is stored").argument("<uri>", "viking:// memory URI to archive").option("--dry-run", "Print archive content and ov commands without changing anything").option("--kind <kind>", "durable, handoff, incident, preference, or smoke", parseMemoryKind).option("--project <name>", "Override inferred project/repo namespace").option("--topic <name>", "Override inferred topic").action(async (uri, options) => {
|
|
16479
17102
|
await runArchive(getRuntimeConfig(program2), uri, options);
|