devflow-kit 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/CHANGELOG.md +479 -1
  2. package/README.md +15 -15
  3. package/dist/agents/code.md +6 -8
  4. package/dist/agents/design.md +1 -1
  5. package/dist/agents/fix.md +7 -8
  6. package/dist/agents/git.md +3 -3
  7. package/dist/agents/harden.md +1 -1
  8. package/dist/agents/refactor.md +2 -4
  9. package/dist/agents/review.md +4 -4
  10. package/dist/agents/skim.md +2 -2
  11. package/dist/agents/triage.md +9 -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/curation.js +808 -0
  16. package/dist/cli/commands/gate-level-prompts.js +428 -0
  17. package/dist/cli/commands/gates.js +35 -11
  18. package/dist/cli/commands/init-seed.js +11 -8
  19. package/dist/cli/commands/init.js +244 -57
  20. package/dist/cli/commands/memory.js +60 -13
  21. package/dist/cli/commands/prompt-io.js +37 -0
  22. package/dist/cli/commands/uninstall.js +157 -17
  23. package/dist/cli.js +2 -2
  24. package/dist/commands/bug-analysis.md +25 -36
  25. package/dist/commands/code-review.md +40 -44
  26. package/dist/commands/debug.md +5 -5
  27. package/dist/commands/dynamic-build.md +26 -25
  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 +53 -82
  33. package/dist/commands/plan.md +6 -6
  34. package/dist/commands/release.md +5 -5
  35. package/dist/commands/research.md +6 -6
  36. package/dist/commands/resolve.md +39 -41
  37. package/dist/commands/self-review.md +5 -13
  38. package/dist/core/area-pause-notice.js +44 -0
  39. package/dist/core/assets.js +20 -20
  40. package/dist/core/curate-tuning-config.js +5 -6
  41. package/dist/core/curation-move-notice.js +84 -0
  42. package/dist/core/curation-move-skip.js +35 -0
  43. package/dist/core/curation-move.js +35 -0
  44. package/dist/core/curation-queue-cleanup.js +135 -0
  45. package/dist/core/curation-store.js +99 -0
  46. package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
  47. package/dist/core/echo-value.js +11 -5
  48. package/dist/core/evidence-policy.js +11 -9
  49. package/dist/core/feature-switch.js +95 -19
  50. package/dist/core/fs-atomic.js +1 -1
  51. package/dist/core/gate-level.js +10 -2
  52. package/dist/core/git.js +16 -14
  53. package/dist/core/ledger-root.js +82 -22
  54. package/dist/core/linked-path.js +4 -4
  55. package/dist/core/manifest-lock.js +238 -0
  56. package/dist/core/manifest.js +58 -14
  57. package/dist/core/migrations.js +1 -13
  58. package/dist/core/observations.js +2 -2
  59. package/dist/core/plugins.js +19 -19
  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 +159 -0
  64. package/dist/core/tracker.js +1 -1
  65. package/dist/{learning-off → curation-off}/agents/code.md +6 -8
  66. package/dist/{learning-off → curation-off}/agents/fix.md +7 -8
  67. package/dist/{learning-off → curation-off}/agents/refactor.md +2 -4
  68. package/dist/{learning-off → curation-off}/agents/review.md +3 -3
  69. package/dist/{learning-off → curation-off}/agents/triage.md +8 -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 +37 -41
  72. package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
  73. package/dist/{learning-off → curation-off}/commands/dynamic-build.md +23 -22
  74. package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
  75. package/dist/{learning-off → curation-off}/commands/implement.md +50 -79
  76. package/dist/{learning-off → curation-off}/commands/plan.md +2 -2
  77. package/dist/{learning-off → curation-off}/commands/resolve.md +36 -38
  78. package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
  79. package/dist/hud/components/{learning-counts.js → curation-counts.js} +63 -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/post-resolution-summary.md +12 -11
  84. package/dist/skills/git/references/pr/post-review-summary.md +12 -11
  85. package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
  86. package/dist/skills/git/references/pr/validate-branch.md +8 -5
  87. package/dist/skills/git/references/publication-gate.md +6 -7
  88. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
  89. package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
  90. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
  91. package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
  92. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
  93. package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
  94. package/dist/targets/claude-code/{learning-install.js → curation-install.js} +85 -65
  95. package/dist/targets/claude-code/installer.js +15 -15
  96. package/dist/targets/claude-code/language-stamp.js +5 -5
  97. package/dist/targets/claude-code/post-install.js +2 -2
  98. package/dist/targets/claude-code/tracker-servers.js +1 -1
  99. package/package.json +3 -2
  100. package/src/assets/agents/_partials/_code.mds +11 -11
  101. package/src/assets/agents/code.mds +6 -6
  102. package/src/assets/agents/curate.md +8 -8
  103. package/src/assets/agents/debug.mds +6 -6
  104. package/src/assets/agents/design.mds +18 -18
  105. package/src/assets/agents/evaluate.md +6 -12
  106. package/src/assets/agents/fix.mds +7 -6
  107. package/src/assets/agents/git.mds +3 -3
  108. package/src/assets/agents/harden.mds +7 -7
  109. package/src/assets/agents/knowledge.mds +23 -23
  110. package/src/assets/agents/refactor.mds +2 -2
  111. package/src/assets/agents/research.mds +15 -15
  112. package/src/assets/agents/review.mds +43 -43
  113. package/src/assets/agents/skim.mds +11 -11
  114. package/src/assets/agents/triage.mds +29 -22
  115. package/src/assets/agents/validate.md +30 -50
  116. package/src/assets/commands/_partials/_decisions.mds +22 -20
  117. package/src/assets/commands/_partials/_engine.mds +8 -8
  118. package/src/assets/commands/_partials/_knowledge.mds +29 -15
  119. package/src/assets/commands/_partials/_preamble.mds +4 -4
  120. package/src/assets/commands/_partials/_settings.mds +3 -3
  121. package/src/assets/commands/_partials/_wave.mds +7 -7
  122. package/src/assets/commands/bug-analysis.mds +37 -48
  123. package/src/assets/commands/code-review.mds +53 -57
  124. package/src/assets/commands/debug.mds +14 -14
  125. package/src/assets/commands/dynamic-build.mds +38 -37
  126. package/src/assets/commands/dynamic-plan.mds +22 -22
  127. package/src/assets/commands/dynamic-profile.mds +2 -2
  128. package/src/assets/commands/dynamic-tickets.mds +8 -8
  129. package/src/assets/commands/explore.mds +20 -20
  130. package/src/assets/commands/implement.mds +77 -98
  131. package/src/assets/commands/plan.mds +27 -27
  132. package/src/assets/commands/release.mds +12 -12
  133. package/src/assets/commands/research.mds +19 -19
  134. package/src/assets/commands/resolve.mds +66 -60
  135. package/src/assets/commands/self-review.mds +20 -20
  136. package/src/assets/mds/git/_ops.mds +9 -2
  137. package/src/assets/mds/git/_ops_pr.mds +21 -11
  138. package/src/assets/mds/git/_ops_steps.mds +20 -4
  139. package/src/assets/mds/git/_pr.mds +21 -18
  140. package/src/assets/mds/git/_references.mds +6 -7
  141. package/src/assets/mds/tracker/_github.mds +1 -4
  142. package/src/assets/mds/tracker/_jira.mds +1 -4
  143. package/src/assets/mds/tracker/_linear.mds +1 -4
  144. package/src/assets/rules/quality.md +1 -0
  145. package/src/assets/scripts/ci-wait.cjs +389 -92
  146. package/src/assets/scripts/hooks/background-memory-update +81 -3
  147. package/src/assets/scripts/hooks/capture-prompt +12 -12
  148. package/src/assets/scripts/hooks/capture-question +11 -11
  149. package/src/assets/scripts/hooks/capture-turn +12 -12
  150. package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
  151. package/src/assets/scripts/hooks/curation-move +293 -0
  152. package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
  153. package/src/assets/scripts/hooks/ensure-proxy +4 -4
  154. package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
  155. package/src/assets/scripts/hooks/git-marker +81 -0
  156. package/src/assets/scripts/hooks/json-helper.cjs +176 -51
  157. package/src/assets/scripts/hooks/json-parse +1 -1
  158. package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
  159. package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +308 -122
  160. package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
  161. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
  162. package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
  163. package/src/assets/scripts/hooks/lib/render-decisions.cjs +42 -22
  164. package/src/assets/scripts/hooks/lib/tracked-areas.cjs +452 -0
  165. package/src/assets/scripts/hooks/memory-permission +10 -2
  166. package/src/assets/scripts/hooks/memory-worker +12 -1
  167. package/src/assets/scripts/hooks/pre-compact-memory +17 -16
  168. package/src/assets/scripts/hooks/queue-append +444 -102
  169. package/src/assets/scripts/hooks/resolve-project-root +2 -2
  170. package/src/assets/scripts/hooks/session-start-context +198 -72
  171. package/src/assets/scripts/hooks/session-start-memory +42 -43
  172. package/src/assets/scripts/lib/project-config.cjs +12 -5
  173. package/src/assets/scripts/pr-evidence.cjs +5 -2
  174. package/src/assets/scripts/resolve-base.cjs +1043 -0
  175. package/src/assets/scripts/resolve-settings.cjs +283 -45
  176. package/src/assets/scripts/verify-evidence.cjs +307 -75
  177. package/src/assets/skills/agent-references/SKILL.md +11 -2
  178. package/src/assets/skills/agent-references/references/commands/implement-levels.md +29 -0
  179. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
  180. package/src/assets/skills/agent-references/references/commands/resolve-levels.md +25 -0
  181. package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
  182. package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
  183. package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
  184. package/src/assets/skills/apply-decisions/SKILL.md +1 -1
  185. package/src/assets/skills/docs-framework/SKILL.md +7 -7
  186. package/src/assets/skills/docs-framework/references/patterns.md +3 -4
  187. package/src/assets/skills/quality-gates/SKILL.md +1 -1
  188. package/src/assets/skills/worktree-support/references/roots.md +2 -2
  189. package/templates/lint-pack/.lint-pack.conf +86 -0
  190. package/templates/lint-pack/README.md +190 -0
  191. package/templates/lint-pack/lint-pack.sh +905 -0
  192. package/dist/cli/commands/learning.js +0 -554
  193. package/dist/core/learning-queue-cleanup.js +0 -27
  194. package/dist/core/learning-store.js +0 -61
  195. /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
  196. /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
  197. /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
  198. /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
  199. /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
  200. /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
  201. /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
  202. /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
  203. /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
  204. /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
  205. /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
@@ -0,0 +1,428 @@
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 clauses that lean skips Harden on a
42
+ * docs-only diff, that standard and lean run one Evaluate pass and that standard and lean
43
+ * apply severity tiers in /resolve have no facet row in `tests/gate-level-prompts.test.ts`:
44
+ * the docs guard checks each at every doc site, against its row in the /implement or
45
+ * /resolve level reference.
46
+ */
47
+ export const GATE_LEVEL_HINTS = Object.freeze({
48
+ full: 'the most checking: every review focus, the full planning shape and Polish',
49
+ standard: 'fewer review focuses and planning agents, no Polish in /implement and /resolve, severity tiers in /resolve, and one Evaluate pass in /implement (the default)',
50
+ lean: 'the least checking, and no Polish in /implement and /resolve; /resolve also applies severity tiers; /implement also runs one Evaluate pass, has Validate run the project lint pack, and skips Harden on a docs-only diff',
51
+ });
52
+ /** Build clack select options for the gate level list, most to least checking. */
53
+ export function gateLevelChoices() {
54
+ return GATE_LEVELS.map(level => ({ value: level, label: level, hint: GATE_LEVEL_HINTS[level] }));
55
+ }
56
+ /**
57
+ * Format a padded level catalogue suitable for a clack note body.
58
+ * Produces: ` standard fewer review focuses and planning agents, no Polish in /implement and /resolve, severity tiers in /resolve, and one Evaluate pass in /implement (the default)`
59
+ */
60
+ export function formatGateLevelCatalogue() {
61
+ const width = Math.max(...GATE_LEVELS.map(level => level.length));
62
+ return 'Levels:\n' + GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_HINTS[level]}`).join('\n');
63
+ }
64
+ /** `features.gateLevel` of a raw manifest, exactly as stored; `undefined` for an absent key. */
65
+ function rawGateLevelOf(raw) {
66
+ const features = raw.features;
67
+ if (typeof features !== 'object' || features === null)
68
+ return undefined;
69
+ return Object.getOwnPropertyDescriptor(features, 'gateLevel')?.value;
70
+ }
71
+ const UNREADABLE_GATE_LEVEL = Object.freeze({ origin: 'unreadable', value: undefined });
72
+ /**
73
+ * Whether a raw value can be written back as JSON. The file parses a value that nests
74
+ * deeper than the serializer's stack, and writing that value back fails, which would stop
75
+ * init from ever replacing the manifest that stores it.
76
+ */
77
+ function serializes(value) {
78
+ try {
79
+ JSON.stringify(value);
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ /**
87
+ * Classify the stored level from the raw read of a manifest that readManifest()
88
+ * returned null for. Pure — the caller reads the file.
89
+ *
90
+ * absent a first install: nothing stored
91
+ * rejected, with a key the key's value, raw: a malformed value or `null` passes
92
+ * through untouched, never defaulted, narrowed or healed
93
+ * rejected, no key nothing stored
94
+ * corrupt unreadable: no level can be known
95
+ * object readManifest() would accept it, so its level is an ordinary
96
+ * stored level (a file that changed between the two reads)
97
+ * a value that does not serialize
98
+ * unreadable, whatever the file: the level cannot be kept
99
+ */
100
+ export function storedGateLevelFromRaw(read) {
101
+ switch (read.kind) {
102
+ case 'absent':
103
+ return { origin: 'manifest', value: undefined };
104
+ case 'corrupt':
105
+ return UNREADABLE_GATE_LEVEL;
106
+ case 'object':
107
+ case 'rejected': {
108
+ const value = rawGateLevelOf(read.raw);
109
+ if (value !== undefined && !serializes(value))
110
+ return UNREADABLE_GATE_LEVEL;
111
+ return { origin: read.kind === 'rejected' && value !== undefined ? 'rejected' : 'manifest', value };
112
+ }
113
+ default: {
114
+ const exhaustive = read;
115
+ return exhaustive;
116
+ }
117
+ }
118
+ }
119
+ /**
120
+ * Where init finds the stored level when it reads the machine manifest: the manifest
121
+ * readManifest() returned, else the raw file. Never throws — readGateManifest() reports
122
+ * every failure as a result.
123
+ *
124
+ * This is the level the run replaces, which the summary names. Every writer that
125
+ * replaces the manifest reads it through this one function, so `devflow init` and
126
+ * `devflow init --hud-only` report the same stored level. `--reset` forgets a level in
127
+ * what it writes (see {@link carriedGateLevel}), not in what it reports, except for a
128
+ * manifest it cannot read: there is no level to name, and the reset forgets it anyway.
129
+ */
130
+ export async function readStoredGateLevel(devflowDir, manifest, reset) {
131
+ if (manifest !== null)
132
+ return { origin: 'manifest', value: manifest.features.gateLevel };
133
+ const stored = storedGateLevelFromRaw(await readGateManifest(devflowDir));
134
+ return reset && stored.origin === 'unreadable' ? { origin: 'manifest', value: undefined } : stored;
135
+ }
136
+ /**
137
+ * The level a full init starts from, and where it was found: what the manifest it writes
138
+ * holds unless --gate-level or the wizard answer sets another. Pure.
139
+ *
140
+ * The seed holds the level of the manifest readManifest() returned, and none after
141
+ * --reset. When readManifest() returned none, the raw reading is the level to keep: init
142
+ * never writes a level the user did not choose, and `init --hud-only` keeps the same one.
143
+ * That holds for a refused manifest and for a file that changed between the two reads.
144
+ * --reset forgets it.
145
+ */
146
+ export function carriedGateLevel(input) {
147
+ const { manifest, reset, stored, seedLevel } = input;
148
+ if (manifest !== null || reset)
149
+ return { origin: 'manifest', value: seedLevel };
150
+ return stored;
151
+ }
152
+ /**
153
+ * Classify a raw stored value, and where it was found (default: the manifest init
154
+ * reads). Pure and total — the one place that tells absent, valid and malformed
155
+ * apart.
156
+ *
157
+ * `unreadable` is an absent value from a manifest that could not be read: no value
158
+ * was read, so none is shown as the default. A value that is present is classified
159
+ * as itself whatever the origin.
160
+ */
161
+ export function classifyGateLevel(stored, origin = 'manifest') {
162
+ if (stored === undefined)
163
+ return origin === 'unreadable' ? { kind: 'unreadable' } : { kind: 'default' };
164
+ const parsed = parseGateLevel(stored);
165
+ return parsed.ok ? { kind: 'level', level: parsed.value } : { kind: 'malformed' };
166
+ }
167
+ /**
168
+ * The level that applies for a reading, as every reader gives it: the level itself,
169
+ * the default for an absent or unknown one, and the fold for a malformed one.
170
+ */
171
+ function appliedLevel(reading) {
172
+ switch (reading.kind) {
173
+ case 'level':
174
+ return reading.level;
175
+ case 'malformed':
176
+ return MALFORMED_GATE_LEVEL_READS_AS;
177
+ case 'default':
178
+ case 'unreadable':
179
+ return DEFAULT_GATE_LEVEL;
180
+ default: {
181
+ const exhaustive = reading;
182
+ return exhaustive;
183
+ }
184
+ }
185
+ }
186
+ /**
187
+ * A malformed value, in the words of the `Machine:` line of `devflow gates`
188
+ * (MACHINE_INVALID_LINE in gates.ts): a parity test holds the two to the same text.
189
+ * The raw value is never shown.
190
+ */
191
+ const MALFORMED_TEXT = `invalid (manifest.json; reads as ${MALFORMED_GATE_LEVEL_READS_AS})`;
192
+ /** A reading as the note header and the summary line name it. */
193
+ function describeReading(reading) {
194
+ switch (reading.kind) {
195
+ case 'default':
196
+ return `${DEFAULT_GATE_LEVEL} (default)`;
197
+ case 'level':
198
+ return reading.level;
199
+ case 'malformed':
200
+ return MALFORMED_TEXT;
201
+ case 'unreadable':
202
+ return 'unknown (the stored gate level cannot be read)';
203
+ default: {
204
+ const exhaustive = reading;
205
+ return exhaustive;
206
+ }
207
+ }
208
+ }
209
+ /**
210
+ * Name the level a stored manifest value stands for, for the note header and the
211
+ * summary line. Pure function — no I/O, no side effects.
212
+ *
213
+ * undefined no level is recorded: `standard (default)`
214
+ * a level that level
215
+ * otherwise malformed: `invalid`, and the level it reads as, as `devflow gates` words it
216
+ *
217
+ * `standard` carries the `(default)` marker only when nothing is recorded, so a
218
+ * reader can tell "I chose this" from "this is what devflow does when nobody chooses".
219
+ */
220
+ export function describeGateLevel(stored) {
221
+ return describeReading(classifyGateLevel(stored));
222
+ }
223
+ /**
224
+ * The level the select starts on: the stored level on a re-init, the default on a
225
+ * first install, after --reset or when the stored level cannot be read, and the level
226
+ * a malformed value reads as. Pure and total.
227
+ */
228
+ export function gateLevelPromptSeed(stored, origin = 'manifest') {
229
+ return appliedLevel(classifyGateLevel(stored, origin));
230
+ }
231
+ /** The "Current setting" lines of the prompt note, from the stored value and where it was found. */
232
+ function currentSettingLines(stored, origin) {
233
+ switch (origin) {
234
+ case 'manifest':
235
+ return `Current setting: ${describeGateLevel(stored)}`;
236
+ case 'rejected':
237
+ return `Current setting: ${describeGateLevel(stored)}\n` +
238
+ 'It comes from a manifest that devflow rejects. Init replaces that manifest and keeps this level unless you choose another.';
239
+ case 'unreadable':
240
+ return 'Current setting: unknown\n' +
241
+ `The stored gate level cannot be read, so ${DEFAULT_GATE_LEVEL} applies until you choose a level.`;
242
+ default: {
243
+ const exhaustive = origin;
244
+ return exhaustive;
245
+ }
246
+ }
247
+ }
248
+ // ── The --gate-level boundary ──────────────────────────────────────────────────
249
+ /**
250
+ * Parse the --gate-level option at the boundary, before any prompt or write.
251
+ *
252
+ * Strict — reject, never repair — so `Lean` or an empty value exits instead of
253
+ * silently resolving. `--hud-only` installs only the HUD and writes no level, so
254
+ * the pair is refused rather than the flag silently ignored.
255
+ *
256
+ * Returns undefined when the option was not passed.
257
+ */
258
+ export function resolveGateLevelInitState(option, hudOnly) {
259
+ if (typeof option !== 'string')
260
+ return undefined;
261
+ const parsed = parseGateLevel(option);
262
+ if (!parsed.ok)
263
+ return { ok: false, error: parsed.error };
264
+ if (hudOnly) {
265
+ return {
266
+ ok: false,
267
+ error: '--gate-level cannot be used with --hud-only, which installs only the HUD. Use devflow gates --set, or run init without --hud-only.',
268
+ };
269
+ }
270
+ return { ok: true, value: parsed.value };
271
+ }
272
+ // ── Gate predicate ─────────────────────────────────────────────────────────────
273
+ /**
274
+ * Determines whether the gate-level wizard step should run for a given init invocation.
275
+ *
276
+ * The gate table, keyed on modePromptShown and never on the mode name, is
277
+ * `shouldRunPromptedWizardStep` in prompt-io.ts: the one authority for both wizard
278
+ * paths, which reach it through resolveGateLevelForInit. `--gate-level <level>` is
279
+ * the CLI override that suppresses the step on every path.
280
+ *
281
+ * Pure predicate — no side effects, fully testable without a TTY.
282
+ */
283
+ export function shouldRunGateLevelStep(input) {
284
+ return shouldRunPromptedWizardStep(input);
285
+ }
286
+ /**
287
+ * Build the real (clack) GateLevelPromptIO adapter.
288
+ * Delegates the shared note + select to the shared adapters (prompt-io.ts).
289
+ * Translates clack's cancel symbol into the PromptOutcome discriminated union.
290
+ */
291
+ export function buildClackGateLevelPrompts() {
292
+ return {
293
+ note: clackNote,
294
+ select: (opts) => clackSelect(opts),
295
+ selectLevel: (opts) => clackSelect(opts),
296
+ };
297
+ }
298
+ /**
299
+ * Run the gate-level wizard step.
300
+ *
301
+ * Flow:
302
+ * 1. Note — "Current setting: …" header, what the level is, and the catalogue.
303
+ * 2. Level select — full / standard / lean with a one-line effect each, starting
304
+ * on the stored level (re-init) or the default (first install). `p.select`,
305
+ * never `p.confirm`: Enter-through must be an INFORMED keep of a named level.
306
+ *
307
+ * `stored` is the raw manifest value (see FeatureSeed.gateLevel): the step shows
308
+ * and starts from it, and never narrows it. `storedOrigin` says where it was found
309
+ * (default: the manifest); an unreadable level is shown as unknown, not as the default,
310
+ * and the select then starts on the level that applies.
311
+ *
312
+ * Returns:
313
+ * {kind:'resolved', level} — the chosen level.
314
+ * {kind:'cancelled'} — user pressed Escape; caller runs p.cancel + process.exit(0).
315
+ *
316
+ * Invariants:
317
+ * - Never calls process.exit(), never throws.
318
+ * - All I/O is routed through the `prompts` parameter (injectable for tests).
319
+ */
320
+ export async function runGateLevelStep(opts) {
321
+ const { stored, storedOrigin = 'manifest', prompts } = opts;
322
+ prompts.note(`${currentSettingLines(stored, storedOrigin)}\n\n` +
323
+ 'Sets how much checking /code-review, /plan and /implement apply on this\n' +
324
+ 'machine. A personal "gateLevel" in a repository\'s .devflow/config.json\n' +
325
+ 'overrides it in that repository. Change it later with\n' +
326
+ 'devflow gates --set <level>.\n\n' +
327
+ formatGateLevelCatalogue(), 'Gate level');
328
+ const outcome = await prompts.selectLevel({
329
+ message: GATE_LEVEL_SELECT_MESSAGE,
330
+ options: gateLevelChoices(),
331
+ initialValue: gateLevelPromptSeed(stored, storedOrigin),
332
+ });
333
+ if (outcome.kind === 'cancel')
334
+ return { kind: 'cancelled' };
335
+ return { kind: 'resolved', level: outcome.value };
336
+ }
337
+ /**
338
+ * Decide the machine gate level for one init wizard path.
339
+ *
340
+ * Precedence: --gate-level > wizard answer > the stored value (a first install
341
+ * stores none, and the default then applies at read time). Both wizard paths call
342
+ * this with their own `mode`; they share one gate, one cancel idiom and one prompt
343
+ * adapter, so the paths cannot drift.
344
+ */
345
+ export async function resolveGateLevelForInit(opts) {
346
+ const { cliLevel, stored, storedOrigin, mode, modePromptShown, isTTY, prompts } = opts;
347
+ if (shouldRunGateLevelStep({ mode, modePromptShown, isTTY, hasCliOverride: cliLevel !== undefined })) {
348
+ const step = await runGateLevelStep({ stored, storedOrigin, prompts });
349
+ return step.kind === 'cancelled' ? { kind: 'cancelled' } : { kind: 'resolved', level: step.level, carried: false };
350
+ }
351
+ return cliLevel === undefined
352
+ ? { kind: 'resolved', level: stored, carried: true }
353
+ : { kind: 'resolved', level: cliLevel, carried: false };
354
+ }
355
+ // ── Summary ────────────────────────────────────────────────────────────────────
356
+ /**
357
+ * The gate-level row of the Recommended settings note, from the level the run will
358
+ * record (or the stored value it keeps) and where the stored value was found. Pure.
359
+ *
360
+ * It names the level the way the post-install summary does. A level that could not be
361
+ * read, when this run records none, is shown as unknown, never as the default.
362
+ */
363
+ export function formatGateLevelRow(level, origin) {
364
+ const reading = classifyGateLevel(level, origin);
365
+ return reading.kind === 'unreadable'
366
+ ? 'unknown (cannot be read, so init records none)'
367
+ : describeReading(reading);
368
+ }
369
+ /**
370
+ * ` (was X)` when a run changed the level that applies, else empty. It compares the
371
+ * levels the two readings stand for, so a malformed value written back as it was
372
+ * stored, or accepted at the prompt as the level it reads as, is no change. An
373
+ * unreadable prior has no level to name, so none is invented.
374
+ */
375
+ function replacedLevelNote(prior, recorded) {
376
+ if (prior.kind === 'unreadable')
377
+ return '';
378
+ const was = appliedLevel(prior);
379
+ return was === appliedLevel(recorded) ? '' : ` (was ${was})`;
380
+ }
381
+ /**
382
+ * The gate-level lines of init's post-install summary, from the state the run acted
383
+ * on (D-INIT-REAL-OUTCOME): the level this run recorded when the manifest reached
384
+ * disk, else the level the manifest on disk still holds, with a warning that the gate
385
+ * level did not change. Pure — the caller renders.
386
+ *
387
+ * A run that changes the level that applies names the one it replaced: `(was lean)`.
388
+ *
389
+ * `priorOrigin` says where the prior level was found (default: the manifest):
390
+ *
391
+ * rejected the level came from a manifest that was replaced; when `kept` is
392
+ * true, a warning says init kept it
393
+ * unreadable no level could be read; a run that recorded none warns that standard
394
+ * now applies and how to set the level again
395
+ *
396
+ * `kept` is true only when the manifest was written with the stored level carried
397
+ * over: no --reset, no --gate-level and no prompt answer chose the level. A level the
398
+ * user chose is a choice, even when it equals the stored one.
399
+ */
400
+ export function formatGateLevelSummary(input) {
401
+ const origin = input.priorOrigin ?? 'manifest';
402
+ const prior = classifyGateLevel(input.priorLevel, origin);
403
+ if (!input.manifestWritten) {
404
+ return [{
405
+ level: 'warn',
406
+ message: `Gate level: ${describeReading(prior)} — this run could not save the manifest, so the gate level did not change`,
407
+ }];
408
+ }
409
+ const recorded = classifyGateLevel(input.level, origin);
410
+ if (recorded.kind === 'unreadable') {
411
+ return [{
412
+ level: 'warn',
413
+ 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>`,
414
+ }];
415
+ }
416
+ const lines = [{
417
+ level: 'info',
418
+ message: `Gate level: ${describeReading(recorded)}${replacedLevelNote(prior, recorded)} — the machine default; change it with devflow gates --set <level>`,
419
+ }];
420
+ if (origin === 'rejected' && input.kept) {
421
+ lines.push({
422
+ level: 'warn',
423
+ message: 'Init kept the stored gate level while it replaced a manifest that devflow rejects',
424
+ });
425
+ }
426
+ return lines;
427
+ }
428
+ //# sourceMappingURL=gate-level-prompts.js.map
@@ -21,24 +21,38 @@ import { Command } from 'commander';
21
21
  import * as path from 'path';
22
22
  import * as p from '@clack/prompts';
23
23
  import color from 'picocolors';
24
- import { DEFAULT_GATE_LEVEL, GATE_LEVELS, machineGateLevel, parseGateLevel, readGateManifest, writeMachineGateLevel, } from '../../core/gate-level.js';
24
+ import { DEFAULT_GATE_LEVEL, GATE_LEVELS, MALFORMED_GATE_LEVEL_READS_AS, machineGateLevel, parseGateLevel, readGateManifest, writeMachineGateLevel, } from '../../core/gate-level.js';
25
25
  import { describeEchoedValue } from '../../core/echo-value.js';
26
26
  import { loadSettingsModule, settingsSourceFile, trackedConfigWarning, } from '../../core/evidence-policy.js';
27
27
  import { getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
28
28
  // ── Wording ────────────────────────────────────────────────────────────────────
29
29
  /**
30
30
  * What each level means, in one line. Each line says what the word stands for and
31
- * not what a command does with it.
31
+ * not what a command does with it. The line for MALFORMED_GATE_LEVEL_READS_AS says
32
+ * that a value that is not a level reads as it.
32
33
  */
33
34
  export const GATE_LEVEL_MEANINGS = Object.freeze({
34
35
  full: 'the most checking; any value that is not a level also reads as full',
35
36
  standard: 'the default when no level is set',
36
37
  lean: 'the least checking',
37
38
  });
38
- /** What reads the level today; each later reader updates this text. */
39
- export const GATE_LEVEL_EFFECT = '/code-review and /plan read the level: standard and lean run fewer review focuses and fewer planning agents. Other commands read it once issue #480 lands.';
39
+ /**
40
+ * Which commands read the level today; each later command that reads it updates this text.
41
+ *
42
+ * The init prompt says what each level does in GATE_LEVEL_HINTS (gate-level-prompts.ts).
43
+ * Change the hints too when what a level does changes. A command that reads the level and
44
+ * has no rule that changes a phase leaves the hints true, so it changes this text alone.
45
+ * One test pins the exact text of both tables, so a change to either fails it until you
46
+ * check the other table and update the pin; further rows check the clauses the two tables
47
+ * share (the lint pack level, the levels that run fewer review focuses and the levels that
48
+ * skip Polish). The clauses that lean skips Harden on a docs-only diff, that standard
49
+ * and lean run one Evaluate pass and that standard and lean apply severity tiers in /resolve
50
+ * have no facet row in `tests/gate-level-prompts.test.ts`: the docs guard checks each at
51
+ * every doc site, against its row in the /implement or /resolve level reference.
52
+ */
53
+ export const GATE_LEVEL_EFFECT = '/code-review and /plan read the level: standard and lean run fewer review focuses and fewer planning agents. At lean, /implement has Validate run the project\'s lint pack. /implement and /resolve also read the level and name it in their report; at standard and lean they skip Polish, and full keeps it. At lean, /implement skips Harden on a docs-only diff. At standard and lean, /implement runs one Evaluate pass and at most one Fix round. At standard and lean, /resolve applies severity tiers: it fixes or escalates every security finding, fixes CRITICAL, HIGH and severe MEDIUM findings in the branch\'s own code, fixes prose-only MEDIUM findings with text edits alone, and sends lower findings and pre-existing non-security findings to one backlog issue.';
40
54
  const MACHINE_MANIFEST_NAME = 'manifest.json';
41
- const MACHINE_INVALID_LINE = `invalid (${MACHINE_MANIFEST_NAME}; reads as full)`;
55
+ const MACHINE_INVALID_LINE = `invalid (${MACHINE_MANIFEST_NAME}; reads as ${MALFORMED_GATE_LEVEL_READS_AS})`;
42
56
  const VALID_LEVELS = GATE_LEVELS.join(', ');
43
57
  const VALID_LEVELS_OR = `${GATE_LEVELS.slice(0, -1).join(', ')}, or ${GATE_LEVELS[GATE_LEVELS.length - 1]}`;
44
58
  /** The resolved settings, or null when the resolver is unusable or throws despite its contract. */
@@ -54,8 +68,8 @@ function manifestState(read) {
54
68
  switch (read.kind) {
55
69
  case 'absent': return 'absent';
56
70
  case 'object': return 'readable';
57
- case 'corrupt':
58
- case 'rejected': return 'unreadable';
71
+ case 'rejected': return 'rejected';
72
+ case 'corrupt': return 'unreadable';
59
73
  default: {
60
74
  const exhaustive = read;
61
75
  return exhaustive;
@@ -114,9 +128,19 @@ function machineLine(machine) {
114
128
  return `Machine: ${machine}`;
115
129
  }
116
130
  function manifestWarning(status) {
117
- if (status.manifest !== 'unreadable')
118
- return null;
119
- return `Manifest at ${status.manifestPath} cannot be read as a manifest; devflow init will replace this file.`;
131
+ switch (status.manifest) {
132
+ case 'absent':
133
+ case 'readable':
134
+ return null;
135
+ case 'rejected':
136
+ return `Manifest at ${status.manifestPath} is a manifest that devflow rejects; devflow init will replace this file and keep a gate level stored in it unless you choose another level or use --reset.`;
137
+ case 'unreadable':
138
+ return `Manifest at ${status.manifestPath} cannot be read by devflow gates; devflow init will replace this file. Init keeps a gate level stored in it only if init can read the file. If init reports that it could not read the level, set it again with devflow gates --set <level>.`;
139
+ default: {
140
+ const exhaustive = status.manifest;
141
+ return exhaustive;
142
+ }
143
+ }
120
144
  }
121
145
  /**
122
146
  * Render a {@link GateStatus}. Pure and total over every `kind`.
@@ -231,7 +255,7 @@ function levelsHelp() {
231
255
  export const gatesCommand = new Command('gates')
232
256
  .description('Show or set the machine\'s default gate level')
233
257
  .option('--status', 'Show the gate level in effect here, which layer decided it, and the machine default')
234
- .option('--set <level>', `Set the machine's default gate level: ${VALID_LEVELS_OR}`)
258
+ .option(`--set <${GATE_LEVELS.join('|')}>`, `Set the machine's default gate level: ${VALID_LEVELS_OR}`)
235
259
  .addHelpText('after', `\nGate levels:\n${levelsHelp()}\n\n` +
236
260
  `The default is ${DEFAULT_GATE_LEVEL}. --set writes only the machine default. A personal .devflow/config.json ` +
237
261
  '"gateLevel" overrides it in one repository; no command writes that key.\n' +
@@ -21,7 +21,7 @@ export const FEATURE_DEFAULTS = {
21
21
  memory: true,
22
22
  hud: true,
23
23
  knowledge: true,
24
- learning: true,
24
+ curation: true,
25
25
  rules: true,
26
26
  proxy: false,
27
27
  behavior: false,
@@ -35,7 +35,7 @@ export const FEATURE_DEFAULTS = {
35
35
  * manifest.features, with registry defaults when the manifest is absent.
36
36
  *
37
37
  * D-FEATURES-NARROW-ONLY (src/core/feature-switch.ts): the machine switches for
38
- * memory, learning and knowledge are recorded in the manifest alone (a repository
38
+ * memory, curation and knowledge are recorded in the manifest alone (a repository
39
39
  * only narrows them at read time), so the seed takes no per-repo input at all. Seeding from whatever repo init happens to run in
40
40
  * would flip the switch for EVERY repo as a side effect — a stale per-repo
41
41
  * `true` would silently re-enable a feature the user turned off. State-aware
@@ -60,12 +60,12 @@ export function resolveSeedFeatures(manifest) {
60
60
  const tracker = { ...rawTracker };
61
61
  const memory = manifest?.features.memory ?? FEATURE_DEFAULTS.memory;
62
62
  const knowledge = manifest?.features.knowledge ?? FEATURE_DEFAULTS.knowledge;
63
- const learning = manifest?.features.learning ?? FEATURE_DEFAULTS.learning;
63
+ const curation = manifest?.features.curation ?? FEATURE_DEFAULTS.curation;
64
64
  // The raw stored level, with no default operator: `??` would turn a stored `null`
65
65
  // into the default, and init would then write the default over a value the
66
66
  // fold reads as the strictest level.
67
67
  const gateLevel = manifest?.features.gateLevel;
68
- return { ambient, memory, hud, knowledge, learning, rules, proxy, behavior, compliance, tracker, gateLevel };
68
+ return { ambient, memory, hud, knowledge, curation, rules, proxy, behavior, compliance, tracker, gateLevel };
69
69
  }
70
70
  /**
71
71
  * Resolve the flag record for the init seed.
@@ -310,6 +310,11 @@ export function resolveResetGatedInputs(reset, manifest, projectConfig, settings
310
310
  * undefined means "user did not specify this flag, keep the seed value".
311
311
  *
312
312
  * Applies explicit --ambient/--no-ambient etc. passed alongside --recommended.
313
+ *
314
+ * The machine gate level is not a toggle: `resolveGateLevelForInit` (gate-level-prompts.ts)
315
+ * is the one place that decides it (--gate-level, then the wizard answer, then the stored
316
+ * value), and init holds that decision itself. The result carries the seed's raw value only
317
+ * to stay a complete FeatureSeed.
313
318
  */
314
319
  export function applyCliToggles(base, toggles) {
315
320
  return {
@@ -317,15 +322,13 @@ export function applyCliToggles(base, toggles) {
317
322
  memory: toggles.memory ?? base.memory,
318
323
  hud: toggles.hud ?? base.hud,
319
324
  knowledge: toggles.knowledge ?? base.knowledge,
320
- learning: toggles.learning ?? base.learning,
325
+ curation: toggles.curation ?? base.curation,
321
326
  rules: toggles.rules ?? base.rules,
322
327
  proxy: toggles.proxy ?? base.proxy,
323
328
  behavior: toggles.behavior ?? base.behavior,
324
329
  compliance: toggles.compliance ?? base.compliance,
325
330
  tracker: toggles.tracker ?? base.tracker,
326
- // No command-line flag names the gate level, so the seed's raw value passes
327
- // through. Only `undefined` means "not specified": `null` is a value, not a gap.
328
- gateLevel: toggles.gateLevel !== undefined ? toggles.gateLevel : base.gateLevel,
331
+ gateLevel: base.gateLevel,
329
332
  };
330
333
  }
331
334
  //# sourceMappingURL=init-seed.js.map