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
@@ -0,0 +1,374 @@
1
+ /**
2
+ * @file curation-move-step.ts
3
+ *
4
+ * The move of a project's ledger folder to the curation name, as steps over injected
5
+ * I/O: `moveProjectLedgers(opts, io)`, the init step for every project, and
6
+ * `moveLedgerRoot(root, opts, io)`, the one-root move the curation command runs, with
7
+ * the lines each outcome prints (`describeOutcome`). Init and the curation command share
8
+ * it, so neither owns it. It lives in its own module, not in init.ts, so the
9
+ * ordering tests inject their own I/O (the shape of `drainDisabledFeatureQueues`).
10
+ *
11
+ * The step is purity-bounded like init-seed.ts: it imports no `fs`, no `os` and no
12
+ * store. It reaches the machine only through the `io` it is given, so it has no
13
+ * hidden effect. Every answer is a value. Nothing here throws: a store call or a
14
+ * probe that throws becomes an `error` outcome for that project, and the rest go on.
15
+ *
16
+ * The step is state-checked, never run-once. A run-once record would mark a run with
17
+ * no target as done, and later projects would never move. Each init asks the store
18
+ * about each target again, and the store answers `none` where nothing is left to do.
19
+ *
20
+ * Targets: the discovered project roots plus the current repository's own ledger
21
+ * root, each mapped to its ledger root (a linked worktree keeps its ledger in the
22
+ * main checkout) and listed once by physical path. The home directory and the
23
+ * machine directory are never targets, and neither is a project whose git root is
24
+ * null: init writes no repository file at HOME. A project whose git root lookup does
25
+ * not answer in time (a hung mount) is skipped, named in the lines init prints and
26
+ * never counted as moved.
27
+ *
28
+ * Per target the step runs the move, then, when a backup folder exists, the backup
29
+ * sweep. The store takes every lock with no wait, so a busy project is reported and
30
+ * moves at its next event. A per-project outcome never decides init's exit code.
31
+ *
32
+ * Callers: init.ts runs {@link runInitCurationMove} between its legacy sweep and the
33
+ * manifest write, and the curation command runs {@link moveLedgerRoot} for one root
34
+ * (configure and the toggles). The real I/O is built in curation-move-io.ts.
35
+ */
36
+ import * as path from 'path';
37
+ import { pooled } from '../../core/pool.js';
38
+ import { describeEchoedValue } from '../../core/echo-value.js';
39
+ import { MOVE_SKIP_NOTICE } from '../../core/curation-move-skip.js';
40
+ import { CURATION_DIR_NAME, CURATION_TUNING_FILE_NAME, LEGACY_NAMES, getGlobalCurateTuningConfigPath } from '../../core/project-paths.js';
41
+ /** At most this many projects are probed or moved at once (the migration runner's pool). */
42
+ export const MOVE_POOL_LIMIT = 16;
43
+ /** The most code points of one name, message or line the step echoes. A path of 4096 bytes fits. */
44
+ const MAX_MOVE_TEXT = 4096;
45
+ /**
46
+ * A name, a message or a line from a project's repository, made safe to print: control
47
+ * characters (terminal escapes, a newline) become `?`, C1 included, and the text is cut
48
+ * at MAX_MOVE_TEXT. Every echoed root, store message and entry name goes through it.
49
+ */
50
+ export function describeMoveText(raw) {
51
+ return describeEchoedValue(raw, MAX_MOVE_TEXT);
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // The step
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Move every target project's ledger folder to the new name. Answers an error only
58
+ * for options that cannot be right (the two directories must be absolute paths, so a
59
+ * bad call changes nothing); every per-project failure is an outcome in the report.
60
+ */
61
+ export async function moveProjectLedgers(opts, io) {
62
+ const invalid = checkOptions(opts);
63
+ if (invalid !== null)
64
+ return { ok: false, error: { kind: 'invalid-input', message: invalid } };
65
+ const { targets, stalled } = await resolveTargets(opts, io);
66
+ const settled = await pooled([...targets], MOVE_POOL_LIMIT, root => moveOne(root, opts, io));
67
+ const moves = settled.map((entry, index) => entry.status === 'fulfilled'
68
+ ? entry.value
69
+ : { kind: 'error', root: targets[index], message: describeFailure(entry.reason) });
70
+ const skipped = stalled.map((root) => ({ kind: 'skipped', root, reason: 'git-root-timed-out' }));
71
+ const outcomes = [...moves, ...skipped];
72
+ return {
73
+ ok: true,
74
+ value: {
75
+ outcomes,
76
+ movedCount: outcomes.filter(outcome => outcome.kind === 'moved').length,
77
+ lines: outcomes.flatMap(describeOutcome),
78
+ },
79
+ };
80
+ }
81
+ function checkOptions(opts) {
82
+ if (!path.isAbsolute(opts.homeDir) || !path.isAbsolute(opts.machineDir)) {
83
+ return 'the home directory and the machine directory must be absolute paths';
84
+ }
85
+ return null;
86
+ }
87
+ /**
88
+ * The ledger roots init moves, once each. The current repository comes first, then
89
+ * the projects in the order given. A root is listed once by its physical path, and
90
+ * the home directory and the machine directory are dropped by the same test. A project
91
+ * whose lookup timed out has no known ledger root: it is no target, and is listed apart.
92
+ */
93
+ async function resolveTargets(opts, io) {
94
+ const mapped = await pooled([...opts.projectRoots], MOVE_POOL_LIMIT, project => io.ledgerRootOf(project));
95
+ const candidates = opts.ownLedgerRoot === null ? [] : [opts.ownLedgerRoot];
96
+ const stalled = [];
97
+ mapped.forEach((entry, index) => {
98
+ if (entry.status !== 'fulfilled')
99
+ return;
100
+ if (entry.value.kind === 'root')
101
+ candidates.push(entry.value.root);
102
+ else if (entry.value.kind === 'timed-out')
103
+ stalled.push(opts.projectRoots[index]);
104
+ });
105
+ const [homeReal, machineReal] = await Promise.all([physicalOf(opts.homeDir, io), physicalOf(opts.machineDir, io)]);
106
+ const physicals = await pooled(candidates, MOVE_POOL_LIMIT, candidate => physicalOf(candidate, io));
107
+ const seen = new Set([homeReal, machineReal]);
108
+ const targets = [];
109
+ candidates.forEach((candidate, index) => {
110
+ const entry = physicals[index];
111
+ const real = entry.status === 'fulfilled' ? entry.value : candidate;
112
+ if (seen.has(real))
113
+ return;
114
+ seen.add(real);
115
+ targets.push(candidate);
116
+ });
117
+ return { targets, stalled };
118
+ }
119
+ /** The physical path of a path, or the path itself when it cannot be resolved. */
120
+ async function physicalOf(target, io) {
121
+ try {
122
+ return (await io.realpath(target)) ?? target;
123
+ }
124
+ catch {
125
+ return target;
126
+ }
127
+ }
128
+ /**
129
+ * Move one ledger root's folder, then sweep its backups when one exists. The one move
130
+ * that init (per project) and the curation command (configure and the toggles, for
131
+ * the current project) share. Takes every lock with no wait; never throws.
132
+ */
133
+ export async function moveLedgerRoot(root, opts, io) {
134
+ const invalid = checkOptions(opts);
135
+ if (invalid !== null)
136
+ return { kind: 'error', root, message: invalid };
137
+ return moveOne(root, opts, io);
138
+ }
139
+ /** Move one project's ledger folder, then sweep its backups when one exists. */
140
+ async function moveOne(root, opts, io) {
141
+ const storeOpts = () => ({
142
+ machineDir: opts.machineDir,
143
+ homeDir: opts.homeDir,
144
+ now: io.now(),
145
+ });
146
+ let moved;
147
+ try {
148
+ moved = io.store.moveLedger(root, storeOpts());
149
+ }
150
+ catch (error) {
151
+ return { kind: 'error', root, message: describeFailure(error) };
152
+ }
153
+ if (!moved.ok) {
154
+ return moved.error.kind === 'refused'
155
+ ? { kind: 'refused', root, message: moved.error.message }
156
+ : { kind: 'error', root, message: moved.error.message };
157
+ }
158
+ const answer = moved.value;
159
+ if (answer.state === 'busy')
160
+ return answer.reason === 'claim' ? { kind: 'busy', root, reason: 'claim' } : { kind: 'busy', root };
161
+ if (answer.state === 'skipped')
162
+ return { kind: 'left-alone', root, reason: answer.reason };
163
+ const swept = await sweepIfBackup(root, io, storeOpts);
164
+ if (answer.state === 'moved') {
165
+ // A sweep that skips right after a move left the project alone; the next init says why.
166
+ const sweep = swept?.state === 'skipped' ? null : swept;
167
+ return { kind: 'moved', root, mode: answer.mode, backup: answer.backup, rendered: answer.rendered, notes: answer.notes, sweep };
168
+ }
169
+ if (swept?.state === 'skipped')
170
+ return { kind: 'left-alone', root, reason: swept.reason };
171
+ return { kind: 'unchanged', root, sweep: swept };
172
+ }
173
+ /** Run the backup sweep when `.devflow` holds a backup folder; answer null when there is nothing to report. */
174
+ async function sweepIfBackup(root, io, storeOpts) {
175
+ let entries;
176
+ try {
177
+ entries = await io.readdir(path.join(root, '.devflow'));
178
+ }
179
+ catch {
180
+ entries = null;
181
+ }
182
+ if (entries === null || !entries.some(name => name.startsWith(LEGACY_NAMES.backupPrefix)))
183
+ return null;
184
+ let swept;
185
+ try {
186
+ swept = io.store.sweepLedgerBackups(root, storeOpts());
187
+ }
188
+ catch (error) {
189
+ return { state: 'failed', message: describeFailure(error) };
190
+ }
191
+ if (!swept.ok)
192
+ return { state: 'failed', message: swept.error.message };
193
+ return reportSweep(swept.value);
194
+ }
195
+ function reportSweep(swept) {
196
+ switch (swept.state) {
197
+ case 'none':
198
+ return null;
199
+ case 'busy':
200
+ return { state: 'busy' };
201
+ case 'skipped':
202
+ return { state: 'skipped', reason: swept.reason };
203
+ case 'swept':
204
+ return { state: 'swept', files: swept.files, merged: swept.merged, overflowed: swept.overflowed, notes: swept.notes };
205
+ default: {
206
+ const unreachable = swept;
207
+ return unreachable;
208
+ }
209
+ }
210
+ }
211
+ export function describeFailure(error) {
212
+ return error instanceof Error ? error.message : String(error);
213
+ }
214
+ // ---------------------------------------------------------------------------
215
+ // The lines init prints
216
+ // ---------------------------------------------------------------------------
217
+ /** The lines for one project: none when it moved cleanly or had nothing to move. */
218
+ export function describeOutcome(outcome) {
219
+ const lines = [];
220
+ switch (outcome.kind) {
221
+ case 'busy':
222
+ lines.push(outcome.reason === 'claim'
223
+ ? `Ledger move skipped for ${describeMoveText(outcome.root)}: a queue claim holds the move. The project moves after Curate finishes.`
224
+ : `Ledger move skipped for ${describeMoveText(outcome.root)}: a lock is held. The project moves at its next session.`);
225
+ break;
226
+ case 'refused':
227
+ lines.push(`Ledger move refused for ${describeMoveText(outcome.root)}: ${describeMoveText(outcome.message)}. Nothing was changed.`);
228
+ break;
229
+ case 'error':
230
+ lines.push(`Ledger move failed for ${describeMoveText(outcome.root)}: ${describeMoveText(outcome.message)}. ` +
231
+ 'Nothing was copied. The next init tries again.');
232
+ break;
233
+ case 'skipped':
234
+ lines.push(`Ledger move skipped for ${describeMoveText(outcome.root)}: git did not answer in time. ` +
235
+ 'Nothing was moved. The next init or session start tries again.');
236
+ break;
237
+ case 'left-alone':
238
+ lines.push(`Ledger move skipped for ${describeMoveText(outcome.root)}: ${MOVE_SKIP_NOTICE[outcome.reason]}`);
239
+ break;
240
+ case 'moved':
241
+ if (!outcome.rendered) {
242
+ lines.push(`Ledger moved for ${describeMoveText(outcome.root)}, but the index was not rebuilt. The next ledger write rebuilds it.`);
243
+ }
244
+ break;
245
+ case 'unchanged':
246
+ break;
247
+ default: {
248
+ const unreachable = outcome;
249
+ return unreachable;
250
+ }
251
+ }
252
+ if (outcome.kind === 'moved' || outcome.kind === 'unchanged') {
253
+ const sweep = outcome.sweep;
254
+ if (sweep?.state === 'failed') {
255
+ lines.push(`Backup merge stopped for ${describeMoveText(outcome.root)}: ${describeMoveText(sweep.message)}. The next init finishes it.`);
256
+ }
257
+ else if (sweep?.state === 'busy') {
258
+ lines.push(`Backup merge skipped for ${describeMoveText(outcome.root)}: a lock is held. The next init tries again.`);
259
+ }
260
+ }
261
+ return lines;
262
+ }
263
+ /**
264
+ * Rename the global curate tuning file from its old name to the new one, inside the
265
+ * machine directory. State-checked, never run-once: an old file with no new one is
266
+ * renamed; with both present the new name wins, the old file stays and one line says
267
+ * so; with no old file there is nothing to do. The rename never replaces a file, so
268
+ * two inits at once cannot lose the new one. A link or another non-file under the old
269
+ * name is left alone, never followed; one line says its settings no longer apply.
270
+ */
271
+ export async function moveGlobalTuningFile(machineDir, io) {
272
+ const oldPath = path.join(machineDir, LEGACY_NAMES.tuningFile);
273
+ const newPath = getGlobalCurateTuningConfigPath(machineDir);
274
+ const done = (outcome, ...lines) => ({ outcome, lines });
275
+ const oldKind = await probeKind(oldPath, io);
276
+ if (oldKind === 'absent')
277
+ return done({ kind: 'none' });
278
+ if (oldKind === 'error') {
279
+ return done({ kind: 'error', message: 'the file could not be checked' }, `The global tuning file ${oldPath} could not be checked. The next init tries again.`);
280
+ }
281
+ if (oldKind === 'other') {
282
+ return done({ kind: 'left-alone' }, `The global tuning file ${oldPath} is not a regular file. It was not renamed to ${CURATION_TUNING_FILE_NAME}, ` +
283
+ `so its settings no longer apply. Copy them into ${CURATION_TUNING_FILE_NAME}.`);
284
+ }
285
+ const newKind = await probeKind(newPath, io);
286
+ if (newKind === 'error') {
287
+ return done({ kind: 'error', message: 'the new name could not be checked' }, `The global tuning file ${newPath} could not be checked. The next init tries again.`);
288
+ }
289
+ if (newKind !== 'absent')
290
+ return done({ kind: 'kept-new' }, keptNewLine(machineDir));
291
+ let renamed;
292
+ try {
293
+ renamed = await io.renameIfAbsent(oldPath, newPath);
294
+ }
295
+ catch (error) {
296
+ renamed = { state: 'failed', message: describeFailure(error) };
297
+ }
298
+ switch (renamed.state) {
299
+ case 'renamed':
300
+ return done({ kind: 'renamed' });
301
+ case 'exists':
302
+ return done({ kind: 'kept-new' }, keptNewLine(machineDir));
303
+ case 'failed':
304
+ return done({ kind: 'error', message: renamed.message }, `The global tuning file ${oldPath} could not be renamed: ${renamed.message}. The next init tries again.`);
305
+ default: {
306
+ const unreachable = renamed;
307
+ return unreachable;
308
+ }
309
+ }
310
+ }
311
+ async function probeKind(target, io) {
312
+ try {
313
+ return await io.kindOf(target);
314
+ }
315
+ catch {
316
+ return 'error';
317
+ }
318
+ }
319
+ function keptNewLine(machineDir) {
320
+ return `Both ${LEGACY_NAMES.tuningFile} and ${CURATION_TUNING_FILE_NAME} exist in ${machineDir}. ` +
321
+ `${CURATION_TUNING_FILE_NAME} is used, and ${LEGACY_NAMES.tuningFile} was left in place.`;
322
+ }
323
+ /**
324
+ * The reason whose fixed notice the report's lines carry for the project at ledger root
325
+ * `root`, or null when they carry none. A caller that prints a second line about the same
326
+ * project passes it to {@link formatCurationNotDrained}, so the notice is printed once.
327
+ */
328
+ export function shownSkipAt(report, root) {
329
+ if (root === null)
330
+ return null;
331
+ return report.leftAlone.find(project => project.root === root)?.reason ?? null;
332
+ }
333
+ /** The notice init prints once when a project moved. */
334
+ export function moveNotice(movedCount) {
335
+ return `Moved the curation data of ${movedCount} project${movedCount === 1 ? '' : 's'} ` +
336
+ `from .devflow/${LEGACY_NAMES.folder}/ to .devflow/${CURATION_DIR_NAME}/. ` +
337
+ 'Where a project already had both folders, the old one was kept as a backup.';
338
+ }
339
+ /**
340
+ * The init step: rename the global tuning file, then move every target project's
341
+ * ledger folder. Answers lines and one notice; it never throws and never decides
342
+ * init's exit code. A step that cannot run (bad options) becomes one line.
343
+ */
344
+ export async function runInitCurationMove(opts, io) {
345
+ const lines = [];
346
+ try {
347
+ lines.push(...(await moveGlobalTuningFile(opts.machineDir, io.tuning)).lines);
348
+ }
349
+ catch (error) {
350
+ lines.push(`The global tuning file was not renamed: ${describeFailure(error)}.`);
351
+ }
352
+ let movedCount = 0;
353
+ let leftAlone = [];
354
+ if ('unavailable' in io.ledger) {
355
+ lines.push(`The ledger move did not run: ${io.ledger.unavailable}. The next init tries again.`);
356
+ return { lines, notice: null, movedCount, leftAlone };
357
+ }
358
+ try {
359
+ const result = await moveProjectLedgers(opts, io.ledger);
360
+ if (result.ok) {
361
+ lines.push(...result.value.lines);
362
+ movedCount = result.value.movedCount;
363
+ leftAlone = result.value.outcomes.flatMap(outcome => outcome.kind === 'left-alone' ? [{ root: outcome.root, reason: outcome.reason }] : []);
364
+ }
365
+ else {
366
+ lines.push(`The ledger move did not run: ${result.error.message}.`);
367
+ }
368
+ }
369
+ catch (error) {
370
+ lines.push(`The ledger move did not run: ${describeMoveText(describeFailure(error))}.`);
371
+ }
372
+ return { lines, notice: movedCount > 0 ? moveNotice(movedCount) : null, movedCount, leftAlone };
373
+ }
374
+ //# sourceMappingURL=curation-move-step.js.map