devflow-kit 3.7.0 → 3.9.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/CHANGELOG.md +479 -1
- package/README.md +15 -15
- package/dist/agents/code.md +6 -8
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +7 -8
- package/dist/agents/git.md +3 -3
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +2 -4
- package/dist/agents/review.md +4 -4
- package/dist/agents/skim.md +2 -2
- package/dist/agents/triage.md +9 -2
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/curation-move-io.js +102 -0
- package/dist/cli/commands/curation-move-step.js +374 -0
- package/dist/cli/commands/curation.js +808 -0
- package/dist/cli/commands/gate-level-prompts.js +428 -0
- package/dist/cli/commands/gates.js +35 -11
- package/dist/cli/commands/init-seed.js +11 -8
- package/dist/cli/commands/init.js +244 -57
- package/dist/cli/commands/memory.js +60 -13
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +157 -17
- package/dist/cli.js +2 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +40 -44
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +26 -25
- package/dist/commands/dynamic-plan.md +5 -5
- package/dist/commands/dynamic-profile.md +5 -5
- package/dist/commands/dynamic-tickets.md +5 -5
- package/dist/commands/explore.md +6 -6
- package/dist/commands/implement.md +53 -82
- package/dist/commands/plan.md +6 -6
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +39 -41
- package/dist/commands/self-review.md +5 -13
- package/dist/core/area-pause-notice.js +44 -0
- package/dist/core/assets.js +20 -20
- package/dist/core/curate-tuning-config.js +5 -6
- package/dist/core/curation-move-notice.js +84 -0
- package/dist/core/curation-move-skip.js +35 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +135 -0
- package/dist/core/curation-store.js +99 -0
- package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
- package/dist/core/echo-value.js +11 -5
- package/dist/core/evidence-policy.js +11 -9
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +10 -2
- package/dist/core/git.js +16 -14
- package/dist/core/ledger-root.js +82 -22
- package/dist/core/linked-path.js +4 -4
- package/dist/core/manifest-lock.js +238 -0
- package/dist/core/manifest.js +58 -14
- package/dist/core/migrations.js +1 -13
- package/dist/core/observations.js +2 -2
- package/dist/core/plugins.js +19 -19
- package/dist/core/pool.js +26 -0
- package/dist/core/project-paths.js +103 -34
- package/dist/core/queue-drain.js +2 -2
- package/dist/core/tracked-areas.js +159 -0
- package/dist/core/tracker.js +1 -1
- package/dist/{learning-off → curation-off}/agents/code.md +6 -8
- package/dist/{learning-off → curation-off}/agents/fix.md +7 -8
- package/dist/{learning-off → curation-off}/agents/refactor.md +2 -4
- package/dist/{learning-off → curation-off}/agents/review.md +3 -3
- package/dist/{learning-off → curation-off}/agents/triage.md +8 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +37 -41
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +23 -22
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +50 -79
- package/dist/{learning-off → curation-off}/commands/plan.md +2 -2
- package/dist/{learning-off → curation-off}/commands/resolve.md +36 -38
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +63 -17
- package/dist/hud/config.js +1 -1
- package/dist/hud/index.js +6 -6
- package/dist/hud/render.js +3 -3
- package/dist/skills/git/references/pr/post-resolution-summary.md +12 -11
- package/dist/skills/git/references/pr/post-review-summary.md +12 -11
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
- package/dist/skills/git/references/pr/validate-branch.md +8 -5
- package/dist/skills/git/references/publication-gate.md +6 -7
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +85 -65
- package/dist/targets/claude-code/installer.js +15 -15
- package/dist/targets/claude-code/language-stamp.js +5 -5
- package/dist/targets/claude-code/post-install.js +2 -2
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +3 -2
- package/src/assets/agents/_partials/_code.mds +11 -11
- package/src/assets/agents/code.mds +6 -6
- package/src/assets/agents/curate.md +8 -8
- package/src/assets/agents/debug.mds +6 -6
- package/src/assets/agents/design.mds +18 -18
- package/src/assets/agents/evaluate.md +6 -12
- package/src/assets/agents/fix.mds +7 -6
- package/src/assets/agents/git.mds +3 -3
- package/src/assets/agents/harden.mds +7 -7
- package/src/assets/agents/knowledge.mds +23 -23
- package/src/assets/agents/refactor.mds +2 -2
- package/src/assets/agents/research.mds +15 -15
- package/src/assets/agents/review.mds +43 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/triage.mds +29 -22
- package/src/assets/agents/validate.md +30 -50
- package/src/assets/commands/_partials/_decisions.mds +22 -20
- package/src/assets/commands/_partials/_engine.mds +8 -8
- package/src/assets/commands/_partials/_knowledge.mds +29 -15
- package/src/assets/commands/_partials/_preamble.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +3 -3
- package/src/assets/commands/_partials/_wave.mds +7 -7
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +53 -57
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +38 -37
- package/src/assets/commands/dynamic-plan.mds +22 -22
- package/src/assets/commands/dynamic-profile.mds +2 -2
- package/src/assets/commands/dynamic-tickets.mds +8 -8
- package/src/assets/commands/explore.mds +20 -20
- package/src/assets/commands/implement.mds +77 -98
- package/src/assets/commands/plan.mds +27 -27
- package/src/assets/commands/release.mds +12 -12
- package/src/assets/commands/research.mds +19 -19
- package/src/assets/commands/resolve.mds +66 -60
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +9 -2
- package/src/assets/mds/git/_ops_pr.mds +21 -11
- package/src/assets/mds/git/_ops_steps.mds +20 -4
- package/src/assets/mds/git/_pr.mds +21 -18
- package/src/assets/mds/git/_references.mds +6 -7
- package/src/assets/mds/tracker/_github.mds +1 -4
- package/src/assets/mds/tracker/_jira.mds +1 -4
- package/src/assets/mds/tracker/_linear.mds +1 -4
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +389 -92
- package/src/assets/scripts/hooks/background-memory-update +81 -3
- package/src/assets/scripts/hooks/capture-prompt +12 -12
- package/src/assets/scripts/hooks/capture-question +11 -11
- package/src/assets/scripts/hooks/capture-turn +12 -12
- package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
- package/src/assets/scripts/hooks/curation-move +293 -0
- package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
- package/src/assets/scripts/hooks/ensure-proxy +4 -4
- package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
- package/src/assets/scripts/hooks/git-marker +81 -0
- package/src/assets/scripts/hooks/json-helper.cjs +176 -51
- package/src/assets/scripts/hooks/json-parse +1 -1
- package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
- package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +308 -122
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
- package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +42 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +452 -0
- package/src/assets/scripts/hooks/memory-permission +10 -2
- package/src/assets/scripts/hooks/memory-worker +12 -1
- package/src/assets/scripts/hooks/pre-compact-memory +17 -16
- package/src/assets/scripts/hooks/queue-append +444 -102
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +198 -72
- package/src/assets/scripts/hooks/session-start-memory +42 -43
- package/src/assets/scripts/lib/project-config.cjs +12 -5
- package/src/assets/scripts/pr-evidence.cjs +5 -2
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +283 -45
- package/src/assets/scripts/verify-evidence.cjs +307 -75
- package/src/assets/skills/agent-references/SKILL.md +11 -2
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +29 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +25 -0
- package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
- package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
- package/src/assets/skills/apply-decisions/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +7 -7
- package/src/assets/skills/docs-framework/references/patterns.md +3 -4
- package/src/assets/skills/quality-gates/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
- package/templates/lint-pack/.lint-pack.conf +86 -0
- package/templates/lint-pack/README.md +190 -0
- package/templates/lint-pack/lint-pack.sh +905 -0
- package/dist/cli/commands/learning.js +0 -554
- package/dist/core/learning-queue-cleanup.js +0 -27
- package/dist/core/learning-store.js +0 -61
- /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
|
@@ -8,8 +8,11 @@ import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
|
8
8
|
import { discoverProjectGitRoots } from '../../targets/claude-code/post-install.js';
|
|
9
9
|
import { getGitRoot } from '../../core/git.js';
|
|
10
10
|
import { firstSymbolicLink } from '../../core/linked-path.js';
|
|
11
|
+
import { pooled } from '../../core/pool.js';
|
|
11
12
|
import { getMemoryDir, getPendingTurnsPath, getPendingTurnsProcessingPath, } from '../../core/project-paths.js';
|
|
12
13
|
import { drainQueueFiles, formatRefusedDrain } from '../../core/queue-drain.js';
|
|
14
|
+
import { AREA_PAUSE_NOTICE } from '../../core/area-pause-notice.js';
|
|
15
|
+
import { probeTrackedArea } from '../../core/tracked-areas.js';
|
|
13
16
|
import { HOOKS_DIR_SUFFIX, devflowHookOwner, endsWithAny, ensureHook, hasHook, removeHooks, runHookCommand, runHookSuffix, } from '../../targets/claude-code/hooks.js';
|
|
14
17
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
15
18
|
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
@@ -40,7 +43,7 @@ const MEMORY_HOOK_CONFIG = {
|
|
|
40
43
|
*
|
|
41
44
|
* D-EXACT-HOOK-OWNER (hooks.ts): matched as command ENDINGS under any directory —
|
|
42
45
|
* the v1 (≤ v1.2) direct `.sh` scripts, then the retired `run-hook` markers of the
|
|
43
|
-
* prompt-capture,
|
|
46
|
+
* prompt-capture, curation, decisions, knowledge-refresh, sidecar and dream
|
|
44
47
|
* pipelines — never as substrings, so a user's hook that mentions one is theirs.
|
|
45
48
|
*/
|
|
46
49
|
const LEGACY_HOOK_SUFFIXES = {
|
|
@@ -159,21 +162,42 @@ export function convergeMemoryHooks(settingsJson, enabled, devflowDir) {
|
|
|
159
162
|
const cleaned = removeMemoryHooks(settingsJson);
|
|
160
163
|
return enabled ? addMemoryHooks(cleaned, devflowDir) : cleaned;
|
|
161
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Why the memory area at `projectRoot` is paused, or null when git tracks nothing in the
|
|
167
|
+
* memory folder there. The area is paused when git tracks files in the memory folder, or
|
|
168
|
+
* a `.git` entry stands and git did not answer (`unknown`); a root with no `.git` entry
|
|
169
|
+
* makes no git call. The root is the checkout's toplevel, where the memory hooks work.
|
|
170
|
+
*/
|
|
171
|
+
async function memoryPauseReason(projectRoot) {
|
|
172
|
+
const { answer } = await probeTrackedArea(projectRoot, 'memory');
|
|
173
|
+
return answer === 'untracked' ? null : answer;
|
|
174
|
+
}
|
|
162
175
|
/**
|
|
163
176
|
* Drain a project's pending memory queue (and a claimed batch) so stale turns
|
|
164
177
|
* are not processed when memory is next switched on. Shared by `devflow init
|
|
165
|
-
* --no-memory` and `devflow memory --disable`.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
178
|
+
* --no-memory` and `devflow memory --disable`. Deletes nothing, and answers
|
|
179
|
+
* `skipped`, when git tracks files in the memory folder or a `.git` entry stands and
|
|
180
|
+
* git does not answer: a folder that git tracks is the repository's content. Refused,
|
|
181
|
+
* deleting nothing, when `.devflow` or `.devflow/memory` under `projectRoot` is a
|
|
182
|
+
* symbolic link (D-CLI-NO-SYMLINK). ENOENT-tolerant; any other error propagates to the
|
|
183
|
+
* command boundary, like drainCurationQueue.
|
|
169
184
|
*/
|
|
170
185
|
export async function drainMemoryQueue(projectRoot) {
|
|
186
|
+
const skipped = await memoryPauseReason(projectRoot);
|
|
187
|
+
if (skipped !== null)
|
|
188
|
+
return { drained: false, skipped };
|
|
171
189
|
const memoryDir = getMemoryDir(projectRoot);
|
|
172
190
|
return drainQueueFiles([path.dirname(memoryDir), memoryDir], [
|
|
173
191
|
getPendingTurnsPath(projectRoot),
|
|
174
192
|
getPendingTurnsProcessingPath(projectRoot),
|
|
175
193
|
]);
|
|
176
194
|
}
|
|
195
|
+
/** The line a caller prints for a memory drain that deleted nothing: the fixed notice of a paused area, or the refusal at a link. */
|
|
196
|
+
export function formatMemoryNotDrained(drain) {
|
|
197
|
+
if (!('skipped' in drain))
|
|
198
|
+
return formatRefusedDrain('memory', drain.linkedFolder);
|
|
199
|
+
return `The memory queue was not drained. ${AREA_PAUSE_NOTICE.memory[drain.skipped]}`;
|
|
200
|
+
}
|
|
177
201
|
/**
|
|
178
202
|
* Returns true if the given project root contains a `.devflow/memory/` directory.
|
|
179
203
|
* Treats unexpected errors (e.g. EACCES) as absent to avoid false positives.
|
|
@@ -200,15 +224,23 @@ export async function filterProjectsWithMemory(gitRoots) {
|
|
|
200
224
|
const checks = await Promise.all(gitRoots.map(async (root) => ({ root, has: await hasMemoryDir(root) })));
|
|
201
225
|
return checks.filter((c) => c.has).map((c) => c.root);
|
|
202
226
|
}
|
|
227
|
+
/** At most this many projects are checked and cleaned at once, so `memory --clear` starts few git processes. */
|
|
228
|
+
export const CLEAN_QUEUE_POOL_LIMIT = 8;
|
|
203
229
|
/**
|
|
204
230
|
* Clean up memory queue files from the given project paths.
|
|
205
231
|
* Skips projects where the background updater lock is held to avoid data loss,
|
|
206
|
-
*
|
|
207
|
-
* `.
|
|
208
|
-
*
|
|
232
|
+
* leaves a project whose memory area is paused alone (git tracks files in the memory
|
|
233
|
+
* folder, or a `.git` entry stands and git did not answer; `paused` names each such
|
|
234
|
+
* project with the reason), and refuses, deleting nothing there, a project whose
|
|
235
|
+
* `.devflow` or `.devflow/memory` is a symbolic link (D-CLI-NO-SYMLINK); `refused` names
|
|
236
|
+
* each such link. At most {@link CLEAN_QUEUE_POOL_LIMIT} projects are handled at once.
|
|
237
|
+
* Returns the count of projects from which at least one file was removed.
|
|
209
238
|
*/
|
|
210
239
|
export async function cleanQueueFiles(projectPaths) {
|
|
211
|
-
const
|
|
240
|
+
const settled = await pooled(projectPaths, CLEAN_QUEUE_POOL_LIMIT, async (project) => {
|
|
241
|
+
const reason = await memoryPauseReason(project);
|
|
242
|
+
if (reason !== null)
|
|
243
|
+
return { paused: reason, project };
|
|
212
244
|
const memDir = getMemoryDir(project);
|
|
213
245
|
let linkedFolder;
|
|
214
246
|
try {
|
|
@@ -234,10 +266,16 @@ export async function cleanQueueFiles(projectPaths) {
|
|
|
234
266
|
fs.unlink(getPendingTurnsProcessingPath(project)).then(() => true).catch(() => false),
|
|
235
267
|
]);
|
|
236
268
|
return (q || pr) ? { cleaned: project } : null;
|
|
237
|
-
})
|
|
269
|
+
});
|
|
270
|
+
const results = settled.map((entry) => {
|
|
271
|
+
if (entry.status === 'rejected')
|
|
272
|
+
throw entry.reason;
|
|
273
|
+
return entry.value;
|
|
274
|
+
});
|
|
238
275
|
const cleanedProjects = results.flatMap((r) => (r !== null && 'cleaned' in r ? [r.cleaned] : []));
|
|
239
276
|
const refused = results.flatMap((r) => (r !== null && 'refused' in r ? [r.refused] : []));
|
|
240
|
-
|
|
277
|
+
const paused = results.flatMap((r) => (r !== null && 'paused' in r ? [{ project: r.project, reason: r.paused }] : []));
|
|
278
|
+
return { cleaned: cleanedProjects.length, projects: cleanedProjects, refused, paused };
|
|
241
279
|
}
|
|
242
280
|
export const memoryCommand = new Command('memory')
|
|
243
281
|
.description('Enable, disable, or clean up working memory (session context preservation)')
|
|
@@ -297,10 +335,13 @@ export const memoryCommand = new Command('memory')
|
|
|
297
335
|
}
|
|
298
336
|
targets = scope === 'local' && currentProject ? [currentProject] : allProjects;
|
|
299
337
|
}
|
|
300
|
-
const { cleaned, projects: cleanedProjects, refused } = await cleanQueueFiles(targets);
|
|
338
|
+
const { cleaned, projects: cleanedProjects, refused, paused } = await cleanQueueFiles(targets);
|
|
301
339
|
for (const project of cleanedProjects) {
|
|
302
340
|
p.log.info(color.dim(`Cleaned: ${project}`));
|
|
303
341
|
}
|
|
342
|
+
for (const { project, reason } of paused) {
|
|
343
|
+
p.log.warn(`The memory queue of ${project} was not cleaned. ${AREA_PAUSE_NOTICE.memory[reason]}`);
|
|
344
|
+
}
|
|
304
345
|
for (const linkedFolder of refused) {
|
|
305
346
|
p.log.warn(formatRefusedDrain('memory', linkedFolder));
|
|
306
347
|
}
|
|
@@ -340,6 +381,12 @@ export const memoryCommand = new Command('memory')
|
|
|
340
381
|
const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'memory') : null;
|
|
341
382
|
if (narrowed !== null)
|
|
342
383
|
p.log.info(`Effective here: ${color.yellow(narrowed)}`);
|
|
384
|
+
// A paused memory area (git tracks files in the memory folder, or did not answer):
|
|
385
|
+
// the fixed notice, from the checkout's toplevel, where the memory hooks work. The
|
|
386
|
+
// current directory stands in outside a git work tree, as it does for the settings line.
|
|
387
|
+
const paused = await memoryPauseReason((await getGitRoot()) ?? process.cwd());
|
|
388
|
+
if (paused !== null)
|
|
389
|
+
p.log.info(AREA_PAUSE_NOTICE.memory[paused]);
|
|
343
390
|
const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
|
|
344
391
|
if (trackedWarning !== null)
|
|
345
392
|
p.log.warn(trackedWarning);
|
|
@@ -380,7 +427,7 @@ export const memoryCommand = new Command('memory')
|
|
|
380
427
|
if (gitRoot) {
|
|
381
428
|
const drain = await drainMemoryQueue(gitRoot);
|
|
382
429
|
if (!drain.drained)
|
|
383
|
-
p.log.warn(
|
|
430
|
+
p.log.warn(formatMemoryNotDrained(drain));
|
|
384
431
|
}
|
|
385
432
|
p.log.success('Working memory disabled in every project');
|
|
386
433
|
});
|
|
@@ -9,8 +9,45 @@
|
|
|
9
9
|
* D-PROMPT-IO: WizardPromptIO is the base DI seam for all two-action wizard
|
|
10
10
|
* steps (note + boolean select). Modules that add a third prompt extend this
|
|
11
11
|
* interface with an intersection type (e.g. CompliancePromptIO).
|
|
12
|
+
*
|
|
13
|
+
* The shared wizard gate predicate (shouldRunPromptedWizardStep) lives here too,
|
|
14
|
+
* beside the seam every prompted step already imports.
|
|
12
15
|
*/
|
|
13
16
|
import * as p from '@clack/prompts';
|
|
17
|
+
// ── Shared gate predicate ─────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Determines whether a wizard step that runs on both wizard paths should run for a
|
|
20
|
+
* given init invocation.
|
|
21
|
+
*
|
|
22
|
+
* Gate table (key on modePromptShown, never on the mode name):
|
|
23
|
+
*
|
|
24
|
+
* --recommended flag / !isTTY fallback → no (promptless contract preserved)
|
|
25
|
+
* Interactive mode-prompt → Recommended → yes (modePromptShown=true)
|
|
26
|
+
* --advanced flag / re-init (banner path) → yes (mode='advanced', isTTY=true)
|
|
27
|
+
* Interactive mode-prompt → Advanced → yes (modePromptShown=true)
|
|
28
|
+
* Any path with the step's CLI flag → no (hasCliOverride wins)
|
|
29
|
+
*
|
|
30
|
+
* A step that wires only the Recommended path is dead on every re-init, because re-init
|
|
31
|
+
* is Advanced-only by construction, so each step calls this predicate from both paths.
|
|
32
|
+
* The gate-level step delegates here. The attribution step is Advanced-only by design
|
|
33
|
+
* and does not use this table.
|
|
34
|
+
*
|
|
35
|
+
* Pure predicate — no side effects, fully testable without a TTY.
|
|
36
|
+
*/
|
|
37
|
+
export function shouldRunPromptedWizardStep(input) {
|
|
38
|
+
if (input.hasCliOverride)
|
|
39
|
+
return false;
|
|
40
|
+
if (!input.isTTY)
|
|
41
|
+
return false;
|
|
42
|
+
// Advanced path: non-TTY has already exit-1'd, so isTTY=true here → always run.
|
|
43
|
+
// Covers: --advanced flag, re-init banner path, interactive-prompt → advanced.
|
|
44
|
+
if (input.mode === 'advanced')
|
|
45
|
+
return true;
|
|
46
|
+
// Recommended path: only run when the Setup-mode p.select actually ran
|
|
47
|
+
// (user made an active choice). --recommended flag and !isTTY fallback never set
|
|
48
|
+
// modePromptShown=true, preserving their promptless contracts.
|
|
49
|
+
return input.modePromptShown;
|
|
50
|
+
}
|
|
14
51
|
// ── Shared clack adapters ─────────────────────────────────────────────────────
|
|
15
52
|
/** Real clack adapter for the note prompt. */
|
|
16
53
|
export function clackNote(message, title) {
|
|
@@ -31,6 +31,9 @@ import { stripFlags } from '../../core/flags.js';
|
|
|
31
31
|
import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-cleanup.js';
|
|
32
32
|
import { getPackageRoot, isContainedIn } from '../../core/paths.js';
|
|
33
33
|
import { firstSymbolicLink } from '../../core/linked-path.js';
|
|
34
|
+
import { MANIFEST_LOCK_NAME, MANIFEST_LOCK_STALE_MARK } from '../../core/manifest-lock.js';
|
|
35
|
+
import { CURATION_DIR_NAME, CURATION_TUNING_FILE_NAME, LEGACY_NAMES, MOVE_JOURNAL_SUFFIX, MOVE_LOCK_PREFIX, MOVE_LOCK_SUFFIX, getMemoryDir } from '../../core/project-paths.js';
|
|
36
|
+
import { probeTrackedArea } from '../../core/tracked-areas.js';
|
|
34
37
|
import { restampInstalledCommands } from '../../targets/claude-code/language-stamp.js';
|
|
35
38
|
import { installCommandsAndAgents } from '../../targets/claude-code/installer.js';
|
|
36
39
|
/**
|
|
@@ -283,20 +286,66 @@ const TRACKED_ENTRIES = DEVFLOW_TRACKED_PATHS.map((tracked) => ({
|
|
|
283
286
|
name: tracked.replace(/\/$/, ''),
|
|
284
287
|
isDir: tracked.endsWith('/'),
|
|
285
288
|
}));
|
|
289
|
+
/** The memory folder's name under `.devflow/`, by the name the path helper gives it. */
|
|
290
|
+
const MEMORY_FOLDER_NAME = path.basename(getMemoryDir(path.sep));
|
|
291
|
+
/**
|
|
292
|
+
* The data area an entry under `.devflow/` belongs to, or null. The curation area holds the
|
|
293
|
+
* curation folder, the old ledger folder and the backup folders; the memory area holds the
|
|
294
|
+
* memory folder. Decided by name alone, whatever the kind of entry, and without regard to
|
|
295
|
+
* ASCII letter case, as the tracked-areas check matches them.
|
|
296
|
+
*/
|
|
297
|
+
function projectDataAreaOf(name) {
|
|
298
|
+
const lower = name.toLowerCase();
|
|
299
|
+
if (lower === CURATION_DIR_NAME || lower === LEGACY_NAMES.folder || lower.startsWith(LEGACY_NAMES.backupPrefix))
|
|
300
|
+
return 'curation';
|
|
301
|
+
if (lower === MEMORY_FOLDER_NAME)
|
|
302
|
+
return 'memory';
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
286
305
|
/**
|
|
287
306
|
* Split the entries under a repository's `.devflow/` into what a confirmed cleanup
|
|
288
307
|
* removes and what it keeps (D-UNINSTALL-CARVE-OUT). An entry is kept only when both
|
|
289
308
|
* its name and its kind match a tracked path, so a stray FILE named `features` is
|
|
290
|
-
* not mistaken for the tracked directory.
|
|
309
|
+
* not mistaken for the tracked directory. An entry of a paused data area (`pausedAreas`:
|
|
310
|
+
* git tracks files in the area's folders, or did not answer) is kept too, in
|
|
311
|
+
* `keepPaused`. PURE — all three lists sorted by name.
|
|
291
312
|
*/
|
|
292
|
-
export function partitionProjectData(entries) {
|
|
313
|
+
export function partitionProjectData(entries, pausedAreas = new Set()) {
|
|
293
314
|
const isTracked = (entry) => TRACKED_ENTRIES.some((t) => t.name === entry.name && t.isDir === entry.isDir);
|
|
315
|
+
const isPaused = (entry) => {
|
|
316
|
+
const area = projectDataAreaOf(entry.name);
|
|
317
|
+
return area !== null && pausedAreas.has(area);
|
|
318
|
+
};
|
|
294
319
|
const byName = (a, b) => a.name.localeCompare(b.name);
|
|
295
320
|
return {
|
|
296
|
-
remove: entries.filter((e) => !isTracked(e)).sort(byName),
|
|
321
|
+
remove: entries.filter((e) => !isTracked(e) && !isPaused(e)).sort(byName),
|
|
297
322
|
keep: entries.filter(isTracked).sort(byName),
|
|
323
|
+
keepPaused: entries.filter((e) => !isTracked(e) && isPaused(e)).sort(byName),
|
|
298
324
|
};
|
|
299
325
|
}
|
|
326
|
+
/** The check's own answer, from the package's own copy of it. */
|
|
327
|
+
const probeProjectDataArea = async (root, area) => (await probeTrackedArea(root, area)).answer;
|
|
328
|
+
/**
|
|
329
|
+
* The data areas that hold an entry in `entries` and are paused at `gitRoot`: git tracks
|
|
330
|
+
* files in the area's folders, or a `.git` entry stands and git did not answer. An area
|
|
331
|
+
* with no entry is not asked, and a root with no `.git` entry makes no git call. A probe
|
|
332
|
+
* that fails to answer counts as `unknown`, so the area is kept.
|
|
333
|
+
*/
|
|
334
|
+
async function pausedProjectDataAreas(gitRoot, entries, probe) {
|
|
335
|
+
const present = new Set(entries.flatMap((entry) => {
|
|
336
|
+
const area = projectDataAreaOf(entry.name);
|
|
337
|
+
return area === null ? [] : [area];
|
|
338
|
+
}));
|
|
339
|
+
const answers = await Promise.all([...present].map(async (area) => {
|
|
340
|
+
try {
|
|
341
|
+
return [area, await probe(gitRoot, area)];
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return [area, 'unknown'];
|
|
345
|
+
}
|
|
346
|
+
}));
|
|
347
|
+
return new Set(answers.flatMap(([area, answer]) => (answer === 'untracked' ? [] : [area])));
|
|
348
|
+
}
|
|
300
349
|
/**
|
|
301
350
|
* Resolve the project-data step's target: `<gitRoot>/.devflow`, partitioned.
|
|
302
351
|
*
|
|
@@ -310,6 +359,12 @@ export function partitionProjectData(entries) {
|
|
|
310
359
|
* A `.devflow` that is a symbolic link to anywhere else is skipped too, and never
|
|
311
360
|
* followed: its target is a directory devflow cannot prove it wrote, so a
|
|
312
361
|
* confirmed cleanup must not empty it — nor unlink a link the user made.
|
|
362
|
+
*
|
|
363
|
+
* A folder that git tracks is the repository's content, not devflow's data. The
|
|
364
|
+
* curation area (the curation folder, the old ledger folder and the backup folders)
|
|
365
|
+
* and the memory area (the memory folder) are each asked of the tracked-areas check at
|
|
366
|
+
* the git root, when an entry of the area stands. An area that git tracks files in, or
|
|
367
|
+
* that git did not answer for, moves its entries from the removal list to `keepPaused`.
|
|
313
368
|
*/
|
|
314
369
|
export async function resolveProjectDataPlan(opts) {
|
|
315
370
|
if (opts.gitRoot === null)
|
|
@@ -333,8 +388,10 @@ export async function resolveProjectDataPlan(opts) {
|
|
|
333
388
|
catch {
|
|
334
389
|
return { kind: 'skip', reason: 'absent' };
|
|
335
390
|
}
|
|
336
|
-
const
|
|
337
|
-
|
|
391
|
+
const entries = dirents.map((d) => ({ name: d.name, isDir: d.isDirectory() }));
|
|
392
|
+
const paused = await pausedProjectDataAreas(opts.gitRoot, entries, opts.probe ?? probeProjectDataArea);
|
|
393
|
+
const { remove, keep, keepPaused } = partitionProjectData(entries, paused);
|
|
394
|
+
return { kind: 'plan', plan: { dir, remove, keep, keepPaused } };
|
|
338
395
|
}
|
|
339
396
|
/**
|
|
340
397
|
* The line that tells the user why the project-data step was skipped, or null
|
|
@@ -364,15 +421,21 @@ function entryLabel(entry) {
|
|
|
364
421
|
function entryList(entries) {
|
|
365
422
|
return entries.length === 0 ? '(none)' : entries.map(entryLabel).join(', ');
|
|
366
423
|
}
|
|
424
|
+
/** Why the entries of a data area stay, said beside a list of them. */
|
|
425
|
+
const KEEP_PAUSED_REASON = 'git tracks them, or git did not answer';
|
|
426
|
+
/** Why one entry of a data area stays, said beside it. */
|
|
427
|
+
const KEEP_PAUSED_REASON_ONE = 'git tracks it, or git did not answer';
|
|
367
428
|
/**
|
|
368
429
|
* The lines shown before the project-data confirm: what is removed and what is
|
|
369
|
-
* kept
|
|
430
|
+
* kept, and, only where there are such entries, what is kept because git tracks them
|
|
431
|
+
* or did not answer. PURE.
|
|
370
432
|
*/
|
|
371
433
|
export function formatProjectDataPlan(plan) {
|
|
372
434
|
return [
|
|
373
435
|
`Project data in ${plan.dir}/:`,
|
|
374
436
|
` Remove: ${entryList(plan.remove)}`,
|
|
375
437
|
` Keep (shared via git): ${entryList(plan.keep)}`,
|
|
438
|
+
...(plan.keepPaused.length === 0 ? [] : [` Keep (${KEEP_PAUSED_REASON}): ${entryList(plan.keepPaused)}`]),
|
|
376
439
|
];
|
|
377
440
|
}
|
|
378
441
|
/**
|
|
@@ -489,8 +552,10 @@ export function userContentPaths(devflowDir) {
|
|
|
489
552
|
const name = `${TRACKER_LEGACY_CONVENTIONS_FILE}.${id}.bak`;
|
|
490
553
|
return { relPath: name, label: `${name} (previous issue tracker conventions)` };
|
|
491
554
|
}),
|
|
492
|
-
//
|
|
493
|
-
|
|
555
|
+
// The global curate agent tuning config, under both its names: the new name on an
|
|
556
|
+
// upgraded machine, and the old name, which a downgraded machine can still hold.
|
|
557
|
+
{ relPath: CURATION_TUNING_FILE_NAME, label: CURATION_TUNING_FILE_NAME },
|
|
558
|
+
{ relPath: LEGACY_NAMES.tuningFile, label: LEGACY_NAMES.tuningFile },
|
|
494
559
|
// hud.json — user HUD enable/disable preference and display config
|
|
495
560
|
{ relPath: 'hud.json', label: 'hud.json (HUD configuration)' },
|
|
496
561
|
];
|
|
@@ -527,6 +592,54 @@ export async function enumerateUserDevFlowContent(devflowDir) {
|
|
|
527
592
|
}
|
|
528
593
|
return items;
|
|
529
594
|
}
|
|
595
|
+
/** `text` as a regular-expression source, every character literal. */
|
|
596
|
+
function escapeRegExp(text) {
|
|
597
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
598
|
+
}
|
|
599
|
+
/** The name of a move lock or journal: the shared prefix, a 16-digit lowercase hex key, then `suffix`. */
|
|
600
|
+
function moveFileShape(suffix) {
|
|
601
|
+
return new RegExp(`^${escapeRegExp(MOVE_LOCK_PREFIX)}[0-9a-f]{16}${escapeRegExp(suffix)}$`);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* The curation move's lock, one directory per ledger root, taken directly in the machine
|
|
605
|
+
* directory: `.curation-move-<16 lowercase hex>.lock`. The store builds the name from a
|
|
606
|
+
* hash of the real ledger root, so the names exist only on disk. The prefix and the
|
|
607
|
+
* suffix are the ones the path helpers hold (project-paths.ts).
|
|
608
|
+
*/
|
|
609
|
+
const CURATION_MOVE_LOCK_SHAPE = moveFileShape(MOVE_LOCK_SUFFIX);
|
|
610
|
+
/**
|
|
611
|
+
* The curation move's journal, one regular file per ledger root, beside the lock:
|
|
612
|
+
* `.curation-move-<16 lowercase hex>.journal`, with the same key as the lock. It exists
|
|
613
|
+
* only while a set move is unfinished, and a crash leaves it behind. A file named
|
|
614
|
+
* `.curation-move.journal` inside a project's old ledger folder is no journal of the
|
|
615
|
+
* move and is not matched here.
|
|
616
|
+
*/
|
|
617
|
+
const CURATION_MOVE_JOURNAL_SHAPE = moveFileShape(MOVE_JOURNAL_SUFFIX);
|
|
618
|
+
/**
|
|
619
|
+
* The manifest lock, a directory in the machine directory (manifest-lock.ts), and the
|
|
620
|
+
* directory a stale break moves aside: `manifest.json.lock` and
|
|
621
|
+
* `manifest.json.lock.stale-<digits>-<12 lowercase hex>`. A holder that dies in its
|
|
622
|
+
* critical section leaves the first, a break whose removal failed leaves the second.
|
|
623
|
+
*/
|
|
624
|
+
const MANIFEST_LOCK_SHAPE = new RegExp(`^${escapeRegExp(MANIFEST_LOCK_NAME)}(?:${escapeRegExp(MANIFEST_LOCK_STALE_MARK)}[0-9]+-[0-9a-f]{12})?$`);
|
|
625
|
+
/** Whether `child` of `devflowDir` is a real directory (a link is not followed). */
|
|
626
|
+
async function isRealDirectory(devflowDir, child) {
|
|
627
|
+
try {
|
|
628
|
+
return (await fs.lstat(path.join(devflowDir, child))).isDirectory();
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/** Whether `child` of `devflowDir` is a regular file (a link is not followed). */
|
|
635
|
+
async function isRegularFile(devflowDir, child) {
|
|
636
|
+
try {
|
|
637
|
+
return (await fs.lstat(path.join(devflowDir, child))).isFile();
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
530
643
|
/**
|
|
531
644
|
* Expand `installArtifactPaths` against real disk.
|
|
532
645
|
*
|
|
@@ -553,8 +666,15 @@ export async function resolveInstallArtifactPaths(devflowDir) {
|
|
|
553
666
|
}
|
|
554
667
|
}
|
|
555
668
|
for (const child of children) {
|
|
556
|
-
if (child.startsWith(entry.relPath))
|
|
557
|
-
|
|
669
|
+
if (!child.startsWith(entry.relPath))
|
|
670
|
+
continue;
|
|
671
|
+
if (entry.shape !== undefined && !entry.shape.test(child))
|
|
672
|
+
continue;
|
|
673
|
+
if (entry.realDirOnly === true && !(await isRealDirectory(devflowDir, child)))
|
|
674
|
+
continue;
|
|
675
|
+
if (entry.realFileOnly === true && !(await isRegularFile(devflowDir, child)))
|
|
676
|
+
continue;
|
|
677
|
+
resolved.push({ relPath: child, isDir: entry.isDir });
|
|
558
678
|
}
|
|
559
679
|
}
|
|
560
680
|
return resolved;
|
|
@@ -621,6 +741,18 @@ export function installArtifactPaths(devflowDir) {
|
|
|
621
741
|
// A prefix, because the names exist only on disk. Content is a scrubbed copy
|
|
622
742
|
// that was never placed, so it is machine state like the three above.
|
|
623
743
|
{ relPath: TRACKER_STAGED_PREFIX, isPrefix: true },
|
|
744
|
+
// The curation move's per-root lock directory. A crash between taking the lock and
|
|
745
|
+
// releasing it leaves one behind, and its name carries a hash that exists only on disk.
|
|
746
|
+
// Only a real directory of exactly that shape goes; the move's skip markers sit under
|
|
747
|
+
// logs/ and go with it.
|
|
748
|
+
{ relPath: MOVE_LOCK_PREFIX, isPrefix: true, isDir: true, shape: CURATION_MOVE_LOCK_SHAPE, realDirOnly: true },
|
|
749
|
+
// The move's journal beside it. A crash between two renames of the ledger set leaves
|
|
750
|
+
// one behind. Only a regular file of exactly that shape goes.
|
|
751
|
+
{ relPath: MOVE_LOCK_PREFIX, isPrefix: true, shape: CURATION_MOVE_JOURNAL_SHAPE, realFileOnly: true },
|
|
752
|
+
// The manifest lock, and the directory a stale break moved aside. A crash inside the
|
|
753
|
+
// critical section leaves the first; a failed cleanup leaves the second, whose name
|
|
754
|
+
// carries a pid and a random suffix. Only a real directory of exactly that shape goes.
|
|
755
|
+
{ relPath: MANIFEST_LOCK_NAME, isPrefix: true, isDir: true, shape: MANIFEST_LOCK_SHAPE, realDirOnly: true },
|
|
624
756
|
// per-project hook logs (logs/{project-slug}/) AND global logs — remove the
|
|
625
757
|
// whole logs/ tree; covers proxy.log, debug logs, and any project-slug dirs.
|
|
626
758
|
{ relPath: 'logs', isDir: true },
|
|
@@ -644,7 +776,8 @@ export function installArtifactPaths(devflowDir) {
|
|
|
644
776
|
* so an entry here is deleted even when the user answers "no" to the full wipe.
|
|
645
777
|
* User-authored state (everything in `userContentPaths`: skill/rule shadows,
|
|
646
778
|
* preference-profile.md, the tracker conventions with the legacy tracker.md and
|
|
647
|
-
* its backups,
|
|
779
|
+
* its backups, the global tuning file under both names, hud.json) is removed only by
|
|
780
|
+
* the confirmed full-dir rm.
|
|
648
781
|
* agent-models.json is an INSTALL ARTIFACT (stale per-agent overrides silently
|
|
649
782
|
* re-apply to renamed/deleted agents on reinstall — AC-P1-F4) and therefore
|
|
650
783
|
* belongs in this list, not in enumerateUserDevFlowContent.
|
|
@@ -874,6 +1007,9 @@ export async function runDryRunPhase(opts) {
|
|
|
874
1007
|
for (const entry of projectData.plan.remove) {
|
|
875
1008
|
extras.push(`${path.join(projectData.plan.dir, entryLabel(entry))} (if confirmed)`);
|
|
876
1009
|
}
|
|
1010
|
+
for (const entry of projectData.plan.keepPaused) {
|
|
1011
|
+
extras.push(`${path.join(projectData.plan.dir, entryLabel(entry))} (kept: ${KEEP_PAUSED_REASON_ONE})`);
|
|
1012
|
+
}
|
|
877
1013
|
}
|
|
878
1014
|
else {
|
|
879
1015
|
const skipped = formatProjectDataSkip(projectData.reason, projectRoot);
|
|
@@ -953,7 +1089,7 @@ export async function refreshRetainedCommandsAndAgents(opts) {
|
|
|
953
1089
|
plugins: retained,
|
|
954
1090
|
agentsMap: buildAssetMaps(retained).agentsMap,
|
|
955
1091
|
claudeDir,
|
|
956
|
-
|
|
1092
|
+
curation: manifest.features.curation,
|
|
957
1093
|
trackerServers: effectiveTrackerServers(manifest.features.tracker),
|
|
958
1094
|
warn: (msg) => p.log.warn(msg),
|
|
959
1095
|
});
|
|
@@ -1073,7 +1209,7 @@ export async function runSelectivePhaseForScope(opts) {
|
|
|
1073
1209
|
* FULL PHASE (per scope): revert agent model overrides, remove all Devflow
|
|
1074
1210
|
* assets from the scope, then perform scope-appropriate devflowDir cleanup.
|
|
1075
1211
|
*
|
|
1076
|
-
* Local scope: remove only install artifacts — project data (memory,
|
|
1212
|
+
* Local scope: remove only install artifacts — project data (memory, curation,
|
|
1077
1213
|
* docs, features) is always preserved under the git root.
|
|
1078
1214
|
* User scope: interactive TTY with user-authored content → confirm before wiping
|
|
1079
1215
|
* ~/.devflow/; non-interactive or no user content → artifacts-only.
|
|
@@ -1115,11 +1251,11 @@ export async function runFullPhaseForScope(opts) {
|
|
|
1115
1251
|
await removeAllDevFlow(claudeDir, devflowScriptsDir, verbose, mayChange);
|
|
1116
1252
|
if (scope === 'local') {
|
|
1117
1253
|
// Local scope: devflowDir is gitRoot/.devflow/ which holds project data
|
|
1118
|
-
// (memory,
|
|
1254
|
+
// (memory, curation, features, docs, config.json). Never remove those —
|
|
1119
1255
|
// only remove install artifacts: scripts/ (done above) + manifest.json and
|
|
1120
1256
|
// the other Devflow-generated artifacts (see removeDevFlowInstallArtifacts).
|
|
1121
1257
|
await removeDevFlowInstallArtifacts(devflowDir, verbose, mayChange);
|
|
1122
|
-
p.log.info('Local project data (memory,
|
|
1258
|
+
p.log.info('Local project data (memory, curation, features, docs) preserved');
|
|
1123
1259
|
}
|
|
1124
1260
|
else {
|
|
1125
1261
|
// User scope (devflowDir = ~/.devflow/): offer full cleanup behind a confirm gate
|
|
@@ -1179,7 +1315,9 @@ export async function runFullPhaseForScope(opts) {
|
|
|
1179
1315
|
async function runProjectDataStep(plan, gates) {
|
|
1180
1316
|
const shown = `${plan.dir}/`;
|
|
1181
1317
|
if (plan.remove.length === 0) {
|
|
1182
|
-
p.log.info(
|
|
1318
|
+
p.log.info(plan.keepPaused.length === 0
|
|
1319
|
+
? `${shown} preserved (holds only files shared via git)`
|
|
1320
|
+
: `${shown} preserved (holds only files shared via git, and ${entryList(plan.keepPaused)}, which stay: ${KEEP_PAUSED_REASON})`);
|
|
1183
1321
|
return;
|
|
1184
1322
|
}
|
|
1185
1323
|
if (gates.keepDocs) {
|
|
@@ -1215,7 +1353,7 @@ async function runProjectDataStep(plan, gates) {
|
|
|
1215
1353
|
failed.push(`${entryLabel(entry)} (${err.code ?? err.message})`);
|
|
1216
1354
|
}
|
|
1217
1355
|
}
|
|
1218
|
-
if (plan.keep.length === 0 && failed.length === 0) {
|
|
1356
|
+
if (plan.keep.length === 0 && plan.keepPaused.length === 0 && failed.length === 0) {
|
|
1219
1357
|
// Nothing tracked was there: drop the now-empty directory. rmdir refuses a
|
|
1220
1358
|
// directory something wrote into meanwhile, which is then left in place.
|
|
1221
1359
|
await fs.rmdir(plan.dir).catch(() => undefined);
|
|
@@ -1226,6 +1364,8 @@ async function runProjectDataStep(plan, gates) {
|
|
|
1226
1364
|
p.log.warn(`Could not remove from ${shown}: ${failed.join(', ')}`);
|
|
1227
1365
|
if (plan.keep.length > 0)
|
|
1228
1366
|
p.log.info(`Kept: ${entryList(plan.keep)}`);
|
|
1367
|
+
if (plan.keepPaused.length > 0)
|
|
1368
|
+
p.log.info(`Kept: ${entryList(plan.keepPaused)} (${KEEP_PAUSED_REASON})`);
|
|
1229
1369
|
}
|
|
1230
1370
|
/**
|
|
1231
1371
|
* CLEANUP PHASE: post-loop extras run only on full uninstall.
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { skillsCommand } from './cli/commands/skills.js';
|
|
|
11
11
|
import { hudCommand } from './cli/commands/hud.js';
|
|
12
12
|
import { flagsCommand } from './cli/commands/flags.js';
|
|
13
13
|
import { knowledgeCommand } from './cli/commands/knowledge/index.js';
|
|
14
|
-
import {
|
|
14
|
+
import { curationCommand } from './cli/commands/curation.js';
|
|
15
15
|
import { rulesCommand } from './cli/commands/rules.js';
|
|
16
16
|
import { debugCommand } from './cli/commands/debug.js';
|
|
17
17
|
import { securityCommand } from './cli/commands/security.js';
|
|
@@ -42,7 +42,7 @@ program.addCommand(skillsCommand);
|
|
|
42
42
|
program.addCommand(hudCommand);
|
|
43
43
|
program.addCommand(flagsCommand);
|
|
44
44
|
program.addCommand(knowledgeCommand);
|
|
45
|
-
program.addCommand(
|
|
45
|
+
program.addCommand(curationCommand);
|
|
46
46
|
program.addCommand(rulesCommand);
|
|
47
47
|
program.addCommand(debugCommand);
|
|
48
48
|
program.addCommand(securityCommand);
|