devflow-kit 3.1.0 → 3.3.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 +52 -0
- package/README.md +2 -2
- package/dist/cli/agents-view/render.js +69 -15
- package/dist/cli/agents-view/state.js +40 -14
- package/dist/cli/commands/agents.js +135 -45
- package/dist/cli/commands/init.js +128 -53
- package/dist/cli/commands/learning.js +61 -13
- package/dist/cli/commands/memory.js +35 -14
- package/dist/cli/commands/uninstall.js +163 -39
- package/dist/commands/code-review.md +1 -3
- package/dist/commands/debug.md +15 -12
- package/dist/commands/dynamic-build.md +172 -135
- package/dist/commands/dynamic-plan.md +9 -3
- package/dist/commands/explore.md +10 -4
- package/dist/commands/implement.md +149 -145
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +8 -2
- package/dist/commands/research.md +8 -2
- package/dist/commands/resolve.md +28 -19
- package/dist/commands/self-review.md +16 -13
- package/dist/core/agent-frontmatter.js +25 -0
- package/dist/core/agent-models.js +201 -36
- package/dist/core/agent-state.js +27 -5
- package/dist/core/assets.js +1 -1
- package/dist/core/feature-config.js +68 -10
- package/dist/core/flags.js +24 -0
- package/dist/core/learning-queue-cleanup.js +10 -11
- package/dist/core/learning-tuning-config.js +8 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/plugins.js +16 -5
- package/dist/core/queue-drain.js +31 -0
- package/dist/hud/components/learning-counts.js +54 -8
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +1 -1
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +1 -1
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +1 -1
- package/dist/targets/claude-code/installer.js +36 -9
- package/dist/targets/claude-code/post-install.js +128 -38
- package/package.json +1 -1
- package/src/assets/agents/code.md +85 -35
- package/src/assets/agents/design.md +12 -0
- package/src/assets/agents/diagnose.md +18 -11
- package/src/assets/agents/evaluate.md +17 -24
- package/src/assets/agents/knowledge.md +7 -3
- package/src/assets/agents/learning.md +4 -6
- package/src/assets/agents/research.md +21 -0
- package/src/assets/agents/review.md +12 -0
- package/src/assets/agents/scrutinize.md +37 -9
- package/src/assets/agents/simplify.md +24 -0
- package/src/assets/agents/skim.md +6 -2
- package/src/assets/agents/synthesize.md +18 -0
- package/src/assets/agents/test.md +19 -11
- package/src/assets/agents/triage.md +8 -0
- package/src/assets/agents/validate.md +20 -11
- package/src/assets/commands/_partials/_engine.mds +36 -55
- package/src/assets/commands/_partials/_knowledge.mds +1 -3
- package/src/assets/commands/_partials/_plan_contract.mds +1 -1
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/_partials/_wave.mds +8 -6
- package/src/assets/commands/code-review.mds +1 -3
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +126 -72
- package/src/assets/commands/dynamic-plan.mds +7 -1
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +147 -141
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +8 -2
- package/src/assets/commands/research.mds +8 -2
- package/src/assets/commands/resolve.mds +27 -16
- package/src/assets/commands/self-review.mds +15 -10
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +2 -2
- package/src/assets/mds/tracker/_jira.mds +2 -2
- package/src/assets/mds/tracker/_linear.mds +2 -2
- package/src/assets/scripts/ci-wait.cjs +636 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
- package/src/assets/scripts/hooks/background-memory-update +356 -17
- 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 +4 -3
- package/src/assets/scripts/hooks/ensure-devflow-init +13 -1
- package/src/assets/scripts/hooks/ensure-root-gitignore +122 -10
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/json-helper.cjs +12 -145
- package/src/assets/scripts/hooks/json-parse +24 -129
- package/src/assets/scripts/hooks/lib/learning-store.cjs +169 -64
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +1 -1
- 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 +53 -21
- package/src/assets/scripts/hooks/session-start-context +108 -29
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/release-trace.cjs +27 -10
- package/src/assets/skills/accessibility/SKILL.md +1 -1
- package/src/assets/skills/apply-decisions/SKILL.md +12 -82
- package/src/assets/skills/apply-feature-knowledge/SKILL.md +8 -42
- package/src/assets/skills/architecture/SKILL.md +1 -1
- package/src/assets/skills/boundary-validation/SKILL.md +1 -1
- package/src/assets/skills/complexity/SKILL.md +1 -1
- package/src/assets/skills/compliance/SKILL.md +1 -1
- package/src/assets/skills/consistency/SKILL.md +1 -1
- package/src/assets/skills/database/SKILL.md +1 -1
- package/src/assets/skills/dependencies/SKILL.md +1 -1
- package/src/assets/skills/dependency-research/SKILL.md +3 -6
- package/src/assets/skills/design-review/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +1 -1
- package/src/assets/skills/documentation/SKILL.md +1 -1
- package/src/assets/skills/gap-analysis/SKILL.md +1 -1
- package/src/assets/skills/git/SKILL.md +1 -1
- package/src/assets/skills/go/SKILL.md +1 -1
- package/src/assets/skills/java/SKILL.md +1 -1
- package/src/assets/skills/patterns/SKILL.md +1 -1
- package/src/assets/skills/performance/SKILL.md +1 -1
- package/src/assets/skills/python/SKILL.md +1 -1
- package/src/assets/skills/qa/SKILL.md +1 -3
- package/src/assets/skills/quality-gates/SKILL.md +9 -12
- package/src/assets/skills/quality-gates/references/report-template.md +20 -20
- package/src/assets/skills/react/SKILL.md +1 -1
- package/src/assets/skills/regression/SKILL.md +1 -1
- package/src/assets/skills/reliability/SKILL.md +1 -1
- package/src/assets/skills/research-codebase/SKILL.md +1 -1
- package/src/assets/skills/research-competitor/SKILL.md +1 -1
- package/src/assets/skills/research-external/SKILL.md +1 -1
- package/src/assets/skills/research-technology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/rust/SKILL.md +1 -1
- package/src/assets/skills/security/SKILL.md +1 -1
- package/src/assets/skills/software-design/SKILL.md +1 -1
- package/src/assets/skills/test-driven-development/SKILL.md +15 -33
- package/src/assets/skills/testing/SKILL.md +1 -1
- package/src/assets/skills/typescript/SKILL.md +1 -1
- package/src/assets/skills/ui-design/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +3 -55
- package/src/assets/skills/worktree-support/references/discovery.md +48 -0
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
|
@@ -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,16 +52,28 @@ 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
|
|
@@ -66,7 +83,12 @@ export function validateSetArgs(args, catalog = { known: false }) {
|
|
|
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,7 +445,8 @@ 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) => {
|
|
@@ -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;
|
|
@@ -12,7 +12,7 @@ import { getLedgerRoot } from '../../core/ledger-root.js';
|
|
|
12
12
|
import { installViaFileCopy, composeScripts } from '../../targets/claude-code/installer.js';
|
|
13
13
|
import { formatOverlaySummary, formatSkillScopeSummary, formatTrackerAssetSummary, isPluginListUnchanged } from './install-report.js';
|
|
14
14
|
import { convergeTrackerArtifacts } from '../../targets/claude-code/tracker-install.js';
|
|
15
|
-
import { installSettings, installManagedSettings, installClaudeignore, discoverProjectGitRoots, ensureDevflowGitignore, applyUserSecurityDenyList, detectDenyState, resolveSecurityAction, assertHistoricalDenySuperset, loadTemplateDenyEntries, stripUserSecurityDenyList, } from '../../targets/claude-code/post-install.js';
|
|
15
|
+
import { installSettings, installManagedSettings, installClaudeignore, hasClaudeignore, discoverProjectGitRoots, ensureDevflowGitignore, applyUserSecurityDenyList, detectDenyState, resolveSecurityAction, assertHistoricalDenySuperset, loadTemplateDenyEntries, stripUserSecurityDenyList, } from '../../targets/claude-code/post-install.js';
|
|
16
16
|
import { DEVFLOW_PLUGINS, LEGACY_COMMAND_NAMES, LEGACY_RULE_NAMES, buildAssetMaps, buildScopedSkillsMap, buildRulesMap, partitionSelectablePlugins, WORKFLOW_ORDER, parsePluginSelection, resolveFeatureRedirect } from '../../core/plugins.js';
|
|
17
17
|
import { LEGACY_SKILL_NAMES } from '../../targets/claude-code/legacy.js';
|
|
18
18
|
import { detectPlatform, detectShell, getProfilePath, getSafeDeleteInfo, hasSafeDelete } from '../../core/safe-delete.js';
|
|
@@ -22,7 +22,7 @@ import { convergeMemoryHooks, drainMemoryQueue } from './memory.js';
|
|
|
22
22
|
import { addCaptureHooks, removeCaptureHooks } from './capture.js';
|
|
23
23
|
import { removeDreamHook } from './legacy-hooks.js';
|
|
24
24
|
import { addProxyHooks, removeProxyHooks, applyProxyEnv, stripProxyEnv, runProxyPreflight, buildRealPreflightDeps } from './proxy.js';
|
|
25
|
-
import { reapplyAgentMapping, readAgentMapping } from '../../core/agent-models.js';
|
|
25
|
+
import { reapplyAgentMapping, readAgentMapping, hasAgentMappingEntries } from '../../core/agent-models.js';
|
|
26
26
|
import { readProxyState, writeProxyState, buildProxyState, buildRoutingConfigJson, DEFAULT_PROXY_PORT, proxyJsonExists } from '../../core/proxy-state.js';
|
|
27
27
|
import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-cleanup.js';
|
|
28
28
|
// Settings/HookMatcher types used by hook utilities — each in their own module
|
|
@@ -34,6 +34,7 @@ import { addContextHook, removeContextHook, hasContextHook } from './context.js'
|
|
|
34
34
|
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
35
35
|
import { writeManagedConfig, readConfigIfPresent, DEFAULT_CONFIG } from '../../core/feature-config.js';
|
|
36
36
|
import { drainLearningQueue } from '../../core/learning-queue-cleanup.js';
|
|
37
|
+
import { formatRefusedDrain } from '../../core/queue-drain.js';
|
|
37
38
|
import { removeManagedDenyList, describeManagedDenyRemoval } from './security.js';
|
|
38
39
|
import { resolveInitSeed, applyCliToggles, resolveResetGatedInputs, resolvePluginsToInstall } from './init-seed.js';
|
|
39
40
|
import { parseFrameworkList, normalizeFrameworks } from '../../core/compliance.js';
|
|
@@ -55,7 +56,7 @@ import { getPackageRoot } from '../../core/paths.js';
|
|
|
55
56
|
/**
|
|
56
57
|
* D32/D35: Orchestrates the init-level migration-runner seam.
|
|
57
58
|
*
|
|
58
|
-
* Computes the project list with the D37 fallback rule:
|
|
59
|
+
* Computes the project list with the D37 fallback rule ({@link projectRoots}):
|
|
59
60
|
* 1. Use discoveredProjects when non-empty.
|
|
60
61
|
* 2. Fall back to [gitRoot] when discoveredProjects is empty and gitRoot is set.
|
|
61
62
|
* 3. Run with no per-project targets when both are absent (global-only; per-project
|
|
@@ -70,8 +71,7 @@ import { getPackageRoot } from '../../core/paths.js';
|
|
|
70
71
|
* this helper testable without real filesystem migration state.
|
|
71
72
|
*/
|
|
72
73
|
export async function runMigrationsWithFallback(discoveredProjects, gitRoot, devflowDir, logger, verbose, runner) {
|
|
73
|
-
const
|
|
74
|
-
const migrationResult = await runner({ devflowDir }, projectsForMigration);
|
|
74
|
+
const migrationResult = await runner({ devflowDir }, projectRoots(discoveredProjects, gitRoot));
|
|
75
75
|
reportMigrationResult(migrationResult, logger, verbose);
|
|
76
76
|
return migrationResult;
|
|
77
77
|
}
|
|
@@ -119,6 +119,9 @@ export function formatSweepSummary(report) {
|
|
|
119
119
|
function logSummaryLines(lines) {
|
|
120
120
|
for (const line of lines) {
|
|
121
121
|
switch (line.level) {
|
|
122
|
+
case 'success':
|
|
123
|
+
p.log.success(line.message);
|
|
124
|
+
break;
|
|
122
125
|
case 'info':
|
|
123
126
|
p.log.info(line.message);
|
|
124
127
|
break;
|
|
@@ -144,6 +147,81 @@ export function classifySafeDeleteState(installedVersion, currentVersion) {
|
|
|
144
147
|
return 'outdated';
|
|
145
148
|
return 'missing';
|
|
146
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* The project roots init writes per-project files into and migrates (D37): every
|
|
152
|
+
* project discovered from Claude's history, else the current repository, else
|
|
153
|
+
* none. Pure.
|
|
154
|
+
*/
|
|
155
|
+
export function projectRoots(discoveredProjects, gitRoot) {
|
|
156
|
+
if (discoveredProjects.length > 0)
|
|
157
|
+
return [...discoveredProjects];
|
|
158
|
+
return gitRoot ? [gitRoot] : [];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The Recommended summary's `.claudeignore` row.
|
|
162
|
+
*
|
|
163
|
+
* D-INIT-REAL-OUTCOME: init's summary and status lines state what this run does,
|
|
164
|
+
* decided from the state it acts on rather than printed whatever happens, and
|
|
165
|
+
* point only at a step the user can still take. The summary prints before the
|
|
166
|
+
* install runs, so this row is decided from whether each targeted project
|
|
167
|
+
* already holds a `.claudeignore` (hasClaudeignore, which matches the install's
|
|
168
|
+
* exclusive create): one without it gets one, so the row says `created`; when
|
|
169
|
+
* all have one, `already present`; when the run targets no project, `skipped`.
|
|
170
|
+
* formatSafeDeleteStatus applies the same rule to safe-delete.
|
|
171
|
+
*
|
|
172
|
+
* Pure function.
|
|
173
|
+
*
|
|
174
|
+
* @param present - for each project the run targets, whether it already holds a
|
|
175
|
+
* `.claudeignore`; empty when the run targets none.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveClaudeignoreOutcome(present) {
|
|
178
|
+
if (present.length === 0)
|
|
179
|
+
return 'skipped';
|
|
180
|
+
return present.every(Boolean) ? 'already present' : 'created';
|
|
181
|
+
}
|
|
182
|
+
/** The Recommended summary's safe-delete row for what the run does to the block; a run that leaves it alone adds none. */
|
|
183
|
+
const SAFE_DELETE_SUMMARY_ROW = {
|
|
184
|
+
install: 'Safe delete: installed',
|
|
185
|
+
upgrade: 'Safe delete: upgraded',
|
|
186
|
+
skip: '',
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* What init says about safe-delete once the install has run (D-INIT-REAL-OUTCOME).
|
|
190
|
+
*
|
|
191
|
+
* The outcome comes first: the block this run installed or upgraded, or the
|
|
192
|
+
* current block it found in place. Failing an outcome, the one step left to the
|
|
193
|
+
* user is installing the platform's trash command, which init cannot do for
|
|
194
|
+
* them. A non-interactive run always takes the Recommended path, which installs
|
|
195
|
+
* or upgrades the block itself, so its outcome is all there is to report.
|
|
196
|
+
*
|
|
197
|
+
* Pure function — returns lines, logs nothing.
|
|
198
|
+
*
|
|
199
|
+
* @param state - The profile's block before this run, or null when no profile was
|
|
200
|
+
* checked (the trash command is missing, or the shell has no profile init writes).
|
|
201
|
+
*/
|
|
202
|
+
export function formatSafeDeleteStatus(input) {
|
|
203
|
+
const { profilePath, info } = input;
|
|
204
|
+
if (profilePath !== null) {
|
|
205
|
+
const restart = { level: 'info', message: 'Restart your shell or run: ' + color.cyan(`source ${profilePath}`) };
|
|
206
|
+
if (input.action === 'install')
|
|
207
|
+
return [{ level: 'success', message: `Safe-delete installed to ${color.dim(profilePath)}` }, restart];
|
|
208
|
+
if (input.action === 'upgrade')
|
|
209
|
+
return [{ level: 'success', message: `Safe-delete upgraded in ${color.dim(profilePath)}` }, restart];
|
|
210
|
+
if (input.state === 'current')
|
|
211
|
+
return [{ level: 'info', message: `Safe-delete already configured in ${color.dim(profilePath)}` }];
|
|
212
|
+
}
|
|
213
|
+
if (input.available || info.installHint === null)
|
|
214
|
+
return [];
|
|
215
|
+
if (!input.interactive) {
|
|
216
|
+
return [{ level: 'info', message: `Protect against accidental ${color.red('rm -rf')}: ${color.cyan(info.installHint)}` }];
|
|
217
|
+
}
|
|
218
|
+
if (profilePath === null)
|
|
219
|
+
return [];
|
|
220
|
+
return [
|
|
221
|
+
{ level: 'info', message: `Install ${color.cyan(info.command ?? 'trash')} first: ${color.dim(info.installHint)}` },
|
|
222
|
+
{ level: 'info', message: `Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.` },
|
|
223
|
+
];
|
|
224
|
+
}
|
|
147
225
|
export { addContextHook, removeContextHook, hasContextHook };
|
|
148
226
|
/**
|
|
149
227
|
* Combine workflow and language selections into a single plugin list.
|
|
@@ -344,14 +422,25 @@ export async function persistManifestThenConvergeTracker(opts) {
|
|
|
344
422
|
* D-LEDGER-MAIN-WORKTREE: each queue drains where the hooks write it — memory at
|
|
345
423
|
* this checkout's toplevel (`gitRoot`), learning at the ledger root (`ledgerRoot`,
|
|
346
424
|
* getLedgerRoot), which in a linked worktree is the main checkout.
|
|
425
|
+
*
|
|
426
|
+
* Returns one warning for each drain refused because a folder on the way to its
|
|
427
|
+
* queue is a symbolic link (D-CLI-NO-SYMLINK); the caller prints them.
|
|
347
428
|
*/
|
|
348
429
|
export async function drainDisabledFeatureQueues(opts, io = { drainMemoryQueue, drainLearningQueue }) {
|
|
349
430
|
if (!opts.manifestWritten)
|
|
350
|
-
return;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
431
|
+
return [];
|
|
432
|
+
const refused = [];
|
|
433
|
+
if (!opts.memoryEnabled && opts.gitRoot !== null) {
|
|
434
|
+
const memory = await io.drainMemoryQueue(opts.gitRoot);
|
|
435
|
+
if (!memory.drained)
|
|
436
|
+
refused.push(formatRefusedDrain('memory', memory.linkedFolder));
|
|
437
|
+
}
|
|
438
|
+
if (!opts.learningEnabled && opts.ledgerRoot !== null) {
|
|
439
|
+
const learning = await io.drainLearningQueue(opts.ledgerRoot);
|
|
440
|
+
if (!learning.drained)
|
|
441
|
+
refused.push(formatRefusedDrain('learning', learning.linkedFolder));
|
|
442
|
+
}
|
|
443
|
+
return refused;
|
|
355
444
|
}
|
|
356
445
|
/**
|
|
357
446
|
* The manifest `devflow init --hud-only` writes. Pure — never mutates `existing`.
|
|
@@ -822,6 +911,8 @@ export const initCommand = new Command('init')
|
|
|
822
911
|
let claudeignoreEnabled = !!gitRoot;
|
|
823
912
|
let discoveredProjects = [];
|
|
824
913
|
let safeDeleteAction = 'skip';
|
|
914
|
+
// The profile's block before this run; null when no profile was checked.
|
|
915
|
+
let safeDeleteState = null;
|
|
825
916
|
let safeDeleteBlock = null;
|
|
826
917
|
// Security mode is resolved from flag + manifest + detected reality via resolveSecurityAction.
|
|
827
918
|
// The final value is written to the manifest and consumed by the dedicated security step.
|
|
@@ -950,14 +1041,17 @@ export const initCommand = new Command('init')
|
|
|
950
1041
|
]);
|
|
951
1042
|
discoveredProjects = discoveredResult;
|
|
952
1043
|
if (needsVersionCheck) {
|
|
953
|
-
|
|
954
|
-
if (
|
|
1044
|
+
safeDeleteState = classifySafeDeleteState(installedVersionResult, SAFE_DELETE_BLOCK_VERSION);
|
|
1045
|
+
if (safeDeleteState === 'current')
|
|
955
1046
|
safeDeleteAction = 'skip';
|
|
956
|
-
else if (
|
|
1047
|
+
else if (safeDeleteState === 'outdated')
|
|
957
1048
|
safeDeleteAction = 'upgrade';
|
|
958
1049
|
else
|
|
959
1050
|
safeDeleteAction = 'install';
|
|
960
1051
|
}
|
|
1052
|
+
// D-INIT-REAL-OUTCOME: the summary prints before the install runs, so its
|
|
1053
|
+
// .claudeignore row comes from what each project the install targets holds.
|
|
1054
|
+
const claudeignorePresent = await Promise.all((claudeignoreEnabled ? projectRoots(discoveredProjects, gitRoot) : []).map(hasClaudeignore));
|
|
961
1055
|
// Print summary
|
|
962
1056
|
const defaultFlagCount = countActiveFlags(enabledFlags);
|
|
963
1057
|
const complianceSummary = formatComplianceSummary(complianceEnabled, complianceFrameworks);
|
|
@@ -976,8 +1070,8 @@ export const initCommand = new Command('init')
|
|
|
976
1070
|
`Tracker: ${formatTrackerSummary(trackerProvider)}`,
|
|
977
1071
|
`View mode: ${readViewMode(enabledFlags)}`,
|
|
978
1072
|
`Claude Code flags: ${defaultFlagCount} configured`,
|
|
979
|
-
|
|
980
|
-
|
|
1073
|
+
`.claudeignore: ${resolveClaudeignoreOutcome(claudeignorePresent)}`,
|
|
1074
|
+
SAFE_DELETE_SUMMARY_ROW[safeDeleteAction],
|
|
981
1075
|
].filter(l => l.trim()).join('\n');
|
|
982
1076
|
p.note(summaryLines + `\n\nCustomize later: ${color.cyan('devflow init --advanced')}`, 'Recommended settings applied');
|
|
983
1077
|
}
|
|
@@ -1271,11 +1365,11 @@ export const initCommand = new Command('init')
|
|
|
1271
1365
|
safeDeleteBlock = generateSafeDeleteBlock(shell, process.platform, trashCmd);
|
|
1272
1366
|
if (safeDeleteBlock) {
|
|
1273
1367
|
const installedVersion = await getInstalledVersion(profilePath);
|
|
1274
|
-
|
|
1275
|
-
if (
|
|
1368
|
+
safeDeleteState = classifySafeDeleteState(installedVersion, SAFE_DELETE_BLOCK_VERSION);
|
|
1369
|
+
if (safeDeleteState === 'current') {
|
|
1276
1370
|
safeDeleteAction = 'skip';
|
|
1277
1371
|
}
|
|
1278
|
-
else if (
|
|
1372
|
+
else if (safeDeleteState === 'outdated') {
|
|
1279
1373
|
safeDeleteAction = 'upgrade';
|
|
1280
1374
|
}
|
|
1281
1375
|
else {
|
|
@@ -1727,7 +1821,9 @@ export const initCommand = new Command('init')
|
|
|
1727
1821
|
{
|
|
1728
1822
|
const agentInstallDir = path.join(claudeDir, 'agents', 'devflow');
|
|
1729
1823
|
const preCheckMapping = await readAgentMapping(devflowDir);
|
|
1730
|
-
|
|
1824
|
+
// hasAgentMappingEntries counts agent entries only: an agents.memory-only
|
|
1825
|
+
// mapping names no installed file, so it keeps this gate closed (D-WORKER-AGENTS).
|
|
1826
|
+
const hasMappingEntries = preCheckMapping.ok && hasAgentMappingEntries(preCheckMapping.value);
|
|
1731
1827
|
if (hasMappingEntries || proxyEnabled) {
|
|
1732
1828
|
const reapplyResult = await reapplyAgentMapping({
|
|
1733
1829
|
proxyEnabled,
|
|
@@ -1856,10 +1952,10 @@ export const initCommand = new Command('init')
|
|
|
1856
1952
|
// Configure HUD
|
|
1857
1953
|
const existingHud = loadHudConfig();
|
|
1858
1954
|
saveHudConfig({ enabled: hudEnabled, detail: existingHud.detail });
|
|
1859
|
-
// File extras
|
|
1955
|
+
// File extras — into the projects the Recommended summary's .claudeignore row checked.
|
|
1860
1956
|
if (claudeignoreEnabled) {
|
|
1957
|
+
const results = await Promise.all(projectRoots(discoveredProjects, gitRoot).map(root => installClaudeignore(root, rootDir, verbose)));
|
|
1861
1958
|
if (discoveredProjects.length > 0) {
|
|
1862
|
-
const results = await Promise.all(discoveredProjects.map(root => installClaudeignore(root, rootDir, verbose)));
|
|
1863
1959
|
const created = results.filter(Boolean).length;
|
|
1864
1960
|
if (created > 0) {
|
|
1865
1961
|
p.log.success(`.claudeignore created in ${created} project(s)`);
|
|
@@ -1868,9 +1964,6 @@ export const initCommand = new Command('init')
|
|
|
1868
1964
|
p.log.info(`.claudeignore already exists in all ${discoveredProjects.length} project(s)`);
|
|
1869
1965
|
}
|
|
1870
1966
|
}
|
|
1871
|
-
else if (gitRoot) {
|
|
1872
|
-
await installClaudeignore(gitRoot, rootDir, verbose);
|
|
1873
|
-
}
|
|
1874
1967
|
}
|
|
1875
1968
|
// Deterministically ensure .devflow/ is gitignored at the repo root — independent
|
|
1876
1969
|
// of every feature toggle. The always-on ensure-root-gitignore
|
|
@@ -2062,34 +2155,14 @@ export const initCommand = new Command('init')
|
|
|
2062
2155
|
p.note(commandsNote, 'Available commands');
|
|
2063
2156
|
}
|
|
2064
2157
|
// Safe-delete status messages (after spinner)
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
}
|
|
2074
|
-
else if (safeDeleteAvailable && safeDeleteBlock) {
|
|
2075
|
-
const installedVersion = await getInstalledVersion(profilePath);
|
|
2076
|
-
if (classifySafeDeleteState(installedVersion, SAFE_DELETE_BLOCK_VERSION) === 'current') {
|
|
2077
|
-
p.log.info(`Safe-delete already configured in ${color.dim(profilePath)}`);
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
else if (!safeDeleteAvailable && safeDeleteInfo.installHint) {
|
|
2081
|
-
p.log.info(`Install ${color.cyan(safeDeleteInfo.command ?? 'trash')} first: ${color.dim(safeDeleteInfo.installHint)}`);
|
|
2082
|
-
p.log.info(`Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.`);
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
else if (!process.stdin.isTTY) {
|
|
2086
|
-
if (safeDeleteAvailable && safeDeleteInfo.command) {
|
|
2087
|
-
p.log.info(`Safe-delete available (${safeDeleteInfo.command}). Run interactively to auto-install.`);
|
|
2088
|
-
}
|
|
2089
|
-
else if (safeDeleteInfo.installHint) {
|
|
2090
|
-
p.log.info(`Protect against accidental ${color.red('rm -rf')}: ${color.cyan(safeDeleteInfo.installHint)}`);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2158
|
+
logSummaryLines(formatSafeDeleteStatus({
|
|
2159
|
+
interactive: process.stdin.isTTY === true,
|
|
2160
|
+
action: safeDeleteAction,
|
|
2161
|
+
state: safeDeleteState,
|
|
2162
|
+
available: safeDeleteAvailable,
|
|
2163
|
+
profilePath,
|
|
2164
|
+
info: safeDeleteInfo,
|
|
2165
|
+
}));
|
|
2093
2166
|
// Verbose mode: show details
|
|
2094
2167
|
if (verbose) {
|
|
2095
2168
|
const pluginsList = pluginsToInstall
|
|
@@ -2157,13 +2230,15 @@ export const initCommand = new Command('init')
|
|
|
2157
2230
|
p.log.info(msg.text);
|
|
2158
2231
|
}
|
|
2159
2232
|
// Only now that the machine-wide switch is on disk (D-INIT-DRAIN-AFTER-SWITCH).
|
|
2160
|
-
await drainDisabledFeatureQueues({
|
|
2233
|
+
const refusedDrains = await drainDisabledFeatureQueues({
|
|
2161
2234
|
gitRoot,
|
|
2162
2235
|
ledgerRoot: learningEnabled || gitRoot === null ? null : await getLedgerRoot(),
|
|
2163
2236
|
memoryEnabled,
|
|
2164
2237
|
learningEnabled,
|
|
2165
2238
|
manifestWritten: trackerLifecycle.manifestWritten,
|
|
2166
2239
|
});
|
|
2240
|
+
for (const line of refusedDrains)
|
|
2241
|
+
p.log.warn(line);
|
|
2167
2242
|
// The hooks' per-directory log folders, capped (D-LOG-DIR-CAP): one pass
|
|
2168
2243
|
// clears every folder it scans beyond the cap; only a backlog beyond the
|
|
2169
2244
|
// scan bound (MAX_LOG_DIRS_SCANNED, 100,000) waits for the next init.
|