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,426 @@
1
+ /**
2
+ * Gate-level prompt helpers for devflow init.
3
+ *
4
+ * CLI-layer module: prompt-rendering logic lives in src/cli/commands/, the closed
5
+ * set of levels and its parser stay in src/core/gate-level.ts.
6
+ *
7
+ * `devflow init` asks for the MACHINE gate level, `features.gateLevel` in
8
+ * `~/.devflow/manifest.json`, the same field `devflow gates --set` writes. It never
9
+ * writes the personal `gateLevel` in a repository's `.devflow/config.json`, which
10
+ * still overrides the machine level there.
11
+ *
12
+ * The wizard gate keys on `modePromptShown` (was the Setup-mode p.select prompt
13
+ * actually shown?), never on the mode name, so --recommended (flag, no prompt) and
14
+ * the non-TTY fallback keep their promptless contracts: they carry the stored value
15
+ * through untouched. `--gate-level <level>` sets the level on every path with no
16
+ * prompt. runGateLevelStep and resolveGateLevelForInit never call process.exit() or
17
+ * throw: callers own the cancel idiom (p.cancel + process.exit(0)).
18
+ *
19
+ * The shared DI seam (PromptOutcome, WizardPromptIO, clackNote, clackSelect) is
20
+ * defined once in prompt-io.ts and imported here, never re-declared.
21
+ *
22
+ * Init never writes a level the user did not choose. readManifest() reads a manifest
23
+ * it refuses as no manifest at all, yet `devflow gates` and the settings resolver
24
+ * still read that file's level, so init reads the raw file in that case
25
+ * (storedGateLevelFromRaw): a level found there is kept as stored, and a file that
26
+ * cannot be read at all is named as unreadable, never shown as the default.
27
+ */
28
+ import { clackNote, clackSelect, shouldRunPromptedWizardStep, } from './prompt-io.js';
29
+ import { DEFAULT_GATE_LEVEL, GATE_LEVELS, MALFORMED_GATE_LEVEL_READS_AS, parseGateLevel, readGateManifest, } from '../../core/gate-level.js';
30
+ // ── Shared prompt content ──────────────────────────────────────────────────────
31
+ /** Message shown on the gate level select prompt. */
32
+ export const GATE_LEVEL_SELECT_MESSAGE = 'Gate level for this machine';
33
+ /**
34
+ * The one-line effect of each level, as the select hint and the catalogue show it.
35
+ * Each hint says what its level does. GATE_LEVEL_EFFECT in gates.ts says which commands
36
+ * read the level. Change a hint when what a level does changes. A command that reads the
37
+ * level and has no rule that changes a phase leaves the hints true, so it changes
38
+ * GATE_LEVEL_EFFECT alone. One test pins the exact text of both tables, so a change to
39
+ * either fails it until you check the other table and update the pin; further rows check
40
+ * the clauses the two tables share (the lint pack level, the levels that run fewer review
41
+ * focuses and the levels that skip Polish). The clause that lean skips Harden on a
42
+ * docs-only diff has no facet row in `tests/gate-level-prompts.test.ts`: the docs guard
43
+ * checks it at each doc site, against the row in the /implement level reference.
44
+ */
45
+ export const GATE_LEVEL_HINTS = Object.freeze({
46
+ full: 'the most checking: every review focus, the full planning shape and Polish',
47
+ standard: 'fewer review focuses and planning agents, and no Polish in /implement and /resolve (the default)',
48
+ lean: 'the least checking, and no Polish in /implement and /resolve; /implement also has Validate run the project lint pack, and skips Harden on a docs-only diff',
49
+ });
50
+ /** Build clack select options for the gate level list, most to least checking. */
51
+ export function gateLevelChoices() {
52
+ return GATE_LEVELS.map(level => ({ value: level, label: level, hint: GATE_LEVEL_HINTS[level] }));
53
+ }
54
+ /**
55
+ * Format a padded level catalogue suitable for a clack note body.
56
+ * Produces: ` standard fewer review focuses and planning agents, and no Polish in /implement and /resolve (the default)`
57
+ */
58
+ export function formatGateLevelCatalogue() {
59
+ const width = Math.max(...GATE_LEVELS.map(level => level.length));
60
+ return 'Levels:\n' + GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_HINTS[level]}`).join('\n');
61
+ }
62
+ /** `features.gateLevel` of a raw manifest, exactly as stored; `undefined` for an absent key. */
63
+ function rawGateLevelOf(raw) {
64
+ const features = raw.features;
65
+ if (typeof features !== 'object' || features === null)
66
+ return undefined;
67
+ return Object.getOwnPropertyDescriptor(features, 'gateLevel')?.value;
68
+ }
69
+ const UNREADABLE_GATE_LEVEL = Object.freeze({ origin: 'unreadable', value: undefined });
70
+ /**
71
+ * Whether a raw value can be written back as JSON. The file parses a value that nests
72
+ * deeper than the serializer's stack, and writing that value back fails, which would stop
73
+ * init from ever replacing the manifest that stores it.
74
+ */
75
+ function serializes(value) {
76
+ try {
77
+ JSON.stringify(value);
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Classify the stored level from the raw read of a manifest that readManifest()
86
+ * returned null for. Pure — the caller reads the file.
87
+ *
88
+ * absent a first install: nothing stored
89
+ * rejected, with a key the key's value, raw: a malformed value or `null` passes
90
+ * through untouched, never defaulted, narrowed or healed
91
+ * rejected, no key nothing stored
92
+ * corrupt unreadable: no level can be known
93
+ * object readManifest() would accept it, so its level is an ordinary
94
+ * stored level (a file that changed between the two reads)
95
+ * a value that does not serialize
96
+ * unreadable, whatever the file: the level cannot be kept
97
+ */
98
+ export function storedGateLevelFromRaw(read) {
99
+ switch (read.kind) {
100
+ case 'absent':
101
+ return { origin: 'manifest', value: undefined };
102
+ case 'corrupt':
103
+ return UNREADABLE_GATE_LEVEL;
104
+ case 'object':
105
+ case 'rejected': {
106
+ const value = rawGateLevelOf(read.raw);
107
+ if (value !== undefined && !serializes(value))
108
+ return UNREADABLE_GATE_LEVEL;
109
+ return { origin: read.kind === 'rejected' && value !== undefined ? 'rejected' : 'manifest', value };
110
+ }
111
+ default: {
112
+ const exhaustive = read;
113
+ return exhaustive;
114
+ }
115
+ }
116
+ }
117
+ /**
118
+ * Where init finds the stored level when it reads the machine manifest: the manifest
119
+ * readManifest() returned, else the raw file. Never throws — readGateManifest() reports
120
+ * every failure as a result.
121
+ *
122
+ * This is the level the run replaces, which the summary names. Every writer that
123
+ * replaces the manifest reads it through this one function, so `devflow init` and
124
+ * `devflow init --hud-only` report the same stored level. `--reset` forgets a level in
125
+ * what it writes (see {@link carriedGateLevel}), not in what it reports, except for a
126
+ * manifest it cannot read: there is no level to name, and the reset forgets it anyway.
127
+ */
128
+ export async function readStoredGateLevel(devflowDir, manifest, reset) {
129
+ if (manifest !== null)
130
+ return { origin: 'manifest', value: manifest.features.gateLevel };
131
+ const stored = storedGateLevelFromRaw(await readGateManifest(devflowDir));
132
+ return reset && stored.origin === 'unreadable' ? { origin: 'manifest', value: undefined } : stored;
133
+ }
134
+ /**
135
+ * The level a full init starts from, and where it was found: what the manifest it writes
136
+ * holds unless --gate-level or the wizard answer sets another. Pure.
137
+ *
138
+ * The seed holds the level of the manifest readManifest() returned, and none after
139
+ * --reset. When readManifest() returned none, the raw reading is the level to keep: init
140
+ * never writes a level the user did not choose, and `init --hud-only` keeps the same one.
141
+ * That holds for a refused manifest and for a file that changed between the two reads.
142
+ * --reset forgets it.
143
+ */
144
+ export function carriedGateLevel(input) {
145
+ const { manifest, reset, stored, seedLevel } = input;
146
+ if (manifest !== null || reset)
147
+ return { origin: 'manifest', value: seedLevel };
148
+ return stored;
149
+ }
150
+ /**
151
+ * Classify a raw stored value, and where it was found (default: the manifest init
152
+ * reads). Pure and total — the one place that tells absent, valid and malformed
153
+ * apart.
154
+ *
155
+ * `unreadable` is an absent value from a manifest that could not be read: no value
156
+ * was read, so none is shown as the default. A value that is present is classified
157
+ * as itself whatever the origin.
158
+ */
159
+ export function classifyGateLevel(stored, origin = 'manifest') {
160
+ if (stored === undefined)
161
+ return origin === 'unreadable' ? { kind: 'unreadable' } : { kind: 'default' };
162
+ const parsed = parseGateLevel(stored);
163
+ return parsed.ok ? { kind: 'level', level: parsed.value } : { kind: 'malformed' };
164
+ }
165
+ /**
166
+ * The level that applies for a reading, as every reader gives it: the level itself,
167
+ * the default for an absent or unknown one, and the fold for a malformed one.
168
+ */
169
+ function appliedLevel(reading) {
170
+ switch (reading.kind) {
171
+ case 'level':
172
+ return reading.level;
173
+ case 'malformed':
174
+ return MALFORMED_GATE_LEVEL_READS_AS;
175
+ case 'default':
176
+ case 'unreadable':
177
+ return DEFAULT_GATE_LEVEL;
178
+ default: {
179
+ const exhaustive = reading;
180
+ return exhaustive;
181
+ }
182
+ }
183
+ }
184
+ /**
185
+ * A malformed value, in the words of the `Machine:` line of `devflow gates`
186
+ * (MACHINE_INVALID_LINE in gates.ts): a parity test holds the two to the same text.
187
+ * The raw value is never shown.
188
+ */
189
+ const MALFORMED_TEXT = `invalid (manifest.json; reads as ${MALFORMED_GATE_LEVEL_READS_AS})`;
190
+ /** A reading as the note header and the summary line name it. */
191
+ function describeReading(reading) {
192
+ switch (reading.kind) {
193
+ case 'default':
194
+ return `${DEFAULT_GATE_LEVEL} (default)`;
195
+ case 'level':
196
+ return reading.level;
197
+ case 'malformed':
198
+ return MALFORMED_TEXT;
199
+ case 'unreadable':
200
+ return 'unknown (the stored gate level cannot be read)';
201
+ default: {
202
+ const exhaustive = reading;
203
+ return exhaustive;
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * Name the level a stored manifest value stands for, for the note header and the
209
+ * summary line. Pure function — no I/O, no side effects.
210
+ *
211
+ * undefined no level is recorded: `standard (default)`
212
+ * a level that level
213
+ * otherwise malformed: `invalid`, and the level it reads as, as `devflow gates` words it
214
+ *
215
+ * `standard` carries the `(default)` marker only when nothing is recorded, so a
216
+ * reader can tell "I chose this" from "this is what devflow does when nobody chooses".
217
+ */
218
+ export function describeGateLevel(stored) {
219
+ return describeReading(classifyGateLevel(stored));
220
+ }
221
+ /**
222
+ * The level the select starts on: the stored level on a re-init, the default on a
223
+ * first install, after --reset or when the stored level cannot be read, and the level
224
+ * a malformed value reads as. Pure and total.
225
+ */
226
+ export function gateLevelPromptSeed(stored, origin = 'manifest') {
227
+ return appliedLevel(classifyGateLevel(stored, origin));
228
+ }
229
+ /** The "Current setting" lines of the prompt note, from the stored value and where it was found. */
230
+ function currentSettingLines(stored, origin) {
231
+ switch (origin) {
232
+ case 'manifest':
233
+ return `Current setting: ${describeGateLevel(stored)}`;
234
+ case 'rejected':
235
+ return `Current setting: ${describeGateLevel(stored)}\n` +
236
+ 'It comes from a manifest that devflow rejects. Init replaces that manifest and keeps this level unless you choose another.';
237
+ case 'unreadable':
238
+ return 'Current setting: unknown\n' +
239
+ `The stored gate level cannot be read, so ${DEFAULT_GATE_LEVEL} applies until you choose a level.`;
240
+ default: {
241
+ const exhaustive = origin;
242
+ return exhaustive;
243
+ }
244
+ }
245
+ }
246
+ // ── The --gate-level boundary ──────────────────────────────────────────────────
247
+ /**
248
+ * Parse the --gate-level option at the boundary, before any prompt or write.
249
+ *
250
+ * Strict — reject, never repair — so `Lean` or an empty value exits instead of
251
+ * silently resolving. `--hud-only` installs only the HUD and writes no level, so
252
+ * the pair is refused rather than the flag silently ignored.
253
+ *
254
+ * Returns undefined when the option was not passed.
255
+ */
256
+ export function resolveGateLevelInitState(option, hudOnly) {
257
+ if (typeof option !== 'string')
258
+ return undefined;
259
+ const parsed = parseGateLevel(option);
260
+ if (!parsed.ok)
261
+ return { ok: false, error: parsed.error };
262
+ if (hudOnly) {
263
+ return {
264
+ ok: false,
265
+ error: '--gate-level cannot be used with --hud-only, which installs only the HUD. Use devflow gates --set, or run init without --hud-only.',
266
+ };
267
+ }
268
+ return { ok: true, value: parsed.value };
269
+ }
270
+ // ── Gate predicate ─────────────────────────────────────────────────────────────
271
+ /**
272
+ * Determines whether the gate-level wizard step should run for a given init invocation.
273
+ *
274
+ * The gate table, keyed on modePromptShown and never on the mode name, is
275
+ * `shouldRunPromptedWizardStep` in prompt-io.ts: the one authority for both wizard
276
+ * paths, which reach it through resolveGateLevelForInit. `--gate-level <level>` is
277
+ * the CLI override that suppresses the step on every path.
278
+ *
279
+ * Pure predicate — no side effects, fully testable without a TTY.
280
+ */
281
+ export function shouldRunGateLevelStep(input) {
282
+ return shouldRunPromptedWizardStep(input);
283
+ }
284
+ /**
285
+ * Build the real (clack) GateLevelPromptIO adapter.
286
+ * Delegates the shared note + select to the shared adapters (prompt-io.ts).
287
+ * Translates clack's cancel symbol into the PromptOutcome discriminated union.
288
+ */
289
+ export function buildClackGateLevelPrompts() {
290
+ return {
291
+ note: clackNote,
292
+ select: (opts) => clackSelect(opts),
293
+ selectLevel: (opts) => clackSelect(opts),
294
+ };
295
+ }
296
+ /**
297
+ * Run the gate-level wizard step.
298
+ *
299
+ * Flow:
300
+ * 1. Note — "Current setting: …" header, what the level is, and the catalogue.
301
+ * 2. Level select — full / standard / lean with a one-line effect each, starting
302
+ * on the stored level (re-init) or the default (first install). `p.select`,
303
+ * never `p.confirm`: Enter-through must be an INFORMED keep of a named level.
304
+ *
305
+ * `stored` is the raw manifest value (see FeatureSeed.gateLevel): the step shows
306
+ * and starts from it, and never narrows it. `storedOrigin` says where it was found
307
+ * (default: the manifest); an unreadable level is shown as unknown, not as the default,
308
+ * and the select then starts on the level that applies.
309
+ *
310
+ * Returns:
311
+ * {kind:'resolved', level} — the chosen level.
312
+ * {kind:'cancelled'} — user pressed Escape; caller runs p.cancel + process.exit(0).
313
+ *
314
+ * Invariants:
315
+ * - Never calls process.exit(), never throws.
316
+ * - All I/O is routed through the `prompts` parameter (injectable for tests).
317
+ */
318
+ export async function runGateLevelStep(opts) {
319
+ const { stored, storedOrigin = 'manifest', prompts } = opts;
320
+ prompts.note(`${currentSettingLines(stored, storedOrigin)}\n\n` +
321
+ 'Sets how much checking /code-review, /plan and /implement apply on this\n' +
322
+ 'machine. A personal "gateLevel" in a repository\'s .devflow/config.json\n' +
323
+ 'overrides it in that repository. Change it later with\n' +
324
+ 'devflow gates --set <level>.\n\n' +
325
+ formatGateLevelCatalogue(), 'Gate level');
326
+ const outcome = await prompts.selectLevel({
327
+ message: GATE_LEVEL_SELECT_MESSAGE,
328
+ options: gateLevelChoices(),
329
+ initialValue: gateLevelPromptSeed(stored, storedOrigin),
330
+ });
331
+ if (outcome.kind === 'cancel')
332
+ return { kind: 'cancelled' };
333
+ return { kind: 'resolved', level: outcome.value };
334
+ }
335
+ /**
336
+ * Decide the machine gate level for one init wizard path.
337
+ *
338
+ * Precedence: --gate-level > wizard answer > the stored value (a first install
339
+ * stores none, and the default then applies at read time). Both wizard paths call
340
+ * this with their own `mode`; they share one gate, one cancel idiom and one prompt
341
+ * adapter, so the paths cannot drift.
342
+ */
343
+ export async function resolveGateLevelForInit(opts) {
344
+ const { cliLevel, stored, storedOrigin, mode, modePromptShown, isTTY, prompts } = opts;
345
+ if (shouldRunGateLevelStep({ mode, modePromptShown, isTTY, hasCliOverride: cliLevel !== undefined })) {
346
+ const step = await runGateLevelStep({ stored, storedOrigin, prompts });
347
+ return step.kind === 'cancelled' ? { kind: 'cancelled' } : { kind: 'resolved', level: step.level, carried: false };
348
+ }
349
+ return cliLevel === undefined
350
+ ? { kind: 'resolved', level: stored, carried: true }
351
+ : { kind: 'resolved', level: cliLevel, carried: false };
352
+ }
353
+ // ── Summary ────────────────────────────────────────────────────────────────────
354
+ /**
355
+ * The gate-level row of the Recommended settings note, from the level the run will
356
+ * record (or the stored value it keeps) and where the stored value was found. Pure.
357
+ *
358
+ * It names the level the way the post-install summary does. A level that could not be
359
+ * read, when this run records none, is shown as unknown, never as the default.
360
+ */
361
+ export function formatGateLevelRow(level, origin) {
362
+ const reading = classifyGateLevel(level, origin);
363
+ return reading.kind === 'unreadable'
364
+ ? 'unknown (cannot be read, so init records none)'
365
+ : describeReading(reading);
366
+ }
367
+ /**
368
+ * ` (was X)` when a run changed the level that applies, else empty. It compares the
369
+ * levels the two readings stand for, so a malformed value written back as it was
370
+ * stored, or accepted at the prompt as the level it reads as, is no change. An
371
+ * unreadable prior has no level to name, so none is invented.
372
+ */
373
+ function replacedLevelNote(prior, recorded) {
374
+ if (prior.kind === 'unreadable')
375
+ return '';
376
+ const was = appliedLevel(prior);
377
+ return was === appliedLevel(recorded) ? '' : ` (was ${was})`;
378
+ }
379
+ /**
380
+ * The gate-level lines of init's post-install summary, from the state the run acted
381
+ * on (D-INIT-REAL-OUTCOME): the level this run recorded when the manifest reached
382
+ * disk, else the level the manifest on disk still holds, with a warning that the gate
383
+ * level did not change. Pure — the caller renders.
384
+ *
385
+ * A run that changes the level that applies names the one it replaced: `(was lean)`.
386
+ *
387
+ * `priorOrigin` says where the prior level was found (default: the manifest):
388
+ *
389
+ * rejected the level came from a manifest that was replaced; when `kept` is
390
+ * true, a warning says init kept it
391
+ * unreadable no level could be read; a run that recorded none warns that standard
392
+ * now applies and how to set the level again
393
+ *
394
+ * `kept` is true only when the manifest was written with the stored level carried
395
+ * over: no --reset, no --gate-level and no prompt answer chose the level. A level the
396
+ * user chose is a choice, even when it equals the stored one.
397
+ */
398
+ export function formatGateLevelSummary(input) {
399
+ const origin = input.priorOrigin ?? 'manifest';
400
+ const prior = classifyGateLevel(input.priorLevel, origin);
401
+ if (!input.manifestWritten) {
402
+ return [{
403
+ level: 'warn',
404
+ message: `Gate level: ${describeReading(prior)} — this run could not save the manifest, so the gate level did not change`,
405
+ }];
406
+ }
407
+ const recorded = classifyGateLevel(input.level, origin);
408
+ if (recorded.kind === 'unreadable') {
409
+ return [{
410
+ level: 'warn',
411
+ message: `Gate level: the stored gate level could not be read, so init recorded none and ${DEFAULT_GATE_LEVEL} applies (the machine default); set it again with devflow gates --set <level>`,
412
+ }];
413
+ }
414
+ const lines = [{
415
+ level: 'info',
416
+ message: `Gate level: ${describeReading(recorded)}${replacedLevelNote(prior, recorded)} — the machine default; change it with devflow gates --set <level>`,
417
+ }];
418
+ if (origin === 'rejected' && input.kept) {
419
+ lines.push({
420
+ level: 'warn',
421
+ message: 'Init kept the stored gate level while it replaced a manifest that devflow rejects',
422
+ });
423
+ }
424
+ return lines;
425
+ }
426
+ //# sourceMappingURL=gate-level-prompts.js.map