threadnote 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,27 @@
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 project folders and uses repo_path evidence in memories, plus matching seed manifest paths when present, to resolve git remote names.",
29
+ "It covers every detected personal memory project, not just the workspace where threadnote update was run.",
30
+ "It also renames matching seed manifest projects and workset references so future seeded guidance uses the remote-derived project name.",
31
+ "Matching memories are copied under the remote-derived project name with updated project metadata, then the old copy is removed.",
32
+ "If a destination file already exists with different content, Threadnote keeps both by writing the migrated copy with a -from-<old-project> suffix.",
33
+ "Shared team memories and projects without repo path evidence are left untouched."
34
+ ],
35
+ "commandArgs": ["migrate-project-names", "--apply"],
36
+ "instructions": [
37
+ "Post-update project-name migration finished. Future default handoffs and memories for migrated repos will use the git remote repository name as their project.",
38
+ "If the migration updated your seed manifest, run threadnote seed --only <new-project-name> to re-ingest seeded resources under the new project URI.",
39
+ "If Threadnote reported any original files were still being processed, rerun the printed threadnote forget <uri> command later."
40
+ ],
41
+ "requiresProjectNameConsolidation": true
21
42
  }
22
43
  ]
23
44
  }
@@ -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: (0, import_node_path.basename)(repoRoot) },
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([params.pinnedUri, seededUri].filter((uri) => uri !== void 0));
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,
@@ -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: (0, import_node_path2.basename)(repoRoot) },
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,453 @@ 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 contexts = await projectNameMigrationContexts(config);
11637
+ if (contexts.length === 0) {
11638
+ console.log("No git remote project-name changes apply across configured projects.");
11639
+ return;
11640
+ }
11641
+ const plans = [];
11642
+ let remaining = limit;
11643
+ for (const context of contexts) {
11644
+ const candidates2 = remaining === 0 ? [] : await projectNameMigrationCandidates(config, context, remaining);
11645
+ plans.push({ candidates: candidates2, context });
11646
+ if (remaining !== void 0) {
11647
+ remaining = Math.max(0, remaining - candidates2.length);
11648
+ }
11649
+ }
11650
+ const seedManifestMigration = await seedManifestProjectNameMigration(config, contexts);
11651
+ if (!plans.some((plan) => plan.candidates.length > 0) && !seedManifestMigration) {
11652
+ console.log("No project-name migration candidates found across configured projects.");
11653
+ return;
11654
+ }
11655
+ const seedManifestUpdated = await migrateSeedManifestProjectNames(config, seedManifestMigration, dryRun);
11656
+ let existingCount = 0;
11657
+ let migratedCount = 0;
11658
+ let skippedCount = 0;
11659
+ const candidates = plans.flatMap((plan) => [...plan.candidates]);
11660
+ if (candidates.length > 0) {
11661
+ const ov = await openVikingCliForMode(dryRun);
11662
+ for (const candidate of candidates) {
11663
+ const action = candidate.destinationExistsWithSameContent ? dryRun ? "Would consolidate duplicate" : "Consolidating duplicate" : dryRun ? "Would migrate" : "Migrating";
11664
+ console.log(`${action} ${candidate.sourceUri} -> ${candidate.destinationUri}`);
11665
+ if (!dryRun) {
11666
+ if (candidate.destinationExistsWithSameContent) {
11667
+ existingCount += 1;
11668
+ } else {
11669
+ await ensureMemoryDirectory(ov, config, parentVikingUri(candidate.destinationUri));
11670
+ await writeMemoryFile(config, ov, candidate.destinationUri, candidate.destinationContent, "create", false);
11671
+ }
11672
+ const removedOriginal = await removeVikingResourceWithRetry(ov, config, candidate.sourceUri);
11673
+ if (!removedOriginal) {
11674
+ console.error(
11675
+ `Migrated copy stored, but original is still processing. Retry later: threadnote forget ${candidate.sourceUri}`
11676
+ );
11677
+ skippedCount += 1;
11678
+ }
11679
+ }
11680
+ migratedCount += 1;
11681
+ }
11682
+ }
11683
+ const activeContexts = projectNameMigrationActiveContexts(plans, seedManifestMigration);
11684
+ const newProjectsToSeed = [...new Set(seedManifestMigration?.newProjects ?? [])];
11685
+ console.log(
11686
+ [
11687
+ projectNameMigrationSummary(migratedCount, dryRun, activeContexts),
11688
+ seedManifestUpdated ? `seed manifest ${dryRun ? "would be updated" : "updated"}` : "seed manifest unchanged",
11689
+ `${existingCount} duplicate destination(s) reused`,
11690
+ `${skippedCount} source(s) still processing`,
11691
+ dryRun ? "Run with --apply to perform this migration." : void 0,
11692
+ ...newProjectsToSeed.map(
11693
+ (project) => `Run threadnote seed --only ${project} to re-ingest seeded resources under the new project URI.`
11694
+ )
11695
+ ].filter((part) => part !== void 0).join("; ")
11696
+ );
11697
+ }
11698
+ async function hasProjectNameMigrationCandidates(config) {
11699
+ const contexts = await projectNameMigrationContexts(config);
11700
+ if (contexts.length === 0) {
11701
+ return false;
11702
+ }
11703
+ for (const context of contexts) {
11704
+ if ((await projectNameMigrationCandidates(config, context, 1)).length > 0) {
11705
+ return true;
11706
+ }
11707
+ }
11708
+ return await seedManifestProjectNameMigration(config, contexts) !== void 0;
11709
+ }
11710
+ async function projectNameMigrationContexts(config) {
11711
+ const evidence = await projectNameMigrationMemoryEvidence(config);
11712
+ const contexts = [];
11713
+ let manifest;
11714
+ try {
11715
+ manifest = await readSeedManifest(config.manifestPath);
11716
+ } catch (_err) {
11717
+ manifest = void 0;
11718
+ }
11719
+ if (manifest) {
11720
+ for (const project of manifest.projects) {
11721
+ const projectEvidence = evidence.get(uriSegment(project.name));
11722
+ if (projectEvidence) {
11723
+ projectEvidence.repoPaths.add(expandPath(project.path));
11724
+ }
11725
+ }
11726
+ }
11727
+ for (const projectEvidence of evidence.values()) {
11728
+ for (const repoPath of projectEvidence.repoPaths) {
11729
+ const context = await projectNameMigrationContextForRepoPath(projectEvidence.oldProject, repoPath);
11730
+ if (context) {
11731
+ contexts.push(context);
11732
+ }
11733
+ }
11734
+ }
11735
+ const currentContext = await currentWorkspaceProjectNameMigrationContext(evidence);
11736
+ if (currentContext) {
11737
+ contexts.push(currentContext);
11738
+ }
11739
+ return dedupeProjectNameMigrationContexts(contexts);
11740
+ }
11741
+ async function projectNameMigrationMemoryEvidence(config) {
11742
+ const evidence = /* @__PURE__ */ new Map();
11743
+ for (const location of projectMemoryLocations()) {
11744
+ const locationRoot = (0, import_node_path8.join)(localUserMemoriesRoot(config), ...location.relativePath);
11745
+ let projectEntries;
11746
+ try {
11747
+ projectEntries = await (0, import_promises6.readdir)(locationRoot, { withFileTypes: true });
11748
+ } catch (_err) {
11749
+ continue;
11750
+ }
11751
+ for (const projectEntry of projectEntries) {
11752
+ if (!projectEntry.isDirectory() || projectEntry.name.startsWith(".")) {
11753
+ continue;
11754
+ }
11755
+ const oldSegment = projectEntry.name;
11756
+ const projectEvidence = ensureProjectNameMigrationEvidence(evidence, oldSegment);
11757
+ const projectDirectory = (0, import_node_path8.join)(locationRoot, projectEntry.name);
11758
+ let memoryEntries;
11759
+ try {
11760
+ memoryEntries = await (0, import_promises6.readdir)(projectDirectory, { withFileTypes: true });
11761
+ } catch (_err) {
11762
+ continue;
11763
+ }
11764
+ for (const memoryEntry of memoryEntries) {
11765
+ if (!memoryEntry.isFile() || memoryEntry.name.startsWith(".") || !memoryEntry.name.endsWith(".md")) {
11766
+ continue;
11767
+ }
11768
+ const content = await readTextIfExists((0, import_node_path8.join)(projectDirectory, memoryEntry.name));
11769
+ if (!content) {
11770
+ continue;
11771
+ }
11772
+ const sourceUri = `viking://user/${uriSegment(config.user)}/memories/${location.uriPath}/${oldSegment}/${memoryEntry.name}`;
11773
+ const record = parseMemoryDocument(sourceUri, content);
11774
+ if (record?.metadata.project && uriSegment(record.metadata.project) === oldSegment) {
11775
+ projectEvidence.oldProject = record.metadata.project;
11776
+ }
11777
+ const repoPath = repoPathEvidenceFromMemory(content);
11778
+ if (repoPath) {
11779
+ projectEvidence.repoPaths.add(repoPath);
11780
+ }
11781
+ }
11782
+ }
11783
+ }
11784
+ return evidence;
11785
+ }
11786
+ function ensureProjectNameMigrationEvidence(evidence, oldSegment) {
11787
+ const existing = evidence.get(oldSegment);
11788
+ if (existing) {
11789
+ return existing;
11790
+ }
11791
+ const created = { oldProject: oldSegment, oldSegment, repoPaths: /* @__PURE__ */ new Set() };
11792
+ evidence.set(oldSegment, created);
11793
+ return created;
11794
+ }
11795
+ function repoPathEvidenceFromMemory(content) {
11796
+ const match = /^repo_path:\s*(.+)$/m.exec(content);
11797
+ if (!match?.[1]) {
11798
+ return void 0;
11799
+ }
11800
+ const cleaned = match[1].trim().replace(/^['"`]+|['"`]+$/g, "").replace(/[.,;]+$/g, "");
11801
+ if (!cleaned.startsWith("/") && !cleaned.startsWith("~/")) {
11802
+ return void 0;
11803
+ }
11804
+ return expandPath(cleaned);
11805
+ }
11806
+ async function projectNameMigrationContextForRepoPath(oldProject, repoPath) {
11807
+ const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], repoPath);
11808
+ if (!repoRoot) {
11809
+ return void 0;
11810
+ }
11811
+ const newProject = await resolveGitRemoteRepoName(repoRoot);
11812
+ if (!newProject) {
11813
+ return void 0;
11814
+ }
11815
+ return projectNameMigrationContextFromParts({
11816
+ newProject,
11817
+ oldProject,
11818
+ repoRoot
11819
+ });
11820
+ }
11821
+ async function currentWorkspaceProjectNameMigrationContext(evidence) {
11822
+ const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
11823
+ if (!repoRoot) {
11824
+ return void 0;
11825
+ }
11826
+ const newProject = await resolveGitRemoteRepoName(repoRoot);
11827
+ const oldProject = await resolveRepoFolderName(repoRoot);
11828
+ if (!newProject || !oldProject) {
11829
+ return void 0;
11830
+ }
11831
+ const oldSegment = uriSegment(oldProject);
11832
+ if (!evidence.has(oldSegment)) {
11833
+ return void 0;
11834
+ }
11835
+ return projectNameMigrationContextFromParts({ newProject, oldProject, repoRoot });
11836
+ }
11837
+ function projectNameMigrationContextFromParts(params) {
11838
+ const newSegment = uriSegment(params.newProject);
11839
+ const oldSegment = uriSegment(params.oldProject);
11840
+ if (newSegment === oldSegment) {
11841
+ return void 0;
11842
+ }
11843
+ return {
11844
+ newProject: params.newProject,
11845
+ newSegment,
11846
+ oldProject: params.oldProject,
11847
+ oldSegment,
11848
+ repoRoot: params.repoRoot
11849
+ };
11850
+ }
11851
+ function dedupeProjectNameMigrationContexts(contexts) {
11852
+ const seen = /* @__PURE__ */ new Set();
11853
+ const out = [];
11854
+ for (const context of contexts) {
11855
+ const key = `${context.oldSegment}\0${context.newSegment}\0${context.repoRoot}`;
11856
+ if (seen.has(key)) {
11857
+ continue;
11858
+ }
11859
+ seen.add(key);
11860
+ out.push(context);
11861
+ }
11862
+ return out;
11863
+ }
11864
+ async function projectNameMigrationCandidates(config, context, limit) {
11865
+ const candidates = [];
11866
+ for (const location of projectMemoryLocations()) {
11867
+ const sourceDirectory = (0, import_node_path8.join)(localUserMemoriesRoot(config), ...location.relativePath, context.oldSegment);
11868
+ const sourceDirectoryUri = `viking://user/${uriSegment(config.user)}/memories/${location.uriPath}/${context.oldSegment}`;
11869
+ let entries;
11870
+ try {
11871
+ entries = await (0, import_promises6.readdir)(sourceDirectory, { withFileTypes: true });
11872
+ } catch (_err) {
11873
+ continue;
11874
+ }
11875
+ for (const entry of entries) {
11876
+ if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
11877
+ continue;
11878
+ }
11879
+ const sourceUri = `${sourceDirectoryUri}/${entry.name}`;
11880
+ const content = await readTextIfExists((0, import_node_path8.join)(sourceDirectory, entry.name));
11881
+ if (!content) {
11882
+ continue;
11883
+ }
11884
+ const record = parseMemoryDocument(sourceUri, content);
11885
+ if (!record || !canMigrateProjectName(record, context)) {
11886
+ continue;
11887
+ }
11888
+ const metadata = { ...record.metadata, project: context.newProject };
11889
+ const destinationDirectoryUri = memoryDirectoryUri(config, metadata);
11890
+ const destinationDirectory = localMemoryPathForUri(config, destinationDirectoryUri);
11891
+ if (!destinationDirectory) {
11892
+ continue;
11893
+ }
11894
+ const destinationContent = formatMemoryDocument2(record.headerTitle, metadata, record.body);
11895
+ const destination = await projectNameMigrationDestination(
11896
+ destinationDirectory,
11897
+ entry.name,
11898
+ destinationContent,
11899
+ context.oldSegment
11900
+ );
11901
+ candidates.push({
11902
+ destinationContent,
11903
+ destinationExistsWithSameContent: destination.existsWithSameContent,
11904
+ destinationUri: `${destinationDirectoryUri}/${destination.filename}`,
11905
+ sourceUri
11906
+ });
11907
+ if (limit !== void 0 && candidates.length >= limit) {
11908
+ return candidates;
11909
+ }
11910
+ }
11911
+ }
11912
+ return candidates;
11913
+ }
11914
+ function projectNameMigrationActiveContexts(plans, seedManifestMigration) {
11915
+ return dedupeProjectNameMigrationContexts([
11916
+ ...plans.filter((plan) => plan.candidates.length > 0).map((plan) => plan.context),
11917
+ ...seedManifestMigration?.contexts ?? []
11918
+ ]);
11919
+ }
11920
+ function projectNameMigrationSummary(migratedCount, dryRun, contexts) {
11921
+ const memoryWord = migratedCount === 1 ? "memory" : "memories";
11922
+ const verb = dryRun ? "would be migrated" : "migrated";
11923
+ if (contexts.length === 1) {
11924
+ const [context] = contexts;
11925
+ return `Project-name migration summary: ${migratedCount} ${memoryWord} ${verb} from ${context.oldProject} to ${context.newProject}`;
11926
+ }
11927
+ const renameSummary = contexts.map((context) => `${context.oldProject} -> ${context.newProject}`).join(", ");
11928
+ return `Project-name migration summary: ${migratedCount} ${memoryWord} ${verb} across ${contexts.length} project rename(s)${renameSummary ? `: ${renameSummary}` : ""}`;
11929
+ }
11930
+ async function migrateSeedManifestProjectNames(config, migration, dryRun) {
11931
+ if (!migration) {
11932
+ return false;
11933
+ }
11934
+ if (dryRun) {
11935
+ console.log(`Would update seed manifest: ${config.manifestPath}`);
11936
+ console.log(migration.output.trimEnd());
11937
+ return true;
11938
+ }
11939
+ await ensureDirectory((0, import_node_path8.dirname)(config.manifestPath), false);
11940
+ const currentContent = await readFileIfExists(config.manifestPath);
11941
+ if (currentContent !== void 0) {
11942
+ const backupPath = `${config.manifestPath}.project-name-${safeTimestamp()}`;
11943
+ await (0, import_promises6.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
11944
+ await (0, import_promises6.chmod)(backupPath, 384);
11945
+ console.log(`Backup: ${backupPath}`);
11946
+ }
11947
+ await (0, import_promises6.writeFile)(config.manifestPath, migration.output, { encoding: "utf8", mode: 384 });
11948
+ await (0, import_promises6.chmod)(config.manifestPath, 384);
11949
+ console.log(`Updated seed manifest: ${config.manifestPath}`);
11950
+ return true;
11951
+ }
11952
+ async function seedManifestProjectNameMigration(config, contexts) {
11953
+ let manifest;
11954
+ try {
11955
+ manifest = await readSeedManifest(config.manifestPath);
11956
+ } catch (_err) {
11957
+ return void 0;
11958
+ }
11959
+ const renamed = /* @__PURE__ */ new Map();
11960
+ let changed = false;
11961
+ const projects = manifest.projects.map((project) => {
11962
+ const context = contexts.find(
11963
+ (candidate) => isSeedManifestProjectNameCandidate(
11964
+ project,
11965
+ candidate,
11966
+ `viking://resources/repos/${candidate.oldSegment}`,
11967
+ `viking://resources/repos/${candidate.newSegment}`
11968
+ )
11969
+ );
11970
+ if (!context) {
11971
+ return project;
11972
+ }
11973
+ const newNameExists = manifest.projects.some(
11974
+ (other) => other !== project && uriSegment(other.name) === context.newSegment
11975
+ );
11976
+ if (newNameExists || [...renamed.values()].some((existing) => existing.newSegment === context.newSegment)) {
11977
+ return project;
11978
+ }
11979
+ changed = true;
11980
+ renamed.set(context.oldSegment, context);
11981
+ return {
11982
+ ...project,
11983
+ name: context.newProject,
11984
+ uri: trimTrailingSlash(project.uri) === `viking://resources/repos/${context.oldSegment}` ? `viking://resources/repos/${context.newSegment}` : project.uri
11985
+ };
11986
+ });
11987
+ const worksets = renamed.size > 0 ? manifest.worksets?.map((workset) => {
11988
+ const members = workset.projects.map((projectName) => {
11989
+ const context = renamed.get(uriSegment(projectName));
11990
+ if (!context) {
11991
+ return projectName;
11992
+ }
11993
+ changed = true;
11994
+ return context.newProject;
11995
+ });
11996
+ return { ...workset, projects: members };
11997
+ }) : manifest.worksets;
11998
+ if (!changed) {
11999
+ return void 0;
12000
+ }
12001
+ return {
12002
+ contexts: [...renamed.values()],
12003
+ newProjects: [...new Set([...renamed.values()].map((context) => context.newProject))],
12004
+ output: `${index_vite_proxy_tmp_default.dump(
12005
+ {
12006
+ version: manifest.version,
12007
+ projects: projects.map((project) => ({
12008
+ name: project.name,
12009
+ path: project.path,
12010
+ uri: project.uri,
12011
+ seed: [...project.seed]
12012
+ })),
12013
+ ...worksets ? {
12014
+ worksets: worksets.map((workset) => ({
12015
+ name: workset.name,
12016
+ ...workset.description ? { description: workset.description } : {},
12017
+ projects: [...workset.projects]
12018
+ }))
12019
+ } : {},
12020
+ ...manifest.futureMonorepo ? {
12021
+ future_monorepo: {
12022
+ path_candidates: [...manifest.futureMonorepo.pathCandidates],
12023
+ uri: manifest.futureMonorepo.uri
12024
+ }
12025
+ } : {}
12026
+ },
12027
+ { lineWidth: 120, noRefs: true }
12028
+ )}`
12029
+ };
12030
+ }
12031
+ function isSeedManifestProjectNameCandidate(project, context, oldDefaultUri, newDefaultUri) {
12032
+ const nameSegment = uriSegment(project.name);
12033
+ const uriMatchesOld = trimTrailingSlash(project.uri) === oldDefaultUri;
12034
+ const pathMatchesRepo = expandPath(project.path) === context.repoRoot;
12035
+ if (nameSegment === context.newSegment && !uriMatchesOld) {
12036
+ return false;
12037
+ }
12038
+ if (nameSegment !== context.oldSegment && !uriMatchesOld && !pathMatchesRepo) {
12039
+ return false;
12040
+ }
12041
+ return nameSegment !== context.newSegment || uriMatchesOld || trimTrailingSlash(project.uri) !== newDefaultUri;
12042
+ }
12043
+ function canMigrateProjectName(record, context) {
12044
+ const projectSegment = record.metadata.project ? uriSegment(record.metadata.project) : context.oldSegment;
12045
+ return projectSegment === context.oldSegment || projectSegment === context.newSegment;
12046
+ }
12047
+ async function projectNameMigrationDestination(destinationDirectory, filename, content, oldProjectSegment) {
12048
+ const direct = await projectNameMigrationDestinationState(destinationDirectory, filename, content);
12049
+ if (!direct.exists || direct.sameContent) {
12050
+ return { existsWithSameContent: direct.sameContent, filename };
12051
+ }
12052
+ const stem = filename.replace(/\.md$/i, "");
12053
+ const fromOldProject = `${stem}-from-${oldProjectSegment}.md`;
12054
+ const renamed = await projectNameMigrationDestinationState(destinationDirectory, fromOldProject, content);
12055
+ if (!renamed.exists || renamed.sameContent) {
12056
+ return { existsWithSameContent: renamed.sameContent, filename: fromOldProject };
12057
+ }
12058
+ return {
12059
+ existsWithSameContent: false,
12060
+ filename: `${stem}-from-${oldProjectSegment}-${sha256(content).slice(0, 12)}.md`
12061
+ };
12062
+ }
12063
+ async function projectNameMigrationDestinationState(destinationDirectory, filename, content) {
12064
+ const existing = await readTextIfExists((0, import_node_path8.join)(destinationDirectory, filename));
12065
+ return { exists: existing !== void 0, sameContent: existing?.trim() === content.trim() };
12066
+ }
12067
+ function projectMemoryLocations() {
12068
+ return [
12069
+ { relativePath: ["durable", "projects"], uriPath: "durable/projects" },
12070
+ { relativePath: ["durable", "archived"], uriPath: "durable/archived" },
12071
+ { relativePath: ["durable", "superseded"], uriPath: "durable/superseded" },
12072
+ { relativePath: ["handoffs", "active"], uriPath: "handoffs/active" },
12073
+ { relativePath: ["handoffs", "archived"], uriPath: "handoffs/archived" },
12074
+ { relativePath: ["handoffs", "superseded"], uriPath: "handoffs/superseded" },
12075
+ { relativePath: ["incidents", "active"], uriPath: "incidents/active" },
12076
+ { relativePath: ["incidents", "archived"], uriPath: "incidents/archived" },
12077
+ { relativePath: ["incidents", "superseded"], uriPath: "incidents/superseded" }
12078
+ ];
12079
+ }
11561
12080
  async function runRecall(config, options) {
11562
12081
  if (options.dryRun !== true) {
11563
12082
  await syncSharedReposAndLog(config);
@@ -11581,6 +12100,9 @@ async function runRecall(config, options) {
11581
12100
  const dryRun = options.dryRun === true;
11582
12101
  const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
11583
12102
  const project = await inferProjectFromQuery(config.manifestPath, options.project ?? projectQuery);
12103
+ const projectMemoryName = await recallProjectMemoryName(options.project, {
12104
+ includeProcessCwd: true
12105
+ });
11584
12106
  const nodeLimit = options.nodeLimit ? parsePositiveInteger(options.nodeLimit, "node limit") : void 0;
11585
12107
  const explicitWorkset = options.workset ? await requireWorkset(config.manifestPath, options.workset) : void 0;
11586
12108
  const searchArgs = (scopeUri) => [
@@ -11600,9 +12122,19 @@ async function runRecall(config, options) {
11600
12122
  const passes = [
11601
12123
  await recallSearchHits(config, ov, searchArgs(inferredUri), { dryRun, includeArchived })
11602
12124
  ];
12125
+ const scopedRecallUris = new Set([inferredUri].filter((uri) => uri !== void 0));
11603
12126
  if (options.project && project) {
11604
12127
  const projectMemoryUri = `viking://user/${uriSegment(config.user)}/memories/durable/projects/${uriSegment(project.name)}`;
11605
- passes.push(await recallSearchHits(config, ov, searchArgs(projectMemoryUri), { dryRun, includeArchived }));
12128
+ if (!scopedRecallUris.has(projectMemoryUri)) {
12129
+ scopedRecallUris.add(projectMemoryUri);
12130
+ passes.push(await recallSearchHits(config, ov, searchArgs(projectMemoryUri), { dryRun, includeArchived }));
12131
+ }
12132
+ }
12133
+ for (const scope of projectMemoryScopeUris(config, projectMemoryName, includeArchived)) {
12134
+ if (!scopedRecallUris.has(scope)) {
12135
+ scopedRecallUris.add(scope);
12136
+ passes.push(await recallSearchHits(config, ov, searchArgs(scope), { dryRun, includeArchived }));
12137
+ }
11606
12138
  }
11607
12139
  const seededUri = project ? trimTrailingSlash(project.uri) : void 0;
11608
12140
  if (seededUri?.startsWith("viking://") && seededUri !== inferredUri && !options.uri && options.inferScope !== false) {
@@ -11611,7 +12143,9 @@ async function runRecall(config, options) {
11611
12143
  const workset = !options.uri && explicitWorkset ? explicitWorkset : !options.uri && options.inferScope !== false ? await inferWorksetFromQuery(config.manifestPath, projectQuery) : void 0;
11612
12144
  if (workset && workset.projects.length > 0) {
11613
12145
  console.log(`Workset scope: ${workset.name} (${workset.projects.map((member) => member.name).join(", ")})`);
11614
- const alreadyScoped = new Set([inferredUri, seededUri].filter((uri) => uri !== void 0));
12146
+ const alreadyScoped = new Set(
12147
+ [inferredUri, seededUri, ...scopedRecallUris].filter((uri) => uri !== void 0)
12148
+ );
11615
12149
  const worksetScopes = worksetScopeUris(config, workset).filter((uri) => !alreadyScoped.has(uri)).slice(0, MAX_WORKSET_PASSES);
11616
12150
  for (const scope of worksetScopes) {
11617
12151
  passes.push(await recallSearchHits(config, ov, searchArgs(scope), { dryRun, includeArchived }));
@@ -12264,6 +12798,27 @@ function worksetScopeUris(config, workset) {
12264
12798
  }
12265
12799
  return [...new Set(scopes)];
12266
12800
  }
12801
+ async function recallProjectMemoryName(explicitProject, options) {
12802
+ return normalizeOptionalMetadata2(explicitProject) ?? await resolveWorkspaceRepoName(options);
12803
+ }
12804
+ function projectMemoryScopeUris(config, projectName, includeArchived) {
12805
+ if (!projectName) {
12806
+ return [];
12807
+ }
12808
+ const base = `viking://user/${uriSegment(config.user)}/memories`;
12809
+ const projectSegment = uriSegment(projectName);
12810
+ const scopes = [
12811
+ `${base}/durable/projects/${projectSegment}`,
12812
+ `${base}/handoffs/active/${projectSegment}`,
12813
+ `${base}/incidents/active/${projectSegment}`
12814
+ ];
12815
+ return includeArchived ? [
12816
+ ...scopes,
12817
+ `${base}/durable/archived/${projectSegment}`,
12818
+ `${base}/handoffs/archived/${projectSegment}`,
12819
+ `${base}/incidents/archived/${projectSegment}`
12820
+ ] : scopes;
12821
+ }
12267
12822
  function exactMemoryScopes(config, includeArchived, query, project) {
12268
12823
  return exactMemoryScopeUris({
12269
12824
  agentMemoriesUri: `viking://agent/${uriSegment(config.agentId)}/memories`,
@@ -13500,7 +14055,7 @@ async function runInitManifest(config, options) {
13500
14055
  continue;
13501
14056
  }
13502
14057
  seen.add(identity);
13503
- projects.push(projectManifestForRepo(repoRoot, projects));
14058
+ projects.push(await projectManifestForRepo(repoRoot, projects));
13504
14059
  }
13505
14060
  const outputManifest = {
13506
14061
  version: 1,
@@ -13608,8 +14163,8 @@ async function projectIdentity(path2) {
13608
14163
  return expanded;
13609
14164
  }
13610
14165
  }
13611
- function projectManifestForRepo(repoRoot, existingProjects) {
13612
- const baseName = uriSegment((0, import_node_path13.basename)(repoRoot));
14166
+ async function projectManifestForRepo(repoRoot, existingProjects) {
14167
+ const baseName = uriSegment(await resolveRepoName(repoRoot) ?? (0, import_node_path13.basename)(repoRoot));
13613
14168
  const usedNames = new Set(existingProjects.map((project) => project.name));
13614
14169
  const usedUris = new Set(existingProjects.map((project) => project.uri));
13615
14170
  let name = baseName;
@@ -14322,6 +14877,9 @@ async function applicablePostUpdateMigrations(config, options) {
14322
14877
  if (migration.requiresLegacyHandoffs === true && !await hasLegacyLifecycleHandoffCandidates(config)) {
14323
14878
  continue;
14324
14879
  }
14880
+ if (migration.requiresProjectNameConsolidation === true && !await hasProjectNameMigrationCandidates(config)) {
14881
+ continue;
14882
+ }
14325
14883
  applicable.push(migration);
14326
14884
  }
14327
14885
  return applicable;
@@ -14348,6 +14906,7 @@ function parsePostUpdateMigration(value) {
14348
14906
  instructions: stringArray(value, "instructions"),
14349
14907
  introducedIn: value.introducedIn,
14350
14908
  requiresLegacyHandoffs: value.requiresLegacyHandoffs === true,
14909
+ requiresProjectNameConsolidation: value.requiresProjectNameConsolidation === true,
14351
14910
  title: value.title
14352
14911
  };
14353
14912
  }
@@ -14697,7 +15256,7 @@ async function repairManifest(config, dryRun) {
14697
15256
  console.log(`WARN cannot create replacement manifest from current directory: ${errorMessage(err)}`);
14698
15257
  return;
14699
15258
  }
14700
- const project = projectManifestForRepo(repoRoot, []);
15259
+ const project = await projectManifestForRepo(repoRoot, []);
14701
15260
  const output2 = index_vite_proxy_tmp_default.dump(
14702
15261
  {
14703
15262
  version: 1,
@@ -17175,6 +17734,9 @@ async function main() {
17175
17734
  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
17735
  await runMigrateLifecycle(getRuntimeConfig(program2), options);
17177
17736
  });
17737
+ 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) => {
17738
+ await runMigrateProjectNames(getRuntimeConfig(program2), options);
17739
+ });
17178
17740
  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
17741
  "--workset <name>",
17180
17742
  "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="340" y="150" width="200" height="60" rx="10" fill="#5ee0c5" />
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",