threadnote 1.4.5 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/seed-manifest.example.yaml +9 -0
- package/dist/mcp_server.cjs +255 -16
- package/dist/threadnote.cjs +928 -155
- package/package.json +4 -3
package/dist/threadnote.cjs
CHANGED
|
@@ -3447,7 +3447,7 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3447
3447
|
];
|
|
3448
3448
|
|
|
3449
3449
|
// src/hooks.ts
|
|
3450
|
-
var
|
|
3450
|
+
var import_promises9 = require("node:fs/promises");
|
|
3451
3451
|
var import_node_path11 = require("node:path");
|
|
3452
3452
|
|
|
3453
3453
|
// src/mcp.ts
|
|
@@ -3663,7 +3663,7 @@ function hasGlob(path2) {
|
|
|
3663
3663
|
return path2.includes("*") || path2.includes("?");
|
|
3664
3664
|
}
|
|
3665
3665
|
function escapeRegExp(value) {
|
|
3666
|
-
return value.replace(/[
|
|
3666
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3667
3667
|
}
|
|
3668
3668
|
async function requiredOpenVikingCli() {
|
|
3669
3669
|
const command2 = await findOpenVikingCli();
|
|
@@ -4202,14 +4202,19 @@ function exactRecallTerms(query) {
|
|
|
4202
4202
|
"after",
|
|
4203
4203
|
"agent",
|
|
4204
4204
|
"anything",
|
|
4205
|
+
"been",
|
|
4205
4206
|
"branch",
|
|
4206
4207
|
"case",
|
|
4207
4208
|
"current",
|
|
4209
|
+
"does",
|
|
4208
4210
|
"durable",
|
|
4209
4211
|
"find",
|
|
4210
4212
|
"feature",
|
|
4211
4213
|
"features",
|
|
4214
|
+
"from",
|
|
4212
4215
|
"handoff",
|
|
4216
|
+
"have",
|
|
4217
|
+
"into",
|
|
4213
4218
|
"issue",
|
|
4214
4219
|
"issues",
|
|
4215
4220
|
"knowledge",
|
|
@@ -4223,11 +4228,22 @@ function exactRecallTerms(query) {
|
|
|
4223
4228
|
"related",
|
|
4224
4229
|
"search",
|
|
4225
4230
|
"stored",
|
|
4231
|
+
"than",
|
|
4232
|
+
"that",
|
|
4233
|
+
"them",
|
|
4234
|
+
"then",
|
|
4235
|
+
"they",
|
|
4226
4236
|
"this",
|
|
4227
4237
|
"the",
|
|
4238
|
+
"were",
|
|
4239
|
+
"what",
|
|
4240
|
+
"when",
|
|
4241
|
+
"which",
|
|
4242
|
+
"while",
|
|
4228
4243
|
"with",
|
|
4229
4244
|
"workspace",
|
|
4230
|
-
"worktree"
|
|
4245
|
+
"worktree",
|
|
4246
|
+
"your"
|
|
4231
4247
|
]);
|
|
4232
4248
|
const seen = /* @__PURE__ */ new Set();
|
|
4233
4249
|
const terms = [];
|
|
@@ -4261,6 +4277,34 @@ function exactRecallScopeIntents(query) {
|
|
|
4261
4277
|
function isSummarySidecarUri(uri) {
|
|
4262
4278
|
return /\.(?:overview|abstract)\.md(?:#|$)/.test(uri);
|
|
4263
4279
|
}
|
|
4280
|
+
function isAgentArtifactPackUri(uri) {
|
|
4281
|
+
return /\/agent-artifacts\/packs\//.test(uri);
|
|
4282
|
+
}
|
|
4283
|
+
function isExcludedRecallUri(uri) {
|
|
4284
|
+
return isSummarySidecarUri(uri) || isAgentArtifactPackUri(uri);
|
|
4285
|
+
}
|
|
4286
|
+
function memoryUriProjectSegment(uri) {
|
|
4287
|
+
const marker = "/memories/";
|
|
4288
|
+
const at = uri.indexOf(marker);
|
|
4289
|
+
if (at < 0) {
|
|
4290
|
+
return void 0;
|
|
4291
|
+
}
|
|
4292
|
+
let segments = stripAnchor(uri).slice(at + marker.length).split("/").filter(Boolean);
|
|
4293
|
+
if (segments[0] === "shared") {
|
|
4294
|
+
segments = segments.slice(2);
|
|
4295
|
+
}
|
|
4296
|
+
const [kind, , project, ...rest] = segments;
|
|
4297
|
+
if ((kind === "durable" || kind === "handoffs" || kind === "incidents") && project && rest.length > 0) {
|
|
4298
|
+
return project;
|
|
4299
|
+
}
|
|
4300
|
+
return void 0;
|
|
4301
|
+
}
|
|
4302
|
+
function memoryFrontmatterField(content, field) {
|
|
4303
|
+
const blankLine = content.indexOf("\n\n");
|
|
4304
|
+
const header = blankLine < 0 ? content : content.slice(0, blankLine);
|
|
4305
|
+
const match = header.match(new RegExp(`^${escapeRegExp(field)}:[ \\t]*(.+)$`, "m"));
|
|
4306
|
+
return match?.[1]?.trim() || void 0;
|
|
4307
|
+
}
|
|
4264
4308
|
function grepUrisFromJson(output2) {
|
|
4265
4309
|
const start = output2.search(/^\{/m);
|
|
4266
4310
|
if (start < 0) {
|
|
@@ -4275,7 +4319,7 @@ function grepUrisFromJson(output2) {
|
|
|
4275
4319
|
}
|
|
4276
4320
|
const uris = [];
|
|
4277
4321
|
for (const match of matches) {
|
|
4278
|
-
if (isJsonObject(match) && typeof match.uri === "string" && !
|
|
4322
|
+
if (isJsonObject(match) && typeof match.uri === "string" && !isExcludedRecallUri(match.uri)) {
|
|
4279
4323
|
uris.push(match.uri);
|
|
4280
4324
|
}
|
|
4281
4325
|
}
|
|
@@ -4317,7 +4361,7 @@ function parseRecallHits(output2, options = {}) {
|
|
|
4317
4361
|
continue;
|
|
4318
4362
|
}
|
|
4319
4363
|
for (const item of items) {
|
|
4320
|
-
if (!isJsonObject(item) || typeof item.uri !== "string" ||
|
|
4364
|
+
if (!isJsonObject(item) || typeof item.uri !== "string" || isExcludedRecallUri(item.uri)) {
|
|
4321
4365
|
continue;
|
|
4322
4366
|
}
|
|
4323
4367
|
if (options.includeArchived !== true && isArchivedMemoryUri(item.uri)) {
|
|
@@ -4376,6 +4420,9 @@ ${hit.snippet}`;
|
|
|
4376
4420
|
return kept;
|
|
4377
4421
|
}
|
|
4378
4422
|
function categoryForUri(uri) {
|
|
4423
|
+
if (uri.includes("/agent-artifacts/")) {
|
|
4424
|
+
return "skills";
|
|
4425
|
+
}
|
|
4379
4426
|
if (uri.includes("/memories/")) {
|
|
4380
4427
|
return "memories";
|
|
4381
4428
|
}
|
|
@@ -4390,6 +4437,36 @@ function contextTypeForCategory(category) {
|
|
|
4390
4437
|
}
|
|
4391
4438
|
return category === "skills" ? "skill" : "resource";
|
|
4392
4439
|
}
|
|
4440
|
+
var RECALL_EXACT_SLUG_BONUS = 4;
|
|
4441
|
+
function uriSlug(uri) {
|
|
4442
|
+
const withoutExtension = stripAnchor(uri).replace(/\.[a-z0-9]+$/i, "");
|
|
4443
|
+
return withoutExtension.slice(withoutExtension.lastIndexOf("/") + 1).toLowerCase();
|
|
4444
|
+
}
|
|
4445
|
+
function exactTermDocumentFrequency(matches) {
|
|
4446
|
+
const frequency = /* @__PURE__ */ new Map();
|
|
4447
|
+
for (const match of matches) {
|
|
4448
|
+
for (const term of new Set(match.terms.map((term2) => term2.toLowerCase()))) {
|
|
4449
|
+
frequency.set(term, (frequency.get(term) ?? 0) + 1);
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
return frequency;
|
|
4453
|
+
}
|
|
4454
|
+
function slugNamesTerm(slug, term) {
|
|
4455
|
+
return new RegExp(`(^|[^a-z0-9])${escapeRegExp(term)}([^a-z0-9]|$)`).test(slug);
|
|
4456
|
+
}
|
|
4457
|
+
function exactMatchStrength(hit, documentFrequency) {
|
|
4458
|
+
if (!hit.exactTerms?.length) {
|
|
4459
|
+
return 0;
|
|
4460
|
+
}
|
|
4461
|
+
const slug = uriSlug(hit.uri);
|
|
4462
|
+
let strength = 0;
|
|
4463
|
+
for (const term of hit.exactTerms) {
|
|
4464
|
+
const normalized = term.toLowerCase();
|
|
4465
|
+
const rarity = 1 / (documentFrequency.get(normalized) ?? 1);
|
|
4466
|
+
strength += rarity * (slugNamesTerm(slug, normalized) ? RECALL_EXACT_SLUG_BONUS : 1);
|
|
4467
|
+
}
|
|
4468
|
+
return strength;
|
|
4469
|
+
}
|
|
4393
4470
|
function applyExactMatchBoost(hits, exactMatches) {
|
|
4394
4471
|
if (exactMatches.length === 0) {
|
|
4395
4472
|
return hits;
|
|
@@ -4411,24 +4488,33 @@ function applyExactMatchBoost(hits, exactMatches) {
|
|
|
4411
4488
|
uri
|
|
4412
4489
|
};
|
|
4413
4490
|
});
|
|
4414
|
-
|
|
4415
|
-
|
|
4491
|
+
const documentFrequency = exactTermDocumentFrequency(exactMatches);
|
|
4492
|
+
const merged = [...annotated, ...promoted];
|
|
4493
|
+
const combinedRelevanceByUri = /* @__PURE__ */ new Map();
|
|
4494
|
+
for (const hit of merged) {
|
|
4495
|
+
combinedRelevanceByUri.set(stripAnchor(hit.uri), exactMatchStrength(hit, documentFrequency) + hit.score);
|
|
4496
|
+
}
|
|
4497
|
+
return merged.sort(
|
|
4498
|
+
(left, right) => recallCategoryRank(left.category) - recallCategoryRank(right.category) || (combinedRelevanceByUri.get(stripAnchor(right.uri)) ?? 0) - (combinedRelevanceByUri.get(stripAnchor(left.uri)) ?? 0) || right.score - left.score
|
|
4416
4499
|
);
|
|
4417
4500
|
}
|
|
4501
|
+
var RECALL_LOW_CONFIDENCE_NOTE = "\u26A0 No semantically-relevant matches \u2014 the results below only contain the query words (the corpus may not cover this topic).";
|
|
4418
4502
|
function renderRecallHits(shown, overflow) {
|
|
4419
4503
|
if (shown.length === 0) {
|
|
4420
4504
|
return void 0;
|
|
4421
4505
|
}
|
|
4422
4506
|
const lines = shown.flatMap((hit, index) => {
|
|
4423
4507
|
const scorePart = hit.score > 0 ? `score ${hit.score.toFixed(2)}` : void 0;
|
|
4424
|
-
const
|
|
4508
|
+
const exactLabel = hit.score > 0 ? "exact" : "keyword-only";
|
|
4509
|
+
const exactPart = hit.exactTerms?.length ? `${exactLabel}: ${hit.exactTerms.join(", ")}` : void 0;
|
|
4425
4510
|
const head = `${index + 1}. ${[hit.contextType, scorePart, exactPart].filter(Boolean).join(" \xB7 ")} \xB7 ${hit.uri}`;
|
|
4426
4511
|
return hit.snippet ? [head, ` ${hit.snippet}`] : [head];
|
|
4427
4512
|
});
|
|
4428
4513
|
if (overflow > 0) {
|
|
4429
4514
|
lines.push(`(+${overflow} more \u2014 refine the query or read a URI above)`);
|
|
4430
4515
|
}
|
|
4431
|
-
|
|
4516
|
+
const noSemanticMatch = shown.every((hit) => hit.score === 0);
|
|
4517
|
+
return (noSemanticMatch ? [RECALL_LOW_CONFIDENCE_NOTE, ...lines] : lines).join("\n");
|
|
4432
4518
|
}
|
|
4433
4519
|
var RECALL_CATEGORY_RESERVE = 2;
|
|
4434
4520
|
function selectShownHits(ranked, limit, reserve) {
|
|
@@ -7462,6 +7548,35 @@ async function inferProjectFromQuery(manifestPath, query) {
|
|
|
7462
7548
|
return void 0;
|
|
7463
7549
|
}
|
|
7464
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
|
+
}
|
|
7465
7580
|
async function readSeedManifest(path2) {
|
|
7466
7581
|
const raw = await (0, import_promises3.readFile)(path2, "utf8");
|
|
7467
7582
|
const loaded = index_vite_proxy_tmp_default.load(raw);
|
|
@@ -7493,7 +7608,33 @@ async function readSeedManifest(path2) {
|
|
|
7493
7608
|
uri: readString(loaded.future_monorepo, "uri")
|
|
7494
7609
|
};
|
|
7495
7610
|
}
|
|
7496
|
-
|
|
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;
|
|
7497
7638
|
}
|
|
7498
7639
|
function readString(object, key) {
|
|
7499
7640
|
const value = object[key];
|
|
@@ -7874,6 +8015,7 @@ function parseMemoryDocument(uri, content) {
|
|
|
7874
8015
|
archivedFrom: headerValue(header, "archived_from"),
|
|
7875
8016
|
kind,
|
|
7876
8017
|
project: normalizeOptionalMetadata(headerValue(header, "project") ?? headerValue(header, "repo")),
|
|
8018
|
+
references: headerValues(header, "references"),
|
|
7877
8019
|
sourceAgentClient: headerValue(header, "source_agent_client") ?? "unknown",
|
|
7878
8020
|
status,
|
|
7879
8021
|
supersedes: headerValue(header, "supersedes"),
|
|
@@ -8075,6 +8217,24 @@ function recallHygieneNudges(text, options) {
|
|
|
8075
8217
|
}
|
|
8076
8218
|
return [...new Set(nudges)];
|
|
8077
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
|
+
}
|
|
8078
8238
|
function activePersonalMemoryUrisFromText(text, user) {
|
|
8079
8239
|
const userSegment = uriSegment(user);
|
|
8080
8240
|
const matches = text.matchAll(/viking:\/\/[^\s)]+/g);
|
|
@@ -8225,7 +8385,8 @@ function formatMemoryDocument(title, metadata, body) {
|
|
|
8225
8385
|
`source_agent_client: ${metadata.sourceAgentClient}`,
|
|
8226
8386
|
`timestamp: ${metadata.timestamp}`,
|
|
8227
8387
|
metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
|
|
8228
|
-
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
|
|
8388
|
+
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0,
|
|
8389
|
+
...(metadata.references ?? []).map((uri) => `references: ${uri}`)
|
|
8229
8390
|
].filter((line) => line !== void 0);
|
|
8230
8391
|
return [...header, "", body.trim()].join("\n");
|
|
8231
8392
|
}
|
|
@@ -8233,6 +8394,11 @@ function headerValue(header, key) {
|
|
|
8233
8394
|
const prefix = `${key}:`;
|
|
8234
8395
|
return header.split("\n").find((line) => line.startsWith(prefix))?.slice(prefix.length).trim();
|
|
8235
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
|
+
}
|
|
8236
8402
|
function parseOptionalMemoryKind(value) {
|
|
8237
8403
|
if (!value) {
|
|
8238
8404
|
return void 0;
|
|
@@ -10217,8 +10383,8 @@ function vikingUriToWorktreeRelative(config, uri, team) {
|
|
|
10217
10383
|
}
|
|
10218
10384
|
async function isRegularFileNoSymlink(path2) {
|
|
10219
10385
|
try {
|
|
10220
|
-
const
|
|
10221
|
-
return
|
|
10386
|
+
const stat6 = await (0, import_promises5.lstat)(path2);
|
|
10387
|
+
return stat6.isFile();
|
|
10222
10388
|
} catch (_err) {
|
|
10223
10389
|
return false;
|
|
10224
10390
|
}
|
|
@@ -10777,7 +10943,7 @@ function stripPersonalProvenance(content) {
|
|
|
10777
10943
|
}
|
|
10778
10944
|
const cleaned = [];
|
|
10779
10945
|
for (let index = 0; index < headerEnd; index += 1) {
|
|
10780
|
-
if (/^(?:supersedes|archived_from):\s/.test(lines[index])) {
|
|
10946
|
+
if (/^(?:supersedes|archived_from|references):\s/.test(lines[index])) {
|
|
10781
10947
|
continue;
|
|
10782
10948
|
}
|
|
10783
10949
|
cleaned.push(lines[index]);
|
|
@@ -11291,6 +11457,7 @@ async function runRecall(config, options) {
|
|
|
11291
11457
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
11292
11458
|
const project = await inferProjectFromQuery(config.manifestPath, options.project ?? projectQuery);
|
|
11293
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;
|
|
11294
11461
|
const searchArgs = (scopeUri) => [
|
|
11295
11462
|
"search",
|
|
11296
11463
|
query,
|
|
@@ -11316,6 +11483,15 @@ async function runRecall(config, options) {
|
|
|
11316
11483
|
if (seededUri?.startsWith("viking://") && seededUri !== inferredUri && !options.uri && options.inferScope !== false) {
|
|
11317
11484
|
passes.push(await recallSearchHits(config, ov, searchArgs(seededUri), { dryRun, includeArchived }));
|
|
11318
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
|
+
}
|
|
11319
11495
|
const recallOutputs = [];
|
|
11320
11496
|
const exactMatches = await collectExactMemoryMatches(config, ov, query, { dryRun, includeArchived, project });
|
|
11321
11497
|
const { semanticSection, exactTail } = buildRecallSections(passes, exactMatches, nodeLimit ?? 12);
|
|
@@ -11329,8 +11505,45 @@ ${semanticSection}`);
|
|
|
11329
11505
|
${exactTail}`);
|
|
11330
11506
|
recallOutputs.push(exactTail);
|
|
11331
11507
|
}
|
|
11508
|
+
const referencedSection = await referencedContextSection(config, recallOutputs.join("\n"));
|
|
11509
|
+
if (referencedSection) {
|
|
11510
|
+
console.log(`
|
|
11511
|
+
${referencedSection}`);
|
|
11512
|
+
recallOutputs.push(referencedSection);
|
|
11513
|
+
}
|
|
11332
11514
|
await printRecallHygieneNudges(config, recallOutputs.join("\n"));
|
|
11333
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
|
+
}
|
|
11334
11547
|
async function recallSearchHits(config, ov, args, options) {
|
|
11335
11548
|
const jsonArgs = withIdentity(config, [...args, "--output", "json"]);
|
|
11336
11549
|
console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, jsonArgs)}`);
|
|
@@ -11768,6 +11981,13 @@ async function storeMemory(config, options) {
|
|
|
11768
11981
|
console.log(`Updated existing memory in place: ${memoryUri}`);
|
|
11769
11982
|
}
|
|
11770
11983
|
}
|
|
11984
|
+
function warnOnSharedProjectDrift(metadata, inferred) {
|
|
11985
|
+
if (inferred?.project && metadata.project && uriSegment(metadata.project) !== inferred.project) {
|
|
11986
|
+
console.log(
|
|
11987
|
+
`WARN keeping shared memory project "${inferred.project}" from its storage path; ignoring requested "${metadata.project}". To change a shared memory's project, forget it and store a new one under the new project.`
|
|
11988
|
+
);
|
|
11989
|
+
}
|
|
11990
|
+
}
|
|
11771
11991
|
async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
|
|
11772
11992
|
if (options.metadata.kind !== "durable") {
|
|
11773
11993
|
throw new Error("Shared memory replacement only supports durable memories.");
|
|
@@ -11778,9 +11998,10 @@ async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
|
|
|
11778
11998
|
}
|
|
11779
11999
|
const team = await resolveTeam(config, teamName);
|
|
11780
12000
|
const inferred = sharedMemoryUriParts(config, targetUri);
|
|
12001
|
+
warnOnSharedProjectDrift(options.metadata, inferred);
|
|
11781
12002
|
const metadata = {
|
|
11782
12003
|
...options.metadata,
|
|
11783
|
-
project: options.metadata.project
|
|
12004
|
+
project: inferred?.project ?? options.metadata.project,
|
|
11784
12005
|
topic: options.metadata.topic ?? inferred?.topic
|
|
11785
12006
|
};
|
|
11786
12007
|
const rawMemory = formatMemoryDocument2(options.title, metadata, options.bodyText);
|
|
@@ -11838,8 +12059,8 @@ async function removeVikingResourceWithRetry(ov, config, uri) {
|
|
|
11838
12059
|
return false;
|
|
11839
12060
|
}
|
|
11840
12061
|
async function vikingResourceExists2(ov, config, uri) {
|
|
11841
|
-
const
|
|
11842
|
-
return
|
|
12062
|
+
const stat6 = await runCommand(ov, withIdentity(config, ["stat", uri]), { allowFailure: true });
|
|
12063
|
+
return stat6.exitCode === 0;
|
|
11843
12064
|
}
|
|
11844
12065
|
async function ensureDurableMemoryDirectory(ov, config) {
|
|
11845
12066
|
await ensureMemoryDirectory(ov, config, durableMemoryDirectoryUri(config));
|
|
@@ -11906,6 +12127,18 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
|
11906
12127
|
function lifecycleMigrationUri(config, metadata, hash) {
|
|
11907
12128
|
return `${memoryDirectoryUri(config, metadata)}/legacy-${hash.slice(0, 16)}.md`;
|
|
11908
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
|
+
}
|
|
11909
12142
|
function exactMemoryScopes(config, includeArchived, query, project) {
|
|
11910
12143
|
return exactMemoryScopeUris({
|
|
11911
12144
|
agentMemoriesUri: `viking://agent/${uriSegment(config.agentId)}/memories`,
|
|
@@ -11968,7 +12201,8 @@ function formatMemoryDocument2(title, metadata, body) {
|
|
|
11968
12201
|
`source_agent_client: ${metadata.sourceAgentClient}`,
|
|
11969
12202
|
`timestamp: ${metadata.timestamp}`,
|
|
11970
12203
|
metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
|
|
11971
|
-
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
|
|
12204
|
+
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0,
|
|
12205
|
+
...(metadata.references ?? []).map((uri) => `references: ${uri}`)
|
|
11972
12206
|
].filter((line) => line !== void 0);
|
|
11973
12207
|
return [...header, "", body.trim()].join("\n");
|
|
11974
12208
|
}
|
|
@@ -12203,9 +12437,25 @@ function isResourceBusy(stderr, stdout2) {
|
|
|
12203
12437
|
${stdout2}`.toLowerCase();
|
|
12204
12438
|
return output2.includes("resource is busy") || output2.includes("resource is being processed");
|
|
12205
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
|
+
}
|
|
12206
12455
|
async function buildHandoff(options) {
|
|
12207
12456
|
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]) ?? getInvocationCwd();
|
|
12208
12457
|
const branch = await gitValue(["branch", "--show-current"], repoRoot) ?? "unknown";
|
|
12458
|
+
const commit = await gitValue(["rev-parse", "HEAD"], repoRoot) ?? "unknown";
|
|
12209
12459
|
const status = await gitValue(["status", "--short"], repoRoot) ?? "";
|
|
12210
12460
|
const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
|
|
12211
12461
|
const touchedFiles = await gitTouchedFiles(repoRoot);
|
|
@@ -12214,16 +12464,24 @@ async function buildHandoff(options) {
|
|
|
12214
12464
|
const metadata = {
|
|
12215
12465
|
kind: "handoff",
|
|
12216
12466
|
project: normalizeOptionalMetadata2(options.project) ?? repoName,
|
|
12467
|
+
references: normalizeReferenceUris(options.references),
|
|
12217
12468
|
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
12218
12469
|
status: "active",
|
|
12219
12470
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12220
12471
|
topic: handoffTopicForBranch(topicBranch, { timestamped: options.timestamped, topic: options.topic })
|
|
12221
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);
|
|
12222
12478
|
const bodyText = [
|
|
12223
12479
|
`repo: ${repoName}`,
|
|
12224
12480
|
`repo_path: ${repoRoot}`,
|
|
12225
12481
|
`branch: ${branch || "unknown"}`,
|
|
12482
|
+
`commit: ${commit}`,
|
|
12226
12483
|
`task: ${options.task ?? "unspecified"}`,
|
|
12484
|
+
...reviewState,
|
|
12227
12485
|
"",
|
|
12228
12486
|
"files_touched:",
|
|
12229
12487
|
formatBlock(touchedFiles, "- none"),
|
|
@@ -12241,7 +12499,9 @@ async function buildHandoff(options) {
|
|
|
12241
12499
|
options.blockers ?? "- none recorded",
|
|
12242
12500
|
"",
|
|
12243
12501
|
"next_step:",
|
|
12244
|
-
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] : []
|
|
12245
12505
|
].join("\n");
|
|
12246
12506
|
return { bodyText, metadata };
|
|
12247
12507
|
}
|
|
@@ -12267,9 +12527,134 @@ function formatBlock(value, emptyValue) {
|
|
|
12267
12527
|
return trimmed.split("\n").map((line) => `- ${line}`).join("\n");
|
|
12268
12528
|
}
|
|
12269
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
|
+
|
|
12270
12655
|
// src/update-check.ts
|
|
12271
12656
|
var import_node_child_process3 = require("node:child_process");
|
|
12272
|
-
var
|
|
12657
|
+
var import_promises8 = require("node:fs/promises");
|
|
12273
12658
|
var import_node_path9 = require("node:path");
|
|
12274
12659
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12275
12660
|
var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
|
|
@@ -12323,7 +12708,7 @@ function isCacheFresh(cache) {
|
|
|
12323
12708
|
}
|
|
12324
12709
|
async function readUpdateCache(cachePath) {
|
|
12325
12710
|
try {
|
|
12326
|
-
const raw = await (0,
|
|
12711
|
+
const raw = await (0, import_promises8.readFile)(cachePath, "utf8");
|
|
12327
12712
|
const parsed = JSON.parse(raw);
|
|
12328
12713
|
if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
|
|
12329
12714
|
return void 0;
|
|
@@ -12335,8 +12720,8 @@ async function readUpdateCache(cachePath) {
|
|
|
12335
12720
|
}
|
|
12336
12721
|
async function writeUpdateCache(cachePath, contents) {
|
|
12337
12722
|
try {
|
|
12338
|
-
await (0,
|
|
12339
|
-
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)}
|
|
12340
12725
|
`, { encoding: "utf8", mode: 384 });
|
|
12341
12726
|
} catch {
|
|
12342
12727
|
}
|
|
@@ -12408,7 +12793,7 @@ async function runHooksInstall(config, agent, options) {
|
|
|
12408
12793
|
}
|
|
12409
12794
|
async function runClaudeHooksInstall(options) {
|
|
12410
12795
|
const path2 = expandPath(CLAUDE_SETTINGS_PATH);
|
|
12411
|
-
const existingRaw = await exists(path2) ? await (0,
|
|
12796
|
+
const existingRaw = await exists(path2) ? await (0, import_promises9.readFile)(path2, "utf8") : "{}";
|
|
12412
12797
|
const parsed = parseJsonConfigObject(existingRaw) ?? {};
|
|
12413
12798
|
const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
|
|
12414
12799
|
const before = JSON.stringify(parsed);
|
|
@@ -12425,11 +12810,11 @@ async function runClaudeHooksInstall(options) {
|
|
|
12425
12810
|
console.log("\nRe-run with --apply to actually modify the file.");
|
|
12426
12811
|
return;
|
|
12427
12812
|
}
|
|
12428
|
-
await (0,
|
|
12813
|
+
await (0, import_promises9.mkdir)((0, import_node_path11.dirname)(path2), { recursive: true });
|
|
12429
12814
|
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
12430
12815
|
`;
|
|
12431
|
-
await (0,
|
|
12432
|
-
await (0,
|
|
12816
|
+
await (0, import_promises9.writeFile)(path2, serialized, { encoding: "utf8", mode: 384 });
|
|
12817
|
+
await (0, import_promises9.chmod)(path2, 384);
|
|
12433
12818
|
console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
|
|
12434
12819
|
}
|
|
12435
12820
|
function withThreadnoteHooks(input2) {
|
|
@@ -12507,7 +12892,7 @@ async function hasManagedClaudeHooks() {
|
|
|
12507
12892
|
if (!await exists(path2)) {
|
|
12508
12893
|
return false;
|
|
12509
12894
|
}
|
|
12510
|
-
const raw = await (0,
|
|
12895
|
+
const raw = await (0, import_promises9.readFile)(path2, "utf8");
|
|
12511
12896
|
const parsed = parseJsonConfigObject(raw);
|
|
12512
12897
|
if (!parsed || !isJsonObject(parsed.hooks)) {
|
|
12513
12898
|
return false;
|
|
@@ -12525,15 +12910,18 @@ async function hasManagedClaudeHooks() {
|
|
|
12525
12910
|
async function runPreCompactHook(config, options = {}) {
|
|
12526
12911
|
try {
|
|
12527
12912
|
const project = await resolveRepoName() ?? "general";
|
|
12913
|
+
const { sessionId, trace } = await captureTraceContext();
|
|
12528
12914
|
await runHandoff(config, {
|
|
12529
12915
|
blockers: "- none recorded",
|
|
12530
12916
|
dryRun: options.dryRun === true,
|
|
12531
12917
|
nextStep: "Continue from this auto-snapshot. A manual `threadnote handoff` will produce a richer write-up if you have more context.",
|
|
12532
12918
|
project,
|
|
12919
|
+
sessionId,
|
|
12533
12920
|
sourceAgentClient: "claude",
|
|
12534
12921
|
task: "Auto-snapshot captured at Claude PreCompact (deterministic safety net before context compaction).",
|
|
12535
12922
|
tests: "- not recorded (auto-snapshot)",
|
|
12536
|
-
topic: HOOK_AUTO_PRECOMPACT_TOPIC
|
|
12923
|
+
topic: HOOK_AUTO_PRECOMPACT_TOPIC,
|
|
12924
|
+
trace
|
|
12537
12925
|
});
|
|
12538
12926
|
} catch (err) {
|
|
12539
12927
|
process.stderr.write(
|
|
@@ -12566,6 +12954,76 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
12566
12954
|
);
|
|
12567
12955
|
}
|
|
12568
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
|
+
}
|
|
12569
13027
|
async function emitUpdateBannerIfOutdated(config) {
|
|
12570
13028
|
try {
|
|
12571
13029
|
const result = await checkForThreadnoteUpdate({
|
|
@@ -12594,8 +13052,144 @@ async function emitUpdateBannerIfOutdated(config) {
|
|
|
12594
13052
|
}
|
|
12595
13053
|
|
|
12596
13054
|
// src/seeding.ts
|
|
12597
|
-
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");
|
|
12598
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
|
|
12599
13193
|
function parseSeedWatchIntervalMinutes(rawValue) {
|
|
12600
13194
|
if (rawValue === void 0) {
|
|
12601
13195
|
return void 0;
|
|
@@ -12614,7 +13208,7 @@ async function runSeed(config, options) {
|
|
|
12614
13208
|
const ignorePatterns = await loadIgnorePatterns();
|
|
12615
13209
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
12616
13210
|
const watchIntervalMinutes = parseSeedWatchIntervalMinutes(process.env[SEED_WATCH_INTERVAL_ENV]);
|
|
12617
|
-
const statePath = (0,
|
|
13211
|
+
const statePath = (0, import_node_path13.join)(config.agentContextHome, SEED_STATE_FILE);
|
|
12618
13212
|
const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
|
|
12619
13213
|
const projects = filterProjects(manifest.projects, options.only);
|
|
12620
13214
|
let importedCount = 0;
|
|
@@ -12666,6 +13260,70 @@ async function runSeed(config, options) {
|
|
|
12666
13260
|
console.log(
|
|
12667
13261
|
`Seed complete: ${importedCount} candidate(s), ${unchangedCount} unchanged, ${skippedCount} skipped for safety.`
|
|
12668
13262
|
);
|
|
13263
|
+
if (options.graph === true) {
|
|
13264
|
+
await seedDependencyGraphs(config, ov, manifest, projects, options.dryRun === true);
|
|
13265
|
+
}
|
|
13266
|
+
}
|
|
13267
|
+
async function seedDependencyGraphs(config, ov, manifest, targetProjects, dryRun) {
|
|
13268
|
+
const factsByProject = /* @__PURE__ */ new Map();
|
|
13269
|
+
for (const project of manifest.projects) {
|
|
13270
|
+
const projectRoot = expandPath(project.path);
|
|
13271
|
+
if (!await exists(projectRoot)) {
|
|
13272
|
+
continue;
|
|
13273
|
+
}
|
|
13274
|
+
factsByProject.set(project.name, await extractDependencyFacts(projectRoot));
|
|
13275
|
+
}
|
|
13276
|
+
const projectByPublishedName = /* @__PURE__ */ new Map();
|
|
13277
|
+
for (const [name, facts] of factsByProject) {
|
|
13278
|
+
if (facts.publishedName) {
|
|
13279
|
+
projectByPublishedName.set(facts.publishedName.toLowerCase(), name);
|
|
13280
|
+
}
|
|
13281
|
+
}
|
|
13282
|
+
let written = 0;
|
|
13283
|
+
let skipped = 0;
|
|
13284
|
+
for (const project of targetProjects) {
|
|
13285
|
+
const facts = factsByProject.get(project.name);
|
|
13286
|
+
if (!facts || facts.manifestFiles.length === 0) {
|
|
13287
|
+
continue;
|
|
13288
|
+
}
|
|
13289
|
+
const { externalCount, internalEdges } = resolveGraphEdges(project.name, facts.dependencies, projectByPublishedName);
|
|
13290
|
+
const document = buildGraphDocument({ externalCount, facts, internalEdges, projectName: project.name });
|
|
13291
|
+
const secretMatches = detectSecretMatches(document);
|
|
13292
|
+
if (secretMatches.length > 0) {
|
|
13293
|
+
skipped += 1;
|
|
13294
|
+
console.log(
|
|
13295
|
+
`SKIP ${project.name}/.graph.md: possible secret (${secretMatches.slice(0, MAX_SECRET_MATCHES_TO_PRINT).join(", ")})`
|
|
13296
|
+
);
|
|
13297
|
+
continue;
|
|
13298
|
+
}
|
|
13299
|
+
const destinationUri = `${trimTrailingSlash(project.uri)}/.graph.md`;
|
|
13300
|
+
if (dryRun) {
|
|
13301
|
+
console.log(`Would seed dependency facts: ${destinationUri} (${internalEdges.length} in-workspace edge(s))`);
|
|
13302
|
+
written += 1;
|
|
13303
|
+
continue;
|
|
13304
|
+
}
|
|
13305
|
+
const graphPath = (0, import_node_path13.join)(config.agentContextHome, "graph", graphCacheFileName(project.name));
|
|
13306
|
+
await ensureDirectory((0, import_node_path13.dirname)(graphPath), false);
|
|
13307
|
+
await (0, import_promises11.writeFile)(graphPath, document, { encoding: "utf8", mode: 384 });
|
|
13308
|
+
await (0, import_promises11.chmod)(graphPath, 384);
|
|
13309
|
+
await maybeRun(
|
|
13310
|
+
false,
|
|
13311
|
+
ov,
|
|
13312
|
+
withIdentity(config, [
|
|
13313
|
+
"add-resource",
|
|
13314
|
+
graphPath,
|
|
13315
|
+
"--to",
|
|
13316
|
+
destinationUri,
|
|
13317
|
+
"--reason",
|
|
13318
|
+
`Dependency facts for ${project.name}`,
|
|
13319
|
+
"--wait"
|
|
13320
|
+
])
|
|
13321
|
+
);
|
|
13322
|
+
written += 1;
|
|
13323
|
+
}
|
|
13324
|
+
console.log(
|
|
13325
|
+
`Dependency graph seed complete: ${written} .graph.md resource(s)${skipped > 0 ? `, ${skipped} skipped for safety` : ""}.`
|
|
13326
|
+
);
|
|
12669
13327
|
}
|
|
12670
13328
|
function filterProjects(projects, only) {
|
|
12671
13329
|
if (!only || only.length === 0) {
|
|
@@ -12682,7 +13340,7 @@ function filterProjects(projects, only) {
|
|
|
12682
13340
|
}
|
|
12683
13341
|
async function statSeedFile(path2) {
|
|
12684
13342
|
try {
|
|
12685
|
-
const result = await (0,
|
|
13343
|
+
const result = await (0, import_promises11.stat)(path2);
|
|
12686
13344
|
return { mtimeMs: result.mtimeMs, size: result.size };
|
|
12687
13345
|
} catch (_err) {
|
|
12688
13346
|
return void 0;
|
|
@@ -12690,7 +13348,7 @@ async function statSeedFile(path2) {
|
|
|
12690
13348
|
}
|
|
12691
13349
|
async function readSeedState(path2) {
|
|
12692
13350
|
try {
|
|
12693
|
-
const raw = await (0,
|
|
13351
|
+
const raw = await (0, import_promises11.readFile)(path2, "utf8");
|
|
12694
13352
|
const parsed = JSON.parse(raw);
|
|
12695
13353
|
if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
|
|
12696
13354
|
return { files: {}, version: 1 };
|
|
@@ -12707,13 +13365,13 @@ async function readSeedState(path2) {
|
|
|
12707
13365
|
}
|
|
12708
13366
|
}
|
|
12709
13367
|
async function writeSeedState(path2, state) {
|
|
12710
|
-
await ensureDirectory((0,
|
|
12711
|
-
await (0,
|
|
13368
|
+
await ensureDirectory((0, import_node_path13.dirname)(path2), false);
|
|
13369
|
+
await (0, import_promises11.writeFile)(path2, `${JSON.stringify(state, void 0, 2)}
|
|
12712
13370
|
`, { encoding: "utf8", mode: 384 });
|
|
12713
13371
|
}
|
|
12714
13372
|
async function runInitManifest(config, options) {
|
|
12715
13373
|
const manifestPath = expandPath(
|
|
12716
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
13374
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path13.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
12717
13375
|
);
|
|
12718
13376
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
12719
13377
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -12750,20 +13408,58 @@ async function runInitManifest(config, options) {
|
|
|
12750
13408
|
uri: existingManifest.futureMonorepo.uri
|
|
12751
13409
|
};
|
|
12752
13410
|
}
|
|
13411
|
+
if (existingManifest?.worksets) {
|
|
13412
|
+
outputManifest.worksets = existingManifest.worksets.map((workset) => ({
|
|
13413
|
+
name: workset.name,
|
|
13414
|
+
...workset.description !== void 0 ? { description: workset.description } : {},
|
|
13415
|
+
projects: [...workset.projects]
|
|
13416
|
+
}));
|
|
13417
|
+
}
|
|
12753
13418
|
const output2 = index_vite_proxy_tmp_default.dump(outputManifest, { lineWidth: 120, noRefs: true });
|
|
12754
13419
|
if (options.dryRun === true) {
|
|
12755
13420
|
console.log(`# Would write ${manifestPath}`);
|
|
12756
13421
|
console.log(output2.trimEnd());
|
|
12757
13422
|
return;
|
|
12758
13423
|
}
|
|
12759
|
-
await ensureDirectory((0,
|
|
12760
|
-
await (0,
|
|
12761
|
-
await (0,
|
|
13424
|
+
await ensureDirectory((0, import_node_path13.dirname)(manifestPath), false);
|
|
13425
|
+
await (0, import_promises11.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
13426
|
+
await (0, import_promises11.chmod)(manifestPath, 384);
|
|
12762
13427
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
12763
13428
|
console.log("Seed with:");
|
|
12764
13429
|
console.log(" threadnote seed --dry-run");
|
|
12765
13430
|
console.log(" threadnote seed");
|
|
12766
13431
|
}
|
|
13432
|
+
async function runWorksetList(config) {
|
|
13433
|
+
const manifest = await readSeedManifest(config.manifestPath);
|
|
13434
|
+
const worksets = manifest.worksets ?? [];
|
|
13435
|
+
if (worksets.length === 0) {
|
|
13436
|
+
console.log(
|
|
13437
|
+
"No worksets defined. Add a top-level `worksets:` list to the seed manifest to group related projects."
|
|
13438
|
+
);
|
|
13439
|
+
return;
|
|
13440
|
+
}
|
|
13441
|
+
console.log(`Worksets (${worksets.length}):`);
|
|
13442
|
+
for (const workset of worksets) {
|
|
13443
|
+
const summary = workset.description ? ` \u2014 ${workset.description}` : "";
|
|
13444
|
+
console.log(`- ${workset.name} (${workset.projects.length} project(s))${summary}`);
|
|
13445
|
+
}
|
|
13446
|
+
}
|
|
13447
|
+
async function runWorksetShow(config, name) {
|
|
13448
|
+
const manifest = await readSeedManifest(config.manifestPath);
|
|
13449
|
+
const workset = manifest.worksets?.find((entry) => entry.name.toLowerCase() === name.toLowerCase());
|
|
13450
|
+
if (!workset) {
|
|
13451
|
+
throw new Error(`No workset named "${name}" in ${config.manifestPath}.`);
|
|
13452
|
+
}
|
|
13453
|
+
console.log(`Workset: ${workset.name}`);
|
|
13454
|
+
if (workset.description) {
|
|
13455
|
+
console.log(workset.description);
|
|
13456
|
+
}
|
|
13457
|
+
console.log("Projects:");
|
|
13458
|
+
for (const memberName of workset.projects) {
|
|
13459
|
+
const project = manifest.projects.find((entry) => entry.name.toLowerCase() === memberName.toLowerCase());
|
|
13460
|
+
console.log(project ? `- ${project.name} (${project.uri})` : `- ${memberName} [not found in manifest projects]`);
|
|
13461
|
+
}
|
|
13462
|
+
}
|
|
12767
13463
|
async function runSeedSkills(config, options) {
|
|
12768
13464
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
12769
13465
|
const catalogItems = await collectSkillCandidates(config);
|
|
@@ -12804,13 +13500,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
12804
13500
|
async function projectIdentity(path2) {
|
|
12805
13501
|
const expanded = expandPath(path2);
|
|
12806
13502
|
try {
|
|
12807
|
-
return await (0,
|
|
13503
|
+
return await (0, import_promises11.realpath)(expanded);
|
|
12808
13504
|
} catch (_err) {
|
|
12809
13505
|
return expanded;
|
|
12810
13506
|
}
|
|
12811
13507
|
}
|
|
12812
13508
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
12813
|
-
const baseName = uriSegment((0,
|
|
13509
|
+
const baseName = uriSegment((0, import_node_path13.basename)(repoRoot));
|
|
12814
13510
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
12815
13511
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
12816
13512
|
let name = baseName;
|
|
@@ -12832,7 +13528,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
12832
13528
|
for (const pattern of project.seed) {
|
|
12833
13529
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
12834
13530
|
for (const filePath of files) {
|
|
12835
|
-
const relativePath = toPosixPath((0,
|
|
13531
|
+
const relativePath = toPosixPath((0, import_node_path13.relative)(projectRoot, filePath));
|
|
12836
13532
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
12837
13533
|
continue;
|
|
12838
13534
|
}
|
|
@@ -12850,20 +13546,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
12850
13546
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
12851
13547
|
const normalizedPattern = toPosixPath(pattern);
|
|
12852
13548
|
if (!hasGlob(normalizedPattern)) {
|
|
12853
|
-
const filePath = (0,
|
|
13549
|
+
const filePath = (0, import_node_path13.join)(projectRoot, normalizedPattern);
|
|
12854
13550
|
return await isFile(filePath) ? [filePath] : [];
|
|
12855
13551
|
}
|
|
12856
13552
|
const globBase = getGlobBase(normalizedPattern);
|
|
12857
|
-
const basePath = (0,
|
|
13553
|
+
const basePath = (0, import_node_path13.join)(projectRoot, globBase);
|
|
12858
13554
|
if (!await exists(basePath)) {
|
|
12859
13555
|
return [];
|
|
12860
13556
|
}
|
|
12861
13557
|
const regex = globToRegExp(normalizedPattern);
|
|
12862
13558
|
const files = await walkFiles(basePath);
|
|
12863
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
13559
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path13.relative)(projectRoot, filePath))));
|
|
12864
13560
|
}
|
|
12865
13561
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
12866
|
-
const content = await (0,
|
|
13562
|
+
const content = await (0, import_promises11.readFile)(candidate.filePath, "utf8");
|
|
12867
13563
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
12868
13564
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
12869
13565
|
if (secretMatches.length > 0) {
|
|
@@ -12875,21 +13571,24 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
12875
13571
|
if (redactedContent === content) {
|
|
12876
13572
|
return candidate.filePath;
|
|
12877
13573
|
}
|
|
12878
|
-
const redactedPath = (0,
|
|
13574
|
+
const redactedPath = (0, import_node_path13.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
12879
13575
|
if (dryRun) {
|
|
12880
13576
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
12881
13577
|
return redactedPath;
|
|
12882
13578
|
}
|
|
12883
|
-
await ensureDirectory((0,
|
|
12884
|
-
await (0,
|
|
12885
|
-
await (0,
|
|
13579
|
+
await ensureDirectory((0, import_node_path13.dirname)(redactedPath), false);
|
|
13580
|
+
await (0, import_promises11.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
13581
|
+
await (0, import_promises11.chmod)(redactedPath, 384);
|
|
12886
13582
|
return redactedPath;
|
|
12887
13583
|
}
|
|
12888
13584
|
function seedResourceReason(candidate) {
|
|
12889
13585
|
return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
|
|
12890
13586
|
}
|
|
13587
|
+
function graphCacheFileName(projectName) {
|
|
13588
|
+
return `${uriSegment(projectName)}-${sha256(projectName).slice(0, 8)}.graph.md`;
|
|
13589
|
+
}
|
|
12891
13590
|
function skillResourceReason(skill) {
|
|
12892
|
-
return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0,
|
|
13591
|
+
return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path13.basename)(
|
|
12893
13592
|
skill.filePath
|
|
12894
13593
|
)}`;
|
|
12895
13594
|
}
|
|
@@ -12922,7 +13621,7 @@ async function collectSkillCandidates(config) {
|
|
|
12922
13621
|
for (const source of sources) {
|
|
12923
13622
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
12924
13623
|
for (const filePath of files) {
|
|
12925
|
-
const content = await (0,
|
|
13624
|
+
const content = await (0, import_promises11.readFile)(filePath, "utf8");
|
|
12926
13625
|
const matches = detectSecretMatches(content);
|
|
12927
13626
|
if (matches.length > 0) {
|
|
12928
13627
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -12956,16 +13655,16 @@ function skillResourceUri(skill) {
|
|
|
12956
13655
|
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
|
|
12957
13656
|
}
|
|
12958
13657
|
function skillResourceName(skill) {
|
|
12959
|
-
const fileName = (0,
|
|
13658
|
+
const fileName = (0, import_node_path13.basename)(skill.filePath);
|
|
12960
13659
|
if (fileName.toLowerCase() === "skill.md") {
|
|
12961
|
-
return uriSegment((0,
|
|
13660
|
+
return uriSegment((0, import_node_path13.basename)((0, import_node_path13.dirname)(skill.filePath)));
|
|
12962
13661
|
}
|
|
12963
13662
|
const extensionIndex = fileName.lastIndexOf(".");
|
|
12964
13663
|
const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
|
|
12965
13664
|
return uriSegment(stem);
|
|
12966
13665
|
}
|
|
12967
13666
|
async function loadIgnorePatterns() {
|
|
12968
|
-
const raw = await (0,
|
|
13667
|
+
const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
12969
13668
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
12970
13669
|
}
|
|
12971
13670
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -13038,18 +13737,18 @@ function detectSecretMatches(content) {
|
|
|
13038
13737
|
// src/lifecycle.ts
|
|
13039
13738
|
var import_node_child_process4 = require("node:child_process");
|
|
13040
13739
|
var import_node_fs7 = require("node:fs");
|
|
13041
|
-
var
|
|
13740
|
+
var import_promises14 = require("node:fs/promises");
|
|
13042
13741
|
var import_node_os6 = require("node:os");
|
|
13043
|
-
var
|
|
13742
|
+
var import_node_path15 = require("node:path");
|
|
13044
13743
|
var import_node_process4 = require("node:process");
|
|
13045
|
-
var
|
|
13744
|
+
var import_promises15 = require("node:readline/promises");
|
|
13046
13745
|
|
|
13047
13746
|
// src/update.ts
|
|
13048
13747
|
var import_node_fs6 = require("node:fs");
|
|
13049
|
-
var
|
|
13748
|
+
var import_promises12 = require("node:fs/promises");
|
|
13050
13749
|
var import_node_os5 = require("node:os");
|
|
13051
|
-
var
|
|
13052
|
-
var
|
|
13750
|
+
var import_node_path14 = require("node:path");
|
|
13751
|
+
var import_promises13 = require("node:readline/promises");
|
|
13053
13752
|
var import_node_process3 = require("node:process");
|
|
13054
13753
|
|
|
13055
13754
|
// src/release_notes.ts
|
|
@@ -13403,14 +14102,14 @@ async function waitForOpenVikingPortClosed(config, timeoutMs) {
|
|
|
13403
14102
|
return false;
|
|
13404
14103
|
}
|
|
13405
14104
|
function launchAgentPlistPath() {
|
|
13406
|
-
return (0,
|
|
14105
|
+
return (0, import_node_path14.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
|
|
13407
14106
|
}
|
|
13408
14107
|
async function isLaunchAgentInstalled() {
|
|
13409
14108
|
if (process.platform !== "darwin") {
|
|
13410
14109
|
return false;
|
|
13411
14110
|
}
|
|
13412
14111
|
try {
|
|
13413
|
-
await (0,
|
|
14112
|
+
await (0, import_promises12.access)(launchAgentPlistPath(), import_node_fs6.constants.F_OK);
|
|
13414
14113
|
return true;
|
|
13415
14114
|
} catch (_err) {
|
|
13416
14115
|
return false;
|
|
@@ -13481,11 +14180,11 @@ async function readFreshCache(config, registry) {
|
|
|
13481
14180
|
}
|
|
13482
14181
|
async function writeUpdateCache2(config, cache) {
|
|
13483
14182
|
await ensureDirectory(config.agentContextHome, false);
|
|
13484
|
-
await (0,
|
|
14183
|
+
await (0, import_promises12.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
13485
14184
|
`, { encoding: "utf8", mode: 384 });
|
|
13486
14185
|
}
|
|
13487
14186
|
function updateCachePath(config) {
|
|
13488
|
-
return (0,
|
|
14187
|
+
return (0, import_node_path14.join)(config.agentContextHome, "update-check.json");
|
|
13489
14188
|
}
|
|
13490
14189
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
13491
14190
|
const state = await readPostUpdateState(config);
|
|
@@ -13549,7 +14248,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
13549
14248
|
return applicable;
|
|
13550
14249
|
}
|
|
13551
14250
|
async function readPostUpdateMigrations() {
|
|
13552
|
-
const raw = await readFileIfExists((0,
|
|
14251
|
+
const raw = await readFileIfExists((0, import_node_path14.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
13553
14252
|
if (!raw) {
|
|
13554
14253
|
return [];
|
|
13555
14254
|
}
|
|
@@ -13588,7 +14287,7 @@ function printPostUpdateMigration(migration) {
|
|
|
13588
14287
|
}
|
|
13589
14288
|
}
|
|
13590
14289
|
async function confirmPostUpdateMigration(prompt, defaultYes = false) {
|
|
13591
|
-
const readline = (0,
|
|
14290
|
+
const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
|
|
13592
14291
|
try {
|
|
13593
14292
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
13594
14293
|
if (answer === "") {
|
|
@@ -13623,11 +14322,11 @@ async function readPostUpdateState(config) {
|
|
|
13623
14322
|
}
|
|
13624
14323
|
async function writePostUpdateState(config, state) {
|
|
13625
14324
|
await ensureDirectory(config.agentContextHome, false);
|
|
13626
|
-
await (0,
|
|
14325
|
+
await (0, import_promises12.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
13627
14326
|
`, { encoding: "utf8", mode: 384 });
|
|
13628
14327
|
}
|
|
13629
14328
|
function postUpdateStatePath(config) {
|
|
13630
|
-
return (0,
|
|
14329
|
+
return (0, import_node_path14.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
13631
14330
|
}
|
|
13632
14331
|
async function resolveUpdateRuntime(runtime) {
|
|
13633
14332
|
if (runtime !== "auto") {
|
|
@@ -13657,14 +14356,14 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
13657
14356
|
if (runtime === "npm") {
|
|
13658
14357
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
13659
14358
|
const prefix = result.stdout.trim();
|
|
13660
|
-
return prefix ? (0,
|
|
14359
|
+
return prefix ? (0, import_node_path14.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
13661
14360
|
}
|
|
13662
14361
|
if (runtime === "bun") {
|
|
13663
14362
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
13664
14363
|
const binDir = result.stdout.trim();
|
|
13665
|
-
return binDir ? (0,
|
|
14364
|
+
return binDir ? (0, import_node_path14.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
13666
14365
|
}
|
|
13667
|
-
return (0,
|
|
14366
|
+
return (0, import_node_path14.join)(process.env.DENO_INSTALL ?? (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
13668
14367
|
}
|
|
13669
14368
|
function updatePackageCommand(runtime, registry) {
|
|
13670
14369
|
if (runtime === "npm") {
|
|
@@ -13736,9 +14435,10 @@ async function collectDoctorChecks(config, options = {}) {
|
|
|
13736
14435
|
checks.push(...await userAgentInstructionsChecks());
|
|
13737
14436
|
checks.push(await manifestCheck(config.manifestPath));
|
|
13738
14437
|
checks.push(await recallIndexFreshnessCheck(config));
|
|
13739
|
-
checks.push(await
|
|
13740
|
-
checks.push(await fileCheck((0,
|
|
13741
|
-
checks.push(await fileCheck((0,
|
|
14438
|
+
checks.push(await memoryProjectConsistencyCheck(config));
|
|
14439
|
+
checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
14440
|
+
checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
14441
|
+
checks.push(await fileCheck((0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
13742
14442
|
checks.push(await healthCheck(config));
|
|
13743
14443
|
return checks;
|
|
13744
14444
|
}
|
|
@@ -13746,9 +14446,9 @@ async function runInstall(config, options) {
|
|
|
13746
14446
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
13747
14447
|
const dryRun = options.dryRun === true;
|
|
13748
14448
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
13749
|
-
await ensureDirectory((0,
|
|
13750
|
-
await ensureDirectory((0,
|
|
13751
|
-
await ensureDirectory((0,
|
|
14449
|
+
await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "logs"), dryRun);
|
|
14450
|
+
await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "redacted"), dryRun);
|
|
14451
|
+
await ensureDirectory((0, import_node_path15.join)(config.agentContextHome, "mcp"), dryRun);
|
|
13752
14452
|
await installCommandShim(dryRun);
|
|
13753
14453
|
await installUserAgentInstructions(dryRun);
|
|
13754
14454
|
const serverPath = await findOpenVikingServer();
|
|
@@ -13793,17 +14493,17 @@ async function runInstall(config, options) {
|
|
|
13793
14493
|
}
|
|
13794
14494
|
await writeTemplateIfMissing({
|
|
13795
14495
|
config,
|
|
13796
|
-
destinationPath: (0,
|
|
14496
|
+
destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ov.conf"),
|
|
13797
14497
|
dryRun,
|
|
13798
14498
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
13799
|
-
templatePath: (0,
|
|
14499
|
+
templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
13800
14500
|
});
|
|
13801
14501
|
await writeTemplateIfMissing({
|
|
13802
14502
|
config,
|
|
13803
|
-
destinationPath: (0,
|
|
14503
|
+
destinationPath: (0, import_node_path15.join)(config.agentContextHome, "ovcli.conf"),
|
|
13804
14504
|
dryRun,
|
|
13805
14505
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
13806
|
-
templatePath: (0,
|
|
14506
|
+
templatePath: (0, import_node_path15.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
13807
14507
|
});
|
|
13808
14508
|
await configureOpenVikingCliLanguage(config, dryRun);
|
|
13809
14509
|
if (options.start !== false) {
|
|
@@ -13860,7 +14560,7 @@ async function runUninstall(config, options) {
|
|
|
13860
14560
|
}
|
|
13861
14561
|
console.log("Uninstalling local Threadnote setup.");
|
|
13862
14562
|
await runStop(config, { dryRun });
|
|
13863
|
-
await removePathIfExists((0,
|
|
14563
|
+
await removePathIfExists((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
13864
14564
|
await removeLaunchAgent(dryRun);
|
|
13865
14565
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
13866
14566
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -13917,16 +14617,16 @@ async function repairManifest(config, dryRun) {
|
|
|
13917
14617
|
console.log(output2.trimEnd());
|
|
13918
14618
|
return;
|
|
13919
14619
|
}
|
|
13920
|
-
await ensureDirectory((0,
|
|
14620
|
+
await ensureDirectory((0, import_node_path15.dirname)(config.manifestPath), false);
|
|
13921
14621
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
13922
14622
|
if (currentContent !== void 0) {
|
|
13923
14623
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
13924
|
-
await (0,
|
|
13925
|
-
await (0,
|
|
14624
|
+
await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
14625
|
+
await (0, import_promises14.chmod)(backupPath, 384);
|
|
13926
14626
|
console.log(`Backup: ${backupPath}`);
|
|
13927
14627
|
}
|
|
13928
|
-
await (0,
|
|
13929
|
-
await (0,
|
|
14628
|
+
await (0, import_promises14.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
14629
|
+
await (0, import_promises14.chmod)(config.manifestPath, 384);
|
|
13930
14630
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
13931
14631
|
}
|
|
13932
14632
|
async function repairRecallIndex(config, dryRun) {
|
|
@@ -14001,7 +14701,7 @@ async function findOpenVikingServer() {
|
|
|
14001
14701
|
return onPath;
|
|
14002
14702
|
}
|
|
14003
14703
|
for (const candidateDir of await openVikingServerCandidateDirs()) {
|
|
14004
|
-
const candidate = (0,
|
|
14704
|
+
const candidate = (0, import_node_path15.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
|
|
14005
14705
|
if (await isExecutable(candidate)) {
|
|
14006
14706
|
return candidate;
|
|
14007
14707
|
}
|
|
@@ -14047,7 +14747,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
|
|
|
14047
14747
|
if (onPath) {
|
|
14048
14748
|
return;
|
|
14049
14749
|
}
|
|
14050
|
-
const binDir = (0,
|
|
14750
|
+
const binDir = (0, import_node_path15.dirname)(serverPath);
|
|
14051
14751
|
const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
|
|
14052
14752
|
console.log(
|
|
14053
14753
|
`Note: ${serverPath} is installed but ${binDir} is not on this shell's PATH. Add \`export PATH="${binDir}:$PATH"\` to ${rcHint} so other tools can find openviking-server.`
|
|
@@ -14091,7 +14791,7 @@ async function runStart(config, options) {
|
|
|
14091
14791
|
);
|
|
14092
14792
|
}
|
|
14093
14793
|
const logPath = openVikingLogPath(config);
|
|
14094
|
-
await ensureDirectory((0,
|
|
14794
|
+
await ensureDirectory((0, import_node_path15.dirname)(logPath), false);
|
|
14095
14795
|
if (options.foreground === true) {
|
|
14096
14796
|
const result = await runInteractive(server, args);
|
|
14097
14797
|
process.exitCode = result;
|
|
@@ -14100,7 +14800,7 @@ async function runStart(config, options) {
|
|
|
14100
14800
|
const logFd = (0, import_node_fs7.openSync)(logPath, "a");
|
|
14101
14801
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
14102
14802
|
child.unref();
|
|
14103
|
-
await (0,
|
|
14803
|
+
await (0, import_promises14.writeFile)((0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
14104
14804
|
`, "utf8");
|
|
14105
14805
|
const health = await waitForOpenVikingHealth(
|
|
14106
14806
|
config,
|
|
@@ -14137,7 +14837,7 @@ async function runStop(config, options) {
|
|
|
14137
14837
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
14138
14838
|
}
|
|
14139
14839
|
}
|
|
14140
|
-
const pidPath = (0,
|
|
14840
|
+
const pidPath = (0, import_node_path15.join)(config.agentContextHome, "openviking-server.pid");
|
|
14141
14841
|
const pidText = await readFileIfExists(pidPath);
|
|
14142
14842
|
if (!pidText) {
|
|
14143
14843
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -14183,7 +14883,7 @@ async function openVikingServerCheck() {
|
|
|
14183
14883
|
}
|
|
14184
14884
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
14185
14885
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
14186
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
14886
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
|
|
14187
14887
|
return {
|
|
14188
14888
|
name,
|
|
14189
14889
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -14224,7 +14924,7 @@ async function openVikingCliCheck() {
|
|
|
14224
14924
|
}
|
|
14225
14925
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
14226
14926
|
const onPath = await findExecutable(["ov", "openviking"]);
|
|
14227
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
14927
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path15.dirname)(executable)} to PATH)`;
|
|
14228
14928
|
return {
|
|
14229
14929
|
name: "openviking cli",
|
|
14230
14930
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -14317,7 +15017,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
14317
15017
|
};
|
|
14318
15018
|
}
|
|
14319
15019
|
async function commandShimCheck() {
|
|
14320
|
-
const shimPath = (0,
|
|
15020
|
+
const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
14321
15021
|
const content = await readFileIfExists(shimPath);
|
|
14322
15022
|
if (content === void 0) {
|
|
14323
15023
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -14383,11 +15083,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
14383
15083
|
async function siblingPythonForExecutable(executablePath) {
|
|
14384
15084
|
let resolvedPath;
|
|
14385
15085
|
try {
|
|
14386
|
-
resolvedPath = await (0,
|
|
15086
|
+
resolvedPath = await (0, import_promises14.realpath)(executablePath);
|
|
14387
15087
|
} catch (_err) {
|
|
14388
15088
|
return void 0;
|
|
14389
15089
|
}
|
|
14390
|
-
const pythonPath = (0,
|
|
15090
|
+
const pythonPath = (0, import_node_path15.join)((0, import_node_path15.dirname)(resolvedPath), "python");
|
|
14391
15091
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
14392
15092
|
}
|
|
14393
15093
|
async function manifestCheck(path2) {
|
|
@@ -14428,6 +15128,61 @@ async function recallIndexFreshnessCheck(config) {
|
|
|
14428
15128
|
return { name: "recall index freshness", status: "warn", detail: errorMessage(err) };
|
|
14429
15129
|
}
|
|
14430
15130
|
}
|
|
15131
|
+
async function memoryProjectConsistencyCheck(config) {
|
|
15132
|
+
const name = "memory project consistency";
|
|
15133
|
+
const memoriesRoot = (0, import_node_path15.join)(
|
|
15134
|
+
config.agentContextHome,
|
|
15135
|
+
"data",
|
|
15136
|
+
"viking",
|
|
15137
|
+
config.account,
|
|
15138
|
+
"user",
|
|
15139
|
+
uriSegment(config.user),
|
|
15140
|
+
"memories"
|
|
15141
|
+
);
|
|
15142
|
+
try {
|
|
15143
|
+
let entries;
|
|
15144
|
+
try {
|
|
15145
|
+
entries = await (0, import_promises14.readdir)(memoriesRoot, { recursive: true });
|
|
15146
|
+
} catch {
|
|
15147
|
+
return { name, status: "ok", detail: "no memories directory yet" };
|
|
15148
|
+
}
|
|
15149
|
+
const mismatches = [];
|
|
15150
|
+
let checked = 0;
|
|
15151
|
+
for (const entry of entries) {
|
|
15152
|
+
if (!entry.endsWith(".md") || isSummarySidecarUri(entry)) {
|
|
15153
|
+
continue;
|
|
15154
|
+
}
|
|
15155
|
+
const uri = `viking://user/${uriSegment(config.user)}/memories/${entry.split(import_node_path15.sep).join("/")}`;
|
|
15156
|
+
const pathProject = memoryUriProjectSegment(uri);
|
|
15157
|
+
if (!pathProject) {
|
|
15158
|
+
continue;
|
|
15159
|
+
}
|
|
15160
|
+
let content;
|
|
15161
|
+
try {
|
|
15162
|
+
content = await (0, import_promises14.readFile)((0, import_node_path15.join)(memoriesRoot, entry), "utf8");
|
|
15163
|
+
} catch {
|
|
15164
|
+
continue;
|
|
15165
|
+
}
|
|
15166
|
+
checked += 1;
|
|
15167
|
+
const frontProject = memoryFrontmatterField(content, "project");
|
|
15168
|
+
if (frontProject && uriSegment(frontProject) !== pathProject) {
|
|
15169
|
+
mismatches.push(`${uri} (frontmatter "${frontProject}" vs path "${pathProject}")`);
|
|
15170
|
+
}
|
|
15171
|
+
}
|
|
15172
|
+
if (mismatches.length === 0) {
|
|
15173
|
+
return { name, status: "ok", detail: `${checked} project-scoped memories consistent` };
|
|
15174
|
+
}
|
|
15175
|
+
const sample = mismatches.slice(0, 3).join("; ");
|
|
15176
|
+
const extra = Math.max(0, mismatches.length - 3);
|
|
15177
|
+
return {
|
|
15178
|
+
name,
|
|
15179
|
+
status: "warn",
|
|
15180
|
+
detail: `${mismatches.length} memory(ies) whose frontmatter project differs from their storage path; re-store under the correct project to fix: ${sample}${extra > 0 ? `, +${extra} more` : ""}`
|
|
15181
|
+
};
|
|
15182
|
+
} catch (err) {
|
|
15183
|
+
return { name, status: "warn", detail: errorMessage(err) };
|
|
15184
|
+
}
|
|
15185
|
+
}
|
|
14431
15186
|
async function fileCheck(path2, label) {
|
|
14432
15187
|
return await exists(path2) ? { name: label, status: "ok", detail: path2 } : { name: label, status: "fail", detail: `${path2} missing` };
|
|
14433
15188
|
}
|
|
@@ -14659,7 +15414,7 @@ async function offerToInstallUv() {
|
|
|
14659
15414
|
);
|
|
14660
15415
|
return false;
|
|
14661
15416
|
}
|
|
14662
|
-
const readline = (0,
|
|
15417
|
+
const readline = (0, import_promises15.createInterface)({ input: import_node_process4.stdin, output: import_node_process4.stdout });
|
|
14663
15418
|
let answer;
|
|
14664
15419
|
try {
|
|
14665
15420
|
answer = (await readline.question(
|
|
@@ -14817,38 +15572,38 @@ function printInstallNextSteps(options) {
|
|
|
14817
15572
|
}
|
|
14818
15573
|
async function writeTemplateIfMissing(options) {
|
|
14819
15574
|
if (await exists(options.destinationPath)) {
|
|
14820
|
-
const currentContent = await (0,
|
|
15575
|
+
const currentContent = await (0, import_promises14.readFile)(options.destinationPath, "utf8");
|
|
14821
15576
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
14822
15577
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
14823
15578
|
return;
|
|
14824
15579
|
}
|
|
14825
|
-
const rendered2 = renderTemplate(await (0,
|
|
15580
|
+
const rendered2 = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
|
|
14826
15581
|
if (options.dryRun) {
|
|
14827
15582
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
14828
15583
|
return;
|
|
14829
15584
|
}
|
|
14830
15585
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
14831
|
-
await (0,
|
|
14832
|
-
await (0,
|
|
14833
|
-
await (0,
|
|
14834
|
-
await (0,
|
|
15586
|
+
await (0, import_promises14.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
15587
|
+
await (0, import_promises14.chmod)(backupPath, 384);
|
|
15588
|
+
await (0, import_promises14.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
15589
|
+
await (0, import_promises14.chmod)(options.destinationPath, 384);
|
|
14835
15590
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
14836
15591
|
console.log(`Backup: ${backupPath}`);
|
|
14837
15592
|
return;
|
|
14838
15593
|
}
|
|
14839
|
-
const rendered = renderTemplate(await (0,
|
|
15594
|
+
const rendered = renderTemplate(await (0, import_promises14.readFile)(options.templatePath, "utf8"), options.config);
|
|
14840
15595
|
if (options.dryRun) {
|
|
14841
15596
|
console.log(`Would write ${options.destinationPath}`);
|
|
14842
15597
|
return;
|
|
14843
15598
|
}
|
|
14844
|
-
await ensureDirectory((0,
|
|
14845
|
-
await (0,
|
|
14846
|
-
await (0,
|
|
15599
|
+
await ensureDirectory((0, import_node_path15.dirname)(options.destinationPath), false);
|
|
15600
|
+
await (0, import_promises14.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
15601
|
+
await (0, import_promises14.chmod)(options.destinationPath, 384);
|
|
14847
15602
|
console.log(`Wrote ${options.destinationPath}`);
|
|
14848
15603
|
}
|
|
14849
15604
|
async function installCommandShim(dryRun) {
|
|
14850
15605
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
14851
|
-
const shimPath = (0,
|
|
15606
|
+
const shimPath = (0, import_node_path15.join)(binDir, "threadnote");
|
|
14852
15607
|
const existingContent = await readFileIfExists(shimPath);
|
|
14853
15608
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
14854
15609
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -14864,12 +15619,12 @@ async function installCommandShim(dryRun) {
|
|
|
14864
15619
|
return;
|
|
14865
15620
|
}
|
|
14866
15621
|
await ensureDirectory(binDir, false);
|
|
14867
|
-
await (0,
|
|
14868
|
-
await (0,
|
|
15622
|
+
await (0, import_promises14.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
15623
|
+
await (0, import_promises14.chmod)(shimPath, 493);
|
|
14869
15624
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
14870
15625
|
}
|
|
14871
15626
|
async function removeCommandShim(dryRun) {
|
|
14872
|
-
const shimPath = (0,
|
|
15627
|
+
const shimPath = (0, import_node_path15.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
14873
15628
|
const content = await readFileIfExists(shimPath);
|
|
14874
15629
|
if (content === void 0) {
|
|
14875
15630
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -14903,8 +15658,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
14903
15658
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
14904
15659
|
continue;
|
|
14905
15660
|
}
|
|
14906
|
-
await ensureDirectory((0,
|
|
14907
|
-
await (0,
|
|
15661
|
+
await ensureDirectory((0, import_node_path15.dirname)(targetPath), false);
|
|
15662
|
+
await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
14908
15663
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
14909
15664
|
}
|
|
14910
15665
|
}
|
|
@@ -14941,7 +15696,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
14941
15696
|
console.log(`Would update ${targetPath}`);
|
|
14942
15697
|
continue;
|
|
14943
15698
|
}
|
|
14944
|
-
await (0,
|
|
15699
|
+
await (0, import_promises14.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
14945
15700
|
console.log(`Updated ${targetPath}`);
|
|
14946
15701
|
}
|
|
14947
15702
|
}
|
|
@@ -14961,7 +15716,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
14961
15716
|
].join("\n");
|
|
14962
15717
|
}
|
|
14963
15718
|
async function renderUserAgentInstructionsBlock() {
|
|
14964
|
-
const instructions = (await (0,
|
|
15719
|
+
const instructions = (await (0, import_promises14.readFile)((0, import_node_path15.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
14965
15720
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
14966
15721
|
${instructions}
|
|
14967
15722
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -15045,9 +15800,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
15045
15800
|
`Cannot install LaunchAgent: ${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
|
|
15046
15801
|
);
|
|
15047
15802
|
}
|
|
15048
|
-
const source = (0,
|
|
15803
|
+
const source = (0, import_node_path15.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
15049
15804
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
15050
|
-
const rendered = renderTemplate(await (0,
|
|
15805
|
+
const rendered = renderTemplate(await (0, import_promises14.readFile)(source, "utf8"), config, {
|
|
15051
15806
|
OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
|
|
15052
15807
|
});
|
|
15053
15808
|
if (dryRun) {
|
|
@@ -15059,9 +15814,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
15059
15814
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
15060
15815
|
return;
|
|
15061
15816
|
}
|
|
15062
|
-
await ensureDirectory((0,
|
|
15063
|
-
await ensureDirectory((0,
|
|
15064
|
-
await (0,
|
|
15817
|
+
await ensureDirectory((0, import_node_path15.dirname)(destination), false);
|
|
15818
|
+
await ensureDirectory((0, import_node_path15.dirname)(openVikingLogPath(config)), false);
|
|
15819
|
+
await (0, import_promises14.writeFile)(destination, rendered, "utf8");
|
|
15065
15820
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
15066
15821
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
15067
15822
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -15128,7 +15883,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
15128
15883
|
if (typeof parsed.default_user !== "string") {
|
|
15129
15884
|
return false;
|
|
15130
15885
|
}
|
|
15131
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
15886
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path15.join)(config.agentContextHome, "data")) {
|
|
15132
15887
|
return false;
|
|
15133
15888
|
}
|
|
15134
15889
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -15190,9 +15945,9 @@ function printWhatsNew(whatsNew) {
|
|
|
15190
15945
|
// src/manager.ts
|
|
15191
15946
|
var import_node_http2 = require("node:http");
|
|
15192
15947
|
var import_node_crypto2 = require("node:crypto");
|
|
15193
|
-
var
|
|
15948
|
+
var import_promises16 = require("node:fs/promises");
|
|
15194
15949
|
var import_node_os7 = require("node:os");
|
|
15195
|
-
var
|
|
15950
|
+
var import_node_path16 = require("node:path");
|
|
15196
15951
|
var STATIC_FILES = {
|
|
15197
15952
|
"/": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
15198
15953
|
"/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
@@ -15263,18 +16018,18 @@ async function readManagedMemory(config, uri) {
|
|
|
15263
16018
|
if (!path2) {
|
|
15264
16019
|
throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
|
|
15265
16020
|
}
|
|
15266
|
-
const [content, pathStat] = await Promise.all([(0,
|
|
15267
|
-
const relativePath = (0,
|
|
16021
|
+
const [content, pathStat] = await Promise.all([(0, import_promises16.readFile)(path2, "utf8"), (0, import_promises16.stat)(path2)]);
|
|
16022
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2).split(import_node_path16.sep).join("/");
|
|
15268
16023
|
const record = parseMemoryDocument(uri, content);
|
|
15269
16024
|
return {
|
|
15270
16025
|
content,
|
|
15271
16026
|
node: {
|
|
15272
16027
|
isDir: false,
|
|
15273
16028
|
isShared: isInSharedNamespace(config, uri),
|
|
15274
|
-
isSystem: isSystemMemoryName(path2.split(
|
|
16029
|
+
isSystem: isSystemMemoryName(path2.split(import_node_path16.sep).at(-1) ?? ""),
|
|
15275
16030
|
metadata: record?.metadata,
|
|
15276
16031
|
modTime: pathStat.mtime.toISOString(),
|
|
15277
|
-
name: path2.split(
|
|
16032
|
+
name: path2.split(import_node_path16.sep).at(-1) ?? uri,
|
|
15278
16033
|
relativePath,
|
|
15279
16034
|
sharedTeam: sharedTeamNameForUri(config, uri),
|
|
15280
16035
|
size: pathStat.size,
|
|
@@ -15562,7 +16317,7 @@ async function handleRequest(context, request, response) {
|
|
|
15562
16317
|
}
|
|
15563
16318
|
async function serveStatic(context, url, response) {
|
|
15564
16319
|
const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
|
|
15565
|
-
const content = await (0,
|
|
16320
|
+
const content = await (0, import_promises16.readFile)((0, import_node_path16.join)(toolRoot(), file.root ?? "manager", file.path));
|
|
15566
16321
|
const headers = { "content-type": file.contentType };
|
|
15567
16322
|
if (file.root !== "docs") {
|
|
15568
16323
|
headers["cache-control"] = "no-store";
|
|
@@ -15571,11 +16326,11 @@ async function serveStatic(context, url, response) {
|
|
|
15571
16326
|
response.end(content);
|
|
15572
16327
|
}
|
|
15573
16328
|
async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
15574
|
-
const pathStat = await (0,
|
|
16329
|
+
const pathStat = await (0, import_promises16.stat)(path2);
|
|
15575
16330
|
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
|
|
15576
16331
|
const isDir = pathStat.isDirectory();
|
|
15577
16332
|
if (!isDir) {
|
|
15578
|
-
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0,
|
|
16333
|
+
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises16.readFile)(path2, "utf8").catch(() => ""));
|
|
15579
16334
|
return {
|
|
15580
16335
|
isDir: false,
|
|
15581
16336
|
isShared: isInSharedNamespace(config, uri),
|
|
@@ -15589,13 +16344,13 @@ async function readTree(config, path2, uri, relativePath, options = {}) {
|
|
|
15589
16344
|
uri
|
|
15590
16345
|
};
|
|
15591
16346
|
}
|
|
15592
|
-
const entries = await (0,
|
|
16347
|
+
const entries = await (0, import_promises16.readdir)(path2, { withFileTypes: true });
|
|
15593
16348
|
const children = await Promise.all(
|
|
15594
16349
|
entries.sort(
|
|
15595
16350
|
(left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
|
|
15596
16351
|
).map((entry) => {
|
|
15597
16352
|
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
15598
|
-
return readTree(config, (0,
|
|
16353
|
+
return readTree(config, (0, import_node_path16.join)(path2, entry.name), `${uri}/${entry.name}`, childRelative, options);
|
|
15599
16354
|
})
|
|
15600
16355
|
);
|
|
15601
16356
|
return {
|
|
@@ -15769,27 +16524,27 @@ async function removeManagedFolder(config, uri) {
|
|
|
15769
16524
|
if (!path2) {
|
|
15770
16525
|
throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
|
|
15771
16526
|
}
|
|
15772
|
-
const pathStat = await (0,
|
|
16527
|
+
const pathStat = await (0, import_promises16.stat)(path2);
|
|
15773
16528
|
if (!pathStat.isDirectory()) {
|
|
15774
16529
|
throw new Error(`Not a folder: ${uri}`);
|
|
15775
16530
|
}
|
|
15776
|
-
const relativePath = (0,
|
|
15777
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
16531
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
|
|
16532
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
|
|
15778
16533
|
throw new Error("Refusing to remove a folder outside the memories tree.");
|
|
15779
16534
|
}
|
|
15780
16535
|
const fileUris = await fileUrisUnderFolder(config, path2);
|
|
15781
16536
|
for (const fileUri of fileUris) {
|
|
15782
16537
|
await runForget(config, fileUri, {});
|
|
15783
16538
|
}
|
|
15784
|
-
await (0,
|
|
16539
|
+
await (0, import_promises16.rm)(path2, { force: true, recursive: true });
|
|
15785
16540
|
console.log(`Removed folder: ${uri}`);
|
|
15786
16541
|
console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
|
|
15787
16542
|
}
|
|
15788
16543
|
async function fileUrisUnderFolder(config, folderPath) {
|
|
15789
|
-
const entries = await (0,
|
|
16544
|
+
const entries = await (0, import_promises16.readdir)(folderPath, { withFileTypes: true });
|
|
15790
16545
|
const uris = [];
|
|
15791
16546
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
15792
|
-
const path2 = (0,
|
|
16547
|
+
const path2 = (0, import_node_path16.join)(folderPath, entry.name);
|
|
15793
16548
|
if (entry.isDirectory()) {
|
|
15794
16549
|
uris.push(...await fileUrisUnderFolder(config, path2));
|
|
15795
16550
|
} else if (entry.isFile()) {
|
|
@@ -15887,11 +16642,11 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
15887
16642
|
throw new Error(`${agent} executable was not found.`);
|
|
15888
16643
|
}
|
|
15889
16644
|
const prompt = consolidationPrompt(sources);
|
|
15890
|
-
const stagingDir = await (0,
|
|
15891
|
-
const promptPath = (0,
|
|
16645
|
+
const stagingDir = await (0, import_promises16.mkdtemp)((0, import_node_path16.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
|
|
16646
|
+
const promptPath = (0, import_node_path16.join)(stagingDir, "prompt.txt");
|
|
15892
16647
|
try {
|
|
15893
|
-
await (0,
|
|
15894
|
-
await (0,
|
|
16648
|
+
await (0, import_promises16.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
|
|
16649
|
+
await (0, import_promises16.chmod)(promptPath, 384);
|
|
15895
16650
|
const script = consolidationAgentScript(agent, executable);
|
|
15896
16651
|
const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
|
|
15897
16652
|
allowFailure: true,
|
|
@@ -15907,7 +16662,7 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
15907
16662
|
}
|
|
15908
16663
|
return draft;
|
|
15909
16664
|
} finally {
|
|
15910
|
-
await (0,
|
|
16665
|
+
await (0, import_promises16.rm)(stagingDir, { force: true, recursive: true });
|
|
15911
16666
|
}
|
|
15912
16667
|
}
|
|
15913
16668
|
function consolidationAgentScript(agent, executable) {
|
|
@@ -16045,10 +16800,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
|
|
|
16045
16800
|
}
|
|
16046
16801
|
}
|
|
16047
16802
|
function localMemoriesRoot(config) {
|
|
16048
|
-
return (0,
|
|
16803
|
+
return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
|
|
16049
16804
|
}
|
|
16050
16805
|
function localResourcesRoot(config) {
|
|
16051
|
-
return (0,
|
|
16806
|
+
return (0, import_node_path16.join)(config.agentContextHome, "data", "viking", config.account, "resources");
|
|
16052
16807
|
}
|
|
16053
16808
|
function localPathForMemoryUri(config, uri) {
|
|
16054
16809
|
const prefix = `viking://user/${uriSegment(config.user)}/memories`;
|
|
@@ -16060,17 +16815,17 @@ function localPathForMemoryUri(config, uri) {
|
|
|
16060
16815
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
16061
16816
|
return void 0;
|
|
16062
16817
|
}
|
|
16063
|
-
return (0,
|
|
16818
|
+
return (0, import_node_path16.join)(localMemoriesRoot(config), ...segments);
|
|
16064
16819
|
}
|
|
16065
16820
|
function isMissingPathError(err) {
|
|
16066
16821
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
16067
16822
|
}
|
|
16068
16823
|
function localPathToMemoryUri(config, path2) {
|
|
16069
|
-
const relativePath = (0,
|
|
16070
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
16824
|
+
const relativePath = (0, import_node_path16.relative)(localMemoriesRoot(config), path2);
|
|
16825
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path16.sep).includes("..")) {
|
|
16071
16826
|
throw new Error(`Path is outside the memories tree: ${path2}`);
|
|
16072
16827
|
}
|
|
16073
|
-
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(
|
|
16828
|
+
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path16.sep).join("/")}`;
|
|
16074
16829
|
}
|
|
16075
16830
|
async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
|
|
16076
16831
|
const prefix = "viking://";
|
|
@@ -16264,7 +17019,10 @@ async function main() {
|
|
|
16264
17019
|
).option("--preserve-memories", "Preserve THREADNOTE_HOME and OpenViking memories (default)").option("--erase-memories", "Delete THREADNOTE_HOME, including all OpenViking memories").action(async (options) => {
|
|
16265
17020
|
await runUninstall(getRuntimeConfig(program2), options);
|
|
16266
17021
|
});
|
|
16267
|
-
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option(
|
|
17022
|
+
program2.command("seed").description("Seed curated context from the manifest; never indexes whole repos by default").option("--dry-run", "Print files and ov commands without importing").option("--force", "Re-upload every candidate even if mtime+size match the recorded state").option(
|
|
17023
|
+
"--graph",
|
|
17024
|
+
"Also seed a per-project .graph.md dependency-facts resource (package.json/go.mod), with [[project]] cross-repo edges"
|
|
17025
|
+
).option("--manifest <path>", "Manifest path for this seed run").option(
|
|
16268
17026
|
"--only <project>",
|
|
16269
17027
|
"Restrict seeding to one or more manifest projects by name; repeat for multiple",
|
|
16270
17028
|
collectOption,
|
|
@@ -16310,9 +17068,19 @@ async function main() {
|
|
|
16310
17068
|
program2.command("migrate-lifecycle").description("Move clear legacy handoff memories into lifecycle-aware archive paths").option("--apply", "Perform the migration; without this, prints a dry run").option("--dry-run", "Print migration actions without writing or removing memories").option("--limit <count>", "Maximum number of legacy handoffs to migrate").action(async (options) => {
|
|
16311
17069
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
16312
17070
|
});
|
|
16313
|
-
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").
|
|
17071
|
+
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").option(
|
|
17072
|
+
"--workset <name>",
|
|
17073
|
+
"Recall across a named seed-manifest workset (a set of related repos) as one working set"
|
|
17074
|
+
).action(async (options) => {
|
|
16314
17075
|
await runRecall(getRuntimeConfig(program2), options);
|
|
16315
17076
|
});
|
|
17077
|
+
const workset = program2.command("workset").description("Inspect seed-manifest worksets (named sets of related repos recalled as one working set)");
|
|
17078
|
+
workset.command("list").description("List worksets defined in the seed manifest").action(async () => {
|
|
17079
|
+
await runWorksetList(getRuntimeConfig(program2));
|
|
17080
|
+
});
|
|
17081
|
+
workset.command("show").description("Show the member projects of a workset").argument("<name>", "Workset name").action(async (name) => {
|
|
17082
|
+
await runWorksetShow(getRuntimeConfig(program2), name);
|
|
17083
|
+
});
|
|
16316
17084
|
program2.command("compact").description("Plan or apply scoped memory hygiene for active personal memories").requiredOption("--project <name>", "Project/repo namespace to inspect").option("--apply", "Apply the compact plan; without this, prints a dry run").option("--dry-run", "Print the compact plan without changing anything").option("--kind <kind>", "durable, handoff, or incident", parseCompactKind).option("--topic <name>", "Stable topic name to inspect").action(async (options) => {
|
|
16317
17085
|
await runCompact(getRuntimeConfig(program2), options);
|
|
16318
17086
|
});
|
|
@@ -16322,8 +17090,13 @@ async function main() {
|
|
|
16322
17090
|
program2.command("list").alias("ls").description("List a viking:// directory").argument("[uri]", "viking:// URI to list", "viking://").option("-a, --all", "Show hidden files such as .abstract.md and .overview.md").option("--dry-run", "Print ov command without listing").option("-n, --node-limit <count>", "Maximum number of nodes to list").option("-r, --recursive", "List subdirectories recursively").option("-s, --simple", "Print only paths").action(async (uri, options) => {
|
|
16323
17091
|
await runList(getRuntimeConfig(program2), uri, options);
|
|
16324
17092
|
});
|
|
16325
|
-
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--
|
|
16326
|
-
|
|
17093
|
+
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--ci <text>", "Captured CI status snapshot (free text; not a live status board)").option("--dry-run", "Print handoff without storing").option("--issue <text>", "Related issue reference (number or URL)").option("--next-step <text>", "Suggested next step").option("--pr <text>", "Related pull request reference (number or URL)").option("--project <name>", "Project/repo namespace; defaults to current repo basename").option(
|
|
17094
|
+
"--reference <uri>",
|
|
17095
|
+
"viking:// memory to record as one-way read-only prior context; repeat for multiple",
|
|
17096
|
+
collectOption,
|
|
17097
|
+
[]
|
|
17098
|
+
).option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").option("--timestamped", "Store a historical timestamped handoff instead of updating the current branch handoff").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
|
|
17099
|
+
await runHandoff(getRuntimeConfig(program2), { ...options, references: options.reference });
|
|
16327
17100
|
});
|
|
16328
17101
|
program2.command("archive").description("Move a memory into the archived lifecycle tree, then remove the original after the archive is stored").argument("<uri>", "viking:// memory URI to archive").option("--dry-run", "Print archive content and ov commands without changing anything").option("--kind <kind>", "durable, handoff, incident, preference, or smoke", parseMemoryKind).option("--project <name>", "Override inferred project/repo namespace").option("--topic <name>", "Override inferred topic").action(async (uri, options) => {
|
|
16329
17102
|
await runArchive(getRuntimeConfig(program2), uri, options);
|