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.
Files changed (166) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -1
  3. package/dist/agents/git.md +2 -2
  4. package/dist/cli/agents-view/index.js +1 -1
  5. package/dist/cli/agents-view/render.js +71 -17
  6. package/dist/cli/agents-view/state.js +42 -16
  7. package/dist/cli/agents-view/terminal.js +5 -5
  8. package/dist/cli/commands/agents.js +142 -51
  9. package/dist/cli/commands/ambient.js +1 -1
  10. package/dist/cli/commands/attribution-prompts.js +8 -8
  11. package/dist/cli/commands/capture.js +1 -1
  12. package/dist/cli/commands/compliance-prompts.js +8 -8
  13. package/dist/cli/commands/compliance.js +8 -7
  14. package/dist/cli/commands/flags.js +33 -31
  15. package/dist/cli/commands/hud.js +1 -1
  16. package/dist/cli/commands/init-seed.js +9 -9
  17. package/dist/cli/commands/init.js +162 -85
  18. package/dist/cli/commands/install-report.js +10 -10
  19. package/dist/cli/commands/learning.js +302 -136
  20. package/dist/cli/commands/memory.js +36 -15
  21. package/dist/cli/commands/proxy.js +23 -23
  22. package/dist/cli/commands/rules.js +6 -5
  23. package/dist/cli/commands/tracker-prompts.js +6 -6
  24. package/dist/cli/commands/tracker.js +9 -9
  25. package/dist/cli/commands/uninstall.js +183 -59
  26. package/dist/cli/flags-view/render.js +5 -5
  27. package/dist/cli/flags-view/state.js +9 -9
  28. package/dist/cli/flags-view/terminal.js +4 -4
  29. package/dist/cli/tui/cells.js +1 -1
  30. package/dist/cli/tui/terminal.js +6 -6
  31. package/dist/commands/code-review.md +0 -2
  32. package/dist/commands/debug.md +14 -11
  33. package/dist/commands/dynamic-build.md +51 -47
  34. package/dist/commands/dynamic-plan.md +27 -7
  35. package/dist/commands/dynamic-profile.md +17 -3
  36. package/dist/commands/dynamic-tickets.md +18 -4
  37. package/dist/commands/explore.md +9 -3
  38. package/dist/commands/implement.md +20 -16
  39. package/dist/commands/plan.md +13 -9
  40. package/dist/commands/release.md +23 -3
  41. package/dist/commands/research.md +9 -3
  42. package/dist/commands/resolve.md +9 -12
  43. package/dist/commands/self-review.md +0 -2
  44. package/dist/core/agent-frontmatter.js +28 -3
  45. package/dist/core/agent-models.js +204 -42
  46. package/dist/core/agent-state.js +28 -6
  47. package/dist/core/ansi.js +2 -2
  48. package/dist/core/assets.js +1 -1
  49. package/dist/core/cache.js +7 -8
  50. package/dist/core/codex-auth-inspect.js +4 -4
  51. package/dist/core/compliance-compose.js +3 -3
  52. package/dist/core/compliance.js +3 -4
  53. package/dist/core/evidence-policy.js +14 -13
  54. package/dist/core/external-models.js +1 -1
  55. package/dist/core/feature-config.js +71 -13
  56. package/dist/core/feature-switch.js +3 -3
  57. package/dist/core/flags.js +49 -25
  58. package/dist/core/fs-atomic.js +6 -7
  59. package/dist/core/learning-queue-cleanup.js +16 -81
  60. package/dist/core/learning-store.js +61 -0
  61. package/dist/core/linked-path.js +46 -0
  62. package/dist/core/manifest.js +5 -5
  63. package/dist/core/mds-variants.js +13 -13
  64. package/dist/core/model-discovery.js +8 -8
  65. package/dist/core/observations.js +17 -101
  66. package/dist/core/orphan-sweep.js +4 -4
  67. package/dist/core/plugins.js +13 -8
  68. package/dist/core/project-paths.js +9 -13
  69. package/dist/core/proxy-log.js +8 -8
  70. package/dist/core/proxy-state.js +3 -3
  71. package/dist/core/queue-drain.js +31 -0
  72. package/dist/core/reference-sweep.js +6 -6
  73. package/dist/core/teammate-mode-cleanup.js +1 -1
  74. package/dist/core/tracker.js +14 -14
  75. package/dist/hud/colors.js +2 -2
  76. package/dist/hud/components/learning-counts.js +54 -22
  77. package/dist/hud/components/version-badge.js +1 -1
  78. package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
  79. package/dist/skills/git/references/tracker/github/create-release.md +2 -2
  80. package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
  81. package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
  82. package/dist/targets/claude-code/compliance-install.js +17 -15
  83. package/dist/targets/claude-code/hooks.js +2 -2
  84. package/dist/targets/claude-code/installer.js +59 -32
  85. package/dist/targets/claude-code/legacy.js +1 -1
  86. package/dist/targets/claude-code/post-install.js +135 -45
  87. package/dist/targets/claude-code/tracker-install.js +2 -2
  88. package/package.json +1 -1
  89. package/src/assets/agents/code.md +15 -21
  90. package/src/assets/agents/design.md +4 -2
  91. package/src/assets/agents/diagnose.md +3 -1
  92. package/src/assets/agents/evaluate.md +4 -0
  93. package/src/assets/agents/git.mds +2 -2
  94. package/src/assets/agents/knowledge.md +5 -3
  95. package/src/assets/agents/learning.md +281 -196
  96. package/src/assets/agents/research.md +3 -1
  97. package/src/assets/agents/review.md +5 -3
  98. package/src/assets/agents/scrutinize.md +5 -1
  99. package/src/assets/agents/simplify.md +4 -0
  100. package/src/assets/agents/skim.md +4 -2
  101. package/src/assets/agents/synthesize.md +6 -0
  102. package/src/assets/agents/test.md +18 -10
  103. package/src/assets/agents/triage.md +11 -9
  104. package/src/assets/agents/validate.md +14 -10
  105. package/src/assets/commands/_partials/_decisions.mds +8 -3
  106. package/src/assets/commands/_partials/_docs_root.mds +3 -3
  107. package/src/assets/commands/_partials/_engine.mds +16 -32
  108. package/src/assets/commands/_partials/_knowledge.mds +0 -2
  109. package/src/assets/commands/_partials/_preamble.mds +6 -2
  110. package/src/assets/commands/_partials/_settings.mds +2 -2
  111. package/src/assets/commands/_partials/_tracker.mds +1 -1
  112. package/src/assets/commands/code-review.mds +0 -2
  113. package/src/assets/commands/debug.mds +13 -8
  114. package/src/assets/commands/dynamic-build.mds +18 -12
  115. package/src/assets/commands/dynamic-plan.mds +10 -4
  116. package/src/assets/commands/dynamic-profile.mds +1 -1
  117. package/src/assets/commands/dynamic-tickets.mds +2 -2
  118. package/src/assets/commands/explore.mds +9 -1
  119. package/src/assets/commands/implement.mds +19 -13
  120. package/src/assets/commands/plan.mds +12 -8
  121. package/src/assets/commands/release.md +23 -3
  122. package/src/assets/commands/research.mds +9 -3
  123. package/src/assets/commands/resolve.mds +9 -10
  124. package/src/assets/mds/git/_pr.mds +3 -3
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_github.mds +3 -3
  127. package/src/assets/mds/tracker/_jira.mds +3 -3
  128. package/src/assets/mds/tracker/_linear.mds +3 -3
  129. package/src/assets/mds/tracker/_mcp.mds +6 -5
  130. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
  131. package/src/assets/scripts/hooks/background-memory-update +97 -33
  132. package/src/assets/scripts/hooks/capture-prompt +4 -3
  133. package/src/assets/scripts/hooks/capture-question +4 -3
  134. package/src/assets/scripts/hooks/capture-turn +5 -20
  135. package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
  136. package/src/assets/scripts/hooks/ensure-proxy +5 -6
  137. package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
  138. package/src/assets/scripts/hooks/git-marker +71 -0
  139. package/src/assets/scripts/hooks/is-hex-sha +1 -1
  140. package/src/assets/scripts/hooks/json-helper.cjs +345 -944
  141. package/src/assets/scripts/hooks/json-parse +25 -129
  142. package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
  143. package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
  144. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
  145. package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
  146. package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
  147. package/src/assets/scripts/hooks/memory-worker +10 -0
  148. package/src/assets/scripts/hooks/pre-compact-memory +66 -14
  149. package/src/assets/scripts/hooks/preamble +9 -1
  150. package/src/assets/scripts/hooks/queue-append +55 -23
  151. package/src/assets/scripts/hooks/resolve-project-root +3 -4
  152. package/src/assets/scripts/hooks/session-start-context +146 -45
  153. package/src/assets/scripts/hooks/session-start-memory +33 -11
  154. package/src/assets/scripts/lib/project-config.cjs +2 -2
  155. package/src/assets/scripts/pr-evidence.cjs +3 -3
  156. package/src/assets/scripts/redact-secrets.cjs +20 -20
  157. package/src/assets/scripts/release-trace.cjs +1 -1
  158. package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
  159. package/src/assets/scripts/resolve-settings.cjs +3 -3
  160. package/src/assets/scripts/verify-evidence.cjs +2 -2
  161. package/src/assets/skills/apply-decisions/SKILL.md +37 -17
  162. package/src/assets/skills/docs-framework/SKILL.md +2 -2
  163. package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
  164. package/src/assets/skills/test-driven-development/SKILL.md +6 -4
  165. package/dist/core/observation-io.js +0 -50
  166. package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
@@ -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 projectsForMigration = discoveredProjects.length > 0 ? discoveredProjects : (gitRoot ? [gitRoot] : []);
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
  }
@@ -84,7 +84,7 @@ export async function runMigrationsWithFallback(discoveredProjects, gitRoot, dev
84
84
  * or command still loading in Claude Code with no diagnostic at all. Both outcomes
85
85
  * have to reach the summary.
86
86
  *
87
- * Pure function — returns lines, logs nothing (applies ADR-013).
87
+ * Pure function — returns lines, logs nothing.
88
88
  */
89
89
  export function formatSweepSummary(report) {
90
90
  const lines = [];
@@ -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.
@@ -194,7 +272,8 @@ export function resolveComplianceInitState(complianceOption, seedFrameworks) {
194
272
  return undefined;
195
273
  }
196
274
  // Re-export formatComplianceSummary from compliance-prompts.ts so existing test imports
197
- // (tests/init-logic.test.ts:1615 — imports from '../src/cli/commands/init.js') keep resolving.
275
+ // (the 'formatComplianceSummary' suite in tests/init-logic.test.ts imports it from
276
+ // '../src/cli/commands/init.js') keep resolving.
198
277
  export { formatComplianceSummary } from './compliance-prompts.js';
199
278
  /**
200
279
  * Parse the --tracker CLI option into a tracker override.
@@ -222,7 +301,7 @@ export function resolveTrackerInitState(trackerOption) {
222
301
  /**
223
302
  * The outcome line for a provider that arrived as `--tracker <id>`.
224
303
  *
225
- * D-TRACKER-CLI-SURFACE [PF-029]: the Advanced path prints no end-of-wizard
304
+ * D-TRACKER-CLI-SURFACE: the Advanced path prints no end-of-wizard
226
305
  * summary, and `--tracker` suppresses the wizard step that would otherwise
227
306
  * print one, so the CLI-override arm is the only place the selection can
228
307
  * surface there. Without this line `devflow init --advanced --tracker jira`
@@ -255,7 +334,7 @@ export function buildTrackerLifecycleIO() {
255
334
  *
256
335
  * D-TRACKER-CONVERGE: the manifest write and the tracker file-lifecycle owners
257
336
  * are ONE unit because their relative order is the invariant, not an
258
- * implementation detail (PF-015). The manifest write is explicitly failable —
337
+ * implementation detail. The manifest write is explicitly failable —
259
338
  * init must not abort on it — so converging the sentinel ahead of it leaves the
260
339
  * two disagreeing in both directions: github→jira writes a sentinel naming a
261
340
  * provider the manifest never records, and jira→github removes the sentinel while
@@ -268,7 +347,7 @@ export function buildTrackerLifecycleIO() {
268
347
  * taken as a separate argument, so there is exactly one binding and the sentinel
269
348
  * cannot name a value other than the one on disk.
270
349
  *
271
- * Every step reports rather than aborts (PF-009's isolation posture): a
350
+ * Every step reports rather than aborts: a
272
351
  * feature-state change must never fail `devflow init`.
273
352
  */
274
353
  export async function persistManifestThenConvergeTracker(opts) {
@@ -302,7 +381,7 @@ export async function persistManifestThenConvergeTracker(opts) {
302
381
  messages.push({ level: 'warn', text });
303
382
  // D-TRACKER-PARALLEL: the two owners touch disjoint files — the attempt
304
383
  // counters and the sentinel — depend on nothing the other writes, and both
305
- // report through TrackerResult instead of throwing (PF-014), so they run
384
+ // report through TrackerResult instead of throwing, so they run
306
385
  // concurrently and their warnings are pushed in a fixed order regardless of
307
386
  // which settles first.
308
387
  const [rearm, sentinel] = await Promise.all([
@@ -343,14 +422,25 @@ export async function persistManifestThenConvergeTracker(opts) {
343
422
  * D-LEDGER-MAIN-WORKTREE: each queue drains where the hooks write it — memory at
344
423
  * this checkout's toplevel (`gitRoot`), learning at the ledger root (`ledgerRoot`,
345
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.
346
428
  */
347
429
  export async function drainDisabledFeatureQueues(opts, io = { drainMemoryQueue, drainLearningQueue }) {
348
430
  if (!opts.manifestWritten)
349
- return;
350
- if (!opts.memoryEnabled && opts.gitRoot !== null)
351
- await io.drainMemoryQueue(opts.gitRoot);
352
- if (!opts.learningEnabled && opts.ledgerRoot !== null)
353
- await io.drainLearningQueue(opts.ledgerRoot);
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;
354
444
  }
355
445
  /**
356
446
  * The manifest `devflow init --hud-only` writes. Pure — never mutates `existing`.
@@ -361,7 +451,7 @@ export async function drainDisabledFeatureQueues(opts, io = { drainMemoryQueue,
361
451
  * earlier shape rewrote the whole record as a HUD-only fresh install (ambient,
362
452
  * memory, learning, knowledge, rules and proxy all `false`, plugins `[]`) while
363
453
  * leaving those features' artifacts on disk: the record stopped describing the
364
- * machine, the next re-init seeded every feature off (ADR-014), and with
454
+ * machine, the next re-init seeded every feature off, and with
365
455
  * memory/learning/knowledge switched by the manifest alone
366
456
  * (D-FEATURES-NARROW-ONLY) a HUD install would have really disabled them
367
457
  * everywhere. `version` is kept too: --hud-only reinstalls no plugin, and a
@@ -759,7 +849,7 @@ export const initCommand = new Command('init')
759
849
  // modePromptShown: true only when the Setup-mode p.select actually ran (i.e. the user
760
850
  // was shown a live interactive prompt and made an active choice). Used by
761
851
  // shouldRunComplianceStep to preserve the promptless contracts of --recommended and !isTTY
762
- // without re-checking the mode name (per PF-029).
852
+ // without re-checking the mode name.
763
853
  let modePromptShown = false;
764
854
  let useRecommended;
765
855
  if (options.recommended) {
@@ -821,6 +911,8 @@ export const initCommand = new Command('init')
821
911
  let claudeignoreEnabled = !!gitRoot;
822
912
  let discoveredProjects = [];
823
913
  let safeDeleteAction = 'skip';
914
+ // The profile's block before this run; null when no profile was checked.
915
+ let safeDeleteState = null;
824
916
  let safeDeleteBlock = null;
825
917
  // Security mode is resolved from flag + manifest + detected reality via resolveSecurityAction.
826
918
  // The final value is written to the manifest and consumed by the dedicated security step.
@@ -877,7 +969,7 @@ export const initCommand = new Command('init')
877
969
  // ── Recommended path ──
878
970
  // B4: compliance wizard step — runs only when the Setup-mode prompt actually ran
879
971
  // (modePromptShown=true), preserving the promptless contracts of --recommended and !isTTY.
880
- // shouldRunComplianceStep gates on modePromptShown rather than the mode name (PF-029).
972
+ // shouldRunComplianceStep gates on modePromptShown rather than the mode name.
881
973
  // Re-init never routes here (seedManifest !== null → banner path → Advanced), so this path
882
974
  // always sees fresh-install defaults.
883
975
  let wizardCompliance;
@@ -949,14 +1041,17 @@ export const initCommand = new Command('init')
949
1041
  ]);
950
1042
  discoveredProjects = discoveredResult;
951
1043
  if (needsVersionCheck) {
952
- const state = classifySafeDeleteState(installedVersionResult, SAFE_DELETE_BLOCK_VERSION);
953
- if (state === 'current')
1044
+ safeDeleteState = classifySafeDeleteState(installedVersionResult, SAFE_DELETE_BLOCK_VERSION);
1045
+ if (safeDeleteState === 'current')
954
1046
  safeDeleteAction = 'skip';
955
- else if (state === 'outdated')
1047
+ else if (safeDeleteState === 'outdated')
956
1048
  safeDeleteAction = 'upgrade';
957
1049
  else
958
1050
  safeDeleteAction = 'install';
959
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));
960
1055
  // Print summary
961
1056
  const defaultFlagCount = countActiveFlags(enabledFlags);
962
1057
  const complianceSummary = formatComplianceSummary(complianceEnabled, complianceFrameworks);
@@ -975,8 +1070,8 @@ export const initCommand = new Command('init')
975
1070
  `Tracker: ${formatTrackerSummary(trackerProvider)}`,
976
1071
  `View mode: ${readViewMode(enabledFlags)}`,
977
1072
  `Claude Code flags: ${defaultFlagCount} configured`,
978
- `${claudeignoreEnabled ? '.claudeignore: created' : ''}`,
979
- `${safeDeleteAction !== 'skip' ? 'Safe delete: installed' : ''}`,
1073
+ `.claudeignore: ${resolveClaudeignoreOutcome(claudeignorePresent)}`,
1074
+ SAFE_DELETE_SUMMARY_ROW[safeDeleteAction],
980
1075
  ].filter(l => l.trim()).join('\n');
981
1076
  p.note(summaryLines + `\n\nCustomize later: ${color.cyan('devflow init --advanced')}`, 'Recommended settings applied');
982
1077
  }
@@ -1131,9 +1226,9 @@ export const initCommand = new Command('init')
1131
1226
  isTTY: process.stdin.isTTY,
1132
1227
  hasCliOverride: cliComplianceOverride !== undefined,
1133
1228
  })) {
1134
- // runComplianceStep: note with "Current setting:" header (legible on re-init per PF-029),
1229
+ // runComplianceStep: note with "Current setting:" header (legible on re-init),
1135
1230
  // labeled Yes/No select (immune to Enter-through muscle memory), and framework multiselect.
1136
- // Returns {kind:'cancelled'} on Escape — caller owns the cancel idiom (PF-014).
1231
+ // Returns {kind:'cancelled'} on Escape — caller owns the cancel idiom.
1137
1232
  const complianceStep = await runComplianceStep({
1138
1233
  seed: { enabled: complianceEnabled, frameworks: complianceFrameworks },
1139
1234
  prompts: buildClackCompliancePrompts(),
@@ -1187,19 +1282,19 @@ export const initCommand = new Command('init')
1187
1282
  // silently carries the seeded value. The gate stays an explicit predicate call so the
1188
1283
  // documented gate table in attribution-prompts.ts remains the single authority.
1189
1284
  // isTTY is guaranteed true here (the non-TTY guard above exit-1'd); passing it keeps
1190
- // the promptless contract enforced at the predicate rather than by position (PF-029).
1285
+ // the promptless contract enforced at the predicate rather than by position.
1191
1286
  if (shouldRunAttributionStep({
1192
1287
  // D27-GATE: bind to the resolved mode so all four documented gate-table rows
1193
1288
  // are reachable and the predicate — not lexical placement — enforces Advanced-only.
1194
1289
  // Using useRecommended ? 'recommended' : 'advanced' makes the gate testable from
1195
1290
  // both sides and prevents the Recommended path from accidentally running the step
1196
- // if this block is ever repositioned (applies PF-029).
1291
+ // if this block is ever repositioned.
1197
1292
  mode: useRecommended ? 'recommended' : 'advanced',
1198
1293
  isTTY: process.stdin.isTTY,
1199
1294
  })) {
1200
1295
  const attributionStep = await runAttributionStep({
1201
1296
  // attributionSeedFrom keeps the seed a real boolean regardless of the stored
1202
- // FlagsRecord value type — undefined/null/false all map to false (PF-018).
1297
+ // FlagsRecord value type — undefined/null/false all map to false.
1203
1298
  seed: attributionSeedFrom(enabledFlags),
1204
1299
  prompts: buildClackAttributionPrompts(),
1205
1300
  });
@@ -1220,7 +1315,7 @@ export const initCommand = new Command('init')
1220
1315
  /**
1221
1316
  * D40: init applies seeded flag defaults non-interactively. Flags are customized
1222
1317
  * exclusively via `devflow flags`; re-init preserves existing values and adopts
1223
- * registry defaults only for absent flags (ADR-014). No TUI is opened during init.
1318
+ * registry defaults only for absent flags. No TUI is opened during init.
1224
1319
  */
1225
1320
  {
1226
1321
  const activeCount = countActiveFlags(enabledFlags);
@@ -1270,11 +1365,11 @@ export const initCommand = new Command('init')
1270
1365
  safeDeleteBlock = generateSafeDeleteBlock(shell, process.platform, trashCmd);
1271
1366
  if (safeDeleteBlock) {
1272
1367
  const installedVersion = await getInstalledVersion(profilePath);
1273
- const state = classifySafeDeleteState(installedVersion, SAFE_DELETE_BLOCK_VERSION);
1274
- if (state === 'current') {
1368
+ safeDeleteState = classifySafeDeleteState(installedVersion, SAFE_DELETE_BLOCK_VERSION);
1369
+ if (safeDeleteState === 'current') {
1275
1370
  safeDeleteAction = 'skip';
1276
1371
  }
1277
- else if (state === 'outdated') {
1372
+ else if (safeDeleteState === 'outdated') {
1278
1373
  safeDeleteAction = 'upgrade';
1279
1374
  }
1280
1375
  else {
@@ -1474,10 +1569,10 @@ export const initCommand = new Command('init')
1474
1569
  p.log.error(`${error}`);
1475
1570
  process.exit(1);
1476
1571
  }
1477
- // Converge compliance artifacts (PF-015: always converge, never short-circuit).
1572
+ // Converge compliance artifacts (always converge, never short-circuit).
1478
1573
  // Called unconditionally so that enabling/disabling compliance during init
1479
1574
  // is reflected in the installed artifacts without a separate devflow compliance run.
1480
- // Wrapped in its own try/catch (PF-009: warn-not-abort).
1575
+ // Wrapped in its own try/catch (warn-not-abort).
1481
1576
  let convergeResult = null;
1482
1577
  try {
1483
1578
  convergeResult = await convergeFromManifest({
@@ -1525,7 +1620,7 @@ export const initCommand = new Command('init')
1525
1620
  }
1526
1621
  // Sweep orphaned *-teams.md workflow command variants left by the Agent Teams
1527
1622
  // refactor. None are ever re-installed, so a blanket sweep is safe on any
1528
- // install type (the full-install dir wipe only covers full installs). (PF-009)
1623
+ // install type (the full-install dir wipe only covers full installs).
1529
1624
  try {
1530
1625
  for (const f of await fs.readdir(commandsDir)) {
1531
1626
  if (f.endsWith('-teams.md')) {
@@ -1598,6 +1693,7 @@ export const initCommand = new Command('init')
1598
1693
  'dream-procedure.md',
1599
1694
  'lib/staleness.cjs',
1600
1695
  'dream-lock',
1696
+ 'decisions-usage-scan.cjs',
1601
1697
  ];
1602
1698
  const hooksDir = path.join(devflowDir, 'scripts', 'hooks');
1603
1699
  for (const legacy of LEGACY_HOOK_FILES) {
@@ -1613,7 +1709,7 @@ export const initCommand = new Command('init')
1613
1709
  const settingsPath = path.join(claudeDir, 'settings.json');
1614
1710
  // === Proxy preflight (when enabled) ===
1615
1711
  // Runs before the settings mutation pass so that proxyEnabled reflects reality
1616
- // (preflight failure forces it off without aborting init — avoids PF-009).
1712
+ // (preflight failure forces it off without aborting init).
1617
1713
  //
1618
1714
  // Read existing proxy state once to recover the remembered port.
1619
1715
  // Init has no --port option; the remembered port from proxy.json always
@@ -1660,7 +1756,7 @@ export const initCommand = new Command('init')
1660
1756
  'Routing disabled for this init — run `devflow proxy --enable` after signing in.');
1661
1757
  proxyEnabled = false;
1662
1758
  // Write proxy.json disabled so runtime authority converges with the
1663
- // failed preflight outcome (avoids PF-015). Without this write,
1759
+ // failed preflight outcome. Without this write,
1664
1760
  // proxy.json stays enabled:true from the prior run, causing
1665
1761
  // reapplyAgentMapping to write GPT model IDs into agent frontmatter
1666
1762
  // with no relay — a dormancy inversion.
@@ -1695,7 +1791,7 @@ export const initCommand = new Command('init')
1695
1791
  }
1696
1792
  else {
1697
1793
  // Proxy disabled at entry: if proxy.json exists and is enabled, mark it disabled.
1698
- // avoids PF-015: runtime authority (proxy.json) must converge with proxyEnabled=false.
1794
+ // Runtime authority (proxy.json) must converge with proxyEnabled=false.
1699
1795
  // Check the Result — write errors are surfaced rather than silently ignored
1700
1796
  // (consistent with the enable branch above).
1701
1797
  if (priorProxyStateResult.ok && priorProxyStateResult.value.enabled) {
@@ -1716,7 +1812,7 @@ export const initCommand = new Command('init')
1716
1812
  // MUST run AFTER the proxy preflight block above: preflight can force proxyEnabled=false
1717
1813
  // on failure, and reapply's dormancy (GPT models materialize only while proxy enabled)
1718
1814
  // depends on the FINAL proxyEnabled value — running earlier would leave GPT model lines
1719
- // in agent frontmatter after a preflight failure. Per-item failures are non-fatal (avoids PF-009).
1815
+ // in agent frontmatter after a preflight failure. Per-item failures are non-fatal.
1720
1816
  //
1721
1817
  // Init-only optimization: skip reapply when mapping is empty AND proxy is off.
1722
1818
  // An empty mapping means every agent uses its shipped default; the file copy already
@@ -1725,7 +1821,9 @@ export const initCommand = new Command('init')
1725
1821
  {
1726
1822
  const agentInstallDir = path.join(claudeDir, 'agents', 'devflow');
1727
1823
  const preCheckMapping = await readAgentMapping(devflowDir);
1728
- const hasMappingEntries = preCheckMapping.ok && Object.keys(preCheckMapping.value.agents).length > 0;
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);
1729
1827
  if (hasMappingEntries || proxyEnabled) {
1730
1828
  const reapplyResult = await reapplyAgentMapping({
1731
1829
  proxyEnabled,
@@ -1776,7 +1874,7 @@ export const initCommand = new Command('init')
1776
1874
  // Strip Devflow-managed teammateMode ("auto"). User-set values (e.g. "tmux") are preserved.
1777
1875
  content = stripDevflowTeammateModeFromJson(content);
1778
1876
  // Claude Code flags — convergeFlagsIntoSettings is the single pipeline entry point
1779
- // (ARCH-H1, applies PF-015/PF-017/ADR-014): fold valued flags and view-mode from
1877
+ // (ARCH-H1): fold valued flags and view-mode from
1780
1878
  // existing settings before strip, then strip all managed keys and apply the folded
1781
1879
  // record. ownedRecord=existingManifest?.features.flags??null distinguishes keys
1782
1880
  // devflow previously wrote (must not be overridden by fold) from keys newly adopted
@@ -1806,7 +1904,7 @@ export const initCommand = new Command('init')
1806
1904
  // would silently delete both their URL and the window-enforcement var.
1807
1905
  // Invariant: proxy.json always reflects the final settled state after the
1808
1906
  // preflight block above — all paths that force proxyEnabled=false also write
1809
- // proxy.json enabled:false (avoids PF-015), so managedPort == effectivePort.
1907
+ // proxy.json enabled:false, so managedPort == effectivePort.
1810
1908
  if (await proxyJsonExists(devflowDir)) {
1811
1909
  const proxyStateForStrip = await readProxyState(devflowDir);
1812
1910
  const managedPort = proxyStateForStrip.ok ? proxyStateForStrip.value.port : DEFAULT_PROXY_PORT;
@@ -1838,14 +1936,14 @@ export const initCommand = new Command('init')
1838
1936
  // read-modify-write, not a whole-file write: init owns only reviewPublication,
1839
1937
  // and every other key in the file — the hand-written per-repo `tracker`
1840
1938
  // override first among them — is carried from disk, under --reset too
1841
- // (D-CONFIG-PRESERVE-UNMANAGED in feature-config.ts, avoids PF-071).
1939
+ // (D-CONFIG-PRESERVE-UNMANAGED in feature-config.ts).
1842
1940
  // A malformed or unreadable file is left untouched and named (D-CONFIG-NO-REPAIR).
1843
1941
  if (gitRoot) {
1844
1942
  const configWrite = await writeManagedConfig(gitRoot, {
1845
1943
  // reviewPublication has no prompt, so it is carried over from the
1846
1944
  // reset-gated snapshot rather than re-read from disk: seedConfig is null
1847
1945
  // under --reset, which is what collapses the field back to 'auto' with
1848
- // every other feature (PF-015 — read the post-gate binding, not the file).
1946
+ // every other feature (read the post-gate binding, not the file).
1849
1947
  reviewPublication: seedConfig?.reviewPublication ?? DEFAULT_CONFIG.reviewPublication,
1850
1948
  });
1851
1949
  if (!configWrite.ok)
@@ -1854,10 +1952,10 @@ export const initCommand = new Command('init')
1854
1952
  // Configure HUD
1855
1953
  const existingHud = loadHudConfig();
1856
1954
  saveHudConfig({ enabled: hudEnabled, detail: existingHud.detail });
1857
- // File extras
1955
+ // File extras — into the projects the Recommended summary's .claudeignore row checked.
1858
1956
  if (claudeignoreEnabled) {
1957
+ const results = await Promise.all(projectRoots(discoveredProjects, gitRoot).map(root => installClaudeignore(root, rootDir, verbose)));
1859
1958
  if (discoveredProjects.length > 0) {
1860
- const results = await Promise.all(discoveredProjects.map(root => installClaudeignore(root, rootDir, verbose)));
1861
1959
  const created = results.filter(Boolean).length;
1862
1960
  if (created > 0) {
1863
1961
  p.log.success(`.claudeignore created in ${created} project(s)`);
@@ -1866,14 +1964,11 @@ export const initCommand = new Command('init')
1866
1964
  p.log.info(`.claudeignore already exists in all ${discoveredProjects.length} project(s)`);
1867
1965
  }
1868
1966
  }
1869
- else if (gitRoot) {
1870
- await installClaudeignore(gitRoot, rootDir, verbose);
1871
- }
1872
1967
  }
1873
1968
  // Deterministically ensure .devflow/ is gitignored at the repo root — independent
1874
1969
  // of every feature toggle. The always-on ensure-root-gitignore
1875
1970
  // hook covers projects that never re-run init; this covers the init-time path so a
1876
- // fresh install never tracks .devflow/. Decoupled from memory (avoids PF-014).
1971
+ // fresh install never tracks .devflow/. Decoupled from memory.
1877
1972
  if (gitRoot) {
1878
1973
  await ensureDevflowGitignore(gitRoot, verbose);
1879
1974
  }
@@ -1932,7 +2027,7 @@ export const initCommand = new Command('init')
1932
2027
  s.start('Finalizing installation...');
1933
2028
  }
1934
2029
  else {
1935
- // applies ADR-010: user declined sudo and chose the settings.json fallback.
2030
+ // User declined sudo and chose the settings.json fallback.
1936
2031
  // securityMode is 'managed' (from resolveSecurityAction or interactive choice) but
1937
2032
  // managedSettingsConfirmed is false — honor the "fall back to settings.json" label
1938
2033
  // by writing to user settings. Manifest will record 'user' to match reality.
@@ -1987,7 +2082,7 @@ export const initCommand = new Command('init')
1987
2082
  }
1988
2083
  else {
1989
2084
  // Exhaustive guard — if TypeScript reaches here, a new SecurityMode variant was added
1990
- // without a matching branch. avoids PF-009 (stale references after rename/refactor).
2085
+ // without a matching branch — guards against stale references after rename/refactor.
1991
2086
  const _exhaustive = securityMode;
1992
2087
  void _exhaustive;
1993
2088
  }
@@ -2038,7 +2133,7 @@ export const initCommand = new Command('init')
2038
2133
  // devflow:compliance is never swept here — no suppression predicate is needed.
2039
2134
  logSummaryLines(formatSweepSummary(installReport));
2040
2135
  // Reference-overlay reporting: the overlay rewrites files inside an installed skill
2041
- // the user may have shadowed, and reports any unit it had to leave alone (PF-015).
2136
+ // the user may have shadowed, and reports any unit it had to leave alone.
2042
2137
  logSummaryLines(formatOverlaySummary(installReport));
2043
2138
  // Skill-scoping reporting: a deselected skill is deleted and a dormant shadow
2044
2139
  // is inert, and neither is distinguishable from "never installed" on disk.
@@ -2060,34 +2155,14 @@ export const initCommand = new Command('init')
2060
2155
  p.note(commandsNote, 'Available commands');
2061
2156
  }
2062
2157
  // Safe-delete status messages (after spinner)
2063
- if (process.stdin.isTTY && profilePath) {
2064
- if (safeDeleteAction === 'install') {
2065
- p.log.success(`Safe-delete installed to ${color.dim(profilePath)}`);
2066
- p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
2067
- }
2068
- else if (safeDeleteAction === 'upgrade') {
2069
- p.log.success(`Safe-delete upgraded in ${color.dim(profilePath)}`);
2070
- p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
2071
- }
2072
- else if (safeDeleteAvailable && safeDeleteBlock) {
2073
- const installedVersion = await getInstalledVersion(profilePath);
2074
- if (classifySafeDeleteState(installedVersion, SAFE_DELETE_BLOCK_VERSION) === 'current') {
2075
- p.log.info(`Safe-delete already configured in ${color.dim(profilePath)}`);
2076
- }
2077
- }
2078
- else if (!safeDeleteAvailable && safeDeleteInfo.installHint) {
2079
- p.log.info(`Install ${color.cyan(safeDeleteInfo.command ?? 'trash')} first: ${color.dim(safeDeleteInfo.installHint)}`);
2080
- p.log.info(`Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.`);
2081
- }
2082
- }
2083
- else if (!process.stdin.isTTY) {
2084
- if (safeDeleteAvailable && safeDeleteInfo.command) {
2085
- p.log.info(`Safe-delete available (${safeDeleteInfo.command}). Run interactively to auto-install.`);
2086
- }
2087
- else if (safeDeleteInfo.installHint) {
2088
- p.log.info(`Protect against accidental ${color.red('rm -rf')}: ${color.cyan(safeDeleteInfo.installHint)}`);
2089
- }
2090
- }
2158
+ logSummaryLines(formatSafeDeleteStatus({
2159
+ interactive: process.stdin.isTTY === true,
2160
+ action: safeDeleteAction,
2161
+ state: safeDeleteState,
2162
+ available: safeDeleteAvailable,
2163
+ profilePath,
2164
+ info: safeDeleteInfo,
2165
+ }));
2091
2166
  // Verbose mode: show details
2092
2167
  if (verbose) {
2093
2168
  const pluginsList = pluginsToInstall
@@ -2120,7 +2195,7 @@ export const initCommand = new Command('init')
2120
2195
  knowledge: knowledgeEnabled,
2121
2196
  learning: learningEnabled,
2122
2197
  rules: rulesEnabled,
2123
- // FlagsRecord written directly — key-presence encodes "known" (ADR-014).
2198
+ // FlagsRecord written directly — key-presence encodes "known".
2124
2199
  // view-mode is encoded as flags['view-mode'] (the resolved final value).
2125
2200
  flags: enabledFlags,
2126
2201
  security: securityMode,
@@ -2141,7 +2216,7 @@ export const initCommand = new Command('init')
2141
2216
  // ── Manifest write + tracker selection lifecycle (the ONE call site) ──────
2142
2217
  // persistManifestThenConvergeTracker owns the ordering invariant: the
2143
2218
  // tracker file-lifecycle owners in src/core/tracker.ts converge only against
2144
- // a provider the manifest actually persisted (D-TRACKER-CONVERGE, PF-015).
2219
+ // a provider the manifest actually persisted (D-TRACKER-CONVERGE).
2145
2220
  const trackerLifecycle = await persistManifestThenConvergeTracker({
2146
2221
  devflowDir,
2147
2222
  claudeDir,
@@ -2155,13 +2230,15 @@ export const initCommand = new Command('init')
2155
2230
  p.log.info(msg.text);
2156
2231
  }
2157
2232
  // Only now that the machine-wide switch is on disk (D-INIT-DRAIN-AFTER-SWITCH).
2158
- await drainDisabledFeatureQueues({
2233
+ const refusedDrains = await drainDisabledFeatureQueues({
2159
2234
  gitRoot,
2160
2235
  ledgerRoot: learningEnabled || gitRoot === null ? null : await getLedgerRoot(),
2161
2236
  memoryEnabled,
2162
2237
  learningEnabled,
2163
2238
  manifestWritten: trackerLifecycle.manifestWritten,
2164
2239
  });
2240
+ for (const line of refusedDrains)
2241
+ p.log.warn(line);
2165
2242
  // The hooks' per-directory log folders, capped (D-LOG-DIR-CAP): one pass
2166
2243
  // clears every folder it scans beyond the cap; only a backlog beyond the
2167
2244
  // scan bound (MAX_LOG_DIRS_SCANNED, 100,000) waits for the next init.