@vodmal/vdx-cli 0.38.0 → 0.39.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/README.md +4 -2
- package/package.json +1 -1
- package/rubric/vdx-rubric.yaml +1 -1
- package/src/ai.ts +12 -2
- package/src/conversations.ts +35 -0
- package/src/index.ts +9 -2
- package/src/init.ts +1 -1
- package/src/memory.ts +98 -15
package/README.md
CHANGED
|
@@ -56,7 +56,8 @@ vdx doctor --check [project-path] # one line per hook declared but not running
|
|
|
56
56
|
# not inside — the plugin's SessionStart hook tells the agent
|
|
57
57
|
vdx doctor --fix [project-path] # write the profile's personal hooks (git.hooks) into ~/.gitconfig, and put
|
|
58
58
|
# the project's Claude Code memory inside it: .claude/memory/ (after a
|
|
59
|
-
# folder move, its path and conversations follow
|
|
59
|
+
# folder move, its path and conversations follow; notes a session that
|
|
60
|
+
# started before the move wrote at the old place come in)
|
|
60
61
|
|
|
61
62
|
# Start your agent in a project per your profile (~/.vdx-environment.yaml or $VDX_ENVIRONMENT)
|
|
62
63
|
vdx ai[@host] [project-path] [--new | --conversation <id>] [--focused | --intercom] [--restart] [--detach] [--dry-run] # default: cwd; @host: over ssh, in tmux there; --intercom: the project's mail session
|
|
@@ -116,7 +117,8 @@ The session's `{project}` is `[_.vdx] name` (or the older `[vdx]`) from the proj
|
|
|
116
117
|
first file in the profile's `session.project_names` that has one, else the repo
|
|
117
118
|
name. `vdx ai@<host>` runs the same command on `<host>` over ssh — that machine's vdx,
|
|
118
119
|
profile and tmux, the path taken under its home directory; `--detach` only
|
|
119
|
-
starts it
|
|
120
|
+
starts it, and not in a folder Claude Code does not trust yet: nobody would answer
|
|
121
|
+
its trust window (exit 4; start once without `--detach`). The machine's own label (`$VDX_HOST`) runs here. Where git knows no
|
|
120
122
|
commit author, `vdx ai` proposes one (Enter writes it into `.git/config`).
|
|
121
123
|
For Claude Code `vdx ai` picks the conversation: each one is marked with the
|
|
122
124
|
machine it was last started on (`vdx ai --check`, run by the plugin's hook, writes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vodmal/vdx-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "vdx — unified lifecycle interface (up/down/build/test/check/fix) + versioned maturity audit with drift detection. Pairs with the vdx Claude Code plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vdx",
|
package/rubric/vdx-rubric.yaml
CHANGED
package/src/ai.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { loadManifestAt } from './manifest.ts';
|
|
|
10
10
|
import { type Author, effectiveAuthor, isGitRepo, rankAuthors, scanPool, writeAuthor } from './author.ts';
|
|
11
11
|
import type { Predicate } from './rubric.ts';
|
|
12
12
|
import {
|
|
13
|
+
claudeTrusts,
|
|
13
14
|
type Conversation,
|
|
14
15
|
describeConversation,
|
|
15
16
|
describeAge,
|
|
@@ -1767,9 +1768,10 @@ export function runAiCheck(pathArg: string, deps: AiDeps, selfPid: number = proc
|
|
|
1767
1768
|
`Hand the user the fix — ${reload}` +
|
|
1768
1769
|
(deps.env['TMUX']
|
|
1769
1770
|
? `\`${vdxAi} --restart ${root}\`: it stops this session and starts it again${again}` +
|
|
1770
|
-
// A focused session and the mail session restart into a new conversation
|
|
1771
|
+
// A focused session and the mail session restart into a new conversation, and what lives
|
|
1772
|
+
// only in this one ends with it (ai-dev-plugins, restart-hint-wrap-first).
|
|
1771
1773
|
(focused || intercom
|
|
1772
|
-
?
|
|
1774
|
+
? ", in a new conversation — before handing it over, run the project's wrap check (`/vdm:wrap` where vdm is installed): what lives only in this chat ends with it"
|
|
1773
1775
|
: plan.resumeArgs.length
|
|
1774
1776
|
? `, resuming the conversation (${plan.resumeArgs.join(' ')})`
|
|
1775
1777
|
: '') +
|
|
@@ -2102,6 +2104,14 @@ export function runAi(opts: AiOptions, deps: AiDeps): number {
|
|
|
2102
2104
|
if (live) plan = { ...plan, resumeArgs: resumeId(live) };
|
|
2103
2105
|
}
|
|
2104
2106
|
|
|
2107
|
+
// Nobody sits at a detached start, and Claude Code holds one in a folder it does not trust on
|
|
2108
|
+
// its trust window, unseen (intercom-session-mode Sidetrack #4). The answer is the person's.
|
|
2109
|
+
if (opts.detach && isClaude(plan.command) && claudeTrusts(projectRoot, deps.env, deps.home) === false) {
|
|
2110
|
+
log(`✗ Claude Code has not trusted ${projectRoot} yet: a detached agent would wait on its trust window, unseen`);
|
|
2111
|
+
log(` start it once at a terminal and answer the window: ${vdxAiHere} ${shellQuote(projectRoot)}`);
|
|
2112
|
+
return EXIT_LAUNCH_FAILED;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2105
2115
|
if (current) {
|
|
2106
2116
|
const line = agentLine(plan, [...plan.args, ...plan.resumeArgs]);
|
|
2107
2117
|
log(`↻ ${current.pane.session} ${current.pane.paneId}: restarting — ${line}`);
|
package/src/conversations.ts
CHANGED
|
@@ -69,6 +69,41 @@ export function claudeConfigDir(env: NodeJS.ProcessEnv, home: string): string {
|
|
|
69
69
|
return dir ? path.resolve(dir.replace(/^~(?=$|\/)/, home)) : path.join(home, '.claude');
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Whether Claude Code trusts a folder (2.1.308, claude-memory-in-project DL #13): its global
|
|
74
|
+
* config, `.claude.json` in `CLAUDE_CONFIG_DIR` or the home directory, marks
|
|
75
|
+
* `projects[<folder>].hasTrustDialogAccepted` for the folder or one above it — inside a git
|
|
76
|
+
* checkout only up to the checkout's top. A start in a folder it does not trust stops on the
|
|
77
|
+
* trust window until somebody answers. Null: the config cannot be read, nobody knows.
|
|
78
|
+
*/
|
|
79
|
+
export function claudeTrusts(folder: string, env: NodeJS.ProcessEnv, home: string): boolean | null {
|
|
80
|
+
const dir = env['CLAUDE_CONFIG_DIR']?.trim();
|
|
81
|
+
let projects: Record<string, unknown> | undefined;
|
|
82
|
+
try {
|
|
83
|
+
const file = path.join(dir ? path.resolve(dir.replace(/^~(?=$|\/)/, home)) : home, '.claude.json');
|
|
84
|
+
projects = dict(at(JSON.parse(fs.readFileSync(file, 'utf8')), 'projects'));
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
let d = path.resolve(folder);
|
|
89
|
+
try {
|
|
90
|
+
d = fs.realpathSync(d);
|
|
91
|
+
} catch {
|
|
92
|
+
// A folder that is not there is trusted by nothing but its own record.
|
|
93
|
+
}
|
|
94
|
+
let top: string | null = null;
|
|
95
|
+
for (let p = d; top === null; p = path.dirname(p)) {
|
|
96
|
+
if (fs.existsSync(path.join(p, '.git'))) top = p;
|
|
97
|
+
if (path.dirname(p) === p) break;
|
|
98
|
+
}
|
|
99
|
+
for (;;) {
|
|
100
|
+
if (at(projects?.[d.normalize('NFC')], 'hasTrustDialogAccepted') === true) return true;
|
|
101
|
+
const up = path.dirname(d);
|
|
102
|
+
if (d === top || up === d) return false;
|
|
103
|
+
d = up;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
72
107
|
export function projectDirName(projectRoot: string): string {
|
|
73
108
|
return projectRoot.replace(/[^A-Za-z0-9]/g, '-');
|
|
74
109
|
}
|
package/src/index.ts
CHANGED
|
@@ -129,7 +129,10 @@ the project's files). Hand a person \`vdx ai\`, not \`<agent> --<flag>\`.
|
|
|
129
129
|
Without it, an agent running without the profile's flags is
|
|
130
130
|
a question in a terminal: Enter attaches to it as is, r
|
|
131
131
|
restarts it, q leaves it
|
|
132
|
-
--detach start without attaching; print the command that attaches
|
|
132
|
+
--detach start without attaching; print the command that attaches. In a
|
|
133
|
+
folder Claude Code does not trust yet, nothing starts (exit 4):
|
|
134
|
+
its trust window would wait with nobody at it — start once
|
|
135
|
+
without --detach and answer it
|
|
133
136
|
--dry-run print the plan and the project's running agents; start nothing
|
|
134
137
|
--check for the agent's own session: how the agent here is launched,
|
|
135
138
|
whether this session carries the profile's flags, and whether it
|
|
@@ -258,7 +261,11 @@ takes it along. Reads only, unless --fix. Exit 2 when something is missing.
|
|
|
258
261
|
its commits go — see --staged), else into .claude/settings.local.json,
|
|
259
262
|
with the memory kept out of git (.git/info/exclude) unless you say
|
|
260
263
|
yes. After a folder is moved: the path follows it, and the project's
|
|
261
|
-
conversations move to the new path
|
|
264
|
+
conversations move to the new path; a folder Claude Code has not
|
|
265
|
+
trusted yet is named. Notes a session that started before the move
|
|
266
|
+
wrote at the old place come in, with their MEMORY.md lines; a path
|
|
267
|
+
another clone left in the shared settings.json goes, for this clone,
|
|
268
|
+
into its .claude/settings.local.json
|
|
262
269
|
--staged the profile's pre-commit hook: refuses a commit that stages
|
|
263
270
|
.claude/memory/ unless only you read every place the commit reaches —
|
|
264
271
|
origin on a host of git.personal_hosts or a private GitHub repo with
|
package/src/init.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { at } from './narrow.ts';
|
|
|
6
6
|
export const STANDARD_VERBS = ['up', 'down', 'build', 'test', 'check', 'fix'] as const;
|
|
7
7
|
export type Verb = (typeof STANDARD_VERBS)[number];
|
|
8
8
|
|
|
9
|
-
export const DEFAULT_BASELINE = 'github.com/VoDmAl/vdx-rubric-vodmal@v1.
|
|
9
|
+
export const DEFAULT_BASELINE = 'github.com/VoDmAl/vdx-rubric-vodmal@v1.8.0';
|
|
10
10
|
|
|
11
11
|
const VERB_ALIASES: Record<Verb, string[]> = {
|
|
12
12
|
up: ['docker:up', 'docker-up', 'docker:up:detached', 'start', 'dev', 'serve'],
|
package/src/memory.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { remoteParts } from './author.ts';
|
|
5
|
-
import { claudeConfigDir, projectDirName } from './conversations.ts';
|
|
5
|
+
import { claudeConfigDir, claudeTrusts, projectDirName } from './conversations.ts';
|
|
6
6
|
import { githubToken, machineIo, type HostingIo } from './hosting.ts';
|
|
7
7
|
import { at, dict, errorMessage, list, str } from './narrow.ts';
|
|
8
8
|
|
|
@@ -18,8 +18,11 @@ import { at, dict, errorMessage, list, str } from './narrow.ts';
|
|
|
18
18
|
export type MemoryState =
|
|
19
19
|
/** In `.claude/memory/`, nothing left in the old place. */
|
|
20
20
|
| { kind: 'inside' }
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* In `.claude/memory/`, and notes still lie in the old place — a move made by hand. `afterMove`:
|
|
23
|
+
* vdx set the old place aside before, so a session that started before the move wrote them.
|
|
24
|
+
*/
|
|
25
|
+
| { kind: 'leftovers'; old: string; notes: number; differ: number; onlyOld: number; afterMove: boolean }
|
|
23
26
|
/** Not set: memory goes to the old place. */
|
|
24
27
|
| { kind: 'outside'; old: string; notes: number }
|
|
25
28
|
/**
|
|
@@ -109,6 +112,11 @@ function entries(dir: string): string[] {
|
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
/** vdx set this place aside once already (`asideName`): what lies there now came after (Sidetrack #5). */
|
|
116
|
+
function setAsideBefore(dir: string): boolean {
|
|
117
|
+
return entries(path.dirname(dir)).some((n) => n.startsWith(`${path.basename(dir)}.moved-`));
|
|
118
|
+
}
|
|
119
|
+
|
|
112
120
|
function sameFile(a: string, b: string): boolean {
|
|
113
121
|
try {
|
|
114
122
|
return fs.readFileSync(a).equals(fs.readFileSync(b));
|
|
@@ -130,7 +138,14 @@ export function memoryState(projectRoot: string, ctx: MemoryCtx): MemoryState {
|
|
|
130
138
|
(n) => fs.existsSync(path.join(target, n)) && !sameFile(path.join(old, n), path.join(target, n)),
|
|
131
139
|
);
|
|
132
140
|
const onlyOld = notes.filter((n) => !fs.existsSync(path.join(target, n)));
|
|
133
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
kind: 'leftovers',
|
|
143
|
+
old,
|
|
144
|
+
notes: notes.length,
|
|
145
|
+
differ: differ.length,
|
|
146
|
+
onlyOld: onlyOld.length,
|
|
147
|
+
afterMove: setAsideBefore(old),
|
|
148
|
+
};
|
|
134
149
|
}
|
|
135
150
|
if (path.basename(setting.dir) === 'memory' && path.basename(path.dirname(setting.dir)) === '.claude') {
|
|
136
151
|
const from = path.dirname(path.dirname(setting.dir));
|
|
@@ -253,6 +268,8 @@ export function describeMemory(state: MemoryState, ctx: MemoryCtx): string {
|
|
|
253
268
|
case 'inside':
|
|
254
269
|
return 'in .claude/memory/';
|
|
255
270
|
case 'leftovers': {
|
|
271
|
+
if (state.afterMove)
|
|
272
|
+
return `in .claude/memory/, and ${state.notes} note(s) written in the old place after the move, ${tilde(state.old, ctx.home)} — by a session that started before it; the fix brings them in`;
|
|
256
273
|
const parts = [state.differ ? `${state.differ} differ` : '', state.onlyOld ? `${state.onlyOld} only there` : '']
|
|
257
274
|
.filter(Boolean)
|
|
258
275
|
.join(', ');
|
|
@@ -288,10 +305,10 @@ function asideName(dir: string, today: string): string {
|
|
|
288
305
|
return name;
|
|
289
306
|
}
|
|
290
307
|
|
|
291
|
-
/** Copies what `to` lacks; a name `to` already has stays as it is there. Returns the names kept. */
|
|
292
|
-
function copyMissing(from: string, to: string): { copied:
|
|
308
|
+
/** Copies what `to` lacks; a name `to` already has stays as it is there. Returns the names copied and kept. */
|
|
309
|
+
function copyMissing(from: string, to: string): { copied: string[]; kept: string[] } {
|
|
293
310
|
fs.mkdirSync(to, { recursive: true });
|
|
294
|
-
|
|
311
|
+
const copied: string[] = [];
|
|
295
312
|
const kept: string[] = [];
|
|
296
313
|
for (const name of entries(from)) {
|
|
297
314
|
const src = path.join(from, name);
|
|
@@ -301,11 +318,42 @@ function copyMissing(from: string, to: string): { copied: number; kept: string[]
|
|
|
301
318
|
continue;
|
|
302
319
|
}
|
|
303
320
|
fs.cpSync(src, dst, { recursive: true, errorOnExist: true, force: false });
|
|
304
|
-
copied
|
|
321
|
+
copied.push(name);
|
|
305
322
|
}
|
|
306
323
|
return { copied, kept };
|
|
307
324
|
}
|
|
308
325
|
|
|
326
|
+
const INDEX = 'MEMORY.md';
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Notes a session wrote at the old place after the move, brought into `to`: the agent finds a
|
|
330
|
+
* note by its line in MEMORY.md, so the lines of the old index that point at the notes
|
|
331
|
+
* brought in join `to`'s — those alone. A line about a note `to` already has is the
|
|
332
|
+
* project's agent's to judge (DL #8). Returns what was done, one line each, and the names kept.
|
|
333
|
+
*/
|
|
334
|
+
function bringIn(from: string, to: string, what: string, aside: string): { done: string[]; differ: string[] } {
|
|
335
|
+
const { copied, kept } = copyMissing(from, to);
|
|
336
|
+
const done = [`${copied.length} note(s) ${what} → .claude/memory/`];
|
|
337
|
+
const notes = copied.filter((n) => n !== INDEX);
|
|
338
|
+
const src = path.join(from, INDEX);
|
|
339
|
+
const dst = path.join(to, INDEX);
|
|
340
|
+
if (notes.length && !copied.includes(INDEX) && fs.existsSync(src)) {
|
|
341
|
+
const have = fs.existsSync(dst) ? fs.readFileSync(dst, 'utf8') : '';
|
|
342
|
+
const lines = fs
|
|
343
|
+
.readFileSync(src, 'utf8')
|
|
344
|
+
.split('\n')
|
|
345
|
+
.filter((l) => notes.some((n) => l.includes(`](${n})`) && !have.includes(`(${n})`)));
|
|
346
|
+
if (lines.length) {
|
|
347
|
+
fs.writeFileSync(dst, have + (have && !have.endsWith('\n') ? '\n' : '') + lines.join('\n') + '\n');
|
|
348
|
+
done.push(`${lines.length} line(s) of its MEMORY.md → .claude/memory/MEMORY.md, for the notes brought in`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const differ = kept.filter((n) => n !== INDEX);
|
|
352
|
+
if (differ.length)
|
|
353
|
+
done.push(`kept .claude/memory/ as it is for ${differ.join(', ')} — the later copy differs, it stays in ${aside}`);
|
|
354
|
+
return { done, differ };
|
|
355
|
+
}
|
|
356
|
+
|
|
309
357
|
/** Sets `autoMemoryDirectory` in a settings file, keeping its other keys. */
|
|
310
358
|
function writeSetting(file: string, dir: string): void {
|
|
311
359
|
let doc: Record<string, unknown> = {};
|
|
@@ -380,12 +428,22 @@ export function fixMemory(projectRoot: string, state: MemoryState, ctx: MemoryCt
|
|
|
380
428
|
case 'custom':
|
|
381
429
|
return done;
|
|
382
430
|
case 'leftovers':
|
|
431
|
+
// Written after vdx's move by a session that started before it: newer than anything inside.
|
|
432
|
+
if (state.afterMove)
|
|
433
|
+
done.push(
|
|
434
|
+
...bringIn(
|
|
435
|
+
state.old,
|
|
436
|
+
target,
|
|
437
|
+
'written in the old place after the move',
|
|
438
|
+
tilde(asideName(state.old, opts.today), ctx.home),
|
|
439
|
+
).done,
|
|
440
|
+
);
|
|
383
441
|
setAside(state.old);
|
|
384
442
|
return done;
|
|
385
443
|
case 'outside': {
|
|
386
|
-
const { copied, kept } = fs.existsSync(state.old) ? copyMissing(state.old, target) : { copied:
|
|
444
|
+
const { copied, kept } = fs.existsSync(state.old) ? copyMissing(state.old, target) : { copied: [], kept: [] };
|
|
387
445
|
if (!fs.existsSync(target)) fs.mkdirSync(target, { recursive: true });
|
|
388
|
-
done.push(`${copied} note(s) → .claude/memory/`);
|
|
446
|
+
done.push(`${copied.length} note(s) → .claude/memory/`);
|
|
389
447
|
if (kept.length) done.push(`kept .claude/memory/ as it is for ${kept.join(', ')} — the old copy differs`);
|
|
390
448
|
const file = path.join(root, '.claude', opts.personal ? 'settings.json' : 'settings.local.json');
|
|
391
449
|
writeSetting(file, target);
|
|
@@ -405,18 +463,40 @@ export function fixMemory(projectRoot: string, state: MemoryState, ctx: MemoryCt
|
|
|
405
463
|
}
|
|
406
464
|
case 'moved':
|
|
407
465
|
case 'elsewhere': {
|
|
408
|
-
|
|
409
|
-
|
|
466
|
+
// The path is rewritten where it was read — unless that file is the user's, read by every
|
|
467
|
+
// project, or another clone's path in the repo's shared settings.json: then this clone's
|
|
468
|
+
// own local settings, read first (Sidetrack #4).
|
|
469
|
+
const local = path.join(root, '.claude', 'settings.local.json');
|
|
470
|
+
const inProject = path.dirname(state.setting.file) === path.join(root, '.claude');
|
|
471
|
+
const shared =
|
|
472
|
+
state.kind === 'elsewhere' &&
|
|
473
|
+
state.setting.file !== local &&
|
|
474
|
+
git(root, ['ls-files', '--error-unmatch', '--', path.relative(root, state.setting.file)]) !== null;
|
|
475
|
+
const file = inProject && !shared ? state.setting.file : local;
|
|
476
|
+
writeSetting(file, target);
|
|
477
|
+
done.push(`autoMemoryDirectory → ${target}/ (${path.relative(root, file)})`);
|
|
478
|
+
if (shared) done.push(`${path.relative(root, state.setting.file)} keeps the other clone's path — it is shared`);
|
|
410
479
|
if (state.kind === 'moved' && state.stray) {
|
|
411
|
-
const
|
|
480
|
+
const brought = bringIn(
|
|
481
|
+
state.stray,
|
|
482
|
+
target,
|
|
483
|
+
'written at the old path after the move',
|
|
484
|
+
tilde(state.stray, ctx.home),
|
|
485
|
+
);
|
|
412
486
|
done.push(
|
|
413
|
-
|
|
487
|
+
...brought.done,
|
|
488
|
+
`${tilde(state.from, ctx.home)} holds only them — remove it${brought.differ.length ? ` once ${brought.differ.join(', ')} is settled` : ''}`,
|
|
414
489
|
);
|
|
415
490
|
}
|
|
416
491
|
if (state.kind === 'moved' && state.conversations) {
|
|
417
492
|
const here = path.join(claudeConfigDir(ctx.env, ctx.home), 'projects', projectDirName(root));
|
|
418
493
|
done.push(moveConversations(state.conversations, here, opts.today));
|
|
419
494
|
}
|
|
495
|
+
// Claude Code trusts a folder by its path: the moved one is new to it (Sidetrack #2).
|
|
496
|
+
if (state.kind === 'moved' && claudeTrusts(root, ctx.env, ctx.home) === false)
|
|
497
|
+
done.push(
|
|
498
|
+
'Claude Code has not trusted this folder yet: its first start here stops on the trust window — start vdx ai here once and answer it; a detached start (the mail session) is refused until then',
|
|
499
|
+
);
|
|
420
500
|
return done;
|
|
421
501
|
}
|
|
422
502
|
}
|
|
@@ -451,7 +531,10 @@ export function fixProjectMemory(
|
|
|
451
531
|
underGit = /^y(es)?$/i.test(deps.ask(' Keep .claude/memory/ under git? [y/N] ')?.trim() ?? '');
|
|
452
532
|
}
|
|
453
533
|
const done = fixMemory(projectRoot, state, ctx, { personal, underGit, today: deps.today });
|
|
454
|
-
if (
|
|
534
|
+
if (
|
|
535
|
+
done.length &&
|
|
536
|
+
(state.kind === 'outside' || state.kind === 'moved' || (state.kind === 'leftovers' && state.afterMove))
|
|
537
|
+
) {
|
|
455
538
|
done.push('sessions of this project running now keep the old place until they restart');
|
|
456
539
|
}
|
|
457
540
|
return done;
|