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.
- package/CHANGELOG.md +178 -1
- package/README.md +10 -6
- package/dist/agents/code.md +7 -11
- package/dist/agents/{diagnose.md → debug.md} +4 -4
- package/dist/agents/{code-fix.md → fix.md} +7 -8
- package/dist/agents/{scrutinize.md → harden.md} +6 -6
- package/dist/agents/{code-edit.md → refactor.md} +8 -9
- package/dist/agents/review.md +2 -0
- package/dist/cli/agents-view/render.js +1 -1
- package/dist/cli/commands/gates.js +276 -0
- package/dist/cli/commands/init-seed.js +9 -1
- package/dist/cli/commands/init.js +34 -11
- package/dist/cli/commands/learning.js +12 -12
- package/dist/cli/commands/legacy-hooks.js +1 -1
- package/dist/cli/commands/memory.js +18 -0
- package/dist/cli/commands/uninstall.js +111 -5
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +21 -17
- package/dist/commands/code-review.md +65 -66
- package/dist/commands/debug.md +17 -17
- package/dist/commands/dynamic-build.md +104 -116
- package/dist/commands/dynamic-plan.md +13 -11
- package/dist/commands/dynamic-profile.md +9 -7
- package/dist/commands/dynamic-tickets.md +13 -11
- package/dist/commands/explore.md +15 -15
- package/dist/commands/implement.md +80 -154
- package/dist/commands/plan.md +34 -66
- package/dist/commands/release.md +11 -7
- package/dist/commands/research.md +14 -10
- package/dist/commands/resolve.md +44 -88
- package/dist/commands/self-review.md +44 -44
- package/dist/core/agent-models.js +47 -11
- package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
- package/dist/core/echo-value.js +42 -0
- package/dist/core/evidence-policy.js +17 -5
- package/dist/core/gate-level.js +213 -0
- package/dist/core/learning-queue-cleanup.js +1 -1
- package/dist/core/manifest.js +26 -10
- package/dist/core/migrations.js +63 -44
- package/dist/core/model-discovery.js +7 -1
- package/dist/core/plugins.js +14 -12
- package/dist/core/project-paths.js +3 -3
- package/dist/core/tracker.js +5 -33
- package/dist/hud/codex-usage.js +247 -0
- package/dist/hud/components/codex-quota.js +37 -0
- package/dist/hud/components/usage-quota.js +1 -1
- package/dist/hud/config.js +1 -0
- package/dist/hud/index.js +16 -3
- package/dist/hud/render.js +3 -1
- package/dist/learning-off/agents/code.md +7 -11
- package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
- package/dist/learning-off/agents/{code-fix.md → fix.md} +7 -8
- package/dist/learning-off/agents/{scrutinize.md → harden.md} +6 -6
- package/dist/learning-off/agents/{code-edit.md → refactor.md} +8 -9
- package/dist/learning-off/agents/review.md +2 -0
- package/dist/learning-off/commands/bug-analysis.md +9 -9
- package/dist/learning-off/commands/code-review.md +54 -59
- package/dist/learning-off/commands/debug.md +6 -10
- package/dist/learning-off/commands/dynamic-build.md +98 -112
- package/dist/learning-off/commands/dynamic-plan.md +5 -5
- package/dist/learning-off/commands/dynamic-profile.md +1 -1
- package/dist/learning-off/commands/dynamic-tickets.md +5 -5
- package/dist/learning-off/commands/explore.md +4 -8
- package/dist/learning-off/commands/implement.md +68 -146
- package/dist/learning-off/commands/plan.md +23 -58
- package/dist/learning-off/commands/release.md +4 -2
- package/dist/learning-off/commands/research.md +2 -2
- package/dist/learning-off/commands/resolve.md +33 -81
- package/dist/learning-off/commands/self-review.md +32 -36
- package/dist/skills/git/references/pr/check-merge-readiness.md +2 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +4 -6
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +7 -9
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +7 -15
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +7 -15
- package/dist/targets/claude-code/installer.js +117 -84
- package/dist/targets/claude-code/learning-install.js +15 -3
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +7 -8
- package/src/assets/agents/code.mds +4 -7
- package/src/assets/agents/{learning.md → curate.md} +8 -10
- package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
- package/src/assets/agents/evaluate.md +4 -4
- package/src/assets/agents/{code-fix.mds → fix.mds} +5 -5
- package/src/assets/agents/{scrutinize.mds → harden.mds} +6 -6
- package/src/assets/agents/{simplify.md → polish.md} +4 -4
- package/src/assets/agents/{code-edit.mds → refactor.mds} +6 -6
- package/src/assets/agents/review.mds +2 -0
- package/src/assets/agents/synthesize.md +2 -2
- package/src/assets/agents/test.md +2 -2
- package/src/assets/commands/_partials/_compliance.mds +1 -1
- package/src/assets/commands/_partials/_decisions.mds +26 -6
- package/src/assets/commands/_partials/_engine.mds +15 -15
- package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
- package/src/assets/commands/_partials/_knowledge.mds +14 -9
- package/src/assets/commands/_partials/_preamble.mds +4 -2
- package/src/assets/commands/_partials/_roster.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_wave.mds +10 -17
- package/src/assets/commands/bug-analysis.mds +7 -7
- package/src/assets/commands/code-review.mds +51 -56
- package/src/assets/commands/debug.mds +2 -2
- package/src/assets/commands/dynamic-build.mds +67 -74
- package/src/assets/commands/implement.mds +65 -127
- package/src/assets/commands/plan.mds +21 -61
- package/src/assets/commands/release.mds +5 -1
- package/src/assets/commands/resolve.mds +28 -76
- package/src/assets/commands/self-review.mds +27 -27
- package/src/assets/mds/git/_ops_steps.mds +3 -3
- package/src/assets/mds/git/_pr.mds +6 -6
- package/src/assets/mds/tracker/_common.mds +23 -12
- package/src/assets/mds/tracker/_github.mds +2 -9
- package/src/assets/mds/tracker/_jira.mds +2 -15
- package/src/assets/mds/tracker/_linear.mds +2 -15
- package/src/assets/scripts/ci-wait.cjs +22 -4
- package/src/assets/scripts/claude-md-audit.cjs +66 -51
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
- package/src/assets/scripts/hooks/background-memory-update +404 -95
- package/src/assets/scripts/hooks/capture-prompt +1 -1
- package/src/assets/scripts/hooks/capture-question +1 -1
- package/src/assets/scripts/hooks/capture-turn +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +13 -2
- package/src/assets/scripts/hooks/json-parse +18 -3
- package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -4
- package/src/assets/scripts/hooks/memory-permission +358 -0
- package/src/assets/scripts/hooks/preamble +1 -1
- package/src/assets/scripts/hooks/session-start-context +60 -47
- package/src/assets/scripts/hooks/session-start-memory +26 -5
- package/src/assets/scripts/lib/project-config.cjs +19 -4
- package/src/assets/scripts/resolve-settings.cjs +200 -32
- package/src/assets/scripts/verify-evidence.cjs +726 -9
- package/src/assets/skills/agent-references/SKILL.md +14 -6
- package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
- package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +1 -1
- package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
- package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
- package/src/assets/skills/apply-decisions/SKILL.md +6 -4
- package/src/assets/skills/docs-framework/SKILL.md +3 -3
- package/src/assets/skills/qa/SKILL.md +1 -1
- package/src/assets/skills/quality-gates/SKILL.md +7 -7
- package/src/assets/skills/quality-gates/references/report-template.md +7 -7
- /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
|
-
|
|
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': '
|
|
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(
|
|
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).
|
|
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
|
|
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,
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
292
|
-
return
|
|
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()).
|
|
302
|
+
const shippedModel = (await loadShippedAgentDefaults()).curate?.model;
|
|
303
303
|
const model = await p.select({
|
|
304
304
|
message: 'Model for decision detection',
|
|
305
|
-
options:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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]);
|