threadnote 0.7.5 → 0.7.7
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/README.md +38 -13
- package/dist/mcp_server.cjs +845 -38
- package/dist/threadnote.cjs +891 -146
- package/docs/agent-instructions.md +29 -7
- package/docs/index.html +1 -1
- package/docs/migration.md +4 -2
- package/docs/share.md +18 -1
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3524,7 +3524,7 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3524
3524
|
|
|
3525
3525
|
// src/hooks.ts
|
|
3526
3526
|
var import_promises7 = require("node:fs/promises");
|
|
3527
|
-
var
|
|
3527
|
+
var import_node_path9 = require("node:path");
|
|
3528
3528
|
|
|
3529
3529
|
// src/mcp.ts
|
|
3530
3530
|
var import_node_fs2 = require("node:fs");
|
|
@@ -3958,6 +3958,57 @@ function portablePath(path) {
|
|
|
3958
3958
|
function getInvocationCwd() {
|
|
3959
3959
|
return process.env.THREADNOTE_CALLER_CWD ?? process.cwd();
|
|
3960
3960
|
}
|
|
3961
|
+
function recallQueryRequestsWorkspaceContext(query) {
|
|
3962
|
+
const normalized = query.toLowerCase();
|
|
3963
|
+
return /\b(?:this|current)\s+(?:branch|repo|repository|workspace|worktree)\b/.test(normalized);
|
|
3964
|
+
}
|
|
3965
|
+
async function enrichRecallQueryWithWorkspaceContext(query, options = {}) {
|
|
3966
|
+
return enrichRecallQueryWithWorkspaceTerms(query, options, true);
|
|
3967
|
+
}
|
|
3968
|
+
async function enrichRecallQueryWithWorkspaceProjectContext(query, options = {}) {
|
|
3969
|
+
return enrichRecallQueryWithWorkspaceTerms(query, options, false);
|
|
3970
|
+
}
|
|
3971
|
+
async function enrichRecallQueryWithWorkspaceTerms(query, options, includeBranch) {
|
|
3972
|
+
if (!recallQueryRequestsWorkspaceContext(query)) {
|
|
3973
|
+
return query;
|
|
3974
|
+
}
|
|
3975
|
+
const terms = await currentWorkspaceRecallTerms(options, includeBranch);
|
|
3976
|
+
const additions = terms.filter((term) => !query.toLowerCase().includes(term.toLowerCase()));
|
|
3977
|
+
return additions.length > 0 ? `${query} ${additions.join(" ")}` : query;
|
|
3978
|
+
}
|
|
3979
|
+
async function currentWorkspaceRecallTerms(options, includeBranch) {
|
|
3980
|
+
const cwd = options.cwd ?? (options.includeProcessCwd === false ? void 0 : getInvocationCwd());
|
|
3981
|
+
if (!cwd || !(0, import_node_path.isAbsolute)(cwd)) {
|
|
3982
|
+
return [];
|
|
3983
|
+
}
|
|
3984
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], cwd);
|
|
3985
|
+
if (!repoRoot) {
|
|
3986
|
+
return [];
|
|
3987
|
+
}
|
|
3988
|
+
const branch = await gitValue(["branch", "--show-current"], repoRoot);
|
|
3989
|
+
const parent = (0, import_node_path.dirname)(repoRoot);
|
|
3990
|
+
return uniqueUsefulWorkspaceTerms([
|
|
3991
|
+
{ source: "branch", value: includeBranch ? branch : void 0 },
|
|
3992
|
+
{ source: "path", value: (0, import_node_path.basename)(repoRoot) },
|
|
3993
|
+
{ source: "path", value: parent === (0, import_node_os.homedir)() ? void 0 : (0, import_node_path.basename)(parent) }
|
|
3994
|
+
]);
|
|
3995
|
+
}
|
|
3996
|
+
function uniqueUsefulWorkspaceTerms(values) {
|
|
3997
|
+
const ignored = /* @__PURE__ */ new Set(["repos", "repositories", "workspaces", "worktrees"]);
|
|
3998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3999
|
+
const terms = [];
|
|
4000
|
+
for (const { source, value } of values) {
|
|
4001
|
+
const term = value?.trim();
|
|
4002
|
+
const normalized = term?.toLowerCase();
|
|
4003
|
+
const tooShort = source === "branch" ? false : (term?.length ?? 0) < 4;
|
|
4004
|
+
if (!term || !normalized || tooShort || ignored.has(normalized) || seen.has(normalized)) {
|
|
4005
|
+
continue;
|
|
4006
|
+
}
|
|
4007
|
+
seen.add(normalized);
|
|
4008
|
+
terms.push(term);
|
|
4009
|
+
}
|
|
4010
|
+
return terms;
|
|
4011
|
+
}
|
|
3961
4012
|
function toPosixPath(path) {
|
|
3962
4013
|
return path.split(import_node_path.sep).join("/");
|
|
3963
4014
|
}
|
|
@@ -3981,19 +4032,29 @@ function exactRecallTerms(query) {
|
|
|
3981
4032
|
"branch",
|
|
3982
4033
|
"case",
|
|
3983
4034
|
"current",
|
|
4035
|
+
"durable",
|
|
3984
4036
|
"find",
|
|
4037
|
+
"feature",
|
|
4038
|
+
"features",
|
|
3985
4039
|
"handoff",
|
|
3986
4040
|
"issue",
|
|
3987
4041
|
"issues",
|
|
4042
|
+
"knowledge",
|
|
3988
4043
|
"latest",
|
|
3989
4044
|
"memory",
|
|
3990
4045
|
"memories",
|
|
4046
|
+
"project",
|
|
3991
4047
|
"recall",
|
|
4048
|
+
"repo",
|
|
4049
|
+
"repository",
|
|
3992
4050
|
"related",
|
|
3993
4051
|
"search",
|
|
3994
|
-
"
|
|
4052
|
+
"stored",
|
|
3995
4053
|
"this",
|
|
3996
|
-
"
|
|
4054
|
+
"the",
|
|
4055
|
+
"with",
|
|
4056
|
+
"workspace",
|
|
4057
|
+
"worktree"
|
|
3997
4058
|
]);
|
|
3998
4059
|
const seen = /* @__PURE__ */ new Set();
|
|
3999
4060
|
const terms = [];
|
|
@@ -4541,7 +4602,7 @@ function existsSyncDirectory(path) {
|
|
|
4541
4602
|
|
|
4542
4603
|
// src/memory.ts
|
|
4543
4604
|
var import_promises5 = require("node:fs/promises");
|
|
4544
|
-
var
|
|
4605
|
+
var import_node_path6 = require("node:path");
|
|
4545
4606
|
|
|
4546
4607
|
// src/manifest.ts
|
|
4547
4608
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -7239,10 +7300,445 @@ function readStringArray(object, key) {
|
|
|
7239
7300
|
return value;
|
|
7240
7301
|
}
|
|
7241
7302
|
|
|
7303
|
+
// src/memory_hygiene.ts
|
|
7304
|
+
var import_node_path3 = require("node:path");
|
|
7305
|
+
var HYGIENE_SOURCES_HEADING = "## Threadnote Hygiene Sources";
|
|
7306
|
+
var STALE_HANDOFF_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
7307
|
+
function parseMemoryDocument(uri, content) {
|
|
7308
|
+
const trimmed = content.trim();
|
|
7309
|
+
if (!trimmed) {
|
|
7310
|
+
return void 0;
|
|
7311
|
+
}
|
|
7312
|
+
const separatorIndex = trimmed.indexOf("\n\n");
|
|
7313
|
+
const header = separatorIndex === -1 ? trimmed : trimmed.slice(0, separatorIndex);
|
|
7314
|
+
const body = separatorIndex === -1 ? "" : trimmed.slice(separatorIndex + 2).trim();
|
|
7315
|
+
const firstLine2 = header.split("\n")[0]?.trim();
|
|
7316
|
+
if (firstLine2 !== "MEMORY" && firstLine2 !== "HANDOFF") {
|
|
7317
|
+
return void 0;
|
|
7318
|
+
}
|
|
7319
|
+
const kind = parseOptionalMemoryKind(headerValue(header, "kind")) ?? (firstLine2 === "HANDOFF" ? "handoff" : void 0);
|
|
7320
|
+
const status = parseOptionalMemoryStatus(headerValue(header, "status")) ?? "active";
|
|
7321
|
+
if (!kind) {
|
|
7322
|
+
return void 0;
|
|
7323
|
+
}
|
|
7324
|
+
return {
|
|
7325
|
+
body,
|
|
7326
|
+
content: trimmed,
|
|
7327
|
+
headerTitle: firstLine2,
|
|
7328
|
+
metadata: {
|
|
7329
|
+
archivedFrom: headerValue(header, "archived_from"),
|
|
7330
|
+
kind,
|
|
7331
|
+
project: normalizeOptionalMetadata(headerValue(header, "project") ?? headerValue(header, "repo")),
|
|
7332
|
+
sourceAgentClient: headerValue(header, "source_agent_client") ?? "unknown",
|
|
7333
|
+
status,
|
|
7334
|
+
supersedes: headerValue(header, "supersedes"),
|
|
7335
|
+
timestamp: headerValue(header, "timestamp") ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
7336
|
+
topic: normalizeOptionalMetadata(headerValue(header, "topic"))
|
|
7337
|
+
},
|
|
7338
|
+
uri
|
|
7339
|
+
};
|
|
7340
|
+
}
|
|
7341
|
+
function buildCompactPlan(records, options) {
|
|
7342
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
7343
|
+
const groupedRecords = records.map((record) => groupableRecord(record)).filter((item) => item !== void 0).filter((item) => item.project === options.project).filter((item) => options.topic === void 0 || item.topic === options.topic).filter((item) => options.kind === void 0 || item.record.metadata.kind === options.kind);
|
|
7344
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7345
|
+
for (const item of groupedRecords) {
|
|
7346
|
+
groups.set(item.groupKey, [...groups.get(item.groupKey) ?? [], item]);
|
|
7347
|
+
}
|
|
7348
|
+
const keepUpdates = [];
|
|
7349
|
+
const archives = [];
|
|
7350
|
+
const forgets = [];
|
|
7351
|
+
const manualReview = [];
|
|
7352
|
+
for (const group of [...groups.values()].sort(compareGroupedRecordLists)) {
|
|
7353
|
+
const recordsInGroup = group.map((item) => item.record);
|
|
7354
|
+
const kind = recordsInGroup[0]?.metadata.kind;
|
|
7355
|
+
const topic = group[0]?.topic;
|
|
7356
|
+
const project = group[0]?.project;
|
|
7357
|
+
if (!kind || !project || !isCompactableKind(kind)) {
|
|
7358
|
+
continue;
|
|
7359
|
+
}
|
|
7360
|
+
const duplicateGroups = groupBy(recordsInGroup, (record) => comparableMemoryBody(record.body));
|
|
7361
|
+
const duplicateForgetUris = /* @__PURE__ */ new Set();
|
|
7362
|
+
const distinctBodyCount = duplicateGroups.size;
|
|
7363
|
+
for (const duplicateGroup of duplicateGroups.values()) {
|
|
7364
|
+
if (duplicateGroup.length < 2) {
|
|
7365
|
+
continue;
|
|
7366
|
+
}
|
|
7367
|
+
const duplicateKeep = preferredKeepRecord(duplicateGroup, topic);
|
|
7368
|
+
for (const duplicate of sortedNewestFirst(duplicateGroup).filter((record) => record.uri !== duplicateKeep.uri)) {
|
|
7369
|
+
duplicateForgetUris.add(duplicate.uri);
|
|
7370
|
+
forgets.push({ reason: `exact duplicate of ${duplicateKeep.uri}`, uri: duplicate.uri });
|
|
7371
|
+
}
|
|
7372
|
+
if (distinctBodyCount === 1 || kind !== "handoff") {
|
|
7373
|
+
keepUpdates.push({
|
|
7374
|
+
content: memoryContentWithHygieneSources(
|
|
7375
|
+
duplicateKeep,
|
|
7376
|
+
duplicateGroup.map((record) => record.uri)
|
|
7377
|
+
),
|
|
7378
|
+
reason: "keep exact duplicate group with source URIs",
|
|
7379
|
+
sourceUris: duplicateGroup.map((record) => record.uri),
|
|
7380
|
+
uri: duplicateKeep.uri
|
|
7381
|
+
});
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
const remainingRecords = recordsInGroup.filter((record) => !duplicateForgetUris.has(record.uri));
|
|
7385
|
+
if (recordsInGroup.length > 1 && distinctBodyCount === 1) {
|
|
7386
|
+
continue;
|
|
7387
|
+
}
|
|
7388
|
+
if (remainingRecords.length === 0) {
|
|
7389
|
+
continue;
|
|
7390
|
+
}
|
|
7391
|
+
if (remainingRecords.length === 1) {
|
|
7392
|
+
const [record] = remainingRecords;
|
|
7393
|
+
if (!record) {
|
|
7394
|
+
continue;
|
|
7395
|
+
}
|
|
7396
|
+
if (record.metadata.supersedes === record.uri) {
|
|
7397
|
+
keepUpdates.push({
|
|
7398
|
+
content: memoryContentWithHygieneSources(record, [record.uri]),
|
|
7399
|
+
reason: "strip self-supersedes header",
|
|
7400
|
+
sourceUris: [record.uri],
|
|
7401
|
+
uri: record.uri
|
|
7402
|
+
});
|
|
7403
|
+
}
|
|
7404
|
+
if (isStaleLookingHandoff(record, now)) {
|
|
7405
|
+
manualReview.push({ reason: "stale-looking active handoff", uri: record.uri });
|
|
7406
|
+
}
|
|
7407
|
+
continue;
|
|
7408
|
+
}
|
|
7409
|
+
if (kind === "handoff") {
|
|
7410
|
+
const keep = preferredKeepRecord(remainingRecords, topic);
|
|
7411
|
+
const sourceUris = recordsInGroup.map((record) => record.uri);
|
|
7412
|
+
keepUpdates.push({
|
|
7413
|
+
content: memoryContentWithHygieneSources(keep, sourceUris),
|
|
7414
|
+
reason: "keep latest handoff and preserve source URIs",
|
|
7415
|
+
sourceUris,
|
|
7416
|
+
uri: keep.uri
|
|
7417
|
+
});
|
|
7418
|
+
for (const record of sortedNewestFirst(remainingRecords).filter((item) => item.uri !== keep.uri)) {
|
|
7419
|
+
archives.push({
|
|
7420
|
+
kind,
|
|
7421
|
+
project,
|
|
7422
|
+
reason: `older handoff for ${project}/${topic ?? "unknown"}`,
|
|
7423
|
+
topic,
|
|
7424
|
+
uri: record.uri
|
|
7425
|
+
});
|
|
7426
|
+
}
|
|
7427
|
+
continue;
|
|
7428
|
+
}
|
|
7429
|
+
for (const record of sortedNewestFirst(remainingRecords)) {
|
|
7430
|
+
manualReview.push({ reason: `non-exact ${kind} memory in overlapping group`, uri: record.uri });
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7433
|
+
return {
|
|
7434
|
+
archives: dedupeByUri(archives),
|
|
7435
|
+
forgets: dedupeByUri(forgets),
|
|
7436
|
+
keepUpdates: dedupeByUri(keepUpdates),
|
|
7437
|
+
manualReview: dedupeByUri(manualReview),
|
|
7438
|
+
options,
|
|
7439
|
+
recordsScanned: groupedRecords.length
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
function memoryContentWithHygieneSources(record, sourceUris) {
|
|
7443
|
+
const body = stripHygieneSources(record.body);
|
|
7444
|
+
const uniqueSourceUris = [...new Set(sourceUris)].sort();
|
|
7445
|
+
const metadata = {
|
|
7446
|
+
...record.metadata,
|
|
7447
|
+
supersedes: record.metadata.supersedes === record.uri ? void 0 : record.metadata.supersedes
|
|
7448
|
+
};
|
|
7449
|
+
return formatMemoryDocument(
|
|
7450
|
+
record.headerTitle,
|
|
7451
|
+
metadata,
|
|
7452
|
+
[body, "", HYGIENE_SOURCES_HEADING, "", ...uniqueSourceUris.map((uri) => `- ${uri}`)].join("\n")
|
|
7453
|
+
);
|
|
7454
|
+
}
|
|
7455
|
+
function formatCompactPlan(plan, options) {
|
|
7456
|
+
const scope = [
|
|
7457
|
+
`project ${plan.options.project}`,
|
|
7458
|
+
plan.options.topic ? `topic ${plan.options.topic}` : void 0,
|
|
7459
|
+
plan.options.kind ? `kind ${plan.options.kind}` : void 0
|
|
7460
|
+
].filter((item) => item !== void 0).join(", ");
|
|
7461
|
+
const lines = [
|
|
7462
|
+
`${options.apply ? "Applying" : "Dry-run"} memory hygiene plan for ${scope}`,
|
|
7463
|
+
`Records scanned: ${plan.recordsScanned}`,
|
|
7464
|
+
"",
|
|
7465
|
+
formatPlanSection(
|
|
7466
|
+
"Keep/update",
|
|
7467
|
+
plan.keepUpdates.map((action) => `${action.uri} (${action.reason}; sources: ${action.sourceUris.length})`)
|
|
7468
|
+
),
|
|
7469
|
+
formatPlanSection(
|
|
7470
|
+
"Archive old handoffs",
|
|
7471
|
+
plan.archives.map((action) => `${action.uri} (${action.reason})`)
|
|
7472
|
+
),
|
|
7473
|
+
formatPlanSection(
|
|
7474
|
+
"Forget exact duplicates",
|
|
7475
|
+
plan.forgets.map((action) => `${action.uri} (${action.reason})`)
|
|
7476
|
+
),
|
|
7477
|
+
formatPlanSection(
|
|
7478
|
+
"Manual review",
|
|
7479
|
+
plan.manualReview.map((item) => `${item.uri} (${item.reason})`)
|
|
7480
|
+
)
|
|
7481
|
+
];
|
|
7482
|
+
if (!options.apply) {
|
|
7483
|
+
lines.push("", "No changes made. Re-run with --apply to execute this plan.");
|
|
7484
|
+
}
|
|
7485
|
+
return lines.join("\n");
|
|
7486
|
+
}
|
|
7487
|
+
function recallHygieneNudges(text, options) {
|
|
7488
|
+
const activeUris = activePersonalMemoryUrisFromText(text, options.user);
|
|
7489
|
+
const nudges = [];
|
|
7490
|
+
const returnedUriSet = new Set(activeUris);
|
|
7491
|
+
const returnedRecords = options.records?.filter((record) => returnedUriSet.has(record.uri)).map((record) => groupableRecord(record)) ?? [];
|
|
7492
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7493
|
+
for (const item of returnedRecords) {
|
|
7494
|
+
if (!item) {
|
|
7495
|
+
continue;
|
|
7496
|
+
}
|
|
7497
|
+
groups.set(item.groupKey, [...groups.get(item.groupKey) ?? [], item]);
|
|
7498
|
+
}
|
|
7499
|
+
for (const group of [...groups.values()].sort(compareGroupedRecordLists)) {
|
|
7500
|
+
if (group.length < 3) {
|
|
7501
|
+
continue;
|
|
7502
|
+
}
|
|
7503
|
+
const first = group[0];
|
|
7504
|
+
if (!first || !isCompactableKind(first.record.metadata.kind)) {
|
|
7505
|
+
continue;
|
|
7506
|
+
}
|
|
7507
|
+
const topic = first.topic;
|
|
7508
|
+
if (!topic) {
|
|
7509
|
+
continue;
|
|
7510
|
+
}
|
|
7511
|
+
nudges.push(
|
|
7512
|
+
`${group.length} active ${memoryKindPlural(first.record.metadata.kind)} look overlapping for ${first.project}/${topic}; run compact_context({"project":"${first.project}","topic":"${topic}","dryRun":true}).`
|
|
7513
|
+
);
|
|
7514
|
+
}
|
|
7515
|
+
const projectCounts = /* @__PURE__ */ new Map();
|
|
7516
|
+
for (const uri of activeUris) {
|
|
7517
|
+
const parsed = parsePersonalMemoryUri(uri, options.user);
|
|
7518
|
+
if (!parsed || parsed.kind !== "handoff" || parsed.status !== "active") {
|
|
7519
|
+
continue;
|
|
7520
|
+
}
|
|
7521
|
+
projectCounts.set(parsed.project, (projectCounts.get(parsed.project) ?? 0) + 1);
|
|
7522
|
+
}
|
|
7523
|
+
for (const [project, count] of [...projectCounts.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
7524
|
+
if (count < 10) {
|
|
7525
|
+
continue;
|
|
7526
|
+
}
|
|
7527
|
+
nudges.push(
|
|
7528
|
+
`Many active handoffs surfaced for ${project}; run compact_context({"project":"${project}","dryRun":true}).`
|
|
7529
|
+
);
|
|
7530
|
+
}
|
|
7531
|
+
return [...new Set(nudges)];
|
|
7532
|
+
}
|
|
7533
|
+
function activePersonalMemoryUrisFromText(text, user) {
|
|
7534
|
+
const userSegment = uriSegment(user);
|
|
7535
|
+
const matches = text.matchAll(/viking:\/\/[^\s)]+/g);
|
|
7536
|
+
const uris = [];
|
|
7537
|
+
for (const match of matches) {
|
|
7538
|
+
const uri = match[0]?.replace(/[.,;:]+$/g, "");
|
|
7539
|
+
if (!uri || !parsePersonalMemoryUri(uri, userSegment)) {
|
|
7540
|
+
continue;
|
|
7541
|
+
}
|
|
7542
|
+
uris.push(uri);
|
|
7543
|
+
}
|
|
7544
|
+
return [...new Set(uris)];
|
|
7545
|
+
}
|
|
7546
|
+
function parsePersonalMemoryUri(uri, user) {
|
|
7547
|
+
const prefix = `viking://user/${uriSegment(user)}/memories/`;
|
|
7548
|
+
if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
|
|
7549
|
+
return void 0;
|
|
7550
|
+
}
|
|
7551
|
+
const rest = uri.slice(prefix.length);
|
|
7552
|
+
const parts = rest.split("/").filter(Boolean);
|
|
7553
|
+
if (parts.length < 4) {
|
|
7554
|
+
return void 0;
|
|
7555
|
+
}
|
|
7556
|
+
if (parts[0] === "handoffs" && parts[1] === "active" && parts[2] && parts[3]?.endsWith(".md")) {
|
|
7557
|
+
return { kind: "handoff", project: parts[2], status: "active", topic: parts[3].replace(/\.md$/, "") };
|
|
7558
|
+
}
|
|
7559
|
+
if (parts[0] === "durable" && parts[1] === "projects" && parts[2] && parts[3]?.endsWith(".md")) {
|
|
7560
|
+
return { kind: "durable", project: parts[2], status: "active", topic: parts[3].replace(/\.md$/, "") };
|
|
7561
|
+
}
|
|
7562
|
+
if (parts[0] === "incidents" && parts[1] === "active" && parts[2] && parts[3]?.endsWith(".md")) {
|
|
7563
|
+
return { kind: "incident", project: parts[2], status: "active", topic: parts[3].replace(/\.md$/, "") };
|
|
7564
|
+
}
|
|
7565
|
+
return void 0;
|
|
7566
|
+
}
|
|
7567
|
+
function handoffTopicForBranch(branch, options) {
|
|
7568
|
+
const topic = normalizeOptionalMetadata(options.topic);
|
|
7569
|
+
if (options.timestamped === true) {
|
|
7570
|
+
if (topic) {
|
|
7571
|
+
throw new Error("Cannot combine --timestamped with --topic.");
|
|
7572
|
+
}
|
|
7573
|
+
return void 0;
|
|
7574
|
+
}
|
|
7575
|
+
return topic ?? normalizeOptionalMetadata(branch) ?? "current";
|
|
7576
|
+
}
|
|
7577
|
+
function groupableRecord(record) {
|
|
7578
|
+
if (record.metadata.status !== "active" || !isCompactableKind(record.metadata.kind)) {
|
|
7579
|
+
return void 0;
|
|
7580
|
+
}
|
|
7581
|
+
const project = normalizeOptionalMetadata(record.metadata.project) ?? parseProjectFromUri(record.uri);
|
|
7582
|
+
if (!project) {
|
|
7583
|
+
return void 0;
|
|
7584
|
+
}
|
|
7585
|
+
const topic = topicForRecord(record);
|
|
7586
|
+
const groupKey = [record.metadata.kind, project, topic ?? record.uri].join("\0");
|
|
7587
|
+
return { groupKey, project, record, topic };
|
|
7588
|
+
}
|
|
7589
|
+
function topicForRecord(record) {
|
|
7590
|
+
return normalizeOptionalMetadata(record.metadata.topic) ?? normalizeOptionalMetadata(branchFromBody(record.body)) ?? topicFromUri(record.uri);
|
|
7591
|
+
}
|
|
7592
|
+
function branchFromBody(body) {
|
|
7593
|
+
const branch = /^branch:\s*(.+)$/m.exec(body)?.[1]?.trim();
|
|
7594
|
+
return branch?.split(/\s+/)[0]?.replace(/[.,;:]+$/g, "");
|
|
7595
|
+
}
|
|
7596
|
+
function topicFromUri(uri) {
|
|
7597
|
+
const name = (0, import_node_path3.basename)(uri).replace(/\.md$/, "");
|
|
7598
|
+
return name.startsWith("threadnote-") ? void 0 : name;
|
|
7599
|
+
}
|
|
7600
|
+
function parseProjectFromUri(uri) {
|
|
7601
|
+
const parts = uri.split("/memories/")[1]?.split("/").filter(Boolean) ?? [];
|
|
7602
|
+
if ((parts[0] === "handoffs" || parts[0] === "incidents") && parts[1] === "active") {
|
|
7603
|
+
return parts[2];
|
|
7604
|
+
}
|
|
7605
|
+
if (parts[0] === "durable" && parts[1] === "projects") {
|
|
7606
|
+
return parts[2];
|
|
7607
|
+
}
|
|
7608
|
+
return void 0;
|
|
7609
|
+
}
|
|
7610
|
+
function comparableMemoryBody(body) {
|
|
7611
|
+
return stripHygieneSources(body).trim().replace(/\s+/g, " ");
|
|
7612
|
+
}
|
|
7613
|
+
function stripHygieneSources(body) {
|
|
7614
|
+
const index = body.indexOf(`
|
|
7615
|
+
${HYGIENE_SOURCES_HEADING}`);
|
|
7616
|
+
if (index !== -1) {
|
|
7617
|
+
return body.slice(0, index).trim();
|
|
7618
|
+
}
|
|
7619
|
+
return body.startsWith(HYGIENE_SOURCES_HEADING) ? "" : body.trim();
|
|
7620
|
+
}
|
|
7621
|
+
function preferredKeepRecord(records, topic) {
|
|
7622
|
+
const stableRecords = records.filter((record) => isStableRecord(record, topic));
|
|
7623
|
+
return sortedNewestFirst(stableRecords.length > 0 ? stableRecords : records)[0] ?? records[0];
|
|
7624
|
+
}
|
|
7625
|
+
function isStableRecord(record, topic) {
|
|
7626
|
+
const recordTopic = topic ?? topicForRecord(record);
|
|
7627
|
+
return recordTopic !== void 0 && (0, import_node_path3.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
|
|
7628
|
+
}
|
|
7629
|
+
function sortedNewestFirst(records) {
|
|
7630
|
+
return [...records].sort((left, right) => {
|
|
7631
|
+
const timestampDiff = timestampMs(right) - timestampMs(left);
|
|
7632
|
+
return timestampDiff === 0 ? left.uri.localeCompare(right.uri) : timestampDiff;
|
|
7633
|
+
});
|
|
7634
|
+
}
|
|
7635
|
+
function timestampMs(record) {
|
|
7636
|
+
const parsed = Date.parse(record.metadata.timestamp);
|
|
7637
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7638
|
+
}
|
|
7639
|
+
function isStaleLookingHandoff(record, now) {
|
|
7640
|
+
if (record.metadata.kind !== "handoff") {
|
|
7641
|
+
return false;
|
|
7642
|
+
}
|
|
7643
|
+
if (now.getTime() - timestampMs(record) < STALE_HANDOFF_AGE_MS) {
|
|
7644
|
+
return false;
|
|
7645
|
+
}
|
|
7646
|
+
return /\b(?:PR|pull request)\s+(?:OPEN|open|is open)|awaiting review|waiting for review|next steps?:\s*address PR review/i.test(
|
|
7647
|
+
record.body
|
|
7648
|
+
);
|
|
7649
|
+
}
|
|
7650
|
+
function groupBy(values, keyForValue) {
|
|
7651
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7652
|
+
for (const value of values) {
|
|
7653
|
+
const key = keyForValue(value);
|
|
7654
|
+
groups.set(key, [...groups.get(key) ?? [], value]);
|
|
7655
|
+
}
|
|
7656
|
+
return groups;
|
|
7657
|
+
}
|
|
7658
|
+
function compareGroupedRecordLists(left, right) {
|
|
7659
|
+
return (left[0]?.groupKey ?? "").localeCompare(right[0]?.groupKey ?? "");
|
|
7660
|
+
}
|
|
7661
|
+
function dedupeByUri(items) {
|
|
7662
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7663
|
+
const result = [];
|
|
7664
|
+
for (const item of items) {
|
|
7665
|
+
if (seen.has(item.uri)) {
|
|
7666
|
+
continue;
|
|
7667
|
+
}
|
|
7668
|
+
seen.add(item.uri);
|
|
7669
|
+
result.push(item);
|
|
7670
|
+
}
|
|
7671
|
+
return result;
|
|
7672
|
+
}
|
|
7673
|
+
function formatMemoryDocument(title, metadata, body) {
|
|
7674
|
+
const header = [
|
|
7675
|
+
title,
|
|
7676
|
+
`kind: ${metadata.kind}`,
|
|
7677
|
+
`status: ${metadata.status}`,
|
|
7678
|
+
metadata.project ? `project: ${metadata.project}` : void 0,
|
|
7679
|
+
metadata.topic ? `topic: ${metadata.topic}` : void 0,
|
|
7680
|
+
`source_agent_client: ${metadata.sourceAgentClient}`,
|
|
7681
|
+
`timestamp: ${metadata.timestamp}`,
|
|
7682
|
+
metadata.supersedes ? `supersedes: ${metadata.supersedes}` : void 0,
|
|
7683
|
+
metadata.archivedFrom ? `archived_from: ${metadata.archivedFrom}` : void 0
|
|
7684
|
+
].filter((line) => line !== void 0);
|
|
7685
|
+
return [...header, "", body.trim()].join("\n");
|
|
7686
|
+
}
|
|
7687
|
+
function headerValue(header, key) {
|
|
7688
|
+
const prefix = `${key}:`;
|
|
7689
|
+
return header.split("\n").find((line) => line.startsWith(prefix))?.slice(prefix.length).trim();
|
|
7690
|
+
}
|
|
7691
|
+
function parseOptionalMemoryKind(value) {
|
|
7692
|
+
if (!value) {
|
|
7693
|
+
return void 0;
|
|
7694
|
+
}
|
|
7695
|
+
if (["durable", "handoff", "incident", "preference", "smoke"].includes(value)) {
|
|
7696
|
+
return value;
|
|
7697
|
+
}
|
|
7698
|
+
return void 0;
|
|
7699
|
+
}
|
|
7700
|
+
function parseOptionalMemoryStatus(value) {
|
|
7701
|
+
if (!value) {
|
|
7702
|
+
return void 0;
|
|
7703
|
+
}
|
|
7704
|
+
if (["active", "archived", "superseded"].includes(value)) {
|
|
7705
|
+
return value;
|
|
7706
|
+
}
|
|
7707
|
+
return void 0;
|
|
7708
|
+
}
|
|
7709
|
+
function normalizeOptionalMetadata(value) {
|
|
7710
|
+
const trimmed = value?.trim();
|
|
7711
|
+
return trimmed ? trimmed : void 0;
|
|
7712
|
+
}
|
|
7713
|
+
function isCompactableKind(kind) {
|
|
7714
|
+
return kind === "durable" || kind === "handoff" || kind === "incident";
|
|
7715
|
+
}
|
|
7716
|
+
function memoryKindPlural(kind) {
|
|
7717
|
+
switch (kind) {
|
|
7718
|
+
case "handoff":
|
|
7719
|
+
return "handoffs";
|
|
7720
|
+
case "incident":
|
|
7721
|
+
return "incidents";
|
|
7722
|
+
case "durable":
|
|
7723
|
+
return "durable memories";
|
|
7724
|
+
case "preference":
|
|
7725
|
+
return "preferences";
|
|
7726
|
+
case "smoke":
|
|
7727
|
+
return "smoke memories";
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
function formatPlanSection(title, lines) {
|
|
7731
|
+
if (lines.length === 0) {
|
|
7732
|
+
return `${title}:
|
|
7733
|
+
- none`;
|
|
7734
|
+
}
|
|
7735
|
+
return [`${title}:`, ...lines.map((line) => `- ${line}`)].join("\n");
|
|
7736
|
+
}
|
|
7737
|
+
|
|
7242
7738
|
// src/runtime.ts
|
|
7243
7739
|
var import_node_fs3 = require("node:fs");
|
|
7244
7740
|
var import_node_os3 = require("node:os");
|
|
7245
|
-
var
|
|
7741
|
+
var import_node_path4 = require("node:path");
|
|
7246
7742
|
function getRuntimeConfig(program2, manifestOverride) {
|
|
7247
7743
|
const options = program2.opts();
|
|
7248
7744
|
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
@@ -7261,20 +7757,20 @@ function getRuntimeConfig(program2, manifestOverride) {
|
|
|
7261
7757
|
};
|
|
7262
7758
|
}
|
|
7263
7759
|
function defaultManifestPath(agentContextHome) {
|
|
7264
|
-
const userManifest = (0,
|
|
7760
|
+
const userManifest = (0, import_node_path4.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
7265
7761
|
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
7266
7762
|
}
|
|
7267
7763
|
function builtInExampleManifestPath() {
|
|
7268
|
-
return (0,
|
|
7764
|
+
return (0, import_node_path4.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
7269
7765
|
}
|
|
7270
7766
|
function openVikingHealthUrl(config) {
|
|
7271
7767
|
return `http://${config.host}:${config.port}/health`;
|
|
7272
7768
|
}
|
|
7273
7769
|
function openVikingLogPath(config) {
|
|
7274
|
-
return (0,
|
|
7770
|
+
return (0, import_node_path4.join)(config.agentContextHome, "logs", "server.log");
|
|
7275
7771
|
}
|
|
7276
7772
|
function openVikingServerArgs(config) {
|
|
7277
|
-
return ["--config", (0,
|
|
7773
|
+
return ["--config", (0, import_node_path4.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7278
7774
|
}
|
|
7279
7775
|
function withIdentity(config, args) {
|
|
7280
7776
|
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
@@ -7290,7 +7786,7 @@ function renderTemplate(template, config, extras = {}) {
|
|
|
7290
7786
|
// src/share.ts
|
|
7291
7787
|
var import_promises4 = require("node:fs/promises");
|
|
7292
7788
|
var import_node_os4 = require("node:os");
|
|
7293
|
-
var
|
|
7789
|
+
var import_node_path5 = require("node:path");
|
|
7294
7790
|
var TEAMS_FILE_VERSION = 1;
|
|
7295
7791
|
var SHARED_SEGMENT = "shared";
|
|
7296
7792
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
@@ -7359,8 +7855,8 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
7359
7855
|
if (await exists(gitdir)) {
|
|
7360
7856
|
throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
|
|
7361
7857
|
}
|
|
7362
|
-
await ensureDirectory((0,
|
|
7363
|
-
await ensureDirectory((0,
|
|
7858
|
+
await ensureDirectory((0, import_node_path5.dirname)(worktree), dryRun);
|
|
7859
|
+
await ensureDirectory((0, import_node_path5.dirname)(gitdir), dryRun);
|
|
7364
7860
|
const git = await requiredExecutable("git");
|
|
7365
7861
|
await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
|
|
7366
7862
|
const newConfig = {
|
|
@@ -7391,7 +7887,7 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
7391
7887
|
var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
|
|
7392
7888
|
var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
|
|
7393
7889
|
async function ensureSharedGitignore(worktree, git, push) {
|
|
7394
|
-
const gitignorePath = (0,
|
|
7890
|
+
const gitignorePath = (0, import_node_path5.join)(worktree, ".gitignore");
|
|
7395
7891
|
const existing = await readFileIfExists(gitignorePath) ?? "";
|
|
7396
7892
|
const lines = existing.split("\n").map((line) => line.trim());
|
|
7397
7893
|
const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
@@ -7492,7 +7988,7 @@ function autoShareState(config) {
|
|
|
7492
7988
|
return state;
|
|
7493
7989
|
}
|
|
7494
7990
|
function pendingReindexesPath(config) {
|
|
7495
|
-
return (0,
|
|
7991
|
+
return (0, import_node_path5.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
|
|
7496
7992
|
}
|
|
7497
7993
|
async function loadPendingReindexes(config, state) {
|
|
7498
7994
|
const raw = await readFileIfExists(pendingReindexesPath(config));
|
|
@@ -7536,7 +8032,7 @@ async function writePendingReindexes(config, state) {
|
|
|
7536
8032
|
teams: Object.fromEntries(state.pendingReindexes),
|
|
7537
8033
|
version: 1
|
|
7538
8034
|
};
|
|
7539
|
-
await (0, import_promises4.mkdir)((0,
|
|
8035
|
+
await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
|
|
7540
8036
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
7541
8037
|
await (0, import_promises4.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
|
|
7542
8038
|
`, { encoding: "utf8", mode: 384 });
|
|
@@ -7629,7 +8125,7 @@ async function runShareSync(config, options) {
|
|
|
7629
8125
|
if (dryRun) {
|
|
7630
8126
|
console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
|
|
7631
8127
|
} else if (pullResult && pullResult.exitCode !== 0) {
|
|
7632
|
-
if (await exists((0,
|
|
8128
|
+
if (await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-apply"))) {
|
|
7633
8129
|
throw new Error(
|
|
7634
8130
|
`git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
|
|
7635
8131
|
Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
|
|
@@ -7684,7 +8180,7 @@ async function runShareSyncQuiet(config, state, options) {
|
|
|
7684
8180
|
const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], false);
|
|
7685
8181
|
const pullResult = await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
|
|
7686
8182
|
if (pullResult.exitCode !== 0) {
|
|
7687
|
-
if (await exists((0,
|
|
8183
|
+
if (await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path5.join)(team.config.gitdir, "rebase-apply"))) {
|
|
7688
8184
|
throw new Error(
|
|
7689
8185
|
`Automatic share sync hit git conflicts in ${worktree}. Resolve them in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then rerun recall/read.`
|
|
7690
8186
|
);
|
|
@@ -7858,10 +8354,10 @@ function normalizeTeamName(input2) {
|
|
|
7858
8354
|
return candidate;
|
|
7859
8355
|
}
|
|
7860
8356
|
function teamsFilePath(config) {
|
|
7861
|
-
return (0,
|
|
8357
|
+
return (0, import_node_path5.join)(config.agentContextHome, "share", "teams.json");
|
|
7862
8358
|
}
|
|
7863
8359
|
function teamWorktreePath(config, team) {
|
|
7864
|
-
return (0,
|
|
8360
|
+
return (0, import_node_path5.join)(
|
|
7865
8361
|
config.agentContextHome,
|
|
7866
8362
|
"data",
|
|
7867
8363
|
"viking",
|
|
@@ -7874,7 +8370,7 @@ function teamWorktreePath(config, team) {
|
|
|
7874
8370
|
);
|
|
7875
8371
|
}
|
|
7876
8372
|
function teamGitdirPath(config, team) {
|
|
7877
|
-
return (0,
|
|
8373
|
+
return (0, import_node_path5.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
7878
8374
|
}
|
|
7879
8375
|
async function readTeamsFile(config) {
|
|
7880
8376
|
const path = teamsFilePath(config);
|
|
@@ -7917,7 +8413,7 @@ async function readTeamsFile(config) {
|
|
|
7917
8413
|
}
|
|
7918
8414
|
async function writeTeamsFile(config, contents) {
|
|
7919
8415
|
const path = teamsFilePath(config);
|
|
7920
|
-
await (0, import_promises4.mkdir)((0,
|
|
8416
|
+
await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(path), { recursive: true });
|
|
7921
8417
|
const serializable = {
|
|
7922
8418
|
defaultTeam: contents.defaultTeam,
|
|
7923
8419
|
teams: contents.teams,
|
|
@@ -7986,7 +8482,7 @@ async function walkMemoryFiles(root) {
|
|
|
7986
8482
|
if (entry.name === ".git") {
|
|
7987
8483
|
continue;
|
|
7988
8484
|
}
|
|
7989
|
-
const full = (0,
|
|
8485
|
+
const full = (0, import_node_path5.join)(path, entry.name);
|
|
7990
8486
|
if (entry.isDirectory()) {
|
|
7991
8487
|
if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
|
|
7992
8488
|
continue;
|
|
@@ -8010,11 +8506,39 @@ async function walkMemoryFiles(root) {
|
|
|
8010
8506
|
return out;
|
|
8011
8507
|
}
|
|
8012
8508
|
function workfileToVikingUri(config, team, filePath) {
|
|
8013
|
-
const rel = (0,
|
|
8509
|
+
const rel = (0, import_node_path5.relative)(team.worktree, filePath).split(import_node_path5.sep).join("/");
|
|
8014
8510
|
return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
8015
8511
|
}
|
|
8016
8512
|
function isInSharedNamespace(config, uri) {
|
|
8017
|
-
return
|
|
8513
|
+
return sharedTeamNameForUri(config, uri) !== void 0;
|
|
8514
|
+
}
|
|
8515
|
+
function sharedTeamNameForUri(config, uri) {
|
|
8516
|
+
const prefix = `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/`;
|
|
8517
|
+
if (!uri.startsWith(prefix)) {
|
|
8518
|
+
return void 0;
|
|
8519
|
+
}
|
|
8520
|
+
const [team] = uri.slice(prefix.length).split("/");
|
|
8521
|
+
return team || void 0;
|
|
8522
|
+
}
|
|
8523
|
+
function sharedMemoryUriParts(config, uri) {
|
|
8524
|
+
const prefix = `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/`;
|
|
8525
|
+
if (!uri.startsWith(prefix)) {
|
|
8526
|
+
return void 0;
|
|
8527
|
+
}
|
|
8528
|
+
const [team, kind, scope, project, ...topicParts] = uri.slice(prefix.length).split("/");
|
|
8529
|
+
if (!team) {
|
|
8530
|
+
return void 0;
|
|
8531
|
+
}
|
|
8532
|
+
if (kind !== "durable" || scope !== "projects" || !project || topicParts.length === 0) {
|
|
8533
|
+
return { team };
|
|
8534
|
+
}
|
|
8535
|
+
const topicPath = topicParts.join("/");
|
|
8536
|
+
return {
|
|
8537
|
+
kind,
|
|
8538
|
+
project,
|
|
8539
|
+
team,
|
|
8540
|
+
topic: topicPath.endsWith(".md") ? topicPath.slice(0, -".md".length) : topicPath
|
|
8541
|
+
};
|
|
8018
8542
|
}
|
|
8019
8543
|
function isInTeamNamespace(config, uri, team) {
|
|
8020
8544
|
return uri.startsWith(`viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team}/`);
|
|
@@ -8130,8 +8654,8 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun, op
|
|
|
8130
8654
|
}
|
|
8131
8655
|
return;
|
|
8132
8656
|
}
|
|
8133
|
-
const stagingDir = await (0, import_promises4.mkdtemp)((0,
|
|
8134
|
-
const tempPath = (0,
|
|
8657
|
+
const stagingDir = await (0, import_promises4.mkdtemp)((0, import_node_path5.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
|
|
8658
|
+
const tempPath = (0, import_node_path5.join)(stagingDir, "body.txt");
|
|
8135
8659
|
try {
|
|
8136
8660
|
await (0, import_promises4.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
8137
8661
|
await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode, options);
|
|
@@ -8253,7 +8777,7 @@ async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
|
|
|
8253
8777
|
if (!relative3) {
|
|
8254
8778
|
return;
|
|
8255
8779
|
}
|
|
8256
|
-
await (0, import_promises4.rm)((0,
|
|
8780
|
+
await (0, import_promises4.rm)((0, import_node_path5.join)(worktree, relative3), { force: true });
|
|
8257
8781
|
}
|
|
8258
8782
|
async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
|
|
8259
8783
|
const args = withIdentity(config, ["rm", uri]);
|
|
@@ -8317,8 +8841,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
8317
8841
|
const oldRel = entries[index + 1];
|
|
8318
8842
|
const newRel = entries[index + 2];
|
|
8319
8843
|
if (oldRel && newRel) {
|
|
8320
|
-
changes.push({ path: (0,
|
|
8321
|
-
changes.push({ path: (0,
|
|
8844
|
+
changes.push({ path: (0, import_node_path5.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
|
|
8845
|
+
changes.push({ path: (0, import_node_path5.join)(worktree, newRel), relativePath: newRel, status: "added" });
|
|
8322
8846
|
}
|
|
8323
8847
|
index += 3;
|
|
8324
8848
|
continue;
|
|
@@ -8326,7 +8850,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
8326
8850
|
const rel = entries[index + 1];
|
|
8327
8851
|
if (rel) {
|
|
8328
8852
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
8329
|
-
changes.push({ path: (0,
|
|
8853
|
+
changes.push({ path: (0, import_node_path5.join)(worktree, rel), relativePath: rel, status });
|
|
8330
8854
|
}
|
|
8331
8855
|
index += 2;
|
|
8332
8856
|
}
|
|
@@ -8410,6 +8934,12 @@ function parseMemoryStatus(value) {
|
|
|
8410
8934
|
}
|
|
8411
8935
|
throw new Error(`Unsupported memory status "${value}". Expected active, archived, or superseded.`);
|
|
8412
8936
|
}
|
|
8937
|
+
function parseCompactKind(value) {
|
|
8938
|
+
if (["durable", "handoff", "incident"].includes(value)) {
|
|
8939
|
+
return value;
|
|
8940
|
+
}
|
|
8941
|
+
throw new Error(`Unsupported compact kind "${value}". Expected durable, handoff, or incident.`);
|
|
8942
|
+
}
|
|
8413
8943
|
async function runRemember(config, options) {
|
|
8414
8944
|
const text = await getInputText(options.text, options.stdin === true);
|
|
8415
8945
|
if (!text.trim()) {
|
|
@@ -8417,11 +8947,11 @@ async function runRemember(config, options) {
|
|
|
8417
8947
|
}
|
|
8418
8948
|
const metadata = {
|
|
8419
8949
|
kind: options.kind ?? "durable",
|
|
8420
|
-
project:
|
|
8950
|
+
project: normalizeOptionalMetadata2(options.project),
|
|
8421
8951
|
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
8422
8952
|
status: options.status ?? "active",
|
|
8423
8953
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8424
|
-
topic:
|
|
8954
|
+
topic: normalizeOptionalMetadata2(options.topic)
|
|
8425
8955
|
};
|
|
8426
8956
|
await storeMemory(config, {
|
|
8427
8957
|
bodyText: text.trim(),
|
|
@@ -8442,7 +8972,7 @@ async function runMigrateMemories(config, options) {
|
|
|
8442
8972
|
const candidates = await legacyMemoryCandidates(config, sourceAccounts);
|
|
8443
8973
|
const existingHashes = await existingDurableMemoryHashes(config);
|
|
8444
8974
|
const ov = await openVikingCliForMode(dryRun);
|
|
8445
|
-
const migrationPath = (0,
|
|
8975
|
+
const migrationPath = (0, import_node_path6.join)(config.agentContextHome, "legacy-memory-migration.txt");
|
|
8446
8976
|
let duplicateCount = 0;
|
|
8447
8977
|
let migratedCount = 0;
|
|
8448
8978
|
let sensitiveCount = 0;
|
|
@@ -8505,7 +9035,7 @@ async function runMigrateLifecycle(config, options) {
|
|
|
8505
9035
|
const limit = options.limit ? parsePositiveInteger(options.limit, "lifecycle migration limit") : void 0;
|
|
8506
9036
|
const ov = await openVikingCliForMode(dryRun);
|
|
8507
9037
|
const candidates = await legacyLifecycleHandoffCandidates(config);
|
|
8508
|
-
const migrationPath = (0,
|
|
9038
|
+
const migrationPath = (0, import_node_path6.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
|
|
8509
9039
|
let existingCount = 0;
|
|
8510
9040
|
let migratedCount = 0;
|
|
8511
9041
|
let skippedCount = 0;
|
|
@@ -8515,7 +9045,7 @@ async function runMigrateLifecycle(config, options) {
|
|
|
8515
9045
|
break;
|
|
8516
9046
|
}
|
|
8517
9047
|
const destinationUri = lifecycleMigrationUri(config, candidate.metadata, sha256(candidate.original.trim()));
|
|
8518
|
-
const migratedMemory =
|
|
9048
|
+
const migratedMemory = formatMemoryDocument2(
|
|
8519
9049
|
"HANDOFF",
|
|
8520
9050
|
candidate.metadata,
|
|
8521
9051
|
["Migrated legacy handoff from the historical events trail.", "", candidate.original.trim()].join("\n")
|
|
@@ -8561,8 +9091,10 @@ async function runRecall(config, options) {
|
|
|
8561
9091
|
await syncSharedReposAndLog(config);
|
|
8562
9092
|
}
|
|
8563
9093
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
8564
|
-
const
|
|
8565
|
-
const
|
|
9094
|
+
const query = await enrichRecallQueryWithWorkspaceContext(options.query);
|
|
9095
|
+
const projectQuery = await enrichRecallQueryWithWorkspaceProjectContext(options.query);
|
|
9096
|
+
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
9097
|
+
const args = ["search", query];
|
|
8566
9098
|
if (inferredUri) {
|
|
8567
9099
|
args.push("--uri", inferredUri);
|
|
8568
9100
|
console.log(`Recall scope: ${inferredUri}`);
|
|
@@ -8570,24 +9102,41 @@ async function runRecall(config, options) {
|
|
|
8570
9102
|
if (options.nodeLimit) {
|
|
8571
9103
|
args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
|
|
8572
9104
|
}
|
|
8573
|
-
|
|
8574
|
-
await
|
|
8575
|
-
|
|
9105
|
+
const recallOutputs = [];
|
|
9106
|
+
const baseResult = await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
9107
|
+
if (baseResult) {
|
|
9108
|
+
recallOutputs.push([baseResult.stdout.trim(), baseResult.stderr.trim()].filter(Boolean).join("\n"));
|
|
9109
|
+
}
|
|
9110
|
+
const seededOutput = await augmentRecallWithSeededResources(
|
|
9111
|
+
config,
|
|
9112
|
+
ov,
|
|
9113
|
+
{ ...options, query },
|
|
9114
|
+
inferredUri,
|
|
9115
|
+
projectQuery
|
|
9116
|
+
);
|
|
9117
|
+
if (seededOutput) {
|
|
9118
|
+
recallOutputs.push(seededOutput);
|
|
9119
|
+
}
|
|
9120
|
+
const exactOutput = await printExactMemoryMatches(config, ov, query, {
|
|
8576
9121
|
dryRun: options.dryRun === true,
|
|
8577
9122
|
includeArchived: options.includeArchived === true
|
|
8578
9123
|
});
|
|
9124
|
+
if (exactOutput) {
|
|
9125
|
+
recallOutputs.push(exactOutput);
|
|
9126
|
+
}
|
|
9127
|
+
await printRecallHygieneNudges(config, recallOutputs.join("\n"));
|
|
8579
9128
|
}
|
|
8580
|
-
async function augmentRecallWithSeededResources(config, ov, options, inferredUri) {
|
|
9129
|
+
async function augmentRecallWithSeededResources(config, ov, options, inferredUri, projectQuery = options.query) {
|
|
8581
9130
|
if (options.uri || options.inferScope === false) {
|
|
8582
|
-
return;
|
|
9131
|
+
return void 0;
|
|
8583
9132
|
}
|
|
8584
|
-
const project = await inferProjectFromQuery(config.manifestPath,
|
|
9133
|
+
const project = await inferProjectFromQuery(config.manifestPath, projectQuery);
|
|
8585
9134
|
if (!project) {
|
|
8586
|
-
return;
|
|
9135
|
+
return void 0;
|
|
8587
9136
|
}
|
|
8588
9137
|
const projectResourceUri = trimTrailingSlash(project.uri);
|
|
8589
9138
|
if (!projectResourceUri.startsWith("viking://") || projectResourceUri === inferredUri) {
|
|
8590
|
-
return;
|
|
9139
|
+
return void 0;
|
|
8591
9140
|
}
|
|
8592
9141
|
const args = ["search", options.query, "--uri", projectResourceUri];
|
|
8593
9142
|
if (options.nodeLimit) {
|
|
@@ -8595,7 +9144,8 @@ async function augmentRecallWithSeededResources(config, ov, options, inferredUri
|
|
|
8595
9144
|
}
|
|
8596
9145
|
console.log(`
|
|
8597
9146
|
Also searching seeded resources: ${projectResourceUri}`);
|
|
8598
|
-
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
9147
|
+
const result = await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
9148
|
+
return result ? [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n") : void 0;
|
|
8599
9149
|
}
|
|
8600
9150
|
async function runRead(config, uri, options) {
|
|
8601
9151
|
assertVikingUri(uri);
|
|
@@ -8623,6 +9173,147 @@ async function syncSharedReposAndLog(config) {
|
|
|
8623
9173
|
console.error(`Auto-sync warning: ${err instanceof Error ? err.message : String(err)}`);
|
|
8624
9174
|
}
|
|
8625
9175
|
}
|
|
9176
|
+
async function printRecallHygieneNudges(config, recallOutput) {
|
|
9177
|
+
const uris = activePersonalMemoryUrisFromText(recallOutput, config.user);
|
|
9178
|
+
if (uris.length === 0) {
|
|
9179
|
+
return;
|
|
9180
|
+
}
|
|
9181
|
+
const records = await readMemoryRecordsByUri(config, uris);
|
|
9182
|
+
const nudges = recallHygieneNudges(recallOutput, { records, user: config.user });
|
|
9183
|
+
if (nudges.length === 0) {
|
|
9184
|
+
return;
|
|
9185
|
+
}
|
|
9186
|
+
console.log("\nMemory hygiene hints:");
|
|
9187
|
+
for (const nudge of nudges) {
|
|
9188
|
+
console.log(`- ${nudge}`);
|
|
9189
|
+
}
|
|
9190
|
+
}
|
|
9191
|
+
async function runCompact(config, options) {
|
|
9192
|
+
const project = normalizeOptionalMetadata2(options.project);
|
|
9193
|
+
if (!project) {
|
|
9194
|
+
throw new Error("Provide --project for scoped memory hygiene.");
|
|
9195
|
+
}
|
|
9196
|
+
if (options.apply === true && options.dryRun === true) {
|
|
9197
|
+
throw new Error("Cannot combine --apply with --dry-run.");
|
|
9198
|
+
}
|
|
9199
|
+
const apply = options.apply === true;
|
|
9200
|
+
const records = await scopedCompactRecords(config, {
|
|
9201
|
+
kind: options.kind,
|
|
9202
|
+
project
|
|
9203
|
+
});
|
|
9204
|
+
const plan = buildCompactPlan(records, {
|
|
9205
|
+
kind: options.kind,
|
|
9206
|
+
project,
|
|
9207
|
+
topic: normalizeOptionalMetadata2(options.topic)
|
|
9208
|
+
});
|
|
9209
|
+
console.log(formatCompactPlan(plan, { apply }));
|
|
9210
|
+
if (!apply) {
|
|
9211
|
+
return;
|
|
9212
|
+
}
|
|
9213
|
+
const ov = await openVikingCliForMode(false);
|
|
9214
|
+
const updatePath = (0, import_node_path6.join)(config.agentContextHome, "compact-memory-update.txt");
|
|
9215
|
+
try {
|
|
9216
|
+
for (const action of plan.keepUpdates) {
|
|
9217
|
+
await (0, import_promises5.writeFile)(updatePath, action.content, { encoding: "utf8", mode: 384 });
|
|
9218
|
+
await (0, import_promises5.chmod)(updatePath, 384);
|
|
9219
|
+
await writeDurableMemoryFile(ov, config, action.uri, updatePath, "replace");
|
|
9220
|
+
}
|
|
9221
|
+
} finally {
|
|
9222
|
+
await (0, import_promises5.rm)(updatePath, { force: true });
|
|
9223
|
+
}
|
|
9224
|
+
for (const action of plan.archives) {
|
|
9225
|
+
await runArchive(config, action.uri, {
|
|
9226
|
+
dryRun: false,
|
|
9227
|
+
kind: action.kind,
|
|
9228
|
+
project: action.project,
|
|
9229
|
+
topic: action.topic
|
|
9230
|
+
});
|
|
9231
|
+
}
|
|
9232
|
+
for (const action of plan.forgets) {
|
|
9233
|
+
await runForget(config, action.uri, { dryRun: false });
|
|
9234
|
+
}
|
|
9235
|
+
}
|
|
9236
|
+
async function scopedCompactRecords(config, options) {
|
|
9237
|
+
const kinds = options.kind ? [options.kind] : ["handoff", "durable", "incident"];
|
|
9238
|
+
const records = [];
|
|
9239
|
+
for (const kind of kinds) {
|
|
9240
|
+
const directory = localMemoryDirectoryForCompact(config, kind, options.project);
|
|
9241
|
+
const uriDirectory = memoryUriDirectoryForCompact(config, kind, options.project);
|
|
9242
|
+
let entries;
|
|
9243
|
+
try {
|
|
9244
|
+
entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
|
|
9245
|
+
} catch (_err) {
|
|
9246
|
+
continue;
|
|
9247
|
+
}
|
|
9248
|
+
for (const entry of entries) {
|
|
9249
|
+
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
9250
|
+
continue;
|
|
9251
|
+
}
|
|
9252
|
+
const content = await readTextIfExists((0, import_node_path6.join)(directory, entry.name));
|
|
9253
|
+
if (!content) {
|
|
9254
|
+
continue;
|
|
9255
|
+
}
|
|
9256
|
+
const record = parseMemoryDocument(`${uriDirectory}/${entry.name}`, content);
|
|
9257
|
+
if (record) {
|
|
9258
|
+
records.push(record);
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
}
|
|
9262
|
+
return records;
|
|
9263
|
+
}
|
|
9264
|
+
async function readMemoryRecordsByUri(config, uris) {
|
|
9265
|
+
const records = [];
|
|
9266
|
+
for (const uri of uris) {
|
|
9267
|
+
const localPath = localMemoryPathForUri(config, uri);
|
|
9268
|
+
if (!localPath) {
|
|
9269
|
+
continue;
|
|
9270
|
+
}
|
|
9271
|
+
const content = await readTextIfExists(localPath);
|
|
9272
|
+
if (!content) {
|
|
9273
|
+
continue;
|
|
9274
|
+
}
|
|
9275
|
+
const record = parseMemoryDocument(uri, content);
|
|
9276
|
+
if (record) {
|
|
9277
|
+
records.push(record);
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
return records;
|
|
9281
|
+
}
|
|
9282
|
+
function localMemoryDirectoryForCompact(config, kind, project) {
|
|
9283
|
+
const root = localUserMemoriesRoot(config);
|
|
9284
|
+
const projectSegment = uriSegment(project);
|
|
9285
|
+
switch (kind) {
|
|
9286
|
+
case "durable":
|
|
9287
|
+
return (0, import_node_path6.join)(root, "durable", "projects", projectSegment);
|
|
9288
|
+
case "handoff":
|
|
9289
|
+
return (0, import_node_path6.join)(root, "handoffs", "active", projectSegment);
|
|
9290
|
+
case "incident":
|
|
9291
|
+
return (0, import_node_path6.join)(root, "incidents", "active", projectSegment);
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
9294
|
+
function memoryUriDirectoryForCompact(config, kind, project) {
|
|
9295
|
+
const base = `viking://user/${uriSegment(config.user)}/memories`;
|
|
9296
|
+
const projectSegment = uriSegment(project);
|
|
9297
|
+
switch (kind) {
|
|
9298
|
+
case "durable":
|
|
9299
|
+
return `${base}/durable/projects/${projectSegment}`;
|
|
9300
|
+
case "handoff":
|
|
9301
|
+
return `${base}/handoffs/active/${projectSegment}`;
|
|
9302
|
+
case "incident":
|
|
9303
|
+
return `${base}/incidents/active/${projectSegment}`;
|
|
9304
|
+
}
|
|
9305
|
+
}
|
|
9306
|
+
function localMemoryPathForUri(config, uri) {
|
|
9307
|
+
const prefix = `viking://user/${uriSegment(config.user)}/memories/`;
|
|
9308
|
+
if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
|
|
9309
|
+
return void 0;
|
|
9310
|
+
}
|
|
9311
|
+
const relative3 = uri.slice(prefix.length);
|
|
9312
|
+
if (relative3.includes("..") || relative3.startsWith("/")) {
|
|
9313
|
+
return void 0;
|
|
9314
|
+
}
|
|
9315
|
+
return (0, import_node_path6.join)(localUserMemoriesRoot(config), ...relative3.split("/"));
|
|
9316
|
+
}
|
|
8626
9317
|
async function runList(config, uri, options) {
|
|
8627
9318
|
assertVikingUri(uri);
|
|
8628
9319
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
@@ -8660,11 +9351,11 @@ async function runArchive(config, uri, options) {
|
|
|
8660
9351
|
const fallbackMetadata = {
|
|
8661
9352
|
archivedFrom: uri,
|
|
8662
9353
|
kind: options.kind ?? "handoff",
|
|
8663
|
-
project:
|
|
9354
|
+
project: normalizeOptionalMetadata2(options.project),
|
|
8664
9355
|
sourceAgentClient: "threadnote",
|
|
8665
9356
|
status: "archived",
|
|
8666
9357
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8667
|
-
topic:
|
|
9358
|
+
topic: normalizeOptionalMetadata2(options.topic)
|
|
8668
9359
|
};
|
|
8669
9360
|
await storeMemory(config, {
|
|
8670
9361
|
bodyText: ["Archived original Threadnote memory.", "", "<original memory content would be read here>"].join("\n"),
|
|
@@ -8682,11 +9373,11 @@ async function runArchive(config, uri, options) {
|
|
|
8682
9373
|
const metadata = {
|
|
8683
9374
|
archivedFrom: uri,
|
|
8684
9375
|
kind: options.kind ?? inferredMetadata.kind ?? "handoff",
|
|
8685
|
-
project:
|
|
9376
|
+
project: normalizeOptionalMetadata2(options.project) ?? inferredMetadata.project,
|
|
8686
9377
|
sourceAgentClient: "threadnote",
|
|
8687
9378
|
status: "archived",
|
|
8688
9379
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8689
|
-
topic:
|
|
9380
|
+
topic: normalizeOptionalMetadata2(options.topic) ?? inferredMetadata.topic
|
|
8690
9381
|
};
|
|
8691
9382
|
await storeMemory(config, {
|
|
8692
9383
|
bodyText: ["Archived original Threadnote memory.", "", original].join("\n"),
|
|
@@ -8715,7 +9406,7 @@ async function runForget(config, uri, options) {
|
|
|
8715
9406
|
}
|
|
8716
9407
|
async function runExportPack(config, options) {
|
|
8717
9408
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
8718
|
-
const defaultPath = (0,
|
|
9409
|
+
const defaultPath = (0, import_node_path6.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
|
|
8719
9410
|
const outputPath = expandPath(options.path ?? defaultPath);
|
|
8720
9411
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, ["export", options.uri ?? "viking://", outputPath]));
|
|
8721
9412
|
}
|
|
@@ -8740,7 +9431,7 @@ async function inferRecallUri(config, query) {
|
|
|
8740
9431
|
async function printExactMemoryMatches(config, ov, query, options) {
|
|
8741
9432
|
const terms = exactRecallTerms(query);
|
|
8742
9433
|
if (terms.length === 0) {
|
|
8743
|
-
return;
|
|
9434
|
+
return void 0;
|
|
8744
9435
|
}
|
|
8745
9436
|
const scopes = exactMemoryScopes(config, options.includeArchived);
|
|
8746
9437
|
const outputs = [];
|
|
@@ -8752,30 +9443,36 @@ async function printExactMemoryMatches(config, ov, query, options) {
|
|
|
8752
9443
|
continue;
|
|
8753
9444
|
}
|
|
8754
9445
|
const result = await runCommand(ov, args, { allowFailure: true });
|
|
8755
|
-
const
|
|
8756
|
-
if (result.exitCode === 0 && grepOutputHasMatches(
|
|
8757
|
-
outputs.push(
|
|
9446
|
+
const output3 = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
9447
|
+
if (result.exitCode === 0 && grepOutputHasMatches(output3)) {
|
|
9448
|
+
outputs.push(output3);
|
|
8758
9449
|
}
|
|
8759
9450
|
}
|
|
8760
9451
|
}
|
|
8761
9452
|
if (outputs.length === 0) {
|
|
8762
|
-
return;
|
|
9453
|
+
return void 0;
|
|
8763
9454
|
}
|
|
8764
|
-
console.log("\nExact
|
|
8765
|
-
|
|
9455
|
+
console.log("\nExact memory/resource matches:");
|
|
9456
|
+
const output2 = outputs.join("\n\n");
|
|
9457
|
+
console.log(output2);
|
|
9458
|
+
return output2;
|
|
8766
9459
|
}
|
|
8767
9460
|
async function storeMemory(config, options) {
|
|
8768
9461
|
if (options.replaceUri) {
|
|
8769
9462
|
assertVikingUri(options.replaceUri);
|
|
8770
9463
|
}
|
|
8771
9464
|
const ov = await openVikingCliForMode(options.dryRun);
|
|
8772
|
-
|
|
9465
|
+
if (options.replaceUri && isInSharedNamespace(config, options.replaceUri)) {
|
|
9466
|
+
await storeSharedMemoryReplacement(config, ov, options, options.replaceUri);
|
|
9467
|
+
return;
|
|
9468
|
+
}
|
|
9469
|
+
const memoryPath = (0, import_node_path6.join)(config.agentContextHome, "last-memory.txt");
|
|
8773
9470
|
const candidateMetadata = { ...options.metadata, supersedes: options.replaceUri };
|
|
8774
|
-
const candidateMemory =
|
|
9471
|
+
const candidateMemory = formatMemoryDocument2(options.title, candidateMetadata, options.bodyText);
|
|
8775
9472
|
const memoryUri = memoryUriFor(config, candidateMemory, candidateMetadata);
|
|
8776
9473
|
const isInPlaceUpdate = options.replaceUri !== void 0 && options.replaceUri === memoryUri;
|
|
8777
9474
|
const finalMetadata = isInPlaceUpdate ? { ...options.metadata, supersedes: void 0 } : candidateMetadata;
|
|
8778
|
-
const memory = isInPlaceUpdate ?
|
|
9475
|
+
const memory = isInPlaceUpdate ? formatMemoryDocument2(options.title, finalMetadata, options.bodyText) : candidateMemory;
|
|
8779
9476
|
const writeMode = await memoryWriteMode(ov, config, memoryUri, finalMetadata);
|
|
8780
9477
|
if (options.dryRun) {
|
|
8781
9478
|
console.log(memory);
|
|
@@ -8819,6 +9516,49 @@ async function storeMemory(config, options) {
|
|
|
8819
9516
|
console.log(`Updated existing memory in place: ${memoryUri}`);
|
|
8820
9517
|
}
|
|
8821
9518
|
}
|
|
9519
|
+
async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
|
|
9520
|
+
if (options.metadata.kind !== "durable") {
|
|
9521
|
+
throw new Error("Shared memory replacement only supports durable memories.");
|
|
9522
|
+
}
|
|
9523
|
+
const teamName = sharedTeamNameForUri(config, targetUri);
|
|
9524
|
+
if (!teamName) {
|
|
9525
|
+
throw new Error(`Memory ${targetUri} is not in the shared namespace.`);
|
|
9526
|
+
}
|
|
9527
|
+
const team = await resolveTeam(config, teamName);
|
|
9528
|
+
const inferred = sharedMemoryUriParts(config, targetUri);
|
|
9529
|
+
const metadata = {
|
|
9530
|
+
...options.metadata,
|
|
9531
|
+
project: options.metadata.project ?? inferred?.project,
|
|
9532
|
+
topic: options.metadata.topic ?? inferred?.topic
|
|
9533
|
+
};
|
|
9534
|
+
const rawMemory = formatMemoryDocument2(options.title, metadata, options.bodyText);
|
|
9535
|
+
const scrub = applyScrubber(stripPersonalProvenance(rawMemory), { redact: false });
|
|
9536
|
+
if (scrub.blocker) {
|
|
9537
|
+
throw new Error(
|
|
9538
|
+
`Refusing to update shared memory ${targetUri}: possible ${scrub.blocker}. Strip the sensitive value first.`
|
|
9539
|
+
);
|
|
9540
|
+
}
|
|
9541
|
+
const memory = scrub.cleaned;
|
|
9542
|
+
const relativePath = vikingUriToWorktreeRelative(config, targetUri, team.name);
|
|
9543
|
+
if (options.dryRun) {
|
|
9544
|
+
console.log(memory);
|
|
9545
|
+
console.log("\nWould run:");
|
|
9546
|
+
}
|
|
9547
|
+
await ensureSharedDirectoryChain(config, ov, targetUri, options.dryRun);
|
|
9548
|
+
await writeMemoryFile(config, ov, targetUri, memory, "replace", options.dryRun);
|
|
9549
|
+
const git = await requiredExecutable("git");
|
|
9550
|
+
await maybeRun(options.dryRun, git, ["-C", team.config.worktree, "add", "--", relativePath]);
|
|
9551
|
+
await maybeRun(options.dryRun, git, ["-C", team.config.worktree, "commit", "-m", `share: update ${relativePath}`], {
|
|
9552
|
+
allowFailure: true
|
|
9553
|
+
});
|
|
9554
|
+
await maybeRun(options.dryRun, git, ["-C", team.config.worktree, "push", DEFAULT_GIT_REMOTE_NAME], {
|
|
9555
|
+
allowFailure: true
|
|
9556
|
+
});
|
|
9557
|
+
for (const redaction of scrub.redactions) {
|
|
9558
|
+
console.log(`Redacted ${redaction.count}\xD7 ${redaction.name} before shared update.`);
|
|
9559
|
+
}
|
|
9560
|
+
console.log(`Updated shared memory: ${targetUri}`);
|
|
9561
|
+
}
|
|
8822
9562
|
async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode) {
|
|
8823
9563
|
const args = withIdentity(config, [
|
|
8824
9564
|
"write",
|
|
@@ -8917,7 +9657,7 @@ async function hasLegacyLifecycleHandoffCandidates(config) {
|
|
|
8917
9657
|
return (await legacyLifecycleHandoffCandidates(config, 1)).length > 0;
|
|
8918
9658
|
}
|
|
8919
9659
|
async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
8920
|
-
const eventsRoot = (0,
|
|
9660
|
+
const eventsRoot = (0, import_node_path6.join)(localUserMemoriesRoot(config), "events");
|
|
8921
9661
|
let entries;
|
|
8922
9662
|
try {
|
|
8923
9663
|
entries = await (0, import_promises5.readdir)(eventsRoot, { withFileTypes: true });
|
|
@@ -8929,7 +9669,7 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
|
8929
9669
|
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
8930
9670
|
continue;
|
|
8931
9671
|
}
|
|
8932
|
-
const sourcePath = (0,
|
|
9672
|
+
const sourcePath = (0, import_node_path6.join)(eventsRoot, entry.name);
|
|
8933
9673
|
const original = await readTextIfExists(sourcePath);
|
|
8934
9674
|
if (!original || !isClearLegacyHandoffMemory(original) || sensitiveMemoryReason(original)) {
|
|
8935
9675
|
continue;
|
|
@@ -9015,7 +9755,7 @@ function vikingDirectoryChain(directoryUri) {
|
|
|
9015
9755
|
}
|
|
9016
9756
|
return chain;
|
|
9017
9757
|
}
|
|
9018
|
-
function
|
|
9758
|
+
function formatMemoryDocument2(title, metadata, body) {
|
|
9019
9759
|
const header = [
|
|
9020
9760
|
title,
|
|
9021
9761
|
`kind: ${metadata.kind}`,
|
|
@@ -9032,10 +9772,10 @@ function formatMemoryDocument(title, metadata, body) {
|
|
|
9032
9772
|
function inferMemoryMetadata(memory) {
|
|
9033
9773
|
const header = memory.slice(0, Math.max(0, memory.indexOf("\n\n")) || memory.length);
|
|
9034
9774
|
const firstLine2 = header.split("\n")[0]?.trim();
|
|
9035
|
-
const kind =
|
|
9036
|
-
const status =
|
|
9037
|
-
const project =
|
|
9038
|
-
const topic =
|
|
9775
|
+
const kind = parseOptionalMemoryKind2(parseHeaderValue(header, "kind")) ?? (firstLine2 === "HANDOFF" ? "handoff" : void 0);
|
|
9776
|
+
const status = parseOptionalMemoryStatus2(parseHeaderValue(header, "status"));
|
|
9777
|
+
const project = normalizeOptionalMetadata2(parseHeaderValue(header, "project")) ?? normalizeOptionalMetadata2(parseHeaderValue(header, "repo"));
|
|
9778
|
+
const topic = normalizeOptionalMetadata2(parseHeaderValue(header, "topic")) ?? normalizeOptionalMetadata2(parseHeaderValue(header, "task"));
|
|
9039
9779
|
return {
|
|
9040
9780
|
kind,
|
|
9041
9781
|
project,
|
|
@@ -9071,9 +9811,9 @@ function inferLegacyProject(memory) {
|
|
|
9071
9811
|
return "general";
|
|
9072
9812
|
}
|
|
9073
9813
|
const trimmed = explicit.trim().replace(/[`.,;]+$/g, "");
|
|
9074
|
-
return trimmed.includes("/") ? (0,
|
|
9814
|
+
return trimmed.includes("/") ? (0, import_node_path6.basename)(trimmed) : trimmed;
|
|
9075
9815
|
}
|
|
9076
|
-
function
|
|
9816
|
+
function parseOptionalMemoryKind2(value) {
|
|
9077
9817
|
if (!value) {
|
|
9078
9818
|
return void 0;
|
|
9079
9819
|
}
|
|
@@ -9083,7 +9823,7 @@ function parseOptionalMemoryKind(value) {
|
|
|
9083
9823
|
return void 0;
|
|
9084
9824
|
}
|
|
9085
9825
|
}
|
|
9086
|
-
function
|
|
9826
|
+
function parseOptionalMemoryStatus2(value) {
|
|
9087
9827
|
if (!value) {
|
|
9088
9828
|
return void 0;
|
|
9089
9829
|
}
|
|
@@ -9093,7 +9833,7 @@ function parseOptionalMemoryStatus(value) {
|
|
|
9093
9833
|
return void 0;
|
|
9094
9834
|
}
|
|
9095
9835
|
}
|
|
9096
|
-
function
|
|
9836
|
+
function normalizeOptionalMetadata2(value) {
|
|
9097
9837
|
const trimmed = value?.trim();
|
|
9098
9838
|
return trimmed ? trimmed : void 0;
|
|
9099
9839
|
}
|
|
@@ -9111,14 +9851,14 @@ async function legacySourceAccounts(config, options) {
|
|
|
9111
9851
|
async function legacyMemoryCandidates(config, sourceAccounts) {
|
|
9112
9852
|
const candidates = [];
|
|
9113
9853
|
for (const sourceAccount of sourceAccounts) {
|
|
9114
|
-
const sessionRoot = (0,
|
|
9854
|
+
const sessionRoot = (0, import_node_path6.join)(localVikingDataRoot(config), sourceAccount, "session");
|
|
9115
9855
|
for (const sourceSession of await childDirectoryNames(sessionRoot)) {
|
|
9116
|
-
const historyRoot = (0,
|
|
9856
|
+
const historyRoot = (0, import_node_path6.join)(sessionRoot, sourceSession, "history");
|
|
9117
9857
|
for (const sourceArchive of await childDirectoryNames(historyRoot)) {
|
|
9118
9858
|
if (!sourceArchive.startsWith("archive_")) {
|
|
9119
9859
|
continue;
|
|
9120
9860
|
}
|
|
9121
|
-
const sourcePath = (0,
|
|
9861
|
+
const sourcePath = (0, import_node_path6.join)(historyRoot, sourceArchive, "messages.jsonl");
|
|
9122
9862
|
for (const text of await legacyMemoryTexts(sourcePath)) {
|
|
9123
9863
|
candidates.push({
|
|
9124
9864
|
comparableHash: sha256(comparableMemoryText(text)),
|
|
@@ -9186,7 +9926,7 @@ async function collectDurableMemoryHashes(root, hashes) {
|
|
|
9186
9926
|
return;
|
|
9187
9927
|
}
|
|
9188
9928
|
for (const entry of entries) {
|
|
9189
|
-
const path = (0,
|
|
9929
|
+
const path = (0, import_node_path6.join)(root, entry.name);
|
|
9190
9930
|
if (entry.isDirectory()) {
|
|
9191
9931
|
await collectDurableMemoryHashes(path, hashes);
|
|
9192
9932
|
continue;
|
|
@@ -9203,7 +9943,7 @@ async function collectDurableMemoryHashes(root, hashes) {
|
|
|
9203
9943
|
}
|
|
9204
9944
|
}
|
|
9205
9945
|
function isDurableMemoryPath(path) {
|
|
9206
|
-
return path.split(
|
|
9946
|
+
return path.split(import_node_path6.sep).includes("memories");
|
|
9207
9947
|
}
|
|
9208
9948
|
async function childDirectoryNames(path) {
|
|
9209
9949
|
let entries;
|
|
@@ -9247,10 +9987,10 @@ function legacySourceLabel(candidate) {
|
|
|
9247
9987
|
return `${candidate.sourceAccount}/${candidate.sourceSession}/${candidate.sourceArchive}`;
|
|
9248
9988
|
}
|
|
9249
9989
|
function localVikingDataRoot(config) {
|
|
9250
|
-
return (0,
|
|
9990
|
+
return (0, import_node_path6.join)(config.agentContextHome, "data", "viking");
|
|
9251
9991
|
}
|
|
9252
9992
|
function localUserMemoriesRoot(config) {
|
|
9253
|
-
return (0,
|
|
9993
|
+
return (0, import_node_path6.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
|
|
9254
9994
|
}
|
|
9255
9995
|
function uniqueStrings(values) {
|
|
9256
9996
|
return [...new Set(values)].sort();
|
|
@@ -9266,14 +10006,15 @@ async function buildHandoff(options) {
|
|
|
9266
10006
|
const status = await gitValue(["status", "--short"], repoRoot) ?? "";
|
|
9267
10007
|
const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
|
|
9268
10008
|
const touchedFiles = await gitTouchedFiles(repoRoot);
|
|
9269
|
-
const repoName = (0,
|
|
10009
|
+
const repoName = (0, import_node_path6.basename)(repoRoot);
|
|
10010
|
+
const topicBranch = branch && branch !== "unknown" ? branch : "current";
|
|
9270
10011
|
const metadata = {
|
|
9271
10012
|
kind: "handoff",
|
|
9272
|
-
project:
|
|
10013
|
+
project: normalizeOptionalMetadata2(options.project) ?? repoName,
|
|
9273
10014
|
sourceAgentClient: options.sourceAgentClient ?? "codex",
|
|
9274
10015
|
status: "active",
|
|
9275
10016
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9276
|
-
topic:
|
|
10017
|
+
topic: handoffTopicForBranch(topicBranch, { timestamped: options.timestamped, topic: options.topic })
|
|
9277
10018
|
};
|
|
9278
10019
|
const bodyText = [
|
|
9279
10020
|
`repo: ${repoName}`,
|
|
@@ -9326,7 +10067,7 @@ function formatBlock(value, emptyValue) {
|
|
|
9326
10067
|
// src/update-check.ts
|
|
9327
10068
|
var import_node_child_process2 = require("node:child_process");
|
|
9328
10069
|
var import_promises6 = require("node:fs/promises");
|
|
9329
|
-
var
|
|
10070
|
+
var import_node_path7 = require("node:path");
|
|
9330
10071
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9331
10072
|
var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
|
|
9332
10073
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
@@ -9405,7 +10146,7 @@ async function readUpdateCache(cachePath) {
|
|
|
9405
10146
|
}
|
|
9406
10147
|
async function writeUpdateCache(cachePath, contents) {
|
|
9407
10148
|
try {
|
|
9408
|
-
await (0, import_promises6.mkdir)((0,
|
|
10149
|
+
await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(cachePath), { recursive: true });
|
|
9409
10150
|
await (0, import_promises6.writeFile)(cachePath, `${JSON.stringify(contents)}
|
|
9410
10151
|
`, { encoding: "utf8", mode: 384 });
|
|
9411
10152
|
} catch {
|
|
@@ -9429,14 +10170,14 @@ async function fetchLatestVersion() {
|
|
|
9429
10170
|
|
|
9430
10171
|
// src/version.ts
|
|
9431
10172
|
var import_node_fs4 = require("node:fs");
|
|
9432
|
-
var
|
|
10173
|
+
var import_node_path8 = require("node:path");
|
|
9433
10174
|
var cachedVersion;
|
|
9434
10175
|
function getThreadnoteVersion() {
|
|
9435
10176
|
if (cachedVersion !== void 0) {
|
|
9436
10177
|
return cachedVersion;
|
|
9437
10178
|
}
|
|
9438
10179
|
try {
|
|
9439
|
-
const packageJsonPath = (0,
|
|
10180
|
+
const packageJsonPath = (0, import_node_path8.join)(__dirname, "..", "package.json");
|
|
9440
10181
|
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
|
|
9441
10182
|
cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
|
|
9442
10183
|
} catch {
|
|
@@ -9495,7 +10236,7 @@ async function runClaudeHooksInstall(options) {
|
|
|
9495
10236
|
console.log("\nRe-run with --apply to actually modify the file.");
|
|
9496
10237
|
return;
|
|
9497
10238
|
}
|
|
9498
|
-
await (0, import_promises7.mkdir)((0,
|
|
10239
|
+
await (0, import_promises7.mkdir)((0, import_node_path9.dirname)(path), { recursive: true });
|
|
9499
10240
|
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
9500
10241
|
`;
|
|
9501
10242
|
await (0, import_promises7.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
|
|
@@ -9595,7 +10336,7 @@ async function hasManagedClaudeHooks() {
|
|
|
9595
10336
|
async function runPreCompactHook(config, options = {}) {
|
|
9596
10337
|
try {
|
|
9597
10338
|
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
|
|
9598
|
-
const project = repoRoot ? (0,
|
|
10339
|
+
const project = repoRoot ? (0, import_node_path9.basename)(repoRoot) : "general";
|
|
9599
10340
|
await runHandoff(config, {
|
|
9600
10341
|
blockers: "- none recorded",
|
|
9601
10342
|
dryRun: options.dryRun === true,
|
|
@@ -9619,7 +10360,7 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
9619
10360
|
if (!repoRoot) {
|
|
9620
10361
|
return;
|
|
9621
10362
|
}
|
|
9622
|
-
const project = (0,
|
|
10363
|
+
const project = (0, import_node_path9.basename)(repoRoot);
|
|
9623
10364
|
await emitUpdateBannerIfOutdated(config);
|
|
9624
10365
|
process.stdout.write(`## Threadnote \u2014 latest context for ${project}
|
|
9625
10366
|
|
|
@@ -9628,7 +10369,8 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
9628
10369
|
dryRun: options.dryRun === true,
|
|
9629
10370
|
inferScope: true,
|
|
9630
10371
|
nodeLimit: "5",
|
|
9631
|
-
|
|
10372
|
+
// Keep "current branch" here so recall enriches the query with local git/workspace terms.
|
|
10373
|
+
query: `${project} current branch latest handoff durable feature memory`
|
|
9632
10374
|
});
|
|
9633
10375
|
} catch (err) {
|
|
9634
10376
|
process.stderr.write(
|
|
@@ -9640,7 +10382,7 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
9640
10382
|
async function emitUpdateBannerIfOutdated(config) {
|
|
9641
10383
|
try {
|
|
9642
10384
|
const result = await checkForThreadnoteUpdate({
|
|
9643
|
-
cachePath: (0,
|
|
10385
|
+
cachePath: (0, import_node_path9.join)(config.agentContextHome, ".update-state.json"),
|
|
9644
10386
|
currentVersion: getThreadnoteVersion()
|
|
9645
10387
|
});
|
|
9646
10388
|
if (!result || !result.outdated) {
|
|
@@ -9666,12 +10408,12 @@ async function emitUpdateBannerIfOutdated(config) {
|
|
|
9666
10408
|
|
|
9667
10409
|
// src/seeding.ts
|
|
9668
10410
|
var import_promises8 = require("node:fs/promises");
|
|
9669
|
-
var
|
|
10411
|
+
var import_node_path10 = require("node:path");
|
|
9670
10412
|
async function runSeed(config, options) {
|
|
9671
10413
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
9672
10414
|
const ignorePatterns = await loadIgnorePatterns();
|
|
9673
10415
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
9674
|
-
const statePath = (0,
|
|
10416
|
+
const statePath = (0, import_node_path10.join)(config.agentContextHome, SEED_STATE_FILE);
|
|
9675
10417
|
const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
|
|
9676
10418
|
const projects = filterProjects(manifest.projects, options.only);
|
|
9677
10419
|
let importedCount = 0;
|
|
@@ -9751,13 +10493,13 @@ async function readSeedState(path) {
|
|
|
9751
10493
|
}
|
|
9752
10494
|
}
|
|
9753
10495
|
async function writeSeedState(path, state) {
|
|
9754
|
-
await ensureDirectory((0,
|
|
10496
|
+
await ensureDirectory((0, import_node_path10.dirname)(path), false);
|
|
9755
10497
|
await (0, import_promises8.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
9756
10498
|
`, { encoding: "utf8", mode: 384 });
|
|
9757
10499
|
}
|
|
9758
10500
|
async function runInitManifest(config, options) {
|
|
9759
10501
|
const manifestPath = expandPath(
|
|
9760
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
10502
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path10.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
9761
10503
|
);
|
|
9762
10504
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
9763
10505
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -9800,7 +10542,7 @@ async function runInitManifest(config, options) {
|
|
|
9800
10542
|
console.log(output2.trimEnd());
|
|
9801
10543
|
return;
|
|
9802
10544
|
}
|
|
9803
|
-
await ensureDirectory((0,
|
|
10545
|
+
await ensureDirectory((0, import_node_path10.dirname)(manifestPath), false);
|
|
9804
10546
|
await (0, import_promises8.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
9805
10547
|
await (0, import_promises8.chmod)(manifestPath, 384);
|
|
9806
10548
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
@@ -9838,7 +10580,7 @@ async function projectIdentity(path) {
|
|
|
9838
10580
|
}
|
|
9839
10581
|
}
|
|
9840
10582
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
9841
|
-
const baseName = uriSegment((0,
|
|
10583
|
+
const baseName = uriSegment((0, import_node_path10.basename)(repoRoot));
|
|
9842
10584
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
9843
10585
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
9844
10586
|
let name = baseName;
|
|
@@ -9860,7 +10602,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
9860
10602
|
for (const pattern of project.seed) {
|
|
9861
10603
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
9862
10604
|
for (const filePath of files) {
|
|
9863
|
-
const relativePath = toPosixPath((0,
|
|
10605
|
+
const relativePath = toPosixPath((0, import_node_path10.relative)(projectRoot, filePath));
|
|
9864
10606
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
9865
10607
|
continue;
|
|
9866
10608
|
}
|
|
@@ -9878,17 +10620,17 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
9878
10620
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
9879
10621
|
const normalizedPattern = toPosixPath(pattern);
|
|
9880
10622
|
if (!hasGlob(normalizedPattern)) {
|
|
9881
|
-
const filePath = (0,
|
|
10623
|
+
const filePath = (0, import_node_path10.join)(projectRoot, normalizedPattern);
|
|
9882
10624
|
return await isFile(filePath) ? [filePath] : [];
|
|
9883
10625
|
}
|
|
9884
10626
|
const globBase = getGlobBase(normalizedPattern);
|
|
9885
|
-
const basePath = (0,
|
|
10627
|
+
const basePath = (0, import_node_path10.join)(projectRoot, globBase);
|
|
9886
10628
|
if (!await exists(basePath)) {
|
|
9887
10629
|
return [];
|
|
9888
10630
|
}
|
|
9889
10631
|
const regex = globToRegExp(normalizedPattern);
|
|
9890
10632
|
const files = await walkFiles(basePath);
|
|
9891
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
10633
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path10.relative)(projectRoot, filePath))));
|
|
9892
10634
|
}
|
|
9893
10635
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
9894
10636
|
const content = await (0, import_promises8.readFile)(candidate.filePath, "utf8");
|
|
@@ -9903,12 +10645,12 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
9903
10645
|
if (redactedContent === content) {
|
|
9904
10646
|
return candidate.filePath;
|
|
9905
10647
|
}
|
|
9906
|
-
const redactedPath = (0,
|
|
10648
|
+
const redactedPath = (0, import_node_path10.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
9907
10649
|
if (dryRun) {
|
|
9908
10650
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
9909
10651
|
return redactedPath;
|
|
9910
10652
|
}
|
|
9911
|
-
await ensureDirectory((0,
|
|
10653
|
+
await ensureDirectory((0, import_node_path10.dirname)(redactedPath), false);
|
|
9912
10654
|
await (0, import_promises8.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
9913
10655
|
await (0, import_promises8.chmod)(redactedPath, 384);
|
|
9914
10656
|
return redactedPath;
|
|
@@ -9966,10 +10708,10 @@ async function resolveAbsolutePattern(pattern) {
|
|
|
9966
10708
|
return files.filter((filePath) => regex.test(toPosixPath(filePath)));
|
|
9967
10709
|
}
|
|
9968
10710
|
function skillResourceUri(skill) {
|
|
9969
|
-
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0,
|
|
10711
|
+
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${uriSegment((0, import_node_path10.basename)((0, import_node_path10.dirname)(skill.filePath)))}-${skill.hash.slice(0, 12)}.md`;
|
|
9970
10712
|
}
|
|
9971
10713
|
async function loadIgnorePatterns() {
|
|
9972
|
-
const raw = await (0, import_promises8.readFile)((0,
|
|
10714
|
+
const raw = await (0, import_promises8.readFile)((0, import_node_path10.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
9973
10715
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
9974
10716
|
}
|
|
9975
10717
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -10044,7 +10786,7 @@ var import_node_child_process3 = require("node:child_process");
|
|
|
10044
10786
|
var import_node_fs6 = require("node:fs");
|
|
10045
10787
|
var import_promises11 = require("node:fs/promises");
|
|
10046
10788
|
var import_node_os6 = require("node:os");
|
|
10047
|
-
var
|
|
10789
|
+
var import_node_path12 = require("node:path");
|
|
10048
10790
|
var import_node_process2 = require("node:process");
|
|
10049
10791
|
var import_promises12 = require("node:readline/promises");
|
|
10050
10792
|
|
|
@@ -10052,7 +10794,7 @@ var import_promises12 = require("node:readline/promises");
|
|
|
10052
10794
|
var import_node_fs5 = require("node:fs");
|
|
10053
10795
|
var import_promises9 = require("node:fs/promises");
|
|
10054
10796
|
var import_node_os5 = require("node:os");
|
|
10055
|
-
var
|
|
10797
|
+
var import_node_path11 = require("node:path");
|
|
10056
10798
|
var import_promises10 = require("node:readline/promises");
|
|
10057
10799
|
var import_node_process = require("node:process");
|
|
10058
10800
|
var NPM_PACKAGE_NAME = "threadnote";
|
|
@@ -10267,7 +11009,7 @@ async function waitForOpenVikingHealthy(config, timeoutMs) {
|
|
|
10267
11009
|
return isOpenVikingHealthy(config);
|
|
10268
11010
|
}
|
|
10269
11011
|
function launchAgentPlistPath() {
|
|
10270
|
-
return (0,
|
|
11012
|
+
return (0, import_node_path11.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
|
|
10271
11013
|
}
|
|
10272
11014
|
async function isLaunchAgentInstalled() {
|
|
10273
11015
|
if (process.platform !== "darwin") {
|
|
@@ -10303,7 +11045,7 @@ async function getUpdateInfo(config, options) {
|
|
|
10303
11045
|
};
|
|
10304
11046
|
}
|
|
10305
11047
|
async function currentPackageVersion() {
|
|
10306
|
-
const rawPackage = await (0, import_promises9.readFile)((0,
|
|
11048
|
+
const rawPackage = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), "package.json"), "utf8");
|
|
10307
11049
|
const parsed = JSON.parse(rawPackage);
|
|
10308
11050
|
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
10309
11051
|
throw new Error("Could not read current threadnote package version.");
|
|
@@ -10357,7 +11099,7 @@ async function writeUpdateCache2(config, cache) {
|
|
|
10357
11099
|
`, { encoding: "utf8", mode: 384 });
|
|
10358
11100
|
}
|
|
10359
11101
|
function updateCachePath(config) {
|
|
10360
|
-
return (0,
|
|
11102
|
+
return (0, import_node_path11.join)(config.agentContextHome, "update-check.json");
|
|
10361
11103
|
}
|
|
10362
11104
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
10363
11105
|
const state = await readPostUpdateState(config);
|
|
@@ -10421,7 +11163,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
10421
11163
|
return applicable;
|
|
10422
11164
|
}
|
|
10423
11165
|
async function readPostUpdateMigrations() {
|
|
10424
|
-
const raw = await readFileIfExists((0,
|
|
11166
|
+
const raw = await readFileIfExists((0, import_node_path11.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
10425
11167
|
if (!raw) {
|
|
10426
11168
|
return [];
|
|
10427
11169
|
}
|
|
@@ -10499,7 +11241,7 @@ async function writePostUpdateState(config, state) {
|
|
|
10499
11241
|
`, { encoding: "utf8", mode: 384 });
|
|
10500
11242
|
}
|
|
10501
11243
|
function postUpdateStatePath(config) {
|
|
10502
|
-
return (0,
|
|
11244
|
+
return (0, import_node_path11.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
10503
11245
|
}
|
|
10504
11246
|
async function resolveUpdateRuntime(runtime) {
|
|
10505
11247
|
if (runtime !== "auto") {
|
|
@@ -10529,14 +11271,14 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
10529
11271
|
if (runtime === "npm") {
|
|
10530
11272
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
10531
11273
|
const prefix = result.stdout.trim();
|
|
10532
|
-
return prefix ? (0,
|
|
11274
|
+
return prefix ? (0, import_node_path11.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
10533
11275
|
}
|
|
10534
11276
|
if (runtime === "bun") {
|
|
10535
11277
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
10536
11278
|
const binDir = result.stdout.trim();
|
|
10537
|
-
return binDir ? (0,
|
|
11279
|
+
return binDir ? (0, import_node_path11.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
10538
11280
|
}
|
|
10539
|
-
return (0,
|
|
11281
|
+
return (0, import_node_path11.join)(process.env.DENO_INSTALL ?? (0, import_node_path11.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
10540
11282
|
}
|
|
10541
11283
|
function updatePackageCommand(runtime, registry) {
|
|
10542
11284
|
if (runtime === "npm") {
|
|
@@ -10591,9 +11333,9 @@ async function runDoctor(config, options) {
|
|
|
10591
11333
|
checks.push(await commandShimCheck());
|
|
10592
11334
|
checks.push(...await userAgentInstructionsChecks());
|
|
10593
11335
|
checks.push(await manifestCheck(config.manifestPath));
|
|
10594
|
-
checks.push(await fileCheck((0,
|
|
10595
|
-
checks.push(await fileCheck((0,
|
|
10596
|
-
checks.push(await fileCheck((0,
|
|
11336
|
+
checks.push(await fileCheck((0, import_node_path12.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
11337
|
+
checks.push(await fileCheck((0, import_node_path12.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
11338
|
+
checks.push(await fileCheck((0, import_node_path12.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
10597
11339
|
checks.push(await healthCheck(config));
|
|
10598
11340
|
for (const check of checks) {
|
|
10599
11341
|
console.log(`${formatStatus(check.status)} ${check.name}: ${check.detail}`);
|
|
@@ -10610,9 +11352,9 @@ async function runInstall(config, options) {
|
|
|
10610
11352
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
10611
11353
|
const dryRun = options.dryRun === true;
|
|
10612
11354
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
10613
|
-
await ensureDirectory((0,
|
|
10614
|
-
await ensureDirectory((0,
|
|
10615
|
-
await ensureDirectory((0,
|
|
11355
|
+
await ensureDirectory((0, import_node_path12.join)(config.agentContextHome, "logs"), dryRun);
|
|
11356
|
+
await ensureDirectory((0, import_node_path12.join)(config.agentContextHome, "redacted"), dryRun);
|
|
11357
|
+
await ensureDirectory((0, import_node_path12.join)(config.agentContextHome, "mcp"), dryRun);
|
|
10616
11358
|
await installCommandShim(dryRun);
|
|
10617
11359
|
await installUserAgentInstructions(dryRun);
|
|
10618
11360
|
const serverPath = await findOpenVikingServer();
|
|
@@ -10649,17 +11391,17 @@ async function runInstall(config, options) {
|
|
|
10649
11391
|
}
|
|
10650
11392
|
await writeTemplateIfMissing({
|
|
10651
11393
|
config,
|
|
10652
|
-
destinationPath: (0,
|
|
11394
|
+
destinationPath: (0, import_node_path12.join)(config.agentContextHome, "ov.conf"),
|
|
10653
11395
|
dryRun,
|
|
10654
11396
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
10655
|
-
templatePath: (0,
|
|
11397
|
+
templatePath: (0, import_node_path12.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
10656
11398
|
});
|
|
10657
11399
|
await writeTemplateIfMissing({
|
|
10658
11400
|
config,
|
|
10659
|
-
destinationPath: (0,
|
|
11401
|
+
destinationPath: (0, import_node_path12.join)(config.agentContextHome, "ovcli.conf"),
|
|
10660
11402
|
dryRun,
|
|
10661
11403
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
10662
|
-
templatePath: (0,
|
|
11404
|
+
templatePath: (0, import_node_path12.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
10663
11405
|
});
|
|
10664
11406
|
if (options.start !== false) {
|
|
10665
11407
|
const healthy = await repairServerHealth(config, dryRun);
|
|
@@ -10713,7 +11455,7 @@ async function runUninstall(config, options) {
|
|
|
10713
11455
|
}
|
|
10714
11456
|
console.log("Uninstalling local Threadnote setup.");
|
|
10715
11457
|
await runStop(config, { dryRun });
|
|
10716
|
-
await removePathIfExists((0,
|
|
11458
|
+
await removePathIfExists((0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
10717
11459
|
await removeLaunchAgent(dryRun);
|
|
10718
11460
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
10719
11461
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -10770,7 +11512,7 @@ async function repairManifest(config, dryRun) {
|
|
|
10770
11512
|
console.log(output2.trimEnd());
|
|
10771
11513
|
return;
|
|
10772
11514
|
}
|
|
10773
|
-
await ensureDirectory((0,
|
|
11515
|
+
await ensureDirectory((0, import_node_path12.dirname)(config.manifestPath), false);
|
|
10774
11516
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
10775
11517
|
if (currentContent !== void 0) {
|
|
10776
11518
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
@@ -10788,7 +11530,7 @@ async function findOpenVikingServer() {
|
|
|
10788
11530
|
return onPath;
|
|
10789
11531
|
}
|
|
10790
11532
|
for (const candidateDir of await openVikingServerCandidateDirs()) {
|
|
10791
|
-
const candidate = (0,
|
|
11533
|
+
const candidate = (0, import_node_path12.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
|
|
10792
11534
|
if (await isExecutable(candidate)) {
|
|
10793
11535
|
return candidate;
|
|
10794
11536
|
}
|
|
@@ -10834,7 +11576,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
|
|
|
10834
11576
|
if (onPath) {
|
|
10835
11577
|
return;
|
|
10836
11578
|
}
|
|
10837
|
-
const binDir = (0,
|
|
11579
|
+
const binDir = (0, import_node_path12.dirname)(serverPath);
|
|
10838
11580
|
const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
|
|
10839
11581
|
console.log(
|
|
10840
11582
|
`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.`
|
|
@@ -10878,7 +11620,7 @@ async function runStart(config, options) {
|
|
|
10878
11620
|
);
|
|
10879
11621
|
}
|
|
10880
11622
|
const logPath = openVikingLogPath(config);
|
|
10881
|
-
await ensureDirectory((0,
|
|
11623
|
+
await ensureDirectory((0, import_node_path12.dirname)(logPath), false);
|
|
10882
11624
|
if (options.foreground === true) {
|
|
10883
11625
|
const result = await runInteractive(server, args);
|
|
10884
11626
|
process.exitCode = result;
|
|
@@ -10887,7 +11629,7 @@ async function runStart(config, options) {
|
|
|
10887
11629
|
const logFd = (0, import_node_fs6.openSync)(logPath, "a");
|
|
10888
11630
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
10889
11631
|
child.unref();
|
|
10890
|
-
await (0, import_promises11.writeFile)((0,
|
|
11632
|
+
await (0, import_promises11.writeFile)((0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
10891
11633
|
`, "utf8");
|
|
10892
11634
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
10893
11635
|
if (health) {
|
|
@@ -10920,7 +11662,7 @@ async function runStop(config, options) {
|
|
|
10920
11662
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
10921
11663
|
}
|
|
10922
11664
|
}
|
|
10923
|
-
const pidPath = (0,
|
|
11665
|
+
const pidPath = (0, import_node_path12.join)(config.agentContextHome, "openviking-server.pid");
|
|
10924
11666
|
const pidText = await readFileIfExists(pidPath);
|
|
10925
11667
|
if (!pidText) {
|
|
10926
11668
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -10962,7 +11704,7 @@ async function openVikingServerCheck() {
|
|
|
10962
11704
|
}
|
|
10963
11705
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
10964
11706
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
10965
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
11707
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path12.dirname)(executable)} to PATH)`;
|
|
10966
11708
|
return {
|
|
10967
11709
|
name,
|
|
10968
11710
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -11031,7 +11773,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
11031
11773
|
};
|
|
11032
11774
|
}
|
|
11033
11775
|
async function commandShimCheck() {
|
|
11034
|
-
const shimPath = (0,
|
|
11776
|
+
const shimPath = (0, import_node_path12.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
11035
11777
|
const content = await readFileIfExists(shimPath);
|
|
11036
11778
|
if (content === void 0) {
|
|
11037
11779
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -11101,7 +11843,7 @@ async function siblingPythonForExecutable(executablePath) {
|
|
|
11101
11843
|
} catch (_err) {
|
|
11102
11844
|
return void 0;
|
|
11103
11845
|
}
|
|
11104
|
-
const pythonPath = (0,
|
|
11846
|
+
const pythonPath = (0, import_node_path12.join)((0, import_node_path12.dirname)(resolvedPath), "python");
|
|
11105
11847
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
11106
11848
|
}
|
|
11107
11849
|
async function manifestCheck(path) {
|
|
@@ -11321,14 +12063,14 @@ async function writeTemplateIfMissing(options) {
|
|
|
11321
12063
|
console.log(`Would write ${options.destinationPath}`);
|
|
11322
12064
|
return;
|
|
11323
12065
|
}
|
|
11324
|
-
await ensureDirectory((0,
|
|
12066
|
+
await ensureDirectory((0, import_node_path12.dirname)(options.destinationPath), false);
|
|
11325
12067
|
await (0, import_promises11.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
11326
12068
|
await (0, import_promises11.chmod)(options.destinationPath, 384);
|
|
11327
12069
|
console.log(`Wrote ${options.destinationPath}`);
|
|
11328
12070
|
}
|
|
11329
12071
|
async function installCommandShim(dryRun) {
|
|
11330
12072
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
11331
|
-
const shimPath = (0,
|
|
12073
|
+
const shimPath = (0, import_node_path12.join)(binDir, "threadnote");
|
|
11332
12074
|
const existingContent = await readFileIfExists(shimPath);
|
|
11333
12075
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
11334
12076
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -11349,7 +12091,7 @@ async function installCommandShim(dryRun) {
|
|
|
11349
12091
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
11350
12092
|
}
|
|
11351
12093
|
async function removeCommandShim(dryRun) {
|
|
11352
|
-
const shimPath = (0,
|
|
12094
|
+
const shimPath = (0, import_node_path12.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
11353
12095
|
const content = await readFileIfExists(shimPath);
|
|
11354
12096
|
if (content === void 0) {
|
|
11355
12097
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -11383,7 +12125,7 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
11383
12125
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
11384
12126
|
continue;
|
|
11385
12127
|
}
|
|
11386
|
-
await ensureDirectory((0,
|
|
12128
|
+
await ensureDirectory((0, import_node_path12.dirname)(targetPath), false);
|
|
11387
12129
|
await (0, import_promises11.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
11388
12130
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
11389
12131
|
}
|
|
@@ -11441,7 +12183,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
11441
12183
|
].join("\n");
|
|
11442
12184
|
}
|
|
11443
12185
|
async function renderUserAgentInstructionsBlock() {
|
|
11444
|
-
const instructions = (await (0, import_promises11.readFile)((0,
|
|
12186
|
+
const instructions = (await (0, import_promises11.readFile)((0, import_node_path12.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
11445
12187
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
11446
12188
|
${instructions}
|
|
11447
12189
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -11525,7 +12267,7 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
11525
12267
|
`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.`
|
|
11526
12268
|
);
|
|
11527
12269
|
}
|
|
11528
|
-
const source = (0,
|
|
12270
|
+
const source = (0, import_node_path12.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
11529
12271
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
11530
12272
|
const rendered = renderTemplate(await (0, import_promises11.readFile)(source, "utf8"), config, {
|
|
11531
12273
|
OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
|
|
@@ -11539,8 +12281,8 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
11539
12281
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
11540
12282
|
return;
|
|
11541
12283
|
}
|
|
11542
|
-
await ensureDirectory((0,
|
|
11543
|
-
await ensureDirectory((0,
|
|
12284
|
+
await ensureDirectory((0, import_node_path12.dirname)(destination), false);
|
|
12285
|
+
await ensureDirectory((0, import_node_path12.dirname)(openVikingLogPath(config)), false);
|
|
11544
12286
|
await (0, import_promises11.writeFile)(destination, rendered, "utf8");
|
|
11545
12287
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
11546
12288
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
@@ -11604,7 +12346,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
11604
12346
|
if (typeof parsed.default_user !== "string") {
|
|
11605
12347
|
return false;
|
|
11606
12348
|
}
|
|
11607
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
12349
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path12.join)(config.agentContextHome, "data")) {
|
|
11608
12350
|
return false;
|
|
11609
12351
|
}
|
|
11610
12352
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -11724,16 +12466,19 @@ async function main() {
|
|
|
11724
12466
|
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) => {
|
|
11725
12467
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
11726
12468
|
});
|
|
11727
|
-
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 exact
|
|
12469
|
+
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 exact memory/resource matches").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
|
|
11728
12470
|
await runRecall(getRuntimeConfig(program2), options);
|
|
11729
12471
|
});
|
|
12472
|
+
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) => {
|
|
12473
|
+
await runCompact(getRuntimeConfig(program2), options);
|
|
12474
|
+
});
|
|
11730
12475
|
program2.command("read").description("Read a viking:// URI returned by recall or list").argument("<uri>", "viking:// URI to read").option("--dry-run", "Print ov command without reading").action(async (uri, options) => {
|
|
11731
12476
|
await runRead(getRuntimeConfig(program2), uri, options);
|
|
11732
12477
|
});
|
|
11733
12478
|
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) => {
|
|
11734
12479
|
await runList(getRuntimeConfig(program2), uri, options);
|
|
11735
12480
|
});
|
|
11736
|
-
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--dry-run", "Print handoff without storing").option("--next-step <text>", "Suggested next step").option("--project <name>", "Project/repo namespace; defaults to current repo basename").option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
|
|
12481
|
+
program2.command("handoff").description("Capture current repo state as a durable cross-agent handoff memory").option("--blockers <text>", "Known blockers").option("--dry-run", "Print handoff without storing").option("--next-step <text>", "Suggested next step").option("--project <name>", "Project/repo namespace; defaults to current repo basename").option("--replace <uri>", "Supersede an existing viking:// memory after the new handoff is stored").option("--source-agent-client <name>", "codex, claude, cursor, copilot, or another client name", "codex").option("--task <text>", "Current task summary").option("--tests <text>", "Tests or checks run").option("--timestamped", "Store a historical timestamped handoff instead of updating the current branch handoff").option("--topic <name>", "Stable topic name; active handoffs with the same project/topic update one file").action(async (options) => {
|
|
11737
12482
|
await runHandoff(getRuntimeConfig(program2), options);
|
|
11738
12483
|
});
|
|
11739
12484
|
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) => {
|