devflow-kit 3.5.0 → 3.7.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 (146) hide show
  1. package/CHANGELOG.md +178 -1
  2. package/README.md +10 -6
  3. package/dist/agents/code.md +7 -11
  4. package/dist/agents/{diagnose.md → debug.md} +4 -4
  5. package/dist/agents/{code-fix.md → fix.md} +7 -8
  6. package/dist/agents/{scrutinize.md → harden.md} +6 -6
  7. package/dist/agents/{code-edit.md → refactor.md} +8 -9
  8. package/dist/agents/review.md +2 -0
  9. package/dist/cli/agents-view/render.js +1 -1
  10. package/dist/cli/commands/gates.js +276 -0
  11. package/dist/cli/commands/init-seed.js +9 -1
  12. package/dist/cli/commands/init.js +34 -11
  13. package/dist/cli/commands/learning.js +12 -12
  14. package/dist/cli/commands/legacy-hooks.js +1 -1
  15. package/dist/cli/commands/memory.js +18 -0
  16. package/dist/cli/commands/uninstall.js +111 -5
  17. package/dist/cli.js +2 -0
  18. package/dist/commands/bug-analysis.md +21 -17
  19. package/dist/commands/code-review.md +65 -66
  20. package/dist/commands/debug.md +17 -17
  21. package/dist/commands/dynamic-build.md +104 -116
  22. package/dist/commands/dynamic-plan.md +13 -11
  23. package/dist/commands/dynamic-profile.md +9 -7
  24. package/dist/commands/dynamic-tickets.md +13 -11
  25. package/dist/commands/explore.md +15 -15
  26. package/dist/commands/implement.md +80 -154
  27. package/dist/commands/plan.md +34 -66
  28. package/dist/commands/release.md +11 -7
  29. package/dist/commands/research.md +14 -10
  30. package/dist/commands/resolve.md +44 -88
  31. package/dist/commands/self-review.md +44 -44
  32. package/dist/core/agent-models.js +47 -11
  33. package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
  34. package/dist/core/echo-value.js +42 -0
  35. package/dist/core/evidence-policy.js +17 -5
  36. package/dist/core/gate-level.js +213 -0
  37. package/dist/core/learning-queue-cleanup.js +1 -1
  38. package/dist/core/manifest.js +26 -10
  39. package/dist/core/migrations.js +63 -44
  40. package/dist/core/model-discovery.js +7 -1
  41. package/dist/core/plugins.js +14 -12
  42. package/dist/core/project-paths.js +3 -3
  43. package/dist/core/tracker.js +5 -33
  44. package/dist/hud/codex-usage.js +247 -0
  45. package/dist/hud/components/codex-quota.js +37 -0
  46. package/dist/hud/components/usage-quota.js +1 -1
  47. package/dist/hud/config.js +1 -0
  48. package/dist/hud/index.js +16 -3
  49. package/dist/hud/render.js +3 -1
  50. package/dist/learning-off/agents/code.md +7 -11
  51. package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
  52. package/dist/learning-off/agents/{code-fix.md → fix.md} +7 -8
  53. package/dist/learning-off/agents/{scrutinize.md → harden.md} +6 -6
  54. package/dist/learning-off/agents/{code-edit.md → refactor.md} +8 -9
  55. package/dist/learning-off/agents/review.md +2 -0
  56. package/dist/learning-off/commands/bug-analysis.md +9 -9
  57. package/dist/learning-off/commands/code-review.md +54 -59
  58. package/dist/learning-off/commands/debug.md +6 -10
  59. package/dist/learning-off/commands/dynamic-build.md +98 -112
  60. package/dist/learning-off/commands/dynamic-plan.md +5 -5
  61. package/dist/learning-off/commands/dynamic-profile.md +1 -1
  62. package/dist/learning-off/commands/dynamic-tickets.md +5 -5
  63. package/dist/learning-off/commands/explore.md +4 -8
  64. package/dist/learning-off/commands/implement.md +68 -146
  65. package/dist/learning-off/commands/plan.md +23 -58
  66. package/dist/learning-off/commands/release.md +4 -2
  67. package/dist/learning-off/commands/research.md +2 -2
  68. package/dist/learning-off/commands/resolve.md +33 -81
  69. package/dist/learning-off/commands/self-review.md +32 -36
  70. package/dist/skills/git/references/pr/check-merge-readiness.md +2 -0
  71. package/dist/skills/git/references/pr/update-pr-evidence.md +4 -6
  72. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +7 -9
  73. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +7 -15
  74. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +7 -15
  75. package/dist/targets/claude-code/installer.js +117 -84
  76. package/dist/targets/claude-code/learning-install.js +15 -3
  77. package/dist/targets/claude-code/tracker-servers.js +1 -1
  78. package/package.json +1 -1
  79. package/src/assets/agents/_partials/_code.mds +7 -8
  80. package/src/assets/agents/code.mds +4 -7
  81. package/src/assets/agents/{learning.md → curate.md} +8 -10
  82. package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
  83. package/src/assets/agents/evaluate.md +4 -4
  84. package/src/assets/agents/{code-fix.mds → fix.mds} +5 -5
  85. package/src/assets/agents/{scrutinize.mds → harden.mds} +6 -6
  86. package/src/assets/agents/{simplify.md → polish.md} +4 -4
  87. package/src/assets/agents/{code-edit.mds → refactor.mds} +6 -6
  88. package/src/assets/agents/review.mds +2 -0
  89. package/src/assets/agents/synthesize.md +2 -2
  90. package/src/assets/agents/test.md +2 -2
  91. package/src/assets/commands/_partials/_compliance.mds +1 -1
  92. package/src/assets/commands/_partials/_decisions.mds +26 -6
  93. package/src/assets/commands/_partials/_engine.mds +15 -15
  94. package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
  95. package/src/assets/commands/_partials/_knowledge.mds +14 -9
  96. package/src/assets/commands/_partials/_preamble.mds +4 -2
  97. package/src/assets/commands/_partials/_roster.mds +4 -4
  98. package/src/assets/commands/_partials/_settings.mds +2 -2
  99. package/src/assets/commands/_partials/_wave.mds +10 -17
  100. package/src/assets/commands/bug-analysis.mds +7 -7
  101. package/src/assets/commands/code-review.mds +51 -56
  102. package/src/assets/commands/debug.mds +2 -2
  103. package/src/assets/commands/dynamic-build.mds +67 -74
  104. package/src/assets/commands/implement.mds +65 -127
  105. package/src/assets/commands/plan.mds +21 -61
  106. package/src/assets/commands/release.mds +5 -1
  107. package/src/assets/commands/resolve.mds +28 -76
  108. package/src/assets/commands/self-review.mds +27 -27
  109. package/src/assets/mds/git/_ops_steps.mds +3 -3
  110. package/src/assets/mds/git/_pr.mds +6 -6
  111. package/src/assets/mds/tracker/_common.mds +23 -12
  112. package/src/assets/mds/tracker/_github.mds +2 -9
  113. package/src/assets/mds/tracker/_jira.mds +2 -15
  114. package/src/assets/mds/tracker/_linear.mds +2 -15
  115. package/src/assets/scripts/ci-wait.cjs +22 -4
  116. package/src/assets/scripts/claude-md-audit.cjs +66 -51
  117. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
  118. package/src/assets/scripts/hooks/background-memory-update +404 -95
  119. package/src/assets/scripts/hooks/capture-prompt +1 -1
  120. package/src/assets/scripts/hooks/capture-question +1 -1
  121. package/src/assets/scripts/hooks/capture-turn +1 -1
  122. package/src/assets/scripts/hooks/json-helper.cjs +13 -2
  123. package/src/assets/scripts/hooks/json-parse +18 -3
  124. package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
  125. package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -4
  126. package/src/assets/scripts/hooks/memory-permission +358 -0
  127. package/src/assets/scripts/hooks/preamble +1 -1
  128. package/src/assets/scripts/hooks/session-start-context +60 -47
  129. package/src/assets/scripts/hooks/session-start-memory +26 -5
  130. package/src/assets/scripts/lib/project-config.cjs +19 -4
  131. package/src/assets/scripts/resolve-settings.cjs +200 -32
  132. package/src/assets/scripts/verify-evidence.cjs +726 -9
  133. package/src/assets/skills/agent-references/SKILL.md +14 -6
  134. package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
  135. package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
  136. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
  137. package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
  138. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +1 -1
  139. package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
  140. package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
  141. package/src/assets/skills/apply-decisions/SKILL.md +6 -4
  142. package/src/assets/skills/docs-framework/SKILL.md +3 -3
  143. package/src/assets/skills/qa/SKILL.md +1 -1
  144. package/src/assets/skills/quality-gates/SKILL.md +7 -7
  145. package/src/assets/skills/quality-gates/references/report-template.md +7 -7
  146. /package/src/assets/skills/agent-references/references/{learning → curate}/maintain.md +0 -0
@@ -0,0 +1,276 @@
1
+ /**
2
+ * devflow gates — Show or set the machine's default gate level.
3
+ *
4
+ * The gate level is one of `full`, `standard` or `lean`. `--set` writes the MACHINE
5
+ * default, `features.gateLevel` in `~/.devflow/manifest.json`, and nothing else: not
6
+ * the repository's `project.json`, not the personal `.devflow/config.json` (no
7
+ * command writes that key), and no sentinel, re-arm or install step. `--status` shows
8
+ * the level that applies in the current directory, which layer decided it and what
9
+ * the machine records; it writes nothing.
10
+ *
11
+ * Three parts, so no business logic throws or prints:
12
+ * gatherGateStatus resolves the settings ONCE and reads the manifest raw, and
13
+ * returns a {@link GateStatus} that is a discriminated union
14
+ * renderGateStatus pure: one {@link GateStatus} to lines and warnings
15
+ * gatesCommand the thin shell: parse, call, print, set the exit code
16
+ *
17
+ * No code here chooses a level from a task or a diff: the level comes from the
18
+ * resolver (personal key, machine key, default) or from the word typed after `--set`.
19
+ */
20
+ import { Command } from 'commander';
21
+ import * as path from 'path';
22
+ import * as p from '@clack/prompts';
23
+ import color from 'picocolors';
24
+ import { DEFAULT_GATE_LEVEL, GATE_LEVELS, machineGateLevel, parseGateLevel, readGateManifest, writeMachineGateLevel, } from '../../core/gate-level.js';
25
+ import { describeEchoedValue } from '../../core/echo-value.js';
26
+ import { loadSettingsModule, settingsSourceFile, trackedConfigWarning, } from '../../core/evidence-policy.js';
27
+ import { getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
28
+ // ── Wording ────────────────────────────────────────────────────────────────────
29
+ /**
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.
32
+ */
33
+ export const GATE_LEVEL_MEANINGS = Object.freeze({
34
+ full: 'the most checking; any value that is not a level also reads as full',
35
+ standard: 'the default when no level is set',
36
+ lean: 'the least checking',
37
+ });
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.';
40
+ const MACHINE_MANIFEST_NAME = 'manifest.json';
41
+ const MACHINE_INVALID_LINE = `invalid (${MACHINE_MANIFEST_NAME}; reads as full)`;
42
+ const VALID_LEVELS = GATE_LEVELS.join(', ');
43
+ const VALID_LEVELS_OR = `${GATE_LEVELS.slice(0, -1).join(', ')}, or ${GATE_LEVELS[GATE_LEVELS.length - 1]}`;
44
+ /** The resolved settings, or null when the resolver is unusable or throws despite its contract. */
45
+ function resolveOnce(module, dir) {
46
+ try {
47
+ return module.resolveSettings({ dir });
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function manifestState(read) {
54
+ switch (read.kind) {
55
+ case 'absent': return 'absent';
56
+ case 'object': return 'readable';
57
+ case 'corrupt':
58
+ case 'rejected': return 'unreadable';
59
+ default: {
60
+ const exhaustive = read;
61
+ return exhaustive;
62
+ }
63
+ }
64
+ }
65
+ /** Why a fail-closed result failed, or null when the resolver names no reason. */
66
+ function failClosedReason(settings) {
67
+ return settings.unreadable === null ? null : `${settingsSourceFile(settings.unreadable)} is unreadable`;
68
+ }
69
+ /**
70
+ * Gather one {@link GateStatus}. Never throws, never writes.
71
+ *
72
+ * The settings resolve ONCE, and that one result feeds the level, the machine value
73
+ * and the tracked-config warning. The manifest is read with the gate reader, never
74
+ * with `readManifest()`, which heal-writes and returns null for a manifest it
75
+ * rejects: a read-only status must do neither. The manifest is read twice, once by
76
+ * the gate reader here and once by the resolver; a write between the two reads can
77
+ * mix two versions, which is acceptable because the result is display only and no
78
+ * action follows it.
79
+ */
80
+ export async function gatherGateStatus(deps) {
81
+ const manifestPath = path.join(deps.devflowDir, MACHINE_MANIFEST_NAME);
82
+ const read = await deps.readManifest(deps.devflowDir);
83
+ const base = { manifest: manifestState(read), manifestPath };
84
+ // The TypeScript reader's machine value, for a result that carries none of its own.
85
+ const stored = read.kind === 'object' || read.kind === 'rejected' ? machineGateLevel(read.raw) : null;
86
+ const loaded = deps.loadSettings();
87
+ const settings = loaded.ok ? resolveOnce(loaded.value, deps.dir) : null;
88
+ if (settings === null) {
89
+ return { ...base, kind: 'unloadable', machine: stored };
90
+ }
91
+ const trackedWarning = trackedConfigWarning(settings);
92
+ if (!settings.ok || settings.gateLevelSource === null) {
93
+ // Only a result that names an unreadable file carries the manifest's value; a
94
+ // fail-closed result without one (git did not answer) never read the manifest.
95
+ const machine = settings.unreadable !== null ? settings.gateLevelMachine : stored;
96
+ return { ...base, kind: 'fail-closed', reason: failClosedReason(settings), machine, trackedWarning };
97
+ }
98
+ return {
99
+ ...base,
100
+ kind: 'resolved',
101
+ level: settings.gateLevel,
102
+ source: settings.gateLevelSource,
103
+ invalid: settings.gateLevelInvalid,
104
+ machine: settings.gateLevelMachine,
105
+ trackedWarning,
106
+ };
107
+ }
108
+ /** The machine line: `none` when the manifest records no level or cannot be read (its own warning says which). */
109
+ function machineLine(machine) {
110
+ if (machine === 'invalid')
111
+ return `Machine: ${MACHINE_INVALID_LINE}`;
112
+ if (machine === null)
113
+ return 'Machine: none';
114
+ return `Machine: ${machine}`;
115
+ }
116
+ 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.`;
120
+ }
121
+ /**
122
+ * Render a {@link GateStatus}. Pure and total over every `kind`.
123
+ *
124
+ * A malformed level is reported by the layer that holds it and its state, never by
125
+ * its value: a raw config value never reaches a terminal.
126
+ */
127
+ export function renderGateStatus(status) {
128
+ const warnings = [];
129
+ const lines = [];
130
+ switch (status.kind) {
131
+ case 'resolved': {
132
+ lines.push(`Effective: ${status.level} (${status.source}${status.invalid ? ', invalid' : ''})`);
133
+ lines.push(machineLine(status.machine));
134
+ if (status.invalid && status.source === 'personal') {
135
+ warnings.push(`gateLevel in ${settingsSourceFile('personal')} is not one of ${VALID_LEVELS}, so ${status.level} is in effect. ` +
136
+ 'The value is not shown.');
137
+ }
138
+ if (status.trackedWarning !== null)
139
+ warnings.push(status.trackedWarning);
140
+ break;
141
+ }
142
+ case 'fail-closed': {
143
+ lines.push(`Effective: full (fail-closed: ${status.reason ?? 'reason unknown'})`);
144
+ lines.push(machineLine(status.machine));
145
+ if (status.trackedWarning !== null)
146
+ warnings.push(status.trackedWarning);
147
+ break;
148
+ }
149
+ case 'unloadable': {
150
+ lines.push('Effective: level unknown');
151
+ lines.push(machineLine(status.machine));
152
+ warnings.push('The settings resolver could not be loaded, so the level that applies is unknown.');
153
+ break;
154
+ }
155
+ default: {
156
+ const exhaustive = status;
157
+ return exhaustive;
158
+ }
159
+ }
160
+ const fromManifest = manifestWarning(status);
161
+ if (fromManifest !== null)
162
+ warnings.push(fromManifest);
163
+ return { lines, warnings };
164
+ }
165
+ /**
166
+ * Map the writer's result to a message and an exit code. Pure and total. Every
167
+ * refusal exits 1 and says nothing was written, except a failed write, which names
168
+ * the path and the error code.
169
+ */
170
+ export function describeGateWrite(result, level, manifestPath) {
171
+ switch (result.kind) {
172
+ case 'written':
173
+ return { kind: 'success', message: `Gate level: ${level}`, exitCode: 0 };
174
+ case 'unchanged':
175
+ return { kind: 'info', message: `Gate level: ${level} (unchanged)`, exitCode: 0 };
176
+ case 'not-installed':
177
+ return { kind: 'error', message: `No manifest found at ${manifestPath}. Run devflow init first.`, exitCode: 1 };
178
+ case 'unreadable':
179
+ return {
180
+ kind: 'error',
181
+ message: `Manifest at ${result.path} exists but cannot be read; nothing changed. Move it aside, then run devflow init.`,
182
+ exitCode: 1,
183
+ };
184
+ case 'write-failed':
185
+ return {
186
+ kind: 'error',
187
+ message: `Could not write the manifest at ${result.path} (${result.reason}); nothing changed.`,
188
+ exitCode: 1,
189
+ };
190
+ case 'invalid-level':
191
+ return {
192
+ kind: 'error',
193
+ message: `Unknown gate level: "${describeEchoedValue(level)}"; nothing changed. Valid levels: ${VALID_LEVELS}`,
194
+ exitCode: 1,
195
+ };
196
+ default: {
197
+ const exhaustive = result;
198
+ return exhaustive;
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Write the machine default and describe the result. Never throws. `now` is injected
204
+ * so a test fixes the `updatedAt` stamp.
205
+ */
206
+ export async function runGatesSet(devflowDir, level, now = () => new Date().toISOString()) {
207
+ const result = await writeMachineGateLevel(devflowDir, level, now);
208
+ return describeGateWrite(result, level, path.join(devflowDir, MACHINE_MANIFEST_NAME));
209
+ }
210
+ function printOutcome(outcome) {
211
+ switch (outcome.kind) {
212
+ case 'success':
213
+ p.log.success(outcome.message);
214
+ break;
215
+ case 'info':
216
+ p.log.info(outcome.message);
217
+ break;
218
+ case 'error':
219
+ p.log.error(outcome.message);
220
+ break;
221
+ default: {
222
+ const exhaustive = outcome.kind;
223
+ return exhaustive;
224
+ }
225
+ }
226
+ }
227
+ function levelsHelp() {
228
+ const width = Math.max(...GATE_LEVELS.map(level => level.length));
229
+ return GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_MEANINGS[level]}`).join('\n');
230
+ }
231
+ export const gatesCommand = new Command('gates')
232
+ .description('Show or set the machine\'s default gate level')
233
+ .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}`)
235
+ .addHelpText('after', `\nGate levels:\n${levelsHelp()}\n\n` +
236
+ `The default is ${DEFAULT_GATE_LEVEL}. --set writes only the machine default. A personal .devflow/config.json ` +
237
+ '"gateLevel" overrides it in one repository; no command writes that key.\n' +
238
+ GATE_LEVEL_EFFECT)
239
+ .action(async (options) => {
240
+ const devflowDir = getDevFlowDirectory();
241
+ if (!options.status && options.set === undefined) {
242
+ p.intro(color.bgCyan(color.white(' Gates ')));
243
+ p.note(`${color.cyan('devflow gates --status')} Show the gate level in effect here and the machine default\n` +
244
+ `${color.cyan('devflow gates --set <level>')} Set the machine default\n\n` +
245
+ `Valid levels:\n${levelsHelp()}`, 'Usage');
246
+ p.outro(color.dim(GATE_LEVEL_EFFECT));
247
+ return;
248
+ }
249
+ // Validate --set before any I/O, even beside --status: a bad word is rejected,
250
+ // never repaired, and exits 1 as it does in `devflow tracker` and `devflow compliance`.
251
+ const parsed = options.set === undefined ? undefined : parseGateLevel(options.set);
252
+ if (parsed !== undefined && !parsed.ok) {
253
+ p.log.error(parsed.error);
254
+ process.exitCode = 1;
255
+ return;
256
+ }
257
+ // --status wins over a valid --set and writes nothing, mirroring `devflow tracker`.
258
+ // With neither flag the usage printed above, so no --set here means --status alone.
259
+ if (options.status || parsed === undefined) {
260
+ const status = await gatherGateStatus({
261
+ devflowDir,
262
+ dir: process.cwd(),
263
+ loadSettings: loadSettingsModule,
264
+ readManifest: readGateManifest,
265
+ });
266
+ const rendered = renderGateStatus(status);
267
+ p.note(rendered.lines.join('\n'), 'Gate Level Status');
268
+ for (const warning of rendered.warnings)
269
+ p.log.warn(warning);
270
+ return;
271
+ }
272
+ const outcome = await runGatesSet(devflowDir, parsed.value);
273
+ printOutcome(outcome);
274
+ process.exitCode = outcome.exitCode;
275
+ });
276
+ //# sourceMappingURL=gates.js.map
@@ -27,6 +27,7 @@ export const FEATURE_DEFAULTS = {
27
27
  behavior: false,
28
28
  compliance: { enabled: false, frameworks: [] },
29
29
  tracker: { provider: DEFAULT_TRACKER_PROVIDER },
30
+ gateLevel: undefined,
30
31
  };
31
32
  // ── Helpers ───────────────────────────────────────────────────────────────────
32
33
  /**
@@ -60,7 +61,11 @@ export function resolveSeedFeatures(manifest) {
60
61
  const memory = manifest?.features.memory ?? FEATURE_DEFAULTS.memory;
61
62
  const knowledge = manifest?.features.knowledge ?? FEATURE_DEFAULTS.knowledge;
62
63
  const learning = manifest?.features.learning ?? FEATURE_DEFAULTS.learning;
63
- return { ambient, memory, hud, knowledge, learning, rules, proxy, behavior, compliance, tracker };
64
+ // The raw stored level, with no default operator: `??` would turn a stored `null`
65
+ // into the default, and init would then write the default over a value the
66
+ // fold reads as the strictest level.
67
+ const gateLevel = manifest?.features.gateLevel;
68
+ return { ambient, memory, hud, knowledge, learning, rules, proxy, behavior, compliance, tracker, gateLevel };
64
69
  }
65
70
  /**
66
71
  * Resolve the flag record for the init seed.
@@ -318,6 +323,9 @@ export function applyCliToggles(base, toggles) {
318
323
  behavior: toggles.behavior ?? base.behavior,
319
324
  compliance: toggles.compliance ?? base.compliance,
320
325
  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,
321
329
  };
322
330
  }
323
331
  //# sourceMappingURL=init-seed.js.map
@@ -9,6 +9,7 @@ import { getGitRoot } from '../../core/git.js';
9
9
  import { isSameLocation, withoutHomeRoots } from '../../core/same-location.js';
10
10
  import { pruneHookLogDirs, MAX_HOOK_LOG_DIRS } from '../../core/hook-log-dirs.js';
11
11
  import { getLedgerRoot } from '../../core/ledger-root.js';
12
+ import { describeEchoedValue } from '../../core/echo-value.js';
12
13
  import { installViaFileCopy, composeScripts } from '../../targets/claude-code/installer.js';
13
14
  import { formatOverlaySummary, formatSkillScopeSummary, formatTrackerAssetSummary, isPluginListUnchanged } from './install-report.js';
14
15
  import { convergeTrackerArtifacts } from '../../targets/claude-code/tracker-install.js';
@@ -18,7 +19,7 @@ import { LEGACY_SKILL_NAMES } from '../../targets/claude-code/legacy.js';
18
19
  import { detectPlatform, detectShell, getProfilePath, getSafeDeleteInfo, hasSafeDelete } from '../../core/safe-delete.js';
19
20
  import { generateSafeDeleteBlock, installToProfile, removeFromProfile, getInstalledVersion, SAFE_DELETE_BLOCK_VERSION } from '../../core/safe-delete-install.js';
20
21
  import { convergeAmbientHooks } from './ambient.js';
21
- import { convergeMemoryHooks, drainMemoryQueue } from './memory.js';
22
+ import { convergeMemoryHooks, drainMemoryQueue, workingMemoryNote } from './memory.js';
22
23
  import { addCaptureHooks, removeCaptureHooks } from './capture.js';
23
24
  import { removeDreamHook } from './legacy-hooks.js';
24
25
  import { addProxyHooks, removeProxyHooks, applyProxyEnv, stripProxyEnv, runProxyPreflight, buildRealPreflightDeps } from './proxy.js';
@@ -45,7 +46,7 @@ import { applyTrackerSentinel, effectiveTrackerServers, parseTrackerId, rearmTra
45
46
  import { formatTrackerSummary, shouldRunTrackerStep, runTrackerStep, buildClackTrackerPrompts, } from './tracker-prompts.js';
46
47
  import { shouldRunAttributionStep, runAttributionStep, buildClackAttributionPrompts, applyAttributionAnswer, attributionSeedFrom, } from './attribution-prompts.js';
47
48
  import { convergeFromManifest } from '../../targets/claude-code/compliance-install.js';
48
- import { convergeLearningVariants } from '../../targets/claude-code/learning-install.js';
49
+ import { convergeLearningVariants, installedVersionMatches, readRunningVersion } from '../../targets/claude-code/learning-install.js';
49
50
  import { auditAndRecordClaudeMd, formatClaudeMdAuditNote, formatClaudeMdAuditUnavailable, } from '../../core/claude-md-audit.js';
50
51
  import * as os from 'os';
51
52
  // Re-export pure functions for tests (canonical source is post-install.ts)
@@ -507,6 +508,23 @@ export function buildHudOnlyManifest(existing, version, now) {
507
508
  updatedAt: now,
508
509
  };
509
510
  }
511
+ /**
512
+ * The line `init --hud-only` prints when the version the manifest records is not the
513
+ * running version, or null when there is nothing to say. Pure.
514
+ *
515
+ * `--hud-only` refreshes the scripts under ~/.devflow and leaves the installed
516
+ * commands and agents alone, so after an upgrade or a downgrade the prompts and the
517
+ * scripts they call can disagree until a full init. The window is accepted; this
518
+ * line names it, in either direction, and ends with the remedy. A machine with no
519
+ * readable manifest records no version, so there is nothing to compare. The recorded
520
+ * version is hand-editable file content, so it prints through `describeEchoedValue`.
521
+ */
522
+ export function formatHudOnlySkew(recordedVersion, runningVersion) {
523
+ if (recordedVersion === null || installedVersionMatches(recordedVersion, runningVersion))
524
+ return null;
525
+ return `The installed prompts come from devflow ${describeEchoedValue(recordedVersion)} and this CLI is ${runningVersion ?? 'of unknown version'}. ` +
526
+ '--hud-only refreshed only the scripts, so run `devflow init` to update the prompts.';
527
+ }
510
528
  /**
511
529
  * Decide what `init --scope <value>` does now that there is one install scope.
512
530
  * Pure — the action performs the exit.
@@ -686,6 +704,9 @@ export const initCommand = new Command('init')
686
704
  await writeManifest(devflowDir, buildHudOnlyManifest(existingHudManifest, version, new Date().toISOString()));
687
705
  }
688
706
  catch { /* non-fatal */ }
707
+ const hudOnlySkew = formatHudOnlySkew(existingHudManifest?.version ?? null, await readRunningVersion());
708
+ if (hudOnlySkew !== null)
709
+ p.log.warn(hudOnlySkew);
689
710
  p.log.success('HUD installed');
690
711
  p.log.info(`Configure later: ${color.cyan('devflow hud --status')}`);
691
712
  p.outro(color.green('HUD-only install complete.'));
@@ -784,7 +805,7 @@ export const initCommand = new Command('init')
784
805
  'devflow-explore': 'codebase exploration + knowledge bases',
785
806
  'devflow-research': 'multi-type research with synthesis',
786
807
  'devflow-release': 'adaptive release with learned config',
787
- 'devflow-self-review': 'Simplify agent + Scrutinize agent',
808
+ 'devflow-self-review': 'Polish agent + Harden agent',
788
809
  'devflow-bug-analysis': 'proactive bug finding, post-pipeline',
789
810
  'devflow-typescript': 'TypeScript patterns',
790
811
  'devflow-react': 'React patterns',
@@ -935,6 +956,11 @@ export const initCommand = new Command('init')
935
956
  // alone, so init carries the prior value through untouched: it stamps the installed agents
936
957
  // with it and writes it back to the manifest. Absent stays absent (the default list).
937
958
  const trackerServers = seed.features.tracker.servers;
959
+ // The machine gate level (features.gateLevel) is set by `devflow gates --set` alone, so init
960
+ // carries the prior value through raw: no default, no narrowing, a stored `null` kept. The
961
+ // seed holds `undefined` only for an absent key or a --reset seed, and the full manifest
962
+ // write below then leaves the key out.
963
+ const gateLevel = seed.features.gateLevel;
938
964
  let enabledFlags = { ...seed.flags };
939
965
  // viewModeExplicit: true when --reset is passed; signals resolveFinalViewMode to let the
940
966
  // seed-time view-mode win over an externally-set value in settings.json.
@@ -1145,12 +1171,7 @@ export const initCommand = new Command('init')
1145
1171
  memoryEnabled = options.memory;
1146
1172
  }
1147
1173
  else {
1148
- p.note('Preserves session context across /clear, restarts, and context\n' +
1149
- 'compaction. Clear your session at any point and resume right\n' +
1150
- 'where you left off.\n\n' +
1151
- 'Runs a background agent on session stop that consumes additional\n' +
1152
- 'tokens. Consider skipping if token usage is a concern.\n' +
1153
- 'Applies to every project.', 'Working Memory');
1174
+ p.note(workingMemoryNote(), 'Working Memory');
1154
1175
  const memoryChoice = await p.confirm({
1155
1176
  message: 'Enable working memory? (Recommended)',
1156
1177
  initialValue: seed.features.memory,
@@ -1967,7 +1988,7 @@ export const initCommand = new Command('init')
1967
1988
  content = addCaptureHooks(cleanedForCapture, devflowDir);
1968
1989
  // Memory hooks — Stop (memory-worker), SessionStart (session-start-memory),
1969
1990
  // PreCompact — through the same transform `devflow memory --enable/--disable`
1970
- // uses (D-FEATURES-NARROW-ONLY). Learning agent (spawned via
1991
+ // uses (D-FEATURES-NARROW-ONLY). Curate agent (spawned via
1971
1992
  // session-start-context directive) handles decision/pitfall detection.
1972
1993
  // Knowledge is handled in-command via write-through (knowledge_writeback MDS partial).
1973
1994
  content = convergeMemoryHooks(content, memoryEnabled, devflowDir);
@@ -1980,7 +2001,7 @@ export const initCommand = new Command('init')
1980
2001
  content = addContextHook(cleanedForContext, devflowDir);
1981
2002
  // Legacy dream-worker hook cleanup — strip any stale spawn-dream-worker entry
1982
2003
  // left in settings.json by a prior install (session-start-context now spawns
1983
- // the Learning agent via directive).
2004
+ // the Curate agent via directive).
1984
2005
  content = removeDreamHook(content);
1985
2006
  // Strip Devflow-managed teammateMode ("auto"). User-set values (e.g. "tmux") are preserved.
1986
2007
  content = stripDevflowTeammateModeFromJson(content);
@@ -2324,6 +2345,8 @@ export const initCommand = new Command('init')
2324
2345
  // through parseTrackerId (CLI), the typed wizard select, or the seed, which
2325
2346
  // itself came through normalizeTrackerFeature on read.
2326
2347
  tracker: { provider: trackerProvider, ...(trackerServers !== undefined ? { servers: trackerServers } : {}) },
2348
+ // The prior machine gate level, raw (see where `gateLevel` is read above).
2349
+ ...(gateLevel !== undefined ? { gateLevel } : {}),
2327
2350
  },
2328
2351
  installedAt: existingManifest?.installedAt ?? now,
2329
2352
  updatedAt: now,
@@ -3,7 +3,7 @@ import { promises as fs } from 'fs';
3
3
  import * as path from 'path';
4
4
  import * as p from '@clack/prompts';
5
5
  import color from 'picocolors';
6
- import { getLearningDir, getLearningTuningConfigPath, } from '../../core/project-paths.js';
6
+ import { getLearningDir, getCurateTuningConfigPath, } from '../../core/project-paths.js';
7
7
  import { loadShippedAgentDefaults } from '../../core/agent-models.js';
8
8
  import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
9
9
  import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
@@ -263,7 +263,7 @@ async function handleRestore(anchor) {
263
263
  * The scope choices of `devflow learning --configure`.
264
264
  *
265
265
  * D-LEARNING-MODEL-PRECEDENCE: the session-start hook takes the first layer that
266
- * supplies a model — the project file, then a `devflow agents` Learning mapping,
266
+ * supplies a model — the project file, then a `devflow agents` Curate mapping,
267
267
  * then the global file. A global file therefore has no effect for a user who has
268
268
  * such a mapping, and its hint says so.
269
269
  */
@@ -272,24 +272,24 @@ export const CONFIGURE_SCOPE_OPTIONS = [
272
272
  {
273
273
  value: 'global',
274
274
  label: 'Global',
275
- hint: 'All projects (~/.devflow/learning.json); a devflow agents Learning mapping takes precedence over it',
275
+ hint: 'All projects (~/.devflow/learning.json); a devflow agents Curate mapping takes precedence over it',
276
276
  },
277
277
  ];
278
278
  /** The models `devflow learning --configure` offers, each with what it is good for. */
279
- const LEARNING_MODEL_CHOICES = [
279
+ const CURATE_MODEL_CHOICES = [
280
280
  { value: 'opus', label: 'Opus', note: 'highest quality for detection + curation judgment' },
281
281
  { value: 'sonnet', label: 'Sonnet', note: 'good balance of quality and speed' },
282
282
  { value: 'haiku', label: 'Haiku', note: 'fastest, lowest cost' },
283
283
  ];
284
284
  /**
285
- * D-LEARNING-SHIPPED-ROW: the model offered as "Recommended" is the one the Learning
285
+ * D-LEARNING-SHIPPED-ROW: the model offered as "Recommended" is the one the Curate
286
286
  * agent ships with, the `model:` line of its frontmatter. It is read from there and
287
287
  * written nowhere else, so a change of the shipped tier moves the recommendation with
288
288
  * it and this list never has to be edited. A shipped model outside the three offered
289
289
  * (or none) recommends nothing.
290
290
  */
291
- export function learningModelOptions(shippedModel) {
292
- return LEARNING_MODEL_CHOICES.map(choice => ({
291
+ export function curateModelOptions(shippedModel) {
292
+ return CURATE_MODEL_CHOICES.map(choice => ({
293
293
  value: choice.value,
294
294
  label: choice.label,
295
295
  hint: choice.value === shippedModel
@@ -299,10 +299,10 @@ export function learningModelOptions(shippedModel) {
299
299
  }
300
300
  async function handleConfigure() {
301
301
  p.intro(color.bgCyan(color.black(' Learning Configuration ')));
302
- const shippedModel = (await loadShippedAgentDefaults()).learning?.model;
302
+ const shippedModel = (await loadShippedAgentDefaults()).curate?.model;
303
303
  const model = await p.select({
304
304
  message: 'Model for decision detection',
305
- options: learningModelOptions(shippedModel),
305
+ options: curateModelOptions(shippedModel),
306
306
  });
307
307
  if (p.isCancel(model)) {
308
308
  p.cancel('Configuration cancelled.');
@@ -341,7 +341,7 @@ async function handleConfigure() {
341
341
  // main checkout in a linked worktree; the current directory outside git.
342
342
  const projectRoot = (await getLedgerRoot()) ?? process.cwd();
343
343
  const learningDir = getLearningDir(projectRoot);
344
- const projectConfigPath = getLearningTuningConfigPath(projectRoot);
344
+ const projectConfigPath = getCurateTuningConfigPath(projectRoot);
345
345
  // D-CLI-NO-SYMLINK (core/linked-path.ts): nothing is written through a .devflow,
346
346
  // a learning folder or a learning.json that is a symbolic link.
347
347
  const linked = await firstSymbolicLink([path.dirname(learningDir), learningDir, projectConfigPath]);
@@ -419,7 +419,7 @@ async function handleClear() {
419
419
  process.exitCode = 1;
420
420
  return;
421
421
  }
422
- // A mid-run Learning agent whose claimed batch vanishes stops without further
422
+ // A mid-run Curate agent whose claimed batch vanishes stops without further
423
423
  // writes — the desired outcome of clearing.
424
424
  const drain = await drainLearningQueue(ledgerRoot);
425
425
  p.log.success(`Cleared ${counted(cleared.value.cleared, 'observation')} no entry uses and kept ${cleared.value.kept} ` +
@@ -480,7 +480,7 @@ async function handleToggle(enabled) {
480
480
  return;
481
481
  }
482
482
  // Drain the current project's learning (decisions-detection) queue so stale
483
- // turns don't process on re-enable. A mid-run Learning agent whose claimed
483
+ // turns don't process on re-enable. A mid-run Curate agent whose claimed
484
484
  // batch vanishes aborts without changes — the desired outcome of disabling.
485
485
  const ledgerRoot = await getLedgerRoot();
486
486
  if (ledgerRoot) {
@@ -2,7 +2,7 @@ import { devflowHookOwner, hasHook, removeHooks, } from '../../targets/claude-co
2
2
  // ─── Dream worker hook cleanup ──────────────────────────────────────────────
3
3
  //
4
4
  // The spawn-dream-worker SessionStart hook belonged to the retired detached
5
- // dream worker. Decisions processing runs as the directive-spawned Learning agent
5
+ // dream worker. Decisions processing runs as the directive-spawned Curate agent
6
6
  // (session-start-context Section 2), which needs no hook registration of its
7
7
  // own. remove/has exist for upgrade cleanup: init and uninstall strip any
8
8
  // stale entry left in settings.json by a prior install.
@@ -56,6 +56,24 @@ const LEGACY_HOOK_SUFFIXES = {
56
56
  'sidecar-evaluate', 'dream-evaluate',
57
57
  ].map(runHookSuffix),
58
58
  };
59
+ /**
60
+ * The lowest Claude Code the memory worker runs on: the lowest version on which the
61
+ * worker's write was measured to be limited to its staged file (2026-10-15,
62
+ * docs/reference/platform-assumptions.md). Below it the worker does not run and logs one
63
+ * line. `background-memory-update` holds the same value as MEMORY_MIN_PERMISSION_VERSION;
64
+ * a test holds the two equal.
65
+ */
66
+ export const MEMORY_MIN_PERMISSION_VERSION = '2.1.269';
67
+ /** The note `devflow init` shows at its working-memory step. */
68
+ export function workingMemoryNote() {
69
+ return ('Preserves session context across /clear, restarts, and context\n' +
70
+ 'compaction. Clear your session at any point and resume right\n' +
71
+ 'where you left off.\n\n' +
72
+ 'Runs a background agent on session stop that consumes additional\n' +
73
+ 'tokens. Consider skipping if token usage is a concern.\n' +
74
+ `Needs Claude Code ${MEMORY_MIN_PERMISSION_VERSION} or newer.\n` +
75
+ 'Applies to every project.');
76
+ }
59
77
  /** D-EXACT-HOOK-OWNER: the current memory hook for `marker` (hooks.ts). */
60
78
  function isMemoryHook(marker) {
61
79
  return devflowHookOwner([marker]);