devflow-kit 3.0.1 → 3.2.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 +49 -0
- package/README.md +1 -1
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +71 -17
- package/dist/cli/agents-view/state.js +42 -16
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +142 -51
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +162 -85
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +302 -136
- package/dist/cli/commands/memory.js +36 -15
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +183 -59
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/code-review.md +0 -2
- package/dist/commands/debug.md +14 -11
- package/dist/commands/dynamic-build.md +51 -47
- package/dist/commands/dynamic-plan.md +27 -7
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/explore.md +9 -3
- package/dist/commands/implement.md +20 -16
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +23 -3
- package/dist/commands/research.md +9 -3
- package/dist/commands/resolve.md +9 -12
- package/dist/commands/self-review.md +0 -2
- package/dist/core/agent-frontmatter.js +28 -3
- package/dist/core/agent-models.js +204 -42
- package/dist/core/agent-state.js +28 -6
- package/dist/core/ansi.js +2 -2
- package/dist/core/assets.js +1 -1
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +71 -13
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +49 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -81
- package/dist/core/learning-store.js +61 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +13 -8
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/queue-drain.js +31 -0
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +54 -22
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +59 -32
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +135 -45
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +15 -21
- package/src/assets/agents/design.md +4 -2
- package/src/assets/agents/diagnose.md +3 -1
- package/src/assets/agents/evaluate.md +4 -0
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +5 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +3 -1
- package/src/assets/agents/review.md +5 -3
- package/src/assets/agents/scrutinize.md +5 -1
- package/src/assets/agents/simplify.md +4 -0
- package/src/assets/agents/skim.md +4 -2
- package/src/assets/agents/synthesize.md +6 -0
- package/src/assets/agents/test.md +18 -10
- package/src/assets/agents/triage.md +11 -9
- package/src/assets/agents/validate.md +14 -10
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +16 -32
- package/src/assets/commands/_partials/_knowledge.mds +0 -2
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/code-review.mds +0 -2
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +18 -12
- package/src/assets/commands/dynamic-plan.mds +10 -4
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +19 -13
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +23 -3
- package/src/assets/commands/research.mds +9 -3
- package/src/assets/commands/resolve.mds +9 -10
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +3 -3
- package/src/assets/mds/tracker/_jira.mds +3 -3
- package/src/assets/mds/tracker/_linear.mds +3 -3
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
- package/src/assets/scripts/hooks/background-memory-update +97 -33
- package/src/assets/scripts/hooks/capture-prompt +4 -3
- package/src/assets/scripts/hooks/capture-question +4 -3
- package/src/assets/scripts/hooks/capture-turn +5 -20
- package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +345 -944
- package/src/assets/scripts/hooks/json-parse +25 -129
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/memory-worker +10 -0
- package/src/assets/scripts/hooks/pre-compact-memory +66 -14
- package/src/assets/scripts/hooks/preamble +9 -1
- package/src/assets/scripts/hooks/queue-append +55 -23
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +146 -45
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/src/assets/skills/test-driven-development/SKILL.md +6 -4
- package/dist/core/observation-io.js +0 -50
- package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* devflow agents — Manage per-agent model/effort assignments.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* handles cleanup in Promise resolve, not process.exit).
|
|
4
|
+
* CLI-layer module; core logic in src/core/agent-models.ts.
|
|
5
|
+
* Never process.exit() inside a finally-guarded scope: it skips the finally
|
|
6
|
+
* (terminal.ts handles cleanup in Promise resolve, not process.exit).
|
|
7
7
|
*
|
|
8
8
|
* Branding note: "subswitch" must NEVER appear in user-visible strings.
|
|
9
9
|
* User-facing vocabulary: "external model routing" / "Devflow proxy" /
|
|
@@ -14,14 +14,19 @@
|
|
|
14
14
|
* devflow agents --list → tabular list (safe in non-TTY)
|
|
15
15
|
* devflow agents --set <agent> --model <m> --effort <e>
|
|
16
16
|
* devflow agents --reset [--yes]
|
|
17
|
+
*
|
|
18
|
+
* D-WORKER-AGENTS: `memory` is a background worker, not an agent, and is
|
|
19
|
+
* settable here as `--set memory` and as a row after the agents in `--list` and
|
|
20
|
+
* the TUI. Its state is always 'worker'; its values are held to the worker
|
|
21
|
+
* domain (Claude models and effort levels only) by validateWorkerValue.
|
|
17
22
|
*/
|
|
18
23
|
import { Command } from 'commander';
|
|
19
24
|
import * as path from 'path';
|
|
20
25
|
import * as p from '@clack/prompts';
|
|
21
26
|
import color from 'picocolors';
|
|
22
|
-
import { EFFORT_LEVELS, LEGACY_AGENT_KEYS, readAgentMapping, saveAgentMapping, reapplyAgentMapping,
|
|
27
|
+
import { EFFORT_INHERIT, EFFORT_LEVELS, LEGACY_AGENT_KEYS, WORKER_AGENTS, agentOnlyMapping, isEffortLevel, isWorkerAgent, readAgentMapping, saveAgentMapping, reapplyAgentMapping, loadShippedAgentDefaults, readInstalledAgentNames, validateWorkerValue, } from '../../core/agent-models.js';
|
|
23
28
|
import { CLAUDE_MODEL_ALIASES, isDormantExternalModel, } from '../../core/external-models.js';
|
|
24
|
-
import { classifyAgentState, AGENT_STATE_LABELS, } from '../../core/agent-state.js';
|
|
29
|
+
import { classifyAgentState, formatEffortDisplay, AGENT_STATE_LABELS, } from '../../core/agent-state.js';
|
|
25
30
|
import { isValidModelName } from '../../core/agent-frontmatter.js';
|
|
26
31
|
import { isProxyEnabled } from '../../core/proxy-state.js';
|
|
27
32
|
import { getAllAgentNames } from '../../core/plugins.js';
|
|
@@ -47,26 +52,43 @@ function Err(error) {
|
|
|
47
52
|
* This preserves the configure-first-then-enable provisioning flow and keeps
|
|
48
53
|
* `--set` at zero subprocess cost (AC-P9: cache-only, 0 spawns).
|
|
49
54
|
*
|
|
55
|
+
* D-WORKER-AGENTS: when `agentName` is a worker (`memory`), model and effort are
|
|
56
|
+
* held to the worker value domain instead — validateWorkerValue, the function
|
|
57
|
+
* readAgentMapping applies on read — with no catalog lookup: a worker runs
|
|
58
|
+
* outside any session, so an external model, `inherit` as a model and `inherit`
|
|
59
|
+
* as an effort are all rejected.
|
|
60
|
+
*
|
|
61
|
+
* D-SHIPPED-EFFORT: for an agent, `--effort inherit` is accepted. It stores the
|
|
62
|
+
* sentinel that drops the agent's effort line; `--effort default` deletes the
|
|
63
|
+
* key so the shipped effort applies again.
|
|
64
|
+
*
|
|
50
65
|
* Returns Err when:
|
|
51
66
|
* - neither model nor effort is provided
|
|
52
67
|
* - model is unknown AND catalog is known
|
|
53
|
-
* - effort is unknown (not in EFFORT_LEVELS ∪ 'default')
|
|
68
|
+
* - effort is unknown (not in EFFORT_LEVELS ∪ 'default' ∪ 'inherit')
|
|
69
|
+
* - agentName is a worker and a value is outside the worker domain
|
|
54
70
|
*/
|
|
55
|
-
export function validateSetArgs(args, catalog = { known: false }) {
|
|
71
|
+
export function validateSetArgs(args, catalog = { known: false }, agentName) {
|
|
56
72
|
const { model, effort } = args;
|
|
57
73
|
if (model === undefined && effort === undefined) {
|
|
58
74
|
return Err('Specify at least one of --model or --effort');
|
|
59
75
|
}
|
|
76
|
+
const worker = agentName !== undefined && isWorkerAgent(agentName);
|
|
60
77
|
if (model !== undefined) {
|
|
61
78
|
// Charset gate: applied at every trust boundary regardless of catalog state.
|
|
62
79
|
// Rejects injection payloads (newlines, YAML metacharacters) before they can
|
|
63
|
-
// propagate to agent-models.json or agent frontmatter.
|
|
80
|
+
// propagate to agent-models.json or agent frontmatter. Allowlist
|
|
64
81
|
// what the callee actually consumes rather than denylist individual bad chars.
|
|
65
82
|
if (!isValidModelName(model)) {
|
|
66
83
|
return Err(`Invalid model name "${model}". Model names must start with an alphanumeric character ` +
|
|
67
84
|
`and contain only alphanumeric, dot, underscore, or hyphen characters (max 64 chars).`);
|
|
68
85
|
}
|
|
69
|
-
if (
|
|
86
|
+
if (worker) {
|
|
87
|
+
const check = validateWorkerValue('model', model);
|
|
88
|
+
if (!check.ok)
|
|
89
|
+
return Err(check.error);
|
|
90
|
+
}
|
|
91
|
+
else if (catalog.known) {
|
|
70
92
|
// Full validation against the discovered catalog.
|
|
71
93
|
const valid = ['default', ...CLAUDE_MODEL_ALIASES, ...catalog.selectableNames];
|
|
72
94
|
if (!valid.includes(model)) {
|
|
@@ -77,9 +99,16 @@ export function validateSetArgs(args, catalog = { known: false }) {
|
|
|
77
99
|
// warning fires at agents.ts call site if the proxy is off.
|
|
78
100
|
}
|
|
79
101
|
if (effort !== undefined) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
102
|
+
if (worker) {
|
|
103
|
+
const check = validateWorkerValue('effort', effort);
|
|
104
|
+
if (!check.ok)
|
|
105
|
+
return Err(check.error);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const valid = ['default', ...EFFORT_LEVELS, EFFORT_INHERIT];
|
|
109
|
+
if (!valid.includes(effort)) {
|
|
110
|
+
return Err(`Unknown effort "${effort}". Valid: ${valid.join(', ')}`);
|
|
111
|
+
}
|
|
83
112
|
}
|
|
84
113
|
}
|
|
85
114
|
return Ok(args);
|
|
@@ -106,10 +135,9 @@ export function applySetMapping(mapping, agentName, args) {
|
|
|
106
135
|
if (args.effort === 'default') {
|
|
107
136
|
delete existing.effort;
|
|
108
137
|
}
|
|
109
|
-
else {
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// the CLI entry type permissive; the assertion here is not a compensating cast.
|
|
138
|
+
else if (isEffortLevel(args.effort) || args.effort === EFFORT_INHERIT) {
|
|
139
|
+
// SetArgs.effort is a string to keep the CLI entry type permissive;
|
|
140
|
+
// validateSetArgs has already confirmed it is a level or the inherit sentinel.
|
|
113
141
|
existing.effort = args.effort;
|
|
114
142
|
}
|
|
115
143
|
}
|
|
@@ -133,23 +161,62 @@ export async function buildListRows(input) {
|
|
|
133
161
|
const entry = mapping.agents[name];
|
|
134
162
|
const configured = entry?.model ?? 'default';
|
|
135
163
|
const effort = entry?.effort ?? 'default';
|
|
136
|
-
const defaultModel = shippedDefaults[name] ?? 'unknown';
|
|
164
|
+
const defaultModel = shippedDefaults[name]?.model ?? 'unknown';
|
|
165
|
+
const shippedEffort = shippedDefaults[name]?.effort;
|
|
137
166
|
const installed = installedNames.has(name);
|
|
138
167
|
// inRegistry is always true here — agentNames comes from the registry.
|
|
139
168
|
const state = classifyAgentState({ configured, proxyEnabled, installed, inRegistry: true });
|
|
140
|
-
return { name, defaultModel, configured, effort, state };
|
|
169
|
+
return { name, defaultModel, configured, effort, shippedEffort, state };
|
|
141
170
|
});
|
|
142
171
|
return rows;
|
|
143
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Build the list rows of the background workers (D-WORKER-AGENTS), which follow
|
|
175
|
+
* the agent rows in `--list`.
|
|
176
|
+
*
|
|
177
|
+
* A worker has no installed agent file, so its row never consults the install
|
|
178
|
+
* directory: its state is always 'worker', never classified (it can be neither
|
|
179
|
+
* not installed nor dormant — validateSetArgs rejects an external model for a
|
|
180
|
+
* worker). DEFAULT is the model WORKER_AGENTS ships and EFFORT shows the shipped
|
|
181
|
+
* effort while unconfigured, the same convention as an agent row.
|
|
182
|
+
*
|
|
183
|
+
* Pure function, no I/O.
|
|
184
|
+
*/
|
|
185
|
+
export function buildWorkerListRows(mapping) {
|
|
186
|
+
return Object.entries(WORKER_AGENTS).map(([name, shipped]) => {
|
|
187
|
+
const entry = mapping.agents[name];
|
|
188
|
+
return {
|
|
189
|
+
name,
|
|
190
|
+
defaultModel: shipped.model,
|
|
191
|
+
configured: entry?.model ?? 'default',
|
|
192
|
+
effort: entry?.effort ?? 'default',
|
|
193
|
+
shippedEffort: shipped.effort,
|
|
194
|
+
state: 'worker',
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
}
|
|
144
198
|
// ---------------------------------------------------------------------------
|
|
145
199
|
// --list output formatting
|
|
146
200
|
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Width of the EFFORT column: the longest `default (<level>)` an unconfigured row
|
|
203
|
+
* can show, derived from EFFORT_LEVELS so a new level cannot truncate the cell.
|
|
204
|
+
*/
|
|
205
|
+
const EFFORT_COLUMN_WIDTH = Math.max(...EFFORT_LEVELS.map(level => formatEffortDisplay('default', level).length), EFFORT_INHERIT.length);
|
|
206
|
+
/** Narrowest the DEFAULT column of `--list` renders; it widens to fit a longer shipped default. */
|
|
207
|
+
const DEFAULT_COLUMN_MIN_WIDTH = 10;
|
|
208
|
+
/** Narrowest the CONFIGURED column of `--list` renders; it widens to fit a longer configured model. */
|
|
209
|
+
const CONFIGURED_COLUMN_MIN_WIDTH = 16;
|
|
147
210
|
export function formatListOutput(rows, proxyEnabled) {
|
|
148
211
|
const lines = [];
|
|
149
212
|
const AGENT_W = 20;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
213
|
+
// DEFAULT and CONFIGURED grow to the longest value in their column, so a full
|
|
214
|
+
// model identifier (the memory worker ships claude-sonnet-5-5; a user maps
|
|
215
|
+
// claude-sonnet-4-6) is shown whole, not cut to a fixed width and read as a
|
|
216
|
+
// different model.
|
|
217
|
+
const DEFAULT_W = Math.max(DEFAULT_COLUMN_MIN_WIDTH, ...rows.map(row => stripAnsi(row.defaultModel).length));
|
|
218
|
+
const CONFIGURED_W = Math.max(CONFIGURED_COLUMN_MIN_WIDTH, ...rows.map(row => stripAnsi(row.configured).length));
|
|
219
|
+
const EFFORT_W = EFFORT_COLUMN_WIDTH;
|
|
153
220
|
// Header
|
|
154
221
|
lines.push([
|
|
155
222
|
color.gray('AGENT'.padEnd(AGENT_W)),
|
|
@@ -176,6 +243,9 @@ export function formatListOutput(rows, proxyEnabled) {
|
|
|
176
243
|
case 'unknown':
|
|
177
244
|
stateStr = color.dim(AGENT_STATE_LABELS['unknown']);
|
|
178
245
|
break;
|
|
246
|
+
case 'worker':
|
|
247
|
+
stateStr = color.dim(AGENT_STATE_LABELS['worker']);
|
|
248
|
+
break;
|
|
179
249
|
default: {
|
|
180
250
|
const _ = row.state;
|
|
181
251
|
void _;
|
|
@@ -188,15 +258,18 @@ export function formatListOutput(rows, proxyEnabled) {
|
|
|
188
258
|
stripAnsi(row.name).padEnd(AGENT_W).slice(0, AGENT_W),
|
|
189
259
|
stripAnsi(row.defaultModel).padEnd(DEFAULT_W).slice(0, DEFAULT_W),
|
|
190
260
|
stripAnsi(row.configured).padEnd(CONFIGURED_W).slice(0, CONFIGURED_W),
|
|
191
|
-
stripAnsi(row.effort).padEnd(EFFORT_W).slice(0, EFFORT_W),
|
|
261
|
+
stripAnsi(formatEffortDisplay(row.effort, row.shippedEffort)).padEnd(EFFORT_W).slice(0, EFFORT_W),
|
|
192
262
|
stateStr,
|
|
193
263
|
].join(' '));
|
|
194
264
|
}
|
|
195
|
-
|
|
196
|
-
|
|
265
|
+
// The footer counts agent rows only: a worker row is neither installed nor
|
|
266
|
+
// not installed, and configuring it changes no agent (D-WORKER-AGENTS).
|
|
267
|
+
const agentRows = rows.filter(r => r.state !== 'worker');
|
|
268
|
+
const installed = agentRows.filter(r => r.state !== 'not-installed').length;
|
|
269
|
+
const configured = agentRows.filter(r => r.configured !== 'default' || r.effort !== 'default').length;
|
|
197
270
|
const proxyLabel = proxyEnabled ? color.green('enabled') : color.yellow('disabled');
|
|
198
271
|
lines.push('');
|
|
199
|
-
lines.push(`${installed}/${
|
|
272
|
+
lines.push(`${installed}/${agentRows.length} installed · ${configured} configured · proxy: ${proxyLabel}`);
|
|
200
273
|
return lines.join('\n');
|
|
201
274
|
}
|
|
202
275
|
// ---------------------------------------------------------------------------
|
|
@@ -241,7 +314,8 @@ async function buildTuiState(agentNames, mapping, shippedDefaults, proxyEnabled,
|
|
|
241
314
|
const entry = mapping.agents[name];
|
|
242
315
|
return buildRow({
|
|
243
316
|
name,
|
|
244
|
-
shippedDefault: shippedDefaults[name] ?? 'unknown',
|
|
317
|
+
shippedDefault: shippedDefaults[name]?.model ?? 'unknown',
|
|
318
|
+
shippedEffort: shippedDefaults[name]?.effort,
|
|
245
319
|
savedModel: entry?.model,
|
|
246
320
|
savedEffort: entry?.effort,
|
|
247
321
|
proxyEnabled,
|
|
@@ -251,12 +325,31 @@ async function buildTuiState(agentNames, mapping, shippedDefaults, proxyEnabled,
|
|
|
251
325
|
inRegistry: true,
|
|
252
326
|
});
|
|
253
327
|
});
|
|
254
|
-
//
|
|
328
|
+
// Worker rows (D-WORKER-AGENTS) follow the agents. A worker has no installed
|
|
329
|
+
// file and takes only the worker value domain, so it is built with its own
|
|
330
|
+
// shipped defaults and never classified as an orphan (state 'unknown').
|
|
331
|
+
for (const [workerName, shipped] of Object.entries(WORKER_AGENTS)) {
|
|
332
|
+
const entry = mapping.agents[workerName];
|
|
333
|
+
rows.push(buildRow({
|
|
334
|
+
name: workerName,
|
|
335
|
+
shippedDefault: shipped.model,
|
|
336
|
+
shippedEffort: shipped.effort,
|
|
337
|
+
savedModel: entry?.model,
|
|
338
|
+
savedEffort: entry?.effort,
|
|
339
|
+
proxyEnabled,
|
|
340
|
+
installed: false,
|
|
341
|
+
inRegistry: false,
|
|
342
|
+
worker: true,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
// Orphan rows: agent keys in agent-models.json not present in the registry.
|
|
255
346
|
// Appended at the end so they are visually separated from known agents.
|
|
256
|
-
|
|
347
|
+
// agentOnlyMapping leaves out the worker keys, which have their own rows above.
|
|
348
|
+
const agentEntries = agentOnlyMapping(mapping);
|
|
349
|
+
for (const orphanKey of Object.keys(agentEntries)) {
|
|
257
350
|
if (registrySet.has(orphanKey))
|
|
258
351
|
continue;
|
|
259
|
-
const entry =
|
|
352
|
+
const entry = agentEntries[orphanKey];
|
|
260
353
|
rows.push(buildRow({
|
|
261
354
|
name: orphanKey,
|
|
262
355
|
shippedDefault: 'unknown',
|
|
@@ -352,14 +445,15 @@ export const agentsCommand = new Command('agents')
|
|
|
352
445
|
.option('--list', 'List all agents with their current configuration')
|
|
353
446
|
.option('--set <agent>', 'Set model/effort for a specific agent')
|
|
354
447
|
.option('--model <model>', 'Model to assign (use with --set)')
|
|
355
|
-
.option('--effort <level>',
|
|
448
|
+
.option('--effort <level>', `Effort level to assign (use with --set): ${EFFORT_LEVELS.join(', ')}, ` +
|
|
449
|
+
`default (use the shipped effort), or ${EFFORT_INHERIT} (drop the effort line, follow the session)`)
|
|
356
450
|
.option('--reset', 'Clear all agent customisations and restore defaults')
|
|
357
451
|
.option('--yes', 'Skip confirmation prompt (use with --reset)')
|
|
358
452
|
.action(async (options) => {
|
|
359
453
|
const claudeDir = getClaudeDirectory();
|
|
360
454
|
const devflowDir = getDevFlowDirectory();
|
|
361
455
|
const installDir = path.join(claudeDir, 'agents', 'devflow');
|
|
362
|
-
// Cache directory for model discovery — authoritative path from cache.ts
|
|
456
|
+
// Cache directory for model discovery — authoritative path from cache.ts.
|
|
363
457
|
const cacheDir = modelCacheDir(devflowDir);
|
|
364
458
|
// Log path mirrors proxy.ts for unified proxy diagnostics.
|
|
365
459
|
const logPath = path.join(devflowDir, 'logs', 'proxy.log');
|
|
@@ -376,20 +470,23 @@ export const agentsCommand = new Command('agents')
|
|
|
376
470
|
// An agent with no shipped default renders a blank DEFAULT column and can
|
|
377
471
|
// never be reverted off an external model; surface the gap rather than
|
|
378
472
|
// letting the table imply the agent simply ships without one.
|
|
379
|
-
const shippedDefaults = await
|
|
473
|
+
const shippedDefaults = await loadShippedAgentDefaults(undefined, {
|
|
380
474
|
onWarning: (msg) => p.log.warn(msg),
|
|
381
475
|
});
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
agentNames,
|
|
476
|
+
// Agent rows in registry order, then the worker rows (D-WORKER-AGENTS).
|
|
477
|
+
const loadListRows = async () => {
|
|
478
|
+
const agentRows = await buildListRows({
|
|
479
|
+
agentNames: getAllAgentNames().sort(),
|
|
387
480
|
mapping,
|
|
388
481
|
installDir,
|
|
389
482
|
shippedDefaults,
|
|
390
483
|
proxyEnabled,
|
|
391
484
|
});
|
|
392
|
-
|
|
485
|
+
return [...agentRows, ...buildWorkerListRows(mapping)];
|
|
486
|
+
};
|
|
487
|
+
// ── --list ──────────────────────────────────────────────────────────────
|
|
488
|
+
if (options.list) {
|
|
489
|
+
process.stdout.write(formatListOutput(await loadListRows(), proxyEnabled) + '\n');
|
|
393
490
|
return;
|
|
394
491
|
}
|
|
395
492
|
// ── --reset ─────────────────────────────────────────────────────────────
|
|
@@ -447,10 +544,11 @@ export const agentsCommand = new Command('agents')
|
|
|
447
544
|
p.log.info(`Agent '${agentName}' has been renamed to '${canonical}' — using canonical name.`);
|
|
448
545
|
agentName = canonical;
|
|
449
546
|
}
|
|
450
|
-
// Validate agent name
|
|
547
|
+
// Validate agent name. D-WORKER-AGENTS: the workers are settable too, so a
|
|
548
|
+
// first `--set memory` is accepted although nothing in the mapping names it yet.
|
|
451
549
|
const knownAgents = getAllAgentNames();
|
|
452
550
|
const knownMapping = Object.keys(mapping.agents);
|
|
453
|
-
const allKnown = new Set([...knownAgents, ...knownMapping]);
|
|
551
|
+
const allKnown = new Set([...knownAgents, ...Object.keys(WORKER_AGENTS), ...knownMapping]);
|
|
454
552
|
if (!allKnown.has(agentName)) {
|
|
455
553
|
p.log.error(`Unknown agent "${agentName}". Valid: ${[...allKnown].sort().join(', ')}`);
|
|
456
554
|
process.exitCode = 1;
|
|
@@ -461,7 +559,7 @@ export const agentsCommand = new Command('agents')
|
|
|
461
559
|
// dormancy warning below fires if needed). This preserves the
|
|
462
560
|
// configure-first-then-enable provisioning flow.
|
|
463
561
|
const setCatalog = getExternalModelsCached(cacheDir);
|
|
464
|
-
const validation = validateSetArgs({ model: options.model, effort: options.effort }, setCatalog);
|
|
562
|
+
const validation = validateSetArgs({ model: options.model, effort: options.effort }, setCatalog, agentName);
|
|
465
563
|
if (!validation.ok) {
|
|
466
564
|
p.log.error(validation.error);
|
|
467
565
|
process.exitCode = 1;
|
|
@@ -510,15 +608,7 @@ export const agentsCommand = new Command('agents')
|
|
|
510
608
|
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
511
609
|
if (!isInteractive) {
|
|
512
610
|
// Non-TTY: print list and exit 1 with note
|
|
513
|
-
|
|
514
|
-
const rows = await buildListRows({
|
|
515
|
-
agentNames,
|
|
516
|
-
mapping,
|
|
517
|
-
installDir,
|
|
518
|
-
shippedDefaults,
|
|
519
|
-
proxyEnabled,
|
|
520
|
-
});
|
|
521
|
-
process.stdout.write(formatListOutput(rows, proxyEnabled) + '\n');
|
|
611
|
+
process.stdout.write(formatListOutput(await loadListRows(), proxyEnabled) + '\n');
|
|
522
612
|
process.stderr.write('Note: interactive view requires a terminal. Use --list for non-TTY output.\n');
|
|
523
613
|
process.exitCode = 1;
|
|
524
614
|
return;
|
|
@@ -569,7 +659,8 @@ export const agentsCommand = new Command('agents')
|
|
|
569
659
|
// Lazy-import terminal to avoid loading readline/tty in non-TTY paths
|
|
570
660
|
const { runAgentsTui } = await import('../agents-view/terminal.js');
|
|
571
661
|
// Wrap: runTui rejects on initial-render failure or handler throw.
|
|
572
|
-
// On rejection: log and bail — no partial write
|
|
662
|
+
// On rejection: log and bail — no partial write; set process.exitCode
|
|
663
|
+
// rather than calling process.exit(), which would skip pending cleanup.
|
|
573
664
|
let result;
|
|
574
665
|
try {
|
|
575
666
|
result = await runAgentsTui(tuiState);
|
|
@@ -17,7 +17,7 @@ const ORCHESTRATOR_HOOK_MARKER = 'session-start-orchestrator';
|
|
|
17
17
|
* hook is devflow's when its command ENDS in one of these, under any directory — so
|
|
18
18
|
* installs made under a custom or repo-local devflow directory are still recognised —
|
|
19
19
|
* and never because it merely contains a marker word: a user's
|
|
20
|
-
* `~/bin/preamble-logger.sh` or `echo preamble` is theirs
|
|
20
|
+
* `~/bin/preamble-logger.sh` or `echo preamble` is theirs. The
|
|
21
21
|
* legacy forms are the pre-preamble `ambient-prompt` hook (first a bare
|
|
22
22
|
* `ambient-prompt.sh`, then through `run-hook`) and the retired
|
|
23
23
|
* `session-start-classification` hook, both still swept on enable and disable.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Attribution prompt helpers for devflow init.
|
|
3
3
|
*
|
|
4
|
-
* CLI-layer module
|
|
4
|
+
* CLI-layer module: prompt-rendering logic lives in src/cli/commands/,
|
|
5
5
|
* core business logic stays in src/core/.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* The gate is an exported pure predicate with an explicit isTTY guard,
|
|
8
8
|
* so --recommended (flag, no prompt) and the non-TTY fallback keep their promptless
|
|
9
9
|
* contracts and the reachability rule is unit-testable without a terminal.
|
|
10
|
-
*
|
|
10
|
+
* runAttributionStep never calls process.exit() or throws — callers
|
|
11
11
|
* own the cancel idiom (p.cancel + process.exit(0)), keeping try/finally cleanup safe.
|
|
12
12
|
*
|
|
13
13
|
* D27: suppress-attribution flag — gates Claude Code's AI-attribution injection.
|
|
@@ -37,7 +37,7 @@ import { clackNote, clackSelect } from './prompt-io.js';
|
|
|
37
37
|
* Gating on the mode name is sound here because 'advanced' is only ever resolved on an
|
|
38
38
|
* interactive path (the Advanced branch exit-1s on non-TTY), and the explicit isTTY guard
|
|
39
39
|
* keeps the promptless contracts of --recommended and the non-TTY fallback pinned
|
|
40
|
-
* regardless
|
|
40
|
+
* regardless.
|
|
41
41
|
*
|
|
42
42
|
* There is no CLI override for attribution — it is toggled post-install via
|
|
43
43
|
* `devflow flags --enable/--disable suppress-attribution`.
|
|
@@ -65,7 +65,7 @@ export function buildClackAttributionPrompts() {
|
|
|
65
65
|
*
|
|
66
66
|
* Returns true only when `suppress-attribution` is explicitly set to the boolean
|
|
67
67
|
* true — undefined, null, and false all map to false, giving `p.select` a real
|
|
68
|
-
* boolean rather than an unchecked cast
|
|
68
|
+
* boolean rather than an unchecked cast.
|
|
69
69
|
*
|
|
70
70
|
* Pure function — no side effects, fully testable without a TTY.
|
|
71
71
|
*/
|
|
@@ -94,14 +94,14 @@ export function applyAttributionAnswer(flags, outcome) {
|
|
|
94
94
|
* 1. Note — "Current setting: …" header with context about what the flag does.
|
|
95
95
|
* 2. Enable select — labeled Yes / No with hints (seeded from prior state);
|
|
96
96
|
* p.select is immune to Enter-through muscle memory while still preserving
|
|
97
|
-
* the seeded value (ambient-prompt style
|
|
97
|
+
* the seeded value (ambient-prompt style).
|
|
98
98
|
*
|
|
99
99
|
* Returns:
|
|
100
100
|
* {kind:'resolved', suppress, messages} — step completed; `suppress` is the chosen
|
|
101
101
|
* boolean; `messages` are emitted by the caller.
|
|
102
102
|
* {kind:'cancelled'} — user pressed Escape; caller runs p.cancel + process.exit(0).
|
|
103
103
|
*
|
|
104
|
-
* Invariants
|
|
104
|
+
* Invariants:
|
|
105
105
|
* - Never calls process.exit(), never throws.
|
|
106
106
|
* - All I/O is routed through the `prompts` parameter (injectable for tests).
|
|
107
107
|
*/
|
|
@@ -109,7 +109,7 @@ export async function runAttributionStep(opts) {
|
|
|
109
109
|
const { seed, prompts } = opts;
|
|
110
110
|
const currentStr = seed ? 'suppressed' : 'shown (default)';
|
|
111
111
|
// security-02: name the destructive branch (Yes) BEFORE the user consents.
|
|
112
|
-
//
|
|
112
|
+
// Turning the flag ON replaces any existing attribution
|
|
113
113
|
// value, including a custom one — this is deliberate. Only the exact
|
|
114
114
|
// devflow-managed shape {"commit":"","pr":""} is removed on disable.
|
|
115
115
|
// security-04: surface the org AI-disclosure-policy dimension as a note (not a gate).
|
|
@@ -76,7 +76,7 @@ export function removeCaptureHooks(input) {
|
|
|
76
76
|
const settings = typeof input === 'string' ? JSON.parse(input) : structuredClone(input);
|
|
77
77
|
let changed = false;
|
|
78
78
|
for (const [hookType, marker] of Object.entries(CAPTURE_HOOK_CONFIG)) {
|
|
79
|
-
// Evaluate every removal — never short-circuit
|
|
79
|
+
// Evaluate every removal — never short-circuit.
|
|
80
80
|
const removed = removeHooks(settings, hookType, isCaptureHook(marker));
|
|
81
81
|
changed = changed || removed;
|
|
82
82
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compliance prompt helpers for devflow init.
|
|
3
3
|
*
|
|
4
|
-
* CLI-layer module
|
|
4
|
+
* CLI-layer module: prompt-rendering logic lives in src/cli/commands/,
|
|
5
5
|
* core business logic stays in src/core/compliance.ts.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Every wizard gate keys on `modePromptShown` (was the Setup-mode
|
|
8
8
|
* p.select prompt actually shown?), never on the mode name, so --recommended (flag,
|
|
9
9
|
* no prompt) and the non-TTY fallback preserve their promptless contracts.
|
|
10
|
-
*
|
|
10
|
+
* runComplianceStep never calls process.exit() or throws — callers
|
|
11
11
|
* own the cancel idiom (p.cancel + process.exit(0)), keeping try/finally cleanup safe.
|
|
12
12
|
*
|
|
13
13
|
* Shared DI seam (PromptOutcome, WizardPromptIO, clackNote, clackSelect) lives in
|
|
@@ -53,7 +53,7 @@ export function formatComplianceSummary(enabled, frameworks) {
|
|
|
53
53
|
/**
|
|
54
54
|
* Determines whether the compliance wizard step should run for a given init invocation.
|
|
55
55
|
*
|
|
56
|
-
* Gate table (
|
|
56
|
+
* Gate table (key on modePromptShown, never on the mode name):
|
|
57
57
|
*
|
|
58
58
|
* --recommended flag / !isTTY fallback → no (promptless contract preserved)
|
|
59
59
|
* Interactive mode-prompt → Recommended → yes (modePromptShown=true)
|
|
@@ -106,7 +106,7 @@ export function buildClackCompliancePrompts() {
|
|
|
106
106
|
* 1. Note — "Current setting: …" header then the framework catalogue.
|
|
107
107
|
* 2. Enable select — labeled Yes / No with hints (seeded from prior state);
|
|
108
108
|
* p.select is immune to Enter-through muscle memory while still preserving
|
|
109
|
-
* the seeded value (ambient-prompt style
|
|
109
|
+
* the seeded value (ambient-prompt style).
|
|
110
110
|
* 3. If Yes — framework multiselect (seeded, required:false).
|
|
111
111
|
*
|
|
112
112
|
* Returns:
|
|
@@ -114,7 +114,7 @@ export function buildClackCompliancePrompts() {
|
|
|
114
114
|
* ComplianceFeatureState; `messages` are emitted by the caller.
|
|
115
115
|
* {kind:'cancelled'} — user pressed Escape; caller runs p.cancel + process.exit(0).
|
|
116
116
|
*
|
|
117
|
-
* Invariants
|
|
117
|
+
* Invariants:
|
|
118
118
|
* - Never calls process.exit(), never throws.
|
|
119
119
|
* - Returned arrays never alias seed arrays (defensive copies throughout).
|
|
120
120
|
* - All I/O is routed through the `prompts` parameter (injectable for tests).
|
|
@@ -141,7 +141,7 @@ export async function runComplianceStep(opts) {
|
|
|
141
141
|
if (!enabled) {
|
|
142
142
|
return {
|
|
143
143
|
kind: 'resolved',
|
|
144
|
-
// Never alias seed.frameworks — defensive copy
|
|
144
|
+
// Never alias seed.frameworks — defensive copy.
|
|
145
145
|
state: { enabled: false, frameworks: [...seed.frameworks] },
|
|
146
146
|
messages: [{
|
|
147
147
|
level: 'info',
|
|
@@ -153,7 +153,7 @@ export async function runComplianceStep(opts) {
|
|
|
153
153
|
const multiselectOutcome = await prompts.multiselect({
|
|
154
154
|
message: FRAMEWORK_SELECT_MESSAGE,
|
|
155
155
|
options: frameworkChoices(),
|
|
156
|
-
// Defensive copy: never alias seed.frameworks
|
|
156
|
+
// Defensive copy: never alias seed.frameworks.
|
|
157
157
|
initialValues: [...seed.frameworks],
|
|
158
158
|
required: false,
|
|
159
159
|
});
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* devflow compliance — Enable, disable, set, and check status of the compliance feature.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* CLI-layer module; pure helpers in src/core/compliance.ts,
|
|
5
5
|
* I/O orchestration in src/targets/claude-code/compliance-install.ts.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Compliance is manifest-group (like proxy), not config.json-gated.
|
|
7
|
+
* Per-artifact failures are warn-not-throw, so one failed artifact never aborts the rest.
|
|
8
|
+
* Enable/disable each converge BOTH artifacts unconditionally, so neither is left
|
|
9
|
+
* half-converged.
|
|
9
10
|
* The evidence-policy lines (--status, and the --enable/--set suggestion) come
|
|
10
11
|
* from src/core/evidence-policy.ts, the seam onto the package's own resolvers;
|
|
11
12
|
* the CLI prints the keys to add to .devflow/project.json and never writes it
|
|
12
|
-
* (
|
|
13
|
+
* (the file is team-owned).
|
|
13
14
|
*/
|
|
14
15
|
import { Command } from 'commander';
|
|
15
16
|
import { promises as fs } from 'fs';
|
|
@@ -277,7 +278,7 @@ export const complianceCommand = new Command('compliance')
|
|
|
277
278
|
action = 'disable';
|
|
278
279
|
}
|
|
279
280
|
const resolved = resolveComplianceCliAction(current, action, setFrameworks);
|
|
280
|
-
// Converge artifacts (
|
|
281
|
+
// Converge artifacts (always converge, never short-circuit)
|
|
281
282
|
await convergeFromManifest({
|
|
282
283
|
claudeDir,
|
|
283
284
|
devflowDir,
|
|
@@ -310,7 +311,7 @@ export const complianceCommand = new Command('compliance')
|
|
|
310
311
|
'run `devflow rules --enable` to install the stamped rule'));
|
|
311
312
|
}
|
|
312
313
|
// Suggest the team file compliance now implies. Printed, never written:
|
|
313
|
-
// .devflow/project.json is team-owned (D-POLICY-NO-WRITE
|
|
314
|
+
// .devflow/project.json is team-owned (D-POLICY-NO-WRITE).
|
|
314
315
|
if (resolved.nextState.enabled) {
|
|
315
316
|
const policyModule = loadEvidencePolicyModule();
|
|
316
317
|
const settingsModule = loadSettingsModule();
|