threadnote 1.6.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/post-update-migrations.json +20 -0
- package/dist/mcp_server.cjs +126 -2
- package/dist/threadnote.cjs +382 -10
- package/docs/index.html +1 -1
- package/package.json +1 -1
|
@@ -18,6 +18,26 @@
|
|
|
18
18
|
"If Threadnote reported any original files were still being processed, rerun the printed threadnote forget <uri> command later."
|
|
19
19
|
],
|
|
20
20
|
"requiresLegacyHandoffs": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "remote-project-name-consolidation-v1",
|
|
24
|
+
"introducedIn": "1.6.3",
|
|
25
|
+
"title": "Consolidate memories under the git remote project name",
|
|
26
|
+
"description": [
|
|
27
|
+
"Threadnote now uses the git remote repository name as the default project identifier instead of the clone folder name.",
|
|
28
|
+
"This migration scans personal lifecycle-aware memory folders for the old folder-derived project name from the current workspace.",
|
|
29
|
+
"It also renames the matching seed manifest project and workset references so future seeded guidance uses the remote-derived project name.",
|
|
30
|
+
"Matching memories are copied under the remote-derived project name with updated project metadata, then the old copy is removed.",
|
|
31
|
+
"If a destination file already exists with different content, Threadnote keeps both by writing the migrated copy with a -from-<old-project> suffix.",
|
|
32
|
+
"Shared team memories and unrelated project folders are left untouched."
|
|
33
|
+
],
|
|
34
|
+
"commandArgs": ["migrate-project-names", "--apply"],
|
|
35
|
+
"instructions": [
|
|
36
|
+
"Post-update project-name migration finished. Future default handoffs and memories for this repo will use the git remote repository name as their project.",
|
|
37
|
+
"If the migration updated your seed manifest, run threadnote seed --only <new-project-name> to re-ingest seeded resources under the new project URI.",
|
|
38
|
+
"If Threadnote reported any original files were still being processed, rerun the printed threadnote forget <uri> command later."
|
|
39
|
+
],
|
|
40
|
+
"requiresProjectNameConsolidation": true
|
|
21
41
|
}
|
|
22
42
|
]
|
|
23
43
|
}
|
package/dist/mcp_server.cjs
CHANGED
|
@@ -35592,6 +35592,23 @@ function parseJsonConfigObject(content) {
|
|
|
35592
35592
|
function redactText(content) {
|
|
35593
35593
|
return redactSensitiveText(content);
|
|
35594
35594
|
}
|
|
35595
|
+
var GIT_ENVIRONMENT_KEYS = [
|
|
35596
|
+
"GIT_DIR",
|
|
35597
|
+
"GIT_WORK_TREE",
|
|
35598
|
+
"GIT_INDEX_FILE",
|
|
35599
|
+
"GIT_PREFIX",
|
|
35600
|
+
"GIT_COMMON_DIR",
|
|
35601
|
+
"GIT_OBJECT_DIRECTORY",
|
|
35602
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
35603
|
+
"GIT_QUARANTINE_PATH"
|
|
35604
|
+
];
|
|
35605
|
+
function withoutGitEnvironment(env = process.env) {
|
|
35606
|
+
const next = { ...env };
|
|
35607
|
+
for (const key of GIT_ENVIRONMENT_KEYS) {
|
|
35608
|
+
delete next[key];
|
|
35609
|
+
}
|
|
35610
|
+
return next;
|
|
35611
|
+
}
|
|
35595
35612
|
function escapeRegExp(value) {
|
|
35596
35613
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35597
35614
|
}
|
|
@@ -35711,6 +35728,7 @@ async function runCommand(executable, args, options = {}) {
|
|
|
35711
35728
|
{
|
|
35712
35729
|
cwd: options.cwd,
|
|
35713
35730
|
encoding: "utf8",
|
|
35731
|
+
env: commandEnvironment(executable, options.env),
|
|
35714
35732
|
maxBuffer: maxOutputBytes
|
|
35715
35733
|
},
|
|
35716
35734
|
(err, stdout2, stderr) => {
|
|
@@ -35750,6 +35768,12 @@ async function runCommand(executable, args, options = {}) {
|
|
|
35750
35768
|
}
|
|
35751
35769
|
});
|
|
35752
35770
|
}
|
|
35771
|
+
function commandEnvironment(executable, env) {
|
|
35772
|
+
if ((0, import_node_path.basename)(executable) !== "git") {
|
|
35773
|
+
return env;
|
|
35774
|
+
}
|
|
35775
|
+
return withoutGitEnvironment(env ?? process.env);
|
|
35776
|
+
}
|
|
35753
35777
|
function commandResultFromExecFileCallback(params) {
|
|
35754
35778
|
if (params.failureMessage) {
|
|
35755
35779
|
return { exitCode: 124, stderr: params.failureMessage, stdout: params.stdout };
|
|
@@ -35786,6 +35810,64 @@ async function gitValue(args, cwd = getInvocationCwd()) {
|
|
|
35786
35810
|
}
|
|
35787
35811
|
return result.stdout.trim();
|
|
35788
35812
|
}
|
|
35813
|
+
async function resolveRepoName(cwd = getInvocationCwd()) {
|
|
35814
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], cwd);
|
|
35815
|
+
if (!repoRoot) {
|
|
35816
|
+
return void 0;
|
|
35817
|
+
}
|
|
35818
|
+
const remoteName = await resolveGitRemoteRepoName(repoRoot);
|
|
35819
|
+
if (remoteName) {
|
|
35820
|
+
return remoteName;
|
|
35821
|
+
}
|
|
35822
|
+
return resolveRepoFolderName(repoRoot);
|
|
35823
|
+
}
|
|
35824
|
+
async function resolveRepoFolderName(cwd = getInvocationCwd()) {
|
|
35825
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], cwd);
|
|
35826
|
+
if (!repoRoot) {
|
|
35827
|
+
return void 0;
|
|
35828
|
+
}
|
|
35829
|
+
const commonDir = await gitValue(["rev-parse", "--git-common-dir"], repoRoot);
|
|
35830
|
+
if (commonDir) {
|
|
35831
|
+
const absoluteCommonDir = (0, import_node_path.isAbsolute)(commonDir) ? commonDir : (0, import_node_path.resolve)(repoRoot, commonDir);
|
|
35832
|
+
const primaryRoot = (0, import_node_path.basename)(absoluteCommonDir) === ".git" ? (0, import_node_path.dirname)(absoluteCommonDir) : absoluteCommonDir;
|
|
35833
|
+
const name = (0, import_node_path.basename)(primaryRoot).replace(/\.git$/, "");
|
|
35834
|
+
if (name && name !== ".") {
|
|
35835
|
+
return name;
|
|
35836
|
+
}
|
|
35837
|
+
}
|
|
35838
|
+
return (0, import_node_path.basename)(repoRoot);
|
|
35839
|
+
}
|
|
35840
|
+
async function resolveGitRemoteRepoName(repoRoot) {
|
|
35841
|
+
const originUrl = await gitValue(["remote", "get-url", "origin"], repoRoot);
|
|
35842
|
+
const originName = originUrl ? gitRemoteRepoName(originUrl) : void 0;
|
|
35843
|
+
if (originName) {
|
|
35844
|
+
return originName;
|
|
35845
|
+
}
|
|
35846
|
+
const remotes = await gitValue(["remote"], repoRoot);
|
|
35847
|
+
const remote = remotes?.split(/\r?\n/).map((name) => name.trim()).find((name) => name.length > 0);
|
|
35848
|
+
if (!remote) {
|
|
35849
|
+
return void 0;
|
|
35850
|
+
}
|
|
35851
|
+
const remoteUrl = await gitValue(["remote", "get-url", remote], repoRoot);
|
|
35852
|
+
return remoteUrl ? gitRemoteRepoName(remoteUrl) : void 0;
|
|
35853
|
+
}
|
|
35854
|
+
function gitRemoteRepoName(remoteUrl) {
|
|
35855
|
+
const trimmed = remoteUrl.trim();
|
|
35856
|
+
if (!trimmed) {
|
|
35857
|
+
return void 0;
|
|
35858
|
+
}
|
|
35859
|
+
let remotePath = trimmed.replace(/[?#].*$/, "");
|
|
35860
|
+
try {
|
|
35861
|
+
remotePath = new URL(trimmed).pathname;
|
|
35862
|
+
} catch (_err) {
|
|
35863
|
+
const scpLike = trimmed.match(/^[^@\s/]+@[^:\s]+:(.+)$/);
|
|
35864
|
+
if (scpLike?.[1]) {
|
|
35865
|
+
remotePath = scpLike[1];
|
|
35866
|
+
}
|
|
35867
|
+
}
|
|
35868
|
+
const name = remotePath.replace(/[\\/]+$/, "").split(/[\\/:]/).filter(Boolean).pop()?.replace(/\.git$/i, "");
|
|
35869
|
+
return name && name !== "." && name !== ".." ? name : void 0;
|
|
35870
|
+
}
|
|
35789
35871
|
async function sleep(ms) {
|
|
35790
35872
|
return new Promise((resolvePromise) => {
|
|
35791
35873
|
setTimeout(resolvePromise, ms);
|
|
@@ -35926,6 +36008,13 @@ async function enrichRecallQueryWithWorkspaceContext(query, options = {}) {
|
|
|
35926
36008
|
async function enrichRecallQueryWithWorkspaceProjectContext(query, options = {}) {
|
|
35927
36009
|
return enrichRecallQueryWithWorkspaceTerms(query, options, false);
|
|
35928
36010
|
}
|
|
36011
|
+
async function resolveWorkspaceRepoName(options = {}) {
|
|
36012
|
+
const cwd = options.cwd ?? (options.includeProcessCwd === false ? void 0 : getInvocationCwd());
|
|
36013
|
+
if (!cwd || !(0, import_node_path.isAbsolute)(cwd)) {
|
|
36014
|
+
return void 0;
|
|
36015
|
+
}
|
|
36016
|
+
return resolveRepoName(cwd);
|
|
36017
|
+
}
|
|
35929
36018
|
async function enrichRecallQueryWithWorkspaceTerms(query, options, includeBranch) {
|
|
35930
36019
|
if (!recallQueryRequestsWorkspaceContext(query)) {
|
|
35931
36020
|
return query;
|
|
@@ -35944,10 +36033,11 @@ async function currentWorkspaceRecallTerms(options, includeBranch) {
|
|
|
35944
36033
|
return [];
|
|
35945
36034
|
}
|
|
35946
36035
|
const branch = await gitValue(["branch", "--show-current"], repoRoot);
|
|
36036
|
+
const repoName = await resolveWorkspaceRepoName({ cwd, includeProcessCwd: false });
|
|
35947
36037
|
const parent = (0, import_node_path.dirname)(repoRoot);
|
|
35948
36038
|
return uniqueUsefulWorkspaceTerms([
|
|
35949
36039
|
{ source: "branch", value: includeBranch ? branch : void 0 },
|
|
35950
|
-
{ source: "path", value:
|
|
36040
|
+
{ source: "path", value: repoName },
|
|
35951
36041
|
{ source: "path", value: parent === (0, import_node_os.homedir)() ? void 0 : (0, import_node_path.basename)(parent) }
|
|
35952
36042
|
]);
|
|
35953
36043
|
}
|
|
@@ -40375,6 +40465,7 @@ async function runRecallTool(config2, params) {
|
|
|
40375
40465
|
indexRepairMessages = [`Auto-index repair warning: ${errorMessage(err)}`];
|
|
40376
40466
|
}
|
|
40377
40467
|
const project = params.pinnedUri ? void 0 : await inferProjectFromQuery(config2.manifestPath, projectQuery);
|
|
40468
|
+
const projectMemoryName = params.pinnedUri ? void 0 : await resolveWorkspaceRepoName({ cwd: params.callerCwd, includeProcessCwd: false });
|
|
40378
40469
|
const limitArgs = params.nodeLimit ? ["--node-limit", String(params.nodeLimit)] : [];
|
|
40379
40470
|
const threshold = params.threshold ?? RECALL_SCORE_THRESHOLD;
|
|
40380
40471
|
const explicitWorkset = params.workset ? await requireWorkset(config2.manifestPath, params.workset) : void 0;
|
|
@@ -40386,6 +40477,19 @@ async function runRecallTool(config2, params) {
|
|
|
40386
40477
|
params.includeArchived
|
|
40387
40478
|
);
|
|
40388
40479
|
const passes = [base.hits];
|
|
40480
|
+
const scopedRecallUris = new Set([params.pinnedUri].filter((uri) => uri !== void 0));
|
|
40481
|
+
for (const scope of projectMemoryScopeUris(config2, projectMemoryName, params.includeArchived)) {
|
|
40482
|
+
if (!scopedRecallUris.has(scope)) {
|
|
40483
|
+
scopedRecallUris.add(scope);
|
|
40484
|
+
const projectMemoryPass = await recallSearchHits(
|
|
40485
|
+
config2,
|
|
40486
|
+
["search", query, "--uri", scope, ...limitArgs],
|
|
40487
|
+
threshold,
|
|
40488
|
+
params.includeArchived
|
|
40489
|
+
);
|
|
40490
|
+
passes.push(projectMemoryPass.hits);
|
|
40491
|
+
}
|
|
40492
|
+
}
|
|
40389
40493
|
const seededUri = project ? trimTrailingSlash(project.uri) : void 0;
|
|
40390
40494
|
if (seededUri?.startsWith("viking://") && seededUri !== params.pinnedUri) {
|
|
40391
40495
|
const seeded = await recallSearchHits(
|
|
@@ -40400,7 +40504,9 @@ async function runRecallTool(config2, params) {
|
|
|
40400
40504
|
const workset = params.pinnedUri ? void 0 : explicitWorkset ? explicitWorkset : await inferWorksetFromQuery(config2.manifestPath, projectQuery);
|
|
40401
40505
|
if (workset && workset.projects.length > 0) {
|
|
40402
40506
|
sections.push(`Workset scope: ${workset.name} (${workset.projects.map((member) => member.name).join(", ")})`);
|
|
40403
|
-
const alreadyScoped = new Set(
|
|
40507
|
+
const alreadyScoped = new Set(
|
|
40508
|
+
[params.pinnedUri, seededUri, ...scopedRecallUris].filter((uri) => uri !== void 0)
|
|
40509
|
+
);
|
|
40404
40510
|
const worksetScopes = worksetScopeUris(config2, workset).filter((uri) => !alreadyScoped.has(uri)).slice(0, MAX_WORKSET_PASSES);
|
|
40405
40511
|
for (const scope of worksetScopes) {
|
|
40406
40512
|
const worksetPass = await recallSearchHits(
|
|
@@ -41087,6 +41193,24 @@ function worksetScopeUris(config2, workset) {
|
|
|
41087
41193
|
}
|
|
41088
41194
|
return [...new Set(scopes)];
|
|
41089
41195
|
}
|
|
41196
|
+
function projectMemoryScopeUris(config2, projectName, includeArchived) {
|
|
41197
|
+
if (!projectName) {
|
|
41198
|
+
return [];
|
|
41199
|
+
}
|
|
41200
|
+
const base = `viking://user/${uriSegment2(config2.user)}/memories`;
|
|
41201
|
+
const projectSegment = uriSegment2(projectName);
|
|
41202
|
+
const scopes = [
|
|
41203
|
+
`${base}/durable/projects/${projectSegment}`,
|
|
41204
|
+
`${base}/handoffs/active/${projectSegment}`,
|
|
41205
|
+
`${base}/incidents/active/${projectSegment}`
|
|
41206
|
+
];
|
|
41207
|
+
return includeArchived ? [
|
|
41208
|
+
...scopes,
|
|
41209
|
+
`${base}/durable/archived/${projectSegment}`,
|
|
41210
|
+
`${base}/handoffs/archived/${projectSegment}`,
|
|
41211
|
+
`${base}/incidents/archived/${projectSegment}`
|
|
41212
|
+
] : scopes;
|
|
41213
|
+
}
|
|
41090
41214
|
function formatMemoryDocument2(title, metadata, body) {
|
|
41091
41215
|
const header = [
|
|
41092
41216
|
title,
|
package/dist/threadnote.cjs
CHANGED
|
@@ -3676,6 +3676,23 @@ function parseJsonConfigObject(content) {
|
|
|
3676
3676
|
function redactText(content) {
|
|
3677
3677
|
return redactSensitiveText(content);
|
|
3678
3678
|
}
|
|
3679
|
+
var GIT_ENVIRONMENT_KEYS = [
|
|
3680
|
+
"GIT_DIR",
|
|
3681
|
+
"GIT_WORK_TREE",
|
|
3682
|
+
"GIT_INDEX_FILE",
|
|
3683
|
+
"GIT_PREFIX",
|
|
3684
|
+
"GIT_COMMON_DIR",
|
|
3685
|
+
"GIT_OBJECT_DIRECTORY",
|
|
3686
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
3687
|
+
"GIT_QUARANTINE_PATH"
|
|
3688
|
+
];
|
|
3689
|
+
function withoutGitEnvironment(env = process.env) {
|
|
3690
|
+
const next = { ...env };
|
|
3691
|
+
for (const key of GIT_ENVIRONMENT_KEYS) {
|
|
3692
|
+
delete next[key];
|
|
3693
|
+
}
|
|
3694
|
+
return next;
|
|
3695
|
+
}
|
|
3679
3696
|
async function walkFiles(root) {
|
|
3680
3697
|
const files = [];
|
|
3681
3698
|
async function visit(path2) {
|
|
@@ -3863,6 +3880,7 @@ async function runCommand(executable, args, options = {}) {
|
|
|
3863
3880
|
{
|
|
3864
3881
|
cwd: options.cwd,
|
|
3865
3882
|
encoding: "utf8",
|
|
3883
|
+
env: commandEnvironment(executable, options.env),
|
|
3866
3884
|
maxBuffer: maxOutputBytes
|
|
3867
3885
|
},
|
|
3868
3886
|
(err, stdout2, stderr) => {
|
|
@@ -3902,6 +3920,12 @@ async function runCommand(executable, args, options = {}) {
|
|
|
3902
3920
|
}
|
|
3903
3921
|
});
|
|
3904
3922
|
}
|
|
3923
|
+
function commandEnvironment(executable, env) {
|
|
3924
|
+
if ((0, import_node_path2.basename)(executable) !== "git") {
|
|
3925
|
+
return env;
|
|
3926
|
+
}
|
|
3927
|
+
return withoutGitEnvironment(env ?? process.env);
|
|
3928
|
+
}
|
|
3905
3929
|
function commandResultFromExecFileCallback(params) {
|
|
3906
3930
|
if (params.failureMessage) {
|
|
3907
3931
|
return { exitCode: 124, stderr: params.failureMessage, stdout: params.stdout };
|
|
@@ -3939,6 +3963,17 @@ async function gitValue(args, cwd = getInvocationCwd()) {
|
|
|
3939
3963
|
return result.stdout.trim();
|
|
3940
3964
|
}
|
|
3941
3965
|
async function resolveRepoName(cwd = getInvocationCwd()) {
|
|
3966
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], cwd);
|
|
3967
|
+
if (!repoRoot) {
|
|
3968
|
+
return void 0;
|
|
3969
|
+
}
|
|
3970
|
+
const remoteName = await resolveGitRemoteRepoName(repoRoot);
|
|
3971
|
+
if (remoteName) {
|
|
3972
|
+
return remoteName;
|
|
3973
|
+
}
|
|
3974
|
+
return resolveRepoFolderName(repoRoot);
|
|
3975
|
+
}
|
|
3976
|
+
async function resolveRepoFolderName(cwd = getInvocationCwd()) {
|
|
3942
3977
|
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], cwd);
|
|
3943
3978
|
if (!repoRoot) {
|
|
3944
3979
|
return void 0;
|
|
@@ -3954,6 +3989,37 @@ async function resolveRepoName(cwd = getInvocationCwd()) {
|
|
|
3954
3989
|
}
|
|
3955
3990
|
return (0, import_node_path2.basename)(repoRoot);
|
|
3956
3991
|
}
|
|
3992
|
+
async function resolveGitRemoteRepoName(repoRoot) {
|
|
3993
|
+
const originUrl = await gitValue(["remote", "get-url", "origin"], repoRoot);
|
|
3994
|
+
const originName = originUrl ? gitRemoteRepoName(originUrl) : void 0;
|
|
3995
|
+
if (originName) {
|
|
3996
|
+
return originName;
|
|
3997
|
+
}
|
|
3998
|
+
const remotes = await gitValue(["remote"], repoRoot);
|
|
3999
|
+
const remote = remotes?.split(/\r?\n/).map((name) => name.trim()).find((name) => name.length > 0);
|
|
4000
|
+
if (!remote) {
|
|
4001
|
+
return void 0;
|
|
4002
|
+
}
|
|
4003
|
+
const remoteUrl = await gitValue(["remote", "get-url", remote], repoRoot);
|
|
4004
|
+
return remoteUrl ? gitRemoteRepoName(remoteUrl) : void 0;
|
|
4005
|
+
}
|
|
4006
|
+
function gitRemoteRepoName(remoteUrl) {
|
|
4007
|
+
const trimmed = remoteUrl.trim();
|
|
4008
|
+
if (!trimmed) {
|
|
4009
|
+
return void 0;
|
|
4010
|
+
}
|
|
4011
|
+
let remotePath = trimmed.replace(/[?#].*$/, "");
|
|
4012
|
+
try {
|
|
4013
|
+
remotePath = new URL(trimmed).pathname;
|
|
4014
|
+
} catch (_err) {
|
|
4015
|
+
const scpLike = trimmed.match(/^[^@\s/]+@[^:\s]+:(.+)$/);
|
|
4016
|
+
if (scpLike?.[1]) {
|
|
4017
|
+
remotePath = scpLike[1];
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
const name = remotePath.replace(/[\\/]+$/, "").split(/[\\/:]/).filter(Boolean).pop()?.replace(/\.git$/i, "");
|
|
4021
|
+
return name && name !== "." && name !== ".." ? name : void 0;
|
|
4022
|
+
}
|
|
3957
4023
|
async function runInteractive(executable, args) {
|
|
3958
4024
|
return new Promise((resolvePromise) => {
|
|
3959
4025
|
const child = (0, import_node_child_process2.spawn)(executable, args, { stdio: "inherit" });
|
|
@@ -4223,6 +4289,13 @@ async function enrichRecallQueryWithWorkspaceContext(query, options = {}) {
|
|
|
4223
4289
|
async function enrichRecallQueryWithWorkspaceProjectContext(query, options = {}) {
|
|
4224
4290
|
return enrichRecallQueryWithWorkspaceTerms(query, options, false);
|
|
4225
4291
|
}
|
|
4292
|
+
async function resolveWorkspaceRepoName(options = {}) {
|
|
4293
|
+
const cwd = options.cwd ?? (options.includeProcessCwd === false ? void 0 : getInvocationCwd());
|
|
4294
|
+
if (!cwd || !(0, import_node_path2.isAbsolute)(cwd)) {
|
|
4295
|
+
return void 0;
|
|
4296
|
+
}
|
|
4297
|
+
return resolveRepoName(cwd);
|
|
4298
|
+
}
|
|
4226
4299
|
async function enrichRecallQueryWithWorkspaceTerms(query, options, includeBranch) {
|
|
4227
4300
|
if (!recallQueryRequestsWorkspaceContext(query)) {
|
|
4228
4301
|
return query;
|
|
@@ -4241,10 +4314,11 @@ async function currentWorkspaceRecallTerms(options, includeBranch) {
|
|
|
4241
4314
|
return [];
|
|
4242
4315
|
}
|
|
4243
4316
|
const branch = await gitValue(["branch", "--show-current"], repoRoot);
|
|
4317
|
+
const repoName = await resolveWorkspaceRepoName({ cwd, includeProcessCwd: false });
|
|
4244
4318
|
const parent = (0, import_node_path2.dirname)(repoRoot);
|
|
4245
4319
|
return uniqueUsefulWorkspaceTerms([
|
|
4246
4320
|
{ source: "branch", value: includeBranch ? branch : void 0 },
|
|
4247
|
-
{ source: "path", value:
|
|
4321
|
+
{ source: "path", value: repoName },
|
|
4248
4322
|
{ source: "path", value: parent === (0, import_node_os.homedir)() ? void 0 : (0, import_node_path2.basename)(parent) }
|
|
4249
4323
|
]);
|
|
4250
4324
|
}
|
|
@@ -5251,9 +5325,6 @@ function existsSyncDirectory(path2) {
|
|
|
5251
5325
|
var import_promises6 = require("node:fs/promises");
|
|
5252
5326
|
var import_node_path8 = require("node:path");
|
|
5253
5327
|
|
|
5254
|
-
// src/manifest.ts
|
|
5255
|
-
var import_promises3 = require("node:fs/promises");
|
|
5256
|
-
|
|
5257
5328
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
5258
5329
|
var __create2 = Object.create;
|
|
5259
5330
|
var __defProp2 = Object.defineProperty;
|
|
@@ -7617,6 +7688,7 @@ var { Type, Schema, FAILSAFE_SCHEMA, JSON_SCHEMA, CORE_SCHEMA, DEFAULT_SCHEMA, l
|
|
|
7617
7688
|
var index_vite_proxy_tmp_default = import_js_yaml.default;
|
|
7618
7689
|
|
|
7619
7690
|
// src/manifest.ts
|
|
7691
|
+
var import_promises3 = require("node:fs/promises");
|
|
7620
7692
|
function uriSegment(value) {
|
|
7621
7693
|
const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7622
7694
|
return normalized.length > 0 ? normalized : "unknown";
|
|
@@ -11558,6 +11630,263 @@ async function runMigrateLifecycle(config, options) {
|
|
|
11558
11630
|
].filter((part) => part !== void 0).join("; ")
|
|
11559
11631
|
);
|
|
11560
11632
|
}
|
|
11633
|
+
async function runMigrateProjectNames(config, options) {
|
|
11634
|
+
const dryRun = options.dryRun === true || options.apply !== true;
|
|
11635
|
+
const limit = options.limit ? parsePositiveInteger(options.limit, "project-name migration limit") : void 0;
|
|
11636
|
+
const context = await projectNameMigrationContext();
|
|
11637
|
+
if (!context) {
|
|
11638
|
+
console.log("No git remote project-name change applies in the current workspace.");
|
|
11639
|
+
return;
|
|
11640
|
+
}
|
|
11641
|
+
const candidates = await projectNameMigrationCandidates(config, context, limit);
|
|
11642
|
+
const seedManifestCandidate = await hasSeedManifestProjectNameMigrationCandidate(config, context);
|
|
11643
|
+
if (candidates.length === 0 && !seedManifestCandidate) {
|
|
11644
|
+
console.log(`No project-name migration candidates found for ${context.oldProject} -> ${context.newProject}.`);
|
|
11645
|
+
return;
|
|
11646
|
+
}
|
|
11647
|
+
const seedManifestUpdated = await migrateSeedManifestProjectName(config, context, dryRun);
|
|
11648
|
+
let existingCount = 0;
|
|
11649
|
+
let migratedCount = 0;
|
|
11650
|
+
let skippedCount = 0;
|
|
11651
|
+
if (candidates.length > 0) {
|
|
11652
|
+
const ov = await openVikingCliForMode(dryRun);
|
|
11653
|
+
for (const candidate of candidates) {
|
|
11654
|
+
const action = candidate.destinationExistsWithSameContent ? dryRun ? "Would consolidate duplicate" : "Consolidating duplicate" : dryRun ? "Would migrate" : "Migrating";
|
|
11655
|
+
console.log(`${action} ${candidate.sourceUri} -> ${candidate.destinationUri}`);
|
|
11656
|
+
if (!dryRun) {
|
|
11657
|
+
if (candidate.destinationExistsWithSameContent) {
|
|
11658
|
+
existingCount += 1;
|
|
11659
|
+
} else {
|
|
11660
|
+
await ensureMemoryDirectory(ov, config, parentVikingUri(candidate.destinationUri));
|
|
11661
|
+
await writeMemoryFile(config, ov, candidate.destinationUri, candidate.destinationContent, "create", false);
|
|
11662
|
+
}
|
|
11663
|
+
const removedOriginal = await removeVikingResourceWithRetry(ov, config, candidate.sourceUri);
|
|
11664
|
+
if (!removedOriginal) {
|
|
11665
|
+
console.error(
|
|
11666
|
+
`Migrated copy stored, but original is still processing. Retry later: threadnote forget ${candidate.sourceUri}`
|
|
11667
|
+
);
|
|
11668
|
+
skippedCount += 1;
|
|
11669
|
+
}
|
|
11670
|
+
}
|
|
11671
|
+
migratedCount += 1;
|
|
11672
|
+
}
|
|
11673
|
+
}
|
|
11674
|
+
console.log(
|
|
11675
|
+
[
|
|
11676
|
+
`Project-name migration summary: ${migratedCount} memor${migratedCount === 1 ? "y" : "ies"} ${dryRun ? "would be migrated" : "migrated"} from ${context.oldProject} to ${context.newProject}`,
|
|
11677
|
+
seedManifestUpdated ? `seed manifest ${dryRun ? "would be updated" : "updated"}` : "seed manifest unchanged",
|
|
11678
|
+
`${existingCount} duplicate destination(s) reused`,
|
|
11679
|
+
`${skippedCount} source(s) still processing`,
|
|
11680
|
+
dryRun ? "Run with --apply to perform this migration." : void 0,
|
|
11681
|
+
seedManifestUpdated ? `Run threadnote seed --only ${context.newProject} to re-ingest seeded resources under the new project URI.` : void 0
|
|
11682
|
+
].filter((part) => part !== void 0).join("; ")
|
|
11683
|
+
);
|
|
11684
|
+
}
|
|
11685
|
+
async function hasProjectNameMigrationCandidates(config) {
|
|
11686
|
+
const context = await projectNameMigrationContext();
|
|
11687
|
+
return context ? (await projectNameMigrationCandidates(config, context, 1)).length > 0 || await hasSeedManifestProjectNameMigrationCandidate(config, context) : false;
|
|
11688
|
+
}
|
|
11689
|
+
async function projectNameMigrationContext() {
|
|
11690
|
+
const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
|
|
11691
|
+
if (!repoRoot) {
|
|
11692
|
+
return void 0;
|
|
11693
|
+
}
|
|
11694
|
+
const newProject = await resolveRepoName(repoRoot);
|
|
11695
|
+
const oldProject = await resolveRepoFolderName(repoRoot);
|
|
11696
|
+
if (!newProject || !oldProject) {
|
|
11697
|
+
return void 0;
|
|
11698
|
+
}
|
|
11699
|
+
const newSegment = uriSegment(newProject);
|
|
11700
|
+
const oldSegment = uriSegment(oldProject);
|
|
11701
|
+
if (newSegment === oldSegment) {
|
|
11702
|
+
return void 0;
|
|
11703
|
+
}
|
|
11704
|
+
return { newProject, newSegment, oldProject, oldSegment, repoRoot };
|
|
11705
|
+
}
|
|
11706
|
+
async function projectNameMigrationCandidates(config, context, limit) {
|
|
11707
|
+
const candidates = [];
|
|
11708
|
+
for (const location of projectMemoryLocations()) {
|
|
11709
|
+
const sourceDirectory = (0, import_node_path8.join)(localUserMemoriesRoot(config), ...location.relativePath, context.oldSegment);
|
|
11710
|
+
const sourceDirectoryUri = `viking://user/${uriSegment(config.user)}/memories/${location.uriPath}/${context.oldSegment}`;
|
|
11711
|
+
let entries;
|
|
11712
|
+
try {
|
|
11713
|
+
entries = await (0, import_promises6.readdir)(sourceDirectory, { withFileTypes: true });
|
|
11714
|
+
} catch (_err) {
|
|
11715
|
+
continue;
|
|
11716
|
+
}
|
|
11717
|
+
for (const entry of entries) {
|
|
11718
|
+
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
11719
|
+
continue;
|
|
11720
|
+
}
|
|
11721
|
+
const sourceUri = `${sourceDirectoryUri}/${entry.name}`;
|
|
11722
|
+
const content = await readTextIfExists((0, import_node_path8.join)(sourceDirectory, entry.name));
|
|
11723
|
+
if (!content) {
|
|
11724
|
+
continue;
|
|
11725
|
+
}
|
|
11726
|
+
const record = parseMemoryDocument(sourceUri, content);
|
|
11727
|
+
if (!record || !canMigrateProjectName(record, context)) {
|
|
11728
|
+
continue;
|
|
11729
|
+
}
|
|
11730
|
+
const metadata = { ...record.metadata, project: context.newProject };
|
|
11731
|
+
const destinationDirectoryUri = memoryDirectoryUri(config, metadata);
|
|
11732
|
+
const destinationDirectory = localMemoryPathForUri(config, destinationDirectoryUri);
|
|
11733
|
+
if (!destinationDirectory) {
|
|
11734
|
+
continue;
|
|
11735
|
+
}
|
|
11736
|
+
const destinationContent = formatMemoryDocument2(record.headerTitle, metadata, record.body);
|
|
11737
|
+
const destination = await projectNameMigrationDestination(
|
|
11738
|
+
destinationDirectory,
|
|
11739
|
+
entry.name,
|
|
11740
|
+
destinationContent,
|
|
11741
|
+
context.oldSegment
|
|
11742
|
+
);
|
|
11743
|
+
candidates.push({
|
|
11744
|
+
destinationContent,
|
|
11745
|
+
destinationExistsWithSameContent: destination.existsWithSameContent,
|
|
11746
|
+
destinationUri: `${destinationDirectoryUri}/${destination.filename}`,
|
|
11747
|
+
sourceUri
|
|
11748
|
+
});
|
|
11749
|
+
if (limit !== void 0 && candidates.length >= limit) {
|
|
11750
|
+
return candidates;
|
|
11751
|
+
}
|
|
11752
|
+
}
|
|
11753
|
+
}
|
|
11754
|
+
return candidates;
|
|
11755
|
+
}
|
|
11756
|
+
async function hasSeedManifestProjectNameMigrationCandidate(config, context) {
|
|
11757
|
+
return await seedManifestProjectNameMigration(config, context) !== void 0;
|
|
11758
|
+
}
|
|
11759
|
+
async function migrateSeedManifestProjectName(config, context, dryRun) {
|
|
11760
|
+
const migration = await seedManifestProjectNameMigration(config, context);
|
|
11761
|
+
if (!migration) {
|
|
11762
|
+
return false;
|
|
11763
|
+
}
|
|
11764
|
+
if (dryRun) {
|
|
11765
|
+
console.log(`Would update seed manifest: ${config.manifestPath}`);
|
|
11766
|
+
console.log(migration.output.trimEnd());
|
|
11767
|
+
return true;
|
|
11768
|
+
}
|
|
11769
|
+
await ensureDirectory((0, import_node_path8.dirname)(config.manifestPath), false);
|
|
11770
|
+
const currentContent = await readFileIfExists(config.manifestPath);
|
|
11771
|
+
if (currentContent !== void 0) {
|
|
11772
|
+
const backupPath = `${config.manifestPath}.project-name-${safeTimestamp()}`;
|
|
11773
|
+
await (0, import_promises6.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
11774
|
+
await (0, import_promises6.chmod)(backupPath, 384);
|
|
11775
|
+
console.log(`Backup: ${backupPath}`);
|
|
11776
|
+
}
|
|
11777
|
+
await (0, import_promises6.writeFile)(config.manifestPath, migration.output, { encoding: "utf8", mode: 384 });
|
|
11778
|
+
await (0, import_promises6.chmod)(config.manifestPath, 384);
|
|
11779
|
+
console.log(`Updated seed manifest: ${config.manifestPath}`);
|
|
11780
|
+
return true;
|
|
11781
|
+
}
|
|
11782
|
+
async function seedManifestProjectNameMigration(config, context) {
|
|
11783
|
+
let manifest;
|
|
11784
|
+
try {
|
|
11785
|
+
manifest = await readSeedManifest(config.manifestPath);
|
|
11786
|
+
} catch (_err) {
|
|
11787
|
+
return void 0;
|
|
11788
|
+
}
|
|
11789
|
+
const oldDefaultUri = `viking://resources/repos/${context.oldSegment}`;
|
|
11790
|
+
const newDefaultUri = `viking://resources/repos/${context.newSegment}`;
|
|
11791
|
+
const newNameExists = manifest.projects.some((project) => uriSegment(project.name) === context.newSegment);
|
|
11792
|
+
let changed = false;
|
|
11793
|
+
let renamedProject = false;
|
|
11794
|
+
const projects = manifest.projects.map((project) => {
|
|
11795
|
+
if (!isSeedManifestProjectNameCandidate(project, context, oldDefaultUri) || newNameExists) {
|
|
11796
|
+
return project;
|
|
11797
|
+
}
|
|
11798
|
+
changed = true;
|
|
11799
|
+
renamedProject = true;
|
|
11800
|
+
return {
|
|
11801
|
+
...project,
|
|
11802
|
+
name: context.newProject,
|
|
11803
|
+
uri: trimTrailingSlash(project.uri) === oldDefaultUri ? newDefaultUri : project.uri
|
|
11804
|
+
};
|
|
11805
|
+
});
|
|
11806
|
+
const worksets = renamedProject ? manifest.worksets?.map((workset) => {
|
|
11807
|
+
const members = workset.projects.map((projectName) => {
|
|
11808
|
+
if (uriSegment(projectName) !== context.oldSegment) {
|
|
11809
|
+
return projectName;
|
|
11810
|
+
}
|
|
11811
|
+
changed = true;
|
|
11812
|
+
return context.newProject;
|
|
11813
|
+
});
|
|
11814
|
+
return { ...workset, projects: members };
|
|
11815
|
+
}) : manifest.worksets;
|
|
11816
|
+
if (!changed) {
|
|
11817
|
+
return void 0;
|
|
11818
|
+
}
|
|
11819
|
+
return {
|
|
11820
|
+
output: `${index_vite_proxy_tmp_default.dump(
|
|
11821
|
+
{
|
|
11822
|
+
version: manifest.version,
|
|
11823
|
+
projects: projects.map((project) => ({
|
|
11824
|
+
name: project.name,
|
|
11825
|
+
path: project.path,
|
|
11826
|
+
uri: project.uri,
|
|
11827
|
+
seed: [...project.seed]
|
|
11828
|
+
})),
|
|
11829
|
+
...worksets ? {
|
|
11830
|
+
worksets: worksets.map((workset) => ({
|
|
11831
|
+
name: workset.name,
|
|
11832
|
+
...workset.description ? { description: workset.description } : {},
|
|
11833
|
+
projects: [...workset.projects]
|
|
11834
|
+
}))
|
|
11835
|
+
} : {},
|
|
11836
|
+
...manifest.futureMonorepo ? {
|
|
11837
|
+
future_monorepo: {
|
|
11838
|
+
path_candidates: [...manifest.futureMonorepo.pathCandidates],
|
|
11839
|
+
uri: manifest.futureMonorepo.uri
|
|
11840
|
+
}
|
|
11841
|
+
} : {}
|
|
11842
|
+
},
|
|
11843
|
+
{ lineWidth: 120, noRefs: true }
|
|
11844
|
+
)}`
|
|
11845
|
+
};
|
|
11846
|
+
}
|
|
11847
|
+
function isSeedManifestProjectNameCandidate(project, context, oldDefaultUri) {
|
|
11848
|
+
if (uriSegment(project.name) !== context.oldSegment) {
|
|
11849
|
+
return false;
|
|
11850
|
+
}
|
|
11851
|
+
return trimTrailingSlash(project.uri) === oldDefaultUri || expandPath(project.path) === context.repoRoot;
|
|
11852
|
+
}
|
|
11853
|
+
function canMigrateProjectName(record, context) {
|
|
11854
|
+
const projectSegment = record.metadata.project ? uriSegment(record.metadata.project) : context.oldSegment;
|
|
11855
|
+
return projectSegment === context.oldSegment || projectSegment === context.newSegment;
|
|
11856
|
+
}
|
|
11857
|
+
async function projectNameMigrationDestination(destinationDirectory, filename, content, oldProjectSegment) {
|
|
11858
|
+
const direct = await projectNameMigrationDestinationState(destinationDirectory, filename, content);
|
|
11859
|
+
if (!direct.exists || direct.sameContent) {
|
|
11860
|
+
return { existsWithSameContent: direct.sameContent, filename };
|
|
11861
|
+
}
|
|
11862
|
+
const stem = filename.replace(/\.md$/i, "");
|
|
11863
|
+
const fromOldProject = `${stem}-from-${oldProjectSegment}.md`;
|
|
11864
|
+
const renamed = await projectNameMigrationDestinationState(destinationDirectory, fromOldProject, content);
|
|
11865
|
+
if (!renamed.exists || renamed.sameContent) {
|
|
11866
|
+
return { existsWithSameContent: renamed.sameContent, filename: fromOldProject };
|
|
11867
|
+
}
|
|
11868
|
+
return {
|
|
11869
|
+
existsWithSameContent: false,
|
|
11870
|
+
filename: `${stem}-from-${oldProjectSegment}-${sha256(content).slice(0, 12)}.md`
|
|
11871
|
+
};
|
|
11872
|
+
}
|
|
11873
|
+
async function projectNameMigrationDestinationState(destinationDirectory, filename, content) {
|
|
11874
|
+
const existing = await readTextIfExists((0, import_node_path8.join)(destinationDirectory, filename));
|
|
11875
|
+
return { exists: existing !== void 0, sameContent: existing?.trim() === content.trim() };
|
|
11876
|
+
}
|
|
11877
|
+
function projectMemoryLocations() {
|
|
11878
|
+
return [
|
|
11879
|
+
{ relativePath: ["durable", "projects"], uriPath: "durable/projects" },
|
|
11880
|
+
{ relativePath: ["durable", "archived"], uriPath: "durable/archived" },
|
|
11881
|
+
{ relativePath: ["durable", "superseded"], uriPath: "durable/superseded" },
|
|
11882
|
+
{ relativePath: ["handoffs", "active"], uriPath: "handoffs/active" },
|
|
11883
|
+
{ relativePath: ["handoffs", "archived"], uriPath: "handoffs/archived" },
|
|
11884
|
+
{ relativePath: ["handoffs", "superseded"], uriPath: "handoffs/superseded" },
|
|
11885
|
+
{ relativePath: ["incidents", "active"], uriPath: "incidents/active" },
|
|
11886
|
+
{ relativePath: ["incidents", "archived"], uriPath: "incidents/archived" },
|
|
11887
|
+
{ relativePath: ["incidents", "superseded"], uriPath: "incidents/superseded" }
|
|
11888
|
+
];
|
|
11889
|
+
}
|
|
11561
11890
|
async function runRecall(config, options) {
|
|
11562
11891
|
if (options.dryRun !== true) {
|
|
11563
11892
|
await syncSharedReposAndLog(config);
|
|
@@ -11581,6 +11910,9 @@ async function runRecall(config, options) {
|
|
|
11581
11910
|
const dryRun = options.dryRun === true;
|
|
11582
11911
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
11583
11912
|
const project = await inferProjectFromQuery(config.manifestPath, options.project ?? projectQuery);
|
|
11913
|
+
const projectMemoryName = await recallProjectMemoryName(options.project, {
|
|
11914
|
+
includeProcessCwd: true
|
|
11915
|
+
});
|
|
11584
11916
|
const nodeLimit = options.nodeLimit ? parsePositiveInteger(options.nodeLimit, "node limit") : void 0;
|
|
11585
11917
|
const explicitWorkset = options.workset ? await requireWorkset(config.manifestPath, options.workset) : void 0;
|
|
11586
11918
|
const searchArgs = (scopeUri) => [
|
|
@@ -11600,9 +11932,19 @@ async function runRecall(config, options) {
|
|
|
11600
11932
|
const passes = [
|
|
11601
11933
|
await recallSearchHits(config, ov, searchArgs(inferredUri), { dryRun, includeArchived })
|
|
11602
11934
|
];
|
|
11935
|
+
const scopedRecallUris = new Set([inferredUri].filter((uri) => uri !== void 0));
|
|
11603
11936
|
if (options.project && project) {
|
|
11604
11937
|
const projectMemoryUri = `viking://user/${uriSegment(config.user)}/memories/durable/projects/${uriSegment(project.name)}`;
|
|
11605
|
-
|
|
11938
|
+
if (!scopedRecallUris.has(projectMemoryUri)) {
|
|
11939
|
+
scopedRecallUris.add(projectMemoryUri);
|
|
11940
|
+
passes.push(await recallSearchHits(config, ov, searchArgs(projectMemoryUri), { dryRun, includeArchived }));
|
|
11941
|
+
}
|
|
11942
|
+
}
|
|
11943
|
+
for (const scope of projectMemoryScopeUris(config, projectMemoryName, includeArchived)) {
|
|
11944
|
+
if (!scopedRecallUris.has(scope)) {
|
|
11945
|
+
scopedRecallUris.add(scope);
|
|
11946
|
+
passes.push(await recallSearchHits(config, ov, searchArgs(scope), { dryRun, includeArchived }));
|
|
11947
|
+
}
|
|
11606
11948
|
}
|
|
11607
11949
|
const seededUri = project ? trimTrailingSlash(project.uri) : void 0;
|
|
11608
11950
|
if (seededUri?.startsWith("viking://") && seededUri !== inferredUri && !options.uri && options.inferScope !== false) {
|
|
@@ -11611,7 +11953,9 @@ async function runRecall(config, options) {
|
|
|
11611
11953
|
const workset = !options.uri && explicitWorkset ? explicitWorkset : !options.uri && options.inferScope !== false ? await inferWorksetFromQuery(config.manifestPath, projectQuery) : void 0;
|
|
11612
11954
|
if (workset && workset.projects.length > 0) {
|
|
11613
11955
|
console.log(`Workset scope: ${workset.name} (${workset.projects.map((member) => member.name).join(", ")})`);
|
|
11614
|
-
const alreadyScoped = new Set(
|
|
11956
|
+
const alreadyScoped = new Set(
|
|
11957
|
+
[inferredUri, seededUri, ...scopedRecallUris].filter((uri) => uri !== void 0)
|
|
11958
|
+
);
|
|
11615
11959
|
const worksetScopes = worksetScopeUris(config, workset).filter((uri) => !alreadyScoped.has(uri)).slice(0, MAX_WORKSET_PASSES);
|
|
11616
11960
|
for (const scope of worksetScopes) {
|
|
11617
11961
|
passes.push(await recallSearchHits(config, ov, searchArgs(scope), { dryRun, includeArchived }));
|
|
@@ -12264,6 +12608,27 @@ function worksetScopeUris(config, workset) {
|
|
|
12264
12608
|
}
|
|
12265
12609
|
return [...new Set(scopes)];
|
|
12266
12610
|
}
|
|
12611
|
+
async function recallProjectMemoryName(explicitProject, options) {
|
|
12612
|
+
return normalizeOptionalMetadata2(explicitProject) ?? await resolveWorkspaceRepoName(options);
|
|
12613
|
+
}
|
|
12614
|
+
function projectMemoryScopeUris(config, projectName, includeArchived) {
|
|
12615
|
+
if (!projectName) {
|
|
12616
|
+
return [];
|
|
12617
|
+
}
|
|
12618
|
+
const base = `viking://user/${uriSegment(config.user)}/memories`;
|
|
12619
|
+
const projectSegment = uriSegment(projectName);
|
|
12620
|
+
const scopes = [
|
|
12621
|
+
`${base}/durable/projects/${projectSegment}`,
|
|
12622
|
+
`${base}/handoffs/active/${projectSegment}`,
|
|
12623
|
+
`${base}/incidents/active/${projectSegment}`
|
|
12624
|
+
];
|
|
12625
|
+
return includeArchived ? [
|
|
12626
|
+
...scopes,
|
|
12627
|
+
`${base}/durable/archived/${projectSegment}`,
|
|
12628
|
+
`${base}/handoffs/archived/${projectSegment}`,
|
|
12629
|
+
`${base}/incidents/archived/${projectSegment}`
|
|
12630
|
+
] : scopes;
|
|
12631
|
+
}
|
|
12267
12632
|
function exactMemoryScopes(config, includeArchived, query, project) {
|
|
12268
12633
|
return exactMemoryScopeUris({
|
|
12269
12634
|
agentMemoriesUri: `viking://agent/${uriSegment(config.agentId)}/memories`,
|
|
@@ -13500,7 +13865,7 @@ async function runInitManifest(config, options) {
|
|
|
13500
13865
|
continue;
|
|
13501
13866
|
}
|
|
13502
13867
|
seen.add(identity);
|
|
13503
|
-
projects.push(projectManifestForRepo(repoRoot, projects));
|
|
13868
|
+
projects.push(await projectManifestForRepo(repoRoot, projects));
|
|
13504
13869
|
}
|
|
13505
13870
|
const outputManifest = {
|
|
13506
13871
|
version: 1,
|
|
@@ -13608,8 +13973,8 @@ async function projectIdentity(path2) {
|
|
|
13608
13973
|
return expanded;
|
|
13609
13974
|
}
|
|
13610
13975
|
}
|
|
13611
|
-
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
13612
|
-
const baseName = uriSegment((0, import_node_path13.basename)(repoRoot));
|
|
13976
|
+
async function projectManifestForRepo(repoRoot, existingProjects) {
|
|
13977
|
+
const baseName = uriSegment(await resolveRepoName(repoRoot) ?? (0, import_node_path13.basename)(repoRoot));
|
|
13613
13978
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
13614
13979
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
13615
13980
|
let name = baseName;
|
|
@@ -14322,6 +14687,9 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
14322
14687
|
if (migration.requiresLegacyHandoffs === true && !await hasLegacyLifecycleHandoffCandidates(config)) {
|
|
14323
14688
|
continue;
|
|
14324
14689
|
}
|
|
14690
|
+
if (migration.requiresProjectNameConsolidation === true && !await hasProjectNameMigrationCandidates(config)) {
|
|
14691
|
+
continue;
|
|
14692
|
+
}
|
|
14325
14693
|
applicable.push(migration);
|
|
14326
14694
|
}
|
|
14327
14695
|
return applicable;
|
|
@@ -14348,6 +14716,7 @@ function parsePostUpdateMigration(value) {
|
|
|
14348
14716
|
instructions: stringArray(value, "instructions"),
|
|
14349
14717
|
introducedIn: value.introducedIn,
|
|
14350
14718
|
requiresLegacyHandoffs: value.requiresLegacyHandoffs === true,
|
|
14719
|
+
requiresProjectNameConsolidation: value.requiresProjectNameConsolidation === true,
|
|
14351
14720
|
title: value.title
|
|
14352
14721
|
};
|
|
14353
14722
|
}
|
|
@@ -14697,7 +15066,7 @@ async function repairManifest(config, dryRun) {
|
|
|
14697
15066
|
console.log(`WARN cannot create replacement manifest from current directory: ${errorMessage(err)}`);
|
|
14698
15067
|
return;
|
|
14699
15068
|
}
|
|
14700
|
-
const project = projectManifestForRepo(repoRoot, []);
|
|
15069
|
+
const project = await projectManifestForRepo(repoRoot, []);
|
|
14701
15070
|
const output2 = index_vite_proxy_tmp_default.dump(
|
|
14702
15071
|
{
|
|
14703
15072
|
version: 1,
|
|
@@ -17175,6 +17544,9 @@ async function main() {
|
|
|
17175
17544
|
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) => {
|
|
17176
17545
|
await runMigrateLifecycle(getRuntimeConfig(program2), options);
|
|
17177
17546
|
});
|
|
17547
|
+
program2.command("migrate-project-names").description("Move memories from old clone-folder project names to the git remote repo name").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 memories to migrate").action(async (options) => {
|
|
17548
|
+
await runMigrateProjectNames(getRuntimeConfig(program2), options);
|
|
17549
|
+
});
|
|
17178
17550
|
program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").option(
|
|
17179
17551
|
"--workset <name>",
|
|
17180
17552
|
"Recall across a named seed-manifest workset (a set of related repos) as one working set"
|
package/docs/index.html
CHANGED
|
@@ -1006,7 +1006,7 @@
|
|
|
1006
1006
|
|
|
1007
1007
|
<!-- threadnote MCP -->
|
|
1008
1008
|
<g font-family="JetBrains Mono, monospace" font-size="13" fill="#ececf1">
|
|
1009
|
-
<rect x="
|
|
1009
|
+
<rect x="300" y="150" width="280" height="60" rx="10" fill="#5ee0c5" />
|
|
1010
1010
|
<text x="440" y="178" text-anchor="middle" fill="#0a0a0d" font-weight="600">threadnote-mcp-server</text>
|
|
1011
1011
|
<text x="440" y="196" text-anchor="middle" fill="#0a0a0d" opacity="0.7" font-size="11">
|
|
1012
1012
|
recall · read · list · remember · share
|