devflow-kit 3.6.0 → 3.8.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.
Files changed (197) hide show
  1. package/CHANGELOG.md +438 -1
  2. package/README.md +15 -11
  3. package/dist/agents/code.md +9 -15
  4. package/dist/agents/design.md +1 -1
  5. package/dist/agents/fix.md +11 -13
  6. package/dist/agents/git.md +1 -1
  7. package/dist/agents/harden.md +1 -1
  8. package/dist/agents/refactor.md +7 -10
  9. package/dist/agents/review.md +6 -4
  10. package/dist/agents/skim.md +2 -2
  11. package/dist/agents/triage.md +2 -2
  12. package/dist/cli/commands/capture.js +1 -1
  13. package/dist/cli/commands/curation-move-io.js +102 -0
  14. package/dist/cli/commands/curation-move-step.js +374 -0
  15. package/dist/cli/commands/{learning.js → curation.js} +302 -99
  16. package/dist/cli/commands/gate-level-prompts.js +426 -0
  17. package/dist/cli/commands/gates.js +299 -0
  18. package/dist/cli/commands/init-seed.js +16 -5
  19. package/dist/cli/commands/init.js +263 -54
  20. package/dist/cli/commands/memory.js +20 -2
  21. package/dist/cli/commands/prompt-io.js +37 -0
  22. package/dist/cli/commands/uninstall.js +81 -9
  23. package/dist/cli.js +4 -2
  24. package/dist/commands/bug-analysis.md +25 -36
  25. package/dist/commands/code-review.md +77 -73
  26. package/dist/commands/debug.md +5 -5
  27. package/dist/commands/dynamic-build.md +76 -83
  28. package/dist/commands/dynamic-plan.md +5 -5
  29. package/dist/commands/dynamic-profile.md +5 -5
  30. package/dist/commands/dynamic-tickets.md +5 -5
  31. package/dist/commands/explore.md +6 -6
  32. package/dist/commands/implement.md +68 -102
  33. package/dist/commands/plan.md +19 -29
  34. package/dist/commands/release.md +5 -5
  35. package/dist/commands/research.md +6 -6
  36. package/dist/commands/resolve.md +29 -31
  37. package/dist/commands/self-review.md +5 -13
  38. package/dist/core/assets.js +20 -20
  39. package/dist/core/curate-tuning-config.js +5 -6
  40. package/dist/core/curation-move-notice.js +84 -0
  41. package/dist/core/curation-move-skip.js +36 -0
  42. package/dist/core/curation-move.js +35 -0
  43. package/dist/core/curation-queue-cleanup.js +134 -0
  44. package/dist/core/curation-store.js +98 -0
  45. package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
  46. package/dist/core/echo-value.js +48 -0
  47. package/dist/core/evidence-policy.js +18 -6
  48. package/dist/core/feature-switch.js +95 -19
  49. package/dist/core/fs-atomic.js +1 -1
  50. package/dist/core/gate-level.js +221 -0
  51. package/dist/core/git.js +16 -14
  52. package/dist/core/ledger-root.js +82 -22
  53. package/dist/core/linked-path.js +4 -4
  54. package/dist/core/manifest-lock.js +238 -0
  55. package/dist/core/manifest.js +84 -24
  56. package/dist/core/migrations.js +1 -13
  57. package/dist/core/model-discovery.js +7 -1
  58. package/dist/core/observations.js +2 -2
  59. package/dist/core/plugins.js +15 -15
  60. package/dist/core/pool.js +26 -0
  61. package/dist/core/project-paths.js +103 -34
  62. package/dist/core/queue-drain.js +2 -2
  63. package/dist/core/tracked-areas.js +35 -0
  64. package/dist/core/tracker.js +6 -34
  65. package/dist/{learning-off → curation-off}/agents/code.md +9 -15
  66. package/dist/{learning-off → curation-off}/agents/fix.md +11 -13
  67. package/dist/{learning-off → curation-off}/agents/refactor.md +7 -10
  68. package/dist/{learning-off → curation-off}/agents/review.md +5 -3
  69. package/dist/{learning-off → curation-off}/agents/triage.md +1 -1
  70. package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
  71. package/dist/{learning-off → curation-off}/commands/code-review.md +74 -70
  72. package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
  73. package/dist/{learning-off → curation-off}/commands/dynamic-build.md +73 -80
  74. package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
  75. package/dist/{learning-off → curation-off}/commands/implement.md +65 -99
  76. package/dist/{learning-off → curation-off}/commands/plan.md +15 -25
  77. package/dist/{learning-off → curation-off}/commands/resolve.md +26 -28
  78. package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
  79. package/dist/hud/components/{learning-counts.js → curation-counts.js} +36 -17
  80. package/dist/hud/config.js +1 -1
  81. package/dist/hud/index.js +6 -6
  82. package/dist/hud/render.js +3 -3
  83. package/dist/skills/git/references/pr/validate-branch.md +8 -5
  84. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +20 -14
  85. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +20 -20
  86. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +20 -20
  87. package/dist/targets/claude-code/{learning-install.js → curation-install.js} +99 -67
  88. package/dist/targets/claude-code/installer.js +15 -15
  89. package/dist/targets/claude-code/language-stamp.js +5 -5
  90. package/dist/targets/claude-code/post-install.js +2 -2
  91. package/dist/targets/claude-code/tracker-servers.js +1 -1
  92. package/package.json +3 -2
  93. package/src/assets/agents/_partials/_code.mds +16 -17
  94. package/src/assets/agents/code.mds +4 -7
  95. package/src/assets/agents/curate.md +12 -14
  96. package/src/assets/agents/debug.mds +6 -6
  97. package/src/assets/agents/design.mds +18 -18
  98. package/src/assets/agents/fix.mds +6 -5
  99. package/src/assets/agents/git.mds +1 -1
  100. package/src/assets/agents/harden.mds +7 -7
  101. package/src/assets/agents/knowledge.mds +23 -23
  102. package/src/assets/agents/refactor.mds +2 -2
  103. package/src/assets/agents/research.mds +15 -15
  104. package/src/assets/agents/review.mds +45 -43
  105. package/src/assets/agents/skim.mds +11 -11
  106. package/src/assets/agents/synthesize.md +2 -2
  107. package/src/assets/agents/triage.mds +22 -22
  108. package/src/assets/agents/validate.md +13 -26
  109. package/src/assets/commands/_partials/_decisions.mds +13 -13
  110. package/src/assets/commands/_partials/_engine.mds +8 -8
  111. package/src/assets/commands/_partials/_knowledge.mds +29 -15
  112. package/src/assets/commands/_partials/_preamble.mds +4 -4
  113. package/src/assets/commands/_partials/_settings.mds +3 -3
  114. package/src/assets/commands/_partials/_wave.mds +17 -24
  115. package/src/assets/commands/bug-analysis.mds +37 -48
  116. package/src/assets/commands/code-review.mds +90 -86
  117. package/src/assets/commands/debug.mds +14 -14
  118. package/src/assets/commands/dynamic-build.mds +78 -78
  119. package/src/assets/commands/dynamic-plan.mds +22 -22
  120. package/src/assets/commands/dynamic-profile.mds +2 -2
  121. package/src/assets/commands/dynamic-tickets.mds +8 -8
  122. package/src/assets/commands/explore.mds +20 -20
  123. package/src/assets/commands/implement.mds +92 -118
  124. package/src/assets/commands/plan.mds +41 -51
  125. package/src/assets/commands/release.mds +12 -12
  126. package/src/assets/commands/research.mds +19 -19
  127. package/src/assets/commands/resolve.mds +56 -50
  128. package/src/assets/commands/self-review.mds +20 -20
  129. package/src/assets/mds/git/_ops.mds +3 -1
  130. package/src/assets/mds/git/_ops_pr.mds +1 -1
  131. package/src/assets/mds/git/_ops_steps.mds +13 -7
  132. package/src/assets/mds/git/_pr.mds +7 -4
  133. package/src/assets/mds/tracker/_common.mds +23 -12
  134. package/src/assets/mds/tracker/_github.mds +2 -9
  135. package/src/assets/mds/tracker/_jira.mds +2 -15
  136. package/src/assets/mds/tracker/_linear.mds +2 -15
  137. package/src/assets/rules/quality.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +22 -4
  139. package/src/assets/scripts/claude-md-audit.cjs +66 -51
  140. package/src/assets/scripts/hooks/background-memory-update +406 -97
  141. package/src/assets/scripts/hooks/capture-prompt +12 -12
  142. package/src/assets/scripts/hooks/capture-question +11 -11
  143. package/src/assets/scripts/hooks/capture-turn +12 -12
  144. package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
  145. package/src/assets/scripts/hooks/curation-move +293 -0
  146. package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
  147. package/src/assets/scripts/hooks/ensure-proxy +4 -4
  148. package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
  149. package/src/assets/scripts/hooks/git-marker +81 -0
  150. package/src/assets/scripts/hooks/json-helper.cjs +145 -51
  151. package/src/assets/scripts/hooks/json-parse +19 -4
  152. package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
  153. package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +242 -118
  154. package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
  155. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
  156. package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
  157. package/src/assets/scripts/hooks/lib/render-decisions.cjs +34 -22
  158. package/src/assets/scripts/hooks/lib/tracked-areas.cjs +259 -0
  159. package/src/assets/scripts/hooks/memory-permission +358 -0
  160. package/src/assets/scripts/hooks/memory-worker +1 -1
  161. package/src/assets/scripts/hooks/pre-compact-memory +6 -16
  162. package/src/assets/scripts/hooks/queue-append +228 -103
  163. package/src/assets/scripts/hooks/resolve-project-root +2 -2
  164. package/src/assets/scripts/hooks/session-start-context +145 -71
  165. package/src/assets/scripts/hooks/session-start-memory +27 -46
  166. package/src/assets/scripts/lib/project-config.cjs +31 -9
  167. package/src/assets/scripts/resolve-base.cjs +1043 -0
  168. package/src/assets/scripts/resolve-settings.cjs +238 -46
  169. package/src/assets/skills/agent-references/SKILL.md +9 -2
  170. package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
  171. package/src/assets/skills/agent-references/references/commands/implement-levels.md +27 -0
  172. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
  173. package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
  174. package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
  175. package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
  176. package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
  177. package/src/assets/skills/apply-decisions/SKILL.md +1 -1
  178. package/src/assets/skills/docs-framework/SKILL.md +7 -7
  179. package/src/assets/skills/docs-framework/references/patterns.md +3 -4
  180. package/src/assets/skills/quality-gates/SKILL.md +1 -1
  181. package/src/assets/skills/worktree-support/references/roots.md +2 -2
  182. package/templates/lint-pack/.lint-pack.conf +86 -0
  183. package/templates/lint-pack/README.md +190 -0
  184. package/templates/lint-pack/lint-pack.sh +905 -0
  185. package/dist/core/learning-queue-cleanup.js +0 -27
  186. package/dist/core/learning-store.js +0 -61
  187. /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
  188. /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
  189. /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
  190. /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
  191. /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
  192. /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
  193. /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
  194. /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
  195. /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
  196. /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
  197. /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
@@ -1,26 +1,31 @@
1
1
  import { Command } from 'commander';
2
2
  import { promises as fs } from 'fs';
3
+ import * as os from 'os';
3
4
  import * as path from 'path';
4
5
  import * as p from '@clack/prompts';
5
6
  import color from 'picocolors';
6
- import { getLearningDir, getCurateTuningConfigPath, } from '../../core/project-paths.js';
7
+ import { CURATION_TUNING_FILE_NAME, LEGACY_NAMES, getCurationDir, getGlobalCurateTuningConfigPath, } from '../../core/project-paths.js';
7
8
  import { loadShippedAgentDefaults } from '../../core/agent-models.js';
8
9
  import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
9
10
  import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
10
11
  import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
11
- import { applyLearningToggle, describeLearningConverge, readRunningVersion, } from '../../targets/claude-code/learning-install.js';
12
+ import { applyCurationToggle, describeCurationConverge, installedScriptsSkew, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
12
13
  import { getLedgerRoot } from '../../core/ledger-root.js';
13
- import { drainLearningQueue } from '../../core/learning-queue-cleanup.js';
14
+ import { drainCurationQueue, drainDisabledCurationQueue, formatCurationNotDrained } from '../../core/curation-queue-cleanup.js';
14
15
  import { firstSymbolicLink } from '../../core/linked-path.js';
15
- import { formatRefusedDrain } from '../../core/queue-drain.js';
16
- import { formatLearningStoreUnavailable, loadLearningStore, } from '../../core/learning-store.js';
16
+ import { describeMoveText, describeOutcome } from './curation-move-step.js';
17
+ import { describeMoveNotice, gatherMoveNotice } from '../../core/curation-move-notice.js';
18
+ import { isMoveSkipReason } from '../../core/curation-move-skip.js';
19
+ import { runSharedMove } from './curation-move-io.js';
20
+ import { formatCurationStoreUnavailable, loadCurationStore, } from '../../core/curation-store.js';
21
+ import { loadCurationMove } from '../../core/curation-move.js';
17
22
  // ---------------------------------------------------------------------------
18
23
  // Shared helpers
19
24
  // ---------------------------------------------------------------------------
20
- /** What the read and restore commands say about a project with no `.devflow/learning/`. */
21
- const NO_LEARNING_DATA = 'No learning data in this project yet.';
25
+ /** What the read and restore commands say about a project with no `.devflow/curation/`. */
26
+ const NO_CURATION_DATA = 'No curation data in this project yet.';
22
27
  /**
23
- * How long a writer here (--restore, --clear, --reset) waits for the learning lock
28
+ * How long a writer here (--restore, --clear, --reset) waits for the curation lock
24
29
  * before it refuses as busy. An op holds the lock for milliseconds, so a longer
25
30
  * wait means a stuck holder, and the store's own 30 s wait would leave the command
26
31
  * looking hung.
@@ -58,22 +63,22 @@ function counted(count, noun) {
58
63
  return `${count} ${noun}${count === 1 ? '' : 's'}`;
59
64
  }
60
65
  /**
61
- * The learning store (D-LEARNING-STORE-SEAM), or null after reporting why it
66
+ * The curation store (D-LEARNING-STORE-SEAM), or null after reporting why it
62
67
  * cannot be used and setting exit code 1.
63
68
  */
64
69
  function requireStore() {
65
- const loaded = loadLearningStore();
70
+ const loaded = loadCurationStore();
66
71
  if (loaded.ok)
67
72
  return loaded.value;
68
- p.log.error(`Learning: ${formatLearningStoreUnavailable(loaded.error)}`);
73
+ p.log.error(`Curation: ${formatCurationStoreUnavailable(loaded.error)}`);
69
74
  process.exitCode = 1;
70
75
  return null;
71
76
  }
72
77
  /** What a writer says when the store refused; `undone` completes "Nothing was …". */
73
78
  function storeRefusal(error, undone) {
74
79
  switch (error.kind) {
75
- case 'no-learning-dir': return NO_LEARNING_DATA;
76
- case 'busy': return `The learning store is busy: another run holds its lock. Nothing was ${undone}; try again in a moment.`;
80
+ case 'no-curation-dir': return NO_CURATION_DATA;
81
+ case 'busy': return `The curation store is busy: another run holds its lock. Nothing was ${undone}; try again in a moment.`;
77
82
  default: return error.message;
78
83
  }
79
84
  }
@@ -104,20 +109,91 @@ async function isDirectory(dir) {
104
109
  throw err;
105
110
  }
106
111
  }
112
+ /**
113
+ * Whether the old ledger folder name is present under `root`'s `.devflow`: a folder, a
114
+ * file or a link, dangling or not. The cheap state check that keeps the store unloaded
115
+ * where there is nothing to move. A probe that fails for another reason reads as
116
+ * present, so the move itself reports the problem.
117
+ */
118
+ async function oldLedgerFolderPresent(root) {
119
+ try {
120
+ await fs.lstat(path.join(root, '.devflow', LEGACY_NAMES.folder));
121
+ return true;
122
+ }
123
+ catch (err) {
124
+ const code = err.code;
125
+ return code !== 'ENOENT' && code !== 'ENOTDIR';
126
+ }
127
+ }
128
+ /** The lines a move that did not complete says: its own, or the store's reason when it could not load. */
129
+ function moveProblemLines(outcome) {
130
+ return outcome.kind === 'unavailable'
131
+ ? [`The curation data was not moved: ${describeMoveText(outcome.message)}.`]
132
+ : describeOutcome(outcome);
133
+ }
134
+ /** The move notice of a read-only command, or null when there is none. The old ledger is never opened. */
135
+ async function moveReadNotice(ledgerRoot) {
136
+ const newFolderPresent = await isDirectory(getCurationDir(ledgerRoot)).catch(() => false);
137
+ const notice = gatherMoveNotice(ledgerRoot, os.homedir());
138
+ if (notice === null)
139
+ return null;
140
+ if (!newFolderPresent)
141
+ return { kind: 'instead', line: describeMoveNotice(notice) };
142
+ return isMoveSkipReason(notice) ? { kind: 'beside', line: describeMoveNotice(notice) } : null;
143
+ }
144
+ /**
145
+ * The gate every writing subcommand passes first (--restore, --clear, --reset and the
146
+ * project scope of --configure). Installed scripts of another version read and create
147
+ * another ledger folder, so they refuse with the skew line. Then, where the old folder
148
+ * is present, the shared move runs with no wait: a busy lock prints a retry message,
149
+ * a queue claim that holds the move prints its own message, a refused or failed move
150
+ * prints its reason, and a skipped move (git tracks files in a data folder, or git did
151
+ * not answer) prints the fixed notice; in each case nothing is written.
152
+ * `undone` completes "Nothing was …". Answers true when the caller may write.
153
+ */
154
+ async function prepareToWrite(ledgerRoot, undone) {
155
+ const skew = await installedScriptsSkew(getDevFlowDirectory());
156
+ if (skew !== null) {
157
+ p.log.error(skew);
158
+ process.exitCode = 1;
159
+ return false;
160
+ }
161
+ if (!(await oldLedgerFolderPresent(ledgerRoot)))
162
+ return true;
163
+ const moved = await runSharedMove(ledgerRoot);
164
+ if (moved.kind === 'moved' || moved.kind === 'unchanged')
165
+ return true;
166
+ p.log.error(moved.kind === 'busy'
167
+ ? (moved.reason === 'claim' ? claimHoldsMoveLine(undone) : movingLine(undone))
168
+ : describeMoveText(`${moveProblemLines(moved).join(' ')} Nothing was ${undone}.`));
169
+ process.exitCode = 1;
170
+ return false;
171
+ }
172
+ /** The line a writer prints when a lock holds the move of the old folder. `undone` completes "Nothing was …". */
173
+ function movingLine(undone) {
174
+ return `Another run is moving this project's curation data. Nothing was ${undone}; try again in a moment.`;
175
+ }
176
+ /**
177
+ * The line a writer prints when a Curate run holds the queue claim, which holds the move:
178
+ * no other run moves anything, and the claim lasts up to 15 minutes. `undone` completes "Nothing was …".
179
+ */
180
+ function claimHoldsMoveLine(undone) {
181
+ return `A Curate run holds this project's curation queue, and the move waits for it. Nothing was ${undone}; try again after the run finishes (up to 15 minutes).`;
182
+ }
107
183
  // ---------------------------------------------------------------------------
108
184
  // Sub-command handlers
109
185
  // ---------------------------------------------------------------------------
110
186
  function printUsage() {
111
- p.intro(color.bgCyan(color.black(' Learning ')));
112
- p.note(`${color.cyan('devflow learning --enable')} Enable learning in every project (a repository can opt out)\n` +
113
- `${color.cyan('devflow learning --disable')} Disable learning in every project (drains this project's queue)\n` +
114
- `${color.cyan('devflow learning --status')} Show learning status and entry counts\n` +
115
- `${color.cyan('devflow learning --list')} List entries, inactive entries and observations\n` +
116
- `${color.cyan('devflow learning --show <id>')} Print one entry or observation as JSON\n` +
117
- `${color.cyan('devflow learning --restore <id>')} Make an inactive entry active again\n` +
118
- `${color.cyan('devflow learning --configure')} Configuration wizard\n` +
119
- `${color.cyan('devflow learning --clear')} Drop the observations no entry uses, and drain the queue\n` +
120
- `${color.cyan('devflow learning --reset')} Remove all learning state files`, 'Usage');
187
+ p.intro(color.bgCyan(color.black(' Curation ')));
188
+ p.note(`${color.cyan('devflow curation --enable')} Enable curation in every project (a repository can opt out)\n` +
189
+ `${color.cyan('devflow curation --disable')} Disable curation in every project (drains this project's queue)\n` +
190
+ `${color.cyan('devflow curation --status')} Show curation status and entry counts\n` +
191
+ `${color.cyan('devflow curation --list')} List entries, inactive entries and observations\n` +
192
+ `${color.cyan('devflow curation --show <id>')} Print one entry or observation as JSON\n` +
193
+ `${color.cyan('devflow curation --restore <id>')} Make an inactive entry active again\n` +
194
+ `${color.cyan('devflow curation --configure')} Configuration wizard\n` +
195
+ `${color.cyan('devflow curation --clear')} Drop the observations no entry uses, and drain the queue\n` +
196
+ `${color.cyan('devflow curation --reset')} Remove all curation state files`, 'Usage');
121
197
  p.outro(color.dim('Detects architectural decisions and known pitfalls from your sessions'));
122
198
  }
123
199
  /**
@@ -157,18 +233,50 @@ function entryCountLines(listing, logRowCount, inactiveStatuses) {
157
233
  `Observations: ${logRowCount} in the log, ${listing.observations.length} not yet promoted`,
158
234
  ];
159
235
  }
236
+ /**
237
+ * The `--status` backup lines: one per backup folder a move left under `.devflow`, with
238
+ * the rows its unswept queue files still hold, one more line for the folders past
239
+ * the listing limit (up to the 500th, which are read), and one for those past the 500th
240
+ * (not read, so their rows are not counted). A backup is inert, so the count tells the
241
+ * user when deleting it loses nothing. A count that fails is a warning, not an error.
242
+ */
243
+ function backupLines(ledgerRoot) {
244
+ const mover = loadCurationMove();
245
+ if (!mover.ok)
246
+ return { lines: [], warning: `Backup rows were not counted: ${formatCurationStoreUnavailable(mover.error)}` };
247
+ const rows = mover.value.backupPendingRows(ledgerRoot);
248
+ if (!rows.ok)
249
+ return { lines: [], warning: `Backup rows were not counted: ${describeMoveText(rows.error.message)}` };
250
+ const lines = rows.value.backups.map(({ name, pendingRows }) => `Backup: ${describeMoveText(path.join(ledgerRoot, '.devflow', name))}: ${counted(pendingRows, 'pending row')} in the backup`);
251
+ const beyond = rows.value.beyondLimit;
252
+ if (beyond.backups > 0) {
253
+ const one = beyond.backups === 1;
254
+ lines.push(`Backup: ${counted(beyond.backups, 'more backup folder')} ${one ? 'is' : 'are'} not listed above: `
255
+ + `${counted(beyond.pendingRows, 'pending row')} in ${one ? 'it' : 'them'}`);
256
+ }
257
+ if (beyond.unread > 0) {
258
+ const one = beyond.unread === 1;
259
+ lines.push(`Backup: ${counted(beyond.unread, 'more backup folder')} past the first 500 ${one ? 'is' : 'are'} not read: `
260
+ + `${one ? 'its' : 'their'} pending rows are not counted`);
261
+ }
262
+ return { lines, warning: null };
263
+ }
160
264
  /**
161
265
  * `--status`: the machine switch, then the counts the store reads. Reads only:
162
- * malformed lines are counted, never quarantined, and no scope is checked.
266
+ * malformed lines are counted, never quarantined, and no scope is checked. A project
267
+ * whose ledger still sits in the old folder gets the HUD's pointer (or refused line, or
268
+ * the notice of a skipped move) in place of the counts; a project with both folders and
269
+ * a skipped move gets the notice with the counts; a project with backup folders gets one
270
+ * line for each.
163
271
  */
164
272
  async function handleStatus() {
165
273
  // D-FEATURES-NARROW-ONLY: the machine switch is the manifest's and reads the
166
274
  // same from every directory; a repository layer can only narrow it, and adds a
167
275
  // line only when it does. The entry counts are per-project.
168
- const enabled = await readMachineFeature(getDevFlowDirectory(), 'learning');
276
+ const enabled = await readMachineFeature(getDevFlowDirectory(), 'curation');
169
277
  const settingsModule = loadSettingsModule();
170
- const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'learning') : null;
171
- const stateLine = `Learning: ${enabled ? 'enabled' : 'disabled'}`
278
+ const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'curation') : null;
279
+ const stateLine = `Curation: ${enabled ? 'enabled' : 'disabled'}`
172
280
  + (narrowed === null ? '' : `\nEffective here: ${narrowed}`);
173
281
  const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
174
282
  if (trackedWarning !== null)
@@ -178,15 +286,24 @@ async function handleStatus() {
178
286
  p.log.info(`${stateLine}\nEntries: not in a git project`);
179
287
  return;
180
288
  }
181
- const loaded = loadLearningStore();
289
+ const notice = await moveReadNotice(ledgerRoot);
290
+ if (notice?.kind === 'instead') {
291
+ p.log.info(`${stateLine}\n${notice.line}`);
292
+ return;
293
+ }
294
+ const head = notice === null ? stateLine : `${stateLine}\n${notice.line}`;
295
+ const loaded = loadCurationStore();
182
296
  if (!loaded.ok) {
183
- p.log.info(`${stateLine}\nEntries: unavailable (${formatLearningStoreUnavailable(loaded.error)})`);
297
+ p.log.info(`${head}\nEntries: unavailable (${formatCurationStoreUnavailable(loaded.error)})`);
184
298
  return;
185
299
  }
186
300
  const store = loaded.value;
187
- const { ledgerRows, logRows, rejected } = store.readLearningState(ledgerRoot);
301
+ const { ledgerRows, logRows, rejected } = store.readCurationState(ledgerRoot);
188
302
  const listing = store.buildListing(ledgerRows, logRows, { rejected });
189
- p.log.info([stateLine, ...entryCountLines(listing, logRows.length, store.INACTIVE_STATUSES)].join('\n'));
303
+ const backups = backupLines(ledgerRoot);
304
+ p.log.info([head, ...entryCountLines(listing, logRows.length, store.INACTIVE_STATUSES), ...backups.lines].join('\n'));
305
+ if (backups.warning !== null)
306
+ p.log.warn(backups.warning);
190
307
  const { ledger, log } = listing.malformed;
191
308
  if (ledger + log > 0) {
192
309
  p.log.warn(`Malformed lines skipped: ${ledger} in the ledger, ${log} in the log. ` +
@@ -195,17 +312,25 @@ async function handleStatus() {
195
312
  }
196
313
  /**
197
314
  * `--list`: the store's listing — the same text json-helper's `list` op prints —
198
- * on stdout. Read-only. Outside a git project it reads the current directory.
315
+ * on stdout. Read-only. Outside a git project it reads the current directory. Where
316
+ * the ledger still sits in the old folder it prints the pointer, the refused line or the
317
+ * notice of a skipped move in place of the listing; with both folders and a skipped move
318
+ * it prints the notice before the listing.
199
319
  */
200
320
  async function handleList() {
201
321
  const root = (await getLedgerRoot()) ?? process.cwd();
322
+ const notice = await moveReadNotice(root);
323
+ if (notice !== null)
324
+ p.log.info(notice.line);
325
+ if (notice?.kind === 'instead')
326
+ return;
202
327
  const store = requireStore();
203
328
  if (!store)
204
329
  return;
205
330
  const listed = store.readListing(root);
206
331
  if (!listed.ok) {
207
- if (listed.error.kind === 'no-learning-dir') {
208
- p.log.info(NO_LEARNING_DATA);
332
+ if (listed.error.kind === 'no-curation-dir') {
333
+ p.log.info(NO_CURATION_DATA);
209
334
  return;
210
335
  }
211
336
  p.log.error(listed.error.message);
@@ -217,16 +342,27 @@ async function handleList() {
217
342
  /**
218
343
  * `--show <id>`: one entry, by its anchor or its observation id, as the JSON
219
344
  * json-helper's `show` op prints, made terminal-safe. Read-only. Outside a git
220
- * project it reads the current directory.
345
+ * project it reads the current directory. Where the ledger still sits in the old
346
+ * folder it prints the pointer, the refused line or the notice of a skipped move as the
347
+ * refusal; with both folders and a skipped move it prints the notice on stderr and goes on.
221
348
  */
222
349
  async function handleShow(key) {
223
350
  const root = (await getLedgerRoot()) ?? process.cwd();
351
+ const notice = await moveReadNotice(root);
352
+ if (notice?.kind === 'instead') {
353
+ p.log.error(notice.line);
354
+ process.exitCode = 1;
355
+ return;
356
+ }
357
+ // The JSON goes to stdout, so the notice of a skip goes to stderr.
358
+ if (notice !== null)
359
+ process.stderr.write(`${notice.line}\n`);
224
360
  const store = requireStore();
225
361
  if (!store)
226
362
  return;
227
363
  const shown = store.showByKey(root, key);
228
364
  if (!shown.ok) {
229
- p.log.error(shown.error.kind === 'no-learning-dir' ? NO_LEARNING_DATA : shown.error.message);
365
+ p.log.error(shown.error.kind === 'no-curation-dir' ? NO_CURATION_DATA : shown.error.message);
230
366
  process.exitCode = 1;
231
367
  return;
232
368
  }
@@ -235,7 +371,8 @@ async function handleShow(key) {
235
371
  /**
236
372
  * `--restore <id>`: make an inactive entry active again through the store's
237
373
  * restoreAnchor, which clears its notes and its verification so maintenance
238
- * reviews it again, and re-renders the files. A refusal writes nothing.
374
+ * reviews it again, and re-renders the files. It writes through the store lock, so
375
+ * it moves the old folder first (prepareToWrite). A refusal writes nothing.
239
376
  */
240
377
  async function handleRestore(anchor) {
241
378
  const ledgerRoot = await requireLedgerRoot('restore not performed');
@@ -251,6 +388,8 @@ async function handleRestore(anchor) {
251
388
  process.exitCode = 1;
252
389
  return;
253
390
  }
391
+ if (!(await prepareToWrite(ledgerRoot, 'restored')))
392
+ return;
254
393
  const restored = store.restoreAnchor(ledgerRoot, anchor, { timeoutMs: LOCK_WAIT_MS });
255
394
  if (!restored.ok) {
256
395
  p.log.error(storeRefusal(restored.error, 'restored'));
@@ -260,7 +399,7 @@ async function handleRestore(anchor) {
260
399
  p.log.success(`Restored ${restored.value.anchor_id} (${restored.value.status}); it is due for review again.`);
261
400
  }
262
401
  /**
263
- * The scope choices of `devflow learning --configure`.
402
+ * The scope choices of `devflow curation --configure`.
264
403
  *
265
404
  * D-LEARNING-MODEL-PRECEDENCE: the session-start hook takes the first layer that
266
405
  * supplies a model — the project file, then a `devflow agents` Curate mapping,
@@ -268,14 +407,14 @@ async function handleRestore(anchor) {
268
407
  * such a mapping, and its hint says so.
269
408
  */
270
409
  export const CONFIGURE_SCOPE_OPTIONS = [
271
- { value: 'project', label: 'Project', hint: 'This project only (.devflow/learning/learning.json)' },
410
+ { value: 'project', label: 'Project', hint: 'This project only (.devflow/curation/curation.json)' },
272
411
  {
273
412
  value: 'global',
274
413
  label: 'Global',
275
- hint: 'All projects (~/.devflow/learning.json); a devflow agents Curate mapping takes precedence over it',
414
+ hint: 'All projects (~/.devflow/curation.json); a devflow agents Curate mapping takes precedence over it',
276
415
  },
277
416
  ];
278
- /** The models `devflow learning --configure` offers, each with what it is good for. */
417
+ /** The models `devflow curation --configure` offers, each with what it is good for. */
279
418
  const CURATE_MODEL_CHOICES = [
280
419
  { value: 'opus', label: 'Opus', note: 'highest quality for detection + curation judgment' },
281
420
  { value: 'sonnet', label: 'Sonnet', note: 'good balance of quality and speed' },
@@ -298,7 +437,7 @@ export function curateModelOptions(shippedModel) {
298
437
  }));
299
438
  }
300
439
  async function handleConfigure() {
301
- p.intro(color.bgCyan(color.black(' Learning Configuration ')));
440
+ p.intro(color.bgCyan(color.black(' Curation Configuration ')));
302
441
  const shippedModel = (await loadShippedAgentDefaults()).curate?.model;
303
442
  const model = await p.select({
304
443
  message: 'Model for decision detection',
@@ -332,36 +471,52 @@ async function handleConfigure() {
332
471
  if (scope === 'global') {
333
472
  const globalDir = getDevFlowDirectory();
334
473
  await fs.mkdir(globalDir, { recursive: true });
335
- await fs.writeFile(path.join(globalDir, 'learning.json'), configJson, 'utf-8');
336
- p.log.success(`Global config written to ${color.dim(path.join(globalDir, 'learning.json'))}`);
474
+ const globalConfigPath = getGlobalCurateTuningConfigPath(globalDir);
475
+ await fs.writeFile(globalConfigPath, configJson, 'utf-8');
476
+ p.log.success(`Global config written to ${color.dim(globalConfigPath)}`);
337
477
  }
338
478
  else {
339
479
  // D-LEDGER-MAIN-WORKTREE: session-start-context reads the project tuning config
340
- // from the ledger ($LEDGER_ROOT/.devflow/learning/), so write it there — the
480
+ // from the ledger ($LEDGER_ROOT/.devflow/curation/), so write it there — the
341
481
  // main checkout in a linked worktree; the current directory outside git.
342
482
  const projectRoot = (await getLedgerRoot()) ?? process.cwd();
343
- const learningDir = getLearningDir(projectRoot);
344
- const projectConfigPath = getCurateTuningConfigPath(projectRoot);
483
+ // Skew or a move that cannot run (busy, refused, failed) writes nothing, so the
484
+ // config never lands in a folder the readers do not use.
485
+ if (!(await prepareToWrite(projectRoot, 'written')))
486
+ return;
487
+ // The creator rule: this writer makes the new folder only when the old name is
488
+ // absent, so it looks once more. A folder of the old name can stand again after the
489
+ // move, and the new folder made beside it would hide its ledger from every reader.
490
+ if (await oldLedgerFolderPresent(projectRoot)) {
491
+ p.log.error(movingLine('written'));
492
+ process.exitCode = 1;
493
+ return;
494
+ }
495
+ // The folder that is checked is the folder that is written.
496
+ const curationDir = getCurationDir(projectRoot);
497
+ const projectConfigPath = path.join(curationDir, CURATION_TUNING_FILE_NAME);
345
498
  // D-CLI-NO-SYMLINK (core/linked-path.ts): nothing is written through a .devflow,
346
- // a learning folder or a learning.json that is a symbolic link.
347
- const linked = await firstSymbolicLink([path.dirname(learningDir), learningDir, projectConfigPath]);
499
+ // a curation folder or a curation.json that is a symbolic link.
500
+ const linked = await firstSymbolicLink([path.dirname(curationDir), curationDir, projectConfigPath]);
348
501
  if (linked !== null) {
349
502
  p.log.error(`Project config not written: ${linked} is a symbolic link, and devflow writes nothing through one`);
350
503
  process.exitCode = 1;
351
504
  return;
352
505
  }
353
- await fs.mkdir(learningDir, { recursive: true });
506
+ await fs.mkdir(curationDir, { recursive: true });
354
507
  await fs.writeFile(projectConfigPath, configJson, 'utf-8');
355
508
  p.log.success(`Project config written to ${color.dim(projectConfigPath)}`);
356
509
  }
357
510
  p.outro(color.green('Configuration saved.'));
358
511
  }
359
512
  /**
360
- * `--reset`: remove all learning state — entries, observations, rendered files,
513
+ * `--reset`: remove all curation state — entries, observations, rendered files,
361
514
  * the tuning config, and the queue with its claim — through the store's
362
- * resetLearning, under the learning lock every learning writer takes
363
- * (D-ONE-LEARNING-LOCK, D-RESET-UNDER-LOCK). A project with no learning directory
364
- * has nothing to reset, and nothing is created there (D-NO-STRAY-TREE).
515
+ * resetCuration, under the curation lock every curation writer takes
516
+ * (D-ONE-LEARNING-LOCK, D-RESET-UNDER-LOCK). The old folder moves first
517
+ * (prepareToWrite), so the reset reaches the data the hooks use; a backup folder a
518
+ * move left stays in place. A project with no curation directory has nothing to
519
+ * reset, and nothing is created there (D-NO-STRAY-TREE).
365
520
  */
366
521
  async function handleReset() {
367
522
  const ledgerRoot = await requireLedgerRoot('reset not performed');
@@ -370,30 +525,36 @@ async function handleReset() {
370
525
  const store = requireStore();
371
526
  if (!store)
372
527
  return;
373
- if (!(await isDirectory(getLearningDir(ledgerRoot)))) {
374
- p.log.info('No learning data to reset.');
528
+ if (!(await prepareToWrite(ledgerRoot, 'reset')))
529
+ return;
530
+ if (!(await isDirectory(getCurationDir(ledgerRoot)))) {
531
+ p.log.info('No curation data to reset.');
375
532
  return;
376
533
  }
377
- const proceed = await confirmOnTerminal('Remove all learning state files? This cannot be undone.', 'Reset cancelled.');
534
+ const proceed = await confirmOnTerminal('Remove all curation state files? This cannot be undone.', 'Reset cancelled.');
378
535
  if (!proceed)
379
536
  return;
380
- const reset = store.resetLearning(ledgerRoot, { timeoutMs: LOCK_WAIT_MS });
537
+ const reset = store.resetCuration(ledgerRoot, { timeoutMs: LOCK_WAIT_MS });
381
538
  if (!reset.ok) {
382
- if (reset.error.kind === 'no-learning-dir') {
383
- p.log.info('No learning data to reset.');
539
+ if (reset.error.kind === 'no-curation-dir') {
540
+ p.log.info('No curation data to reset.');
384
541
  return;
385
542
  }
386
543
  p.log.error(storeRefusal(reset.error, 'reset'));
387
544
  process.exitCode = 1;
388
545
  return;
389
546
  }
390
- p.log.success('Reset complete — removed .devflow/learning/ state.');
547
+ p.log.success('Reset complete — removed .devflow/curation/ state.');
391
548
  }
392
549
  /**
393
550
  * `--clear`: drop the observations no entry uses (D-CLEAR-UNREFERENCED), then
394
- * drain the learning queue. The queue is drained only once the clear succeeded:
551
+ * drain the curation queue. The queue is drained only once the clear succeeded:
395
552
  * a busy lock or a refusal leaves both the log and the queue as they were, so a
396
- * failed clear changes nothing.
553
+ * failed clear changes nothing. The old folder moves first (prepareToWrite), so the
554
+ * clear and the drain reach the data the hooks use; a backup folder a move left
555
+ * stays in place. When git tracks any entry under a data folder or a backup folder, or
556
+ * does not answer, the drain deletes no queue file and a warning carries the fixed
557
+ * notice.
397
558
  */
398
559
  async function handleClear() {
399
560
  const ledgerRoot = await requireLedgerRoot('clear not performed');
@@ -402,8 +563,10 @@ async function handleClear() {
402
563
  const store = requireStore();
403
564
  if (!store)
404
565
  return;
405
- if (!(await isDirectory(getLearningDir(ledgerRoot)))) {
406
- p.log.info('No learning data to clear.');
566
+ if (!(await prepareToWrite(ledgerRoot, 'cleared')))
567
+ return;
568
+ if (!(await isDirectory(getCurationDir(ledgerRoot)))) {
569
+ p.log.info('No curation data to clear.');
407
570
  return;
408
571
  }
409
572
  const proceed = await confirmOnTerminal('Drop every observation no entry uses? Entries and the observations they use are kept. This cannot be undone.', 'Clear cancelled.');
@@ -411,8 +574,8 @@ async function handleClear() {
411
574
  return;
412
575
  const cleared = store.clearUnreferenced(ledgerRoot, { timeoutMs: LOCK_WAIT_MS });
413
576
  if (!cleared.ok) {
414
- if (cleared.error.kind === 'no-learning-dir') {
415
- p.log.info('No learning data to clear.');
577
+ if (cleared.error.kind === 'no-curation-dir') {
578
+ p.log.info('No curation data to clear.');
416
579
  return;
417
580
  }
418
581
  p.log.error(storeRefusal(cleared.error, 'cleared'));
@@ -421,15 +584,15 @@ async function handleClear() {
421
584
  }
422
585
  // A mid-run Curate agent whose claimed batch vanishes stops without further
423
586
  // writes — the desired outcome of clearing.
424
- const drain = await drainLearningQueue(ledgerRoot);
587
+ const drain = await drainCurationQueue(ledgerRoot);
425
588
  p.log.success(`Cleared ${counted(cleared.value.cleared, 'observation')} no entry uses and kept ${cleared.value.kept} ` +
426
- `that entries use${drain.drained ? '; drained the learning queue.' : '.'}`);
589
+ `that entries use${drain.drained ? '; drained the curation queue.' : '.'}`);
427
590
  if (!drain.drained)
428
- p.log.warn(formatRefusedDrain('learning', drain.linkedFolder));
591
+ p.log.warn(formatCurationNotDrained(drain));
429
592
  }
430
593
  /**
431
594
  * Make the installed prompts match the switch just written
432
- * (D-LEARNING-VARIANT-INSTALL): the learning-on or learning-off commands and
595
+ * (D-LEARNING-VARIANT-INSTALL): the curation-on or curation-off commands and
433
596
  * agents, and the apply-decisions skill. The same convergence `devflow init` runs,
434
597
  * followed by reapplying the saved agent model mapping so `devflow agents`
435
598
  * overrides survive.
@@ -437,70 +600,110 @@ async function handleClear() {
437
600
  * Warn-not-fail: the switch itself is already recorded, and either mismatch is
438
601
  * safe (an on variant is still gated at run time, an off variant loads no
439
602
  * decisions), so a problem here never changes the exit code.
603
+ *
604
+ * Answers whether the converge ran, which means the installed files are the ones
605
+ * this CLI writes. The toggle moves the ledger folder only then: under version skew
606
+ * (or when that could not be told) it moves nothing.
440
607
  */
441
608
  async function convergeInstalledVariants(enabled) {
442
609
  try {
443
- const outcome = await applyLearningToggle({
610
+ const outcome = await applyCurationToggle({
444
611
  claudeDir: getClaudeDirectory(),
445
612
  devflowDir: getDevFlowDirectory(),
446
- learning: enabled,
613
+ curation: enabled,
447
614
  runningVersion: await readRunningVersion(),
448
615
  warn: (msg) => p.log.warn(msg),
449
616
  });
450
617
  if (outcome.kind === 'skipped') {
451
618
  p.log.warn(outcome.message);
452
- return;
619
+ return 'not-converged';
453
620
  }
454
- const changed = describeLearningConverge(outcome.result, enabled);
621
+ const changed = describeCurationConverge(outcome.result, enabled);
455
622
  if (changed !== null)
456
623
  p.log.info(color.dim(`Installed prompts: ${changed}`));
624
+ return 'converged';
457
625
  }
458
626
  catch (err) {
459
- p.log.warn(`Could not update the installed prompts for the new learning setting — run ${color.cyan('devflow init')}: ${String(err)}`);
627
+ p.log.warn(`Could not update the installed prompts for the new curation setting — run ${color.cyan('devflow init')}: ${String(err)}`);
628
+ return 'not-converged';
460
629
  }
461
630
  }
631
+ /**
632
+ * The toggle's move of the current project's ledger folder: tried once with no wait,
633
+ * and only where the old folder is present. A busy move is not an error — the project
634
+ * moves at its next event — and a refusal, a failure or a skip (git tracks files in a
635
+ * data folder, or git did not answer) is a warning: the switch is already recorded, so
636
+ * none of them changes the exit code. Answers the skip reason whose fixed notice it
637
+ * printed, or null when it printed none.
638
+ */
639
+ async function tryMoveForToggle(ledgerRoot) {
640
+ if (!(await oldLedgerFolderPresent(ledgerRoot)))
641
+ return null;
642
+ const outcome = await runSharedMove(ledgerRoot);
643
+ if (outcome.kind === 'moved' || outcome.kind === 'unchanged')
644
+ return null;
645
+ for (const line of moveProblemLines(outcome)) {
646
+ if (outcome.kind === 'busy')
647
+ p.log.info(color.dim(describeMoveText(line)));
648
+ else
649
+ p.log.warn(describeMoveText(line));
650
+ }
651
+ return outcome.kind === 'left-alone' ? outcome.reason : null;
652
+ }
462
653
  /**
463
654
  * `--enable` / `--disable`: the machine-wide switch (D-FEATURES-NARROW-ONLY),
464
- * converged exactly as `devflow init --learning / --no-learning` converges it —
465
- * the manifest value, the installed prompt variants and skill
466
- * (D-LEARNING-VARIANT-INSTALL), and on disable a drained queue in the current
467
- * project. Never requires a git root: the switch is not a per-project setting.
655
+ * converged exactly as `devflow init --curation / --no-curation` converges it —
656
+ * the manifest value (both machine keys, written first), the installed prompt
657
+ * variants and skill (D-LEARNING-VARIANT-INSTALL), the current project's ledger
658
+ * folder move, and on disable a drained queue in every ledger folder that exists and
659
+ * in the backups a move left. The drain deletes no queue file, and prints the fixed
660
+ * notice of a skipped move once, when git tracks any entry under a data folder or a
661
+ * backup folder, or does not answer.
662
+ * Never requires a git root: the switch is not a per-project setting.
663
+ *
664
+ * Under version skew the toggle records the switch and moves nothing; otherwise it
665
+ * tries the shared move with no wait. Only `--disable` drains, skew included.
468
666
  */
469
667
  async function handleToggle(enabled) {
470
- const recorded = await writeMachineFeature(getDevFlowDirectory(), 'learning', enabled);
668
+ const recorded = await writeMachineFeature(getDevFlowDirectory(), 'curation', enabled);
471
669
  if (!recorded.ok) {
472
670
  p.log.error(`Devflow is not installed on this machine — run ${color.cyan('devflow init')} first`);
473
671
  process.exitCode = 1;
474
672
  return;
475
673
  }
476
- await convergeInstalledVariants(enabled);
674
+ const converged = await convergeInstalledVariants(enabled);
675
+ const ledgerRoot = await getLedgerRoot();
676
+ const shownSkip = converged === 'converged' && ledgerRoot ? await tryMoveForToggle(ledgerRoot) : null;
477
677
  if (enabled) {
478
- p.log.success('Learning enabled in every project (a repository can opt out)');
678
+ p.log.success('Curation enabled in every project (a repository can opt out)');
479
679
  p.log.info(color.dim('Architectural decisions and pitfalls will be detected from your sessions'));
480
680
  return;
481
681
  }
482
- // Drain the current project's learning (decisions-detection) queue so stale
483
- // turns don't process on re-enable. A mid-run Curate agent whose claimed
484
- // batch vanishes aborts without changes — the desired outcome of disabling.
485
- const ledgerRoot = await getLedgerRoot();
682
+ // Drain the current project's curation (decisions-detection) queue, in each ledger
683
+ // folder that exists and in the backups a move left, so stale turns don't process on
684
+ // re-enable. A mid-run Curate agent whose claimed batch vanishes aborts without
685
+ // changes — the desired outcome of disabling. A project whose data folders git tracks
686
+ // keeps every file: the switch is recorded, and the drain warns instead.
486
687
  if (ledgerRoot) {
487
- const drain = await drainLearningQueue(ledgerRoot);
688
+ const drain = await drainDisabledCurationQueue(ledgerRoot);
488
689
  if (!drain.drained)
489
- p.log.warn(formatRefusedDrain('learning', drain.linkedFolder));
690
+ p.log.warn(formatCurationNotDrained(drain, shownSkip));
691
+ else if (drain.warning !== undefined)
692
+ p.log.warn(drain.warning);
490
693
  }
491
- p.log.success('Learning disabled in every project');
694
+ p.log.success('Curation disabled in every project');
492
695
  }
493
- export const learningCommand = new Command('learning')
494
- .description('Enable or disable learning (decision/pitfall detection) in every project')
495
- .option('--enable', 'Enable learning in every project (a repository can opt out)')
496
- .option('--disable', 'Disable learning in every project')
497
- .option('--status', 'Show learning status and entry counts')
696
+ export const curationCommand = new Command('curation')
697
+ .description('Enable or disable curation (decision/pitfall detection) in every project')
698
+ .option('--enable', 'Enable curation in every project (a repository can opt out)')
699
+ .option('--disable', 'Disable curation in every project')
700
+ .option('--status', 'Show curation status and entry counts')
498
701
  .option('--list', 'List entries, inactive entries with their notes, and observations')
499
702
  .option('--show <id>', 'Print one entry (ADR-NNN or PF-NNN) or observation (obs_...) as JSON')
500
703
  .option('--restore <id>', 'Make an inactive entry active again')
501
- .option('--configure', 'Interactive configuration wizard for learning.json')
502
- .option('--clear', 'Drop the observations no entry uses, and drain the learning queue')
503
- .option('--reset', 'Remove all learning state files and artifacts')
704
+ .option('--configure', 'Interactive configuration wizard for curation.json')
705
+ .option('--clear', 'Drop the observations no entry uses, and drain the curation queue')
706
+ .option('--reset', 'Remove all curation state files and artifacts')
504
707
  .action(async (options) => {
505
708
  const knownFlags = [
506
709
  'enable', 'disable', 'status', 'list', 'show', 'restore', 'configure',
@@ -551,4 +754,4 @@ export const learningCommand = new Command('learning')
551
754
  return;
552
755
  }
553
756
  });
554
- //# sourceMappingURL=learning.js.map
757
+ //# sourceMappingURL=curation.js.map