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