devflow-kit 3.2.0 → 3.4.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 (188) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/agents/code.md +330 -0
  4. package/{src/assets → dist}/agents/design.md +11 -1
  5. package/{src/assets → dist}/agents/diagnose.md +17 -13
  6. package/dist/agents/git.md +29 -56
  7. package/{src/assets → dist}/agents/knowledge.md +8 -5
  8. package/{src/assets → dist}/agents/research.md +21 -2
  9. package/{src/assets → dist}/agents/review.md +18 -7
  10. package/{src/assets → dist}/agents/scrutinize.md +35 -11
  11. package/dist/agents/skim.md +148 -0
  12. package/{src/assets → dist}/agents/triage.md +8 -2
  13. package/dist/cli/commands/init.js +62 -0
  14. package/dist/cli/commands/learning.js +63 -8
  15. package/dist/cli/commands/uninstall.js +42 -1
  16. package/dist/commands/bug-analysis.md +30 -8
  17. package/dist/commands/code-review.md +142 -61
  18. package/dist/commands/debug.md +15 -13
  19. package/dist/commands/dynamic-build.md +176 -130
  20. package/dist/commands/dynamic-plan.md +31 -19
  21. package/dist/commands/dynamic-profile.md +27 -13
  22. package/dist/commands/dynamic-tickets.md +28 -14
  23. package/dist/commands/explore.md +16 -14
  24. package/dist/commands/implement.md +163 -158
  25. package/dist/commands/plan.md +37 -24
  26. package/dist/commands/release.md +69 -4
  27. package/dist/commands/research.md +33 -11
  28. package/dist/commands/resolve.md +61 -47
  29. package/dist/commands/self-review.md +50 -32
  30. package/dist/core/agent-models.js +53 -7
  31. package/dist/core/assets.js +55 -10
  32. package/dist/core/claude-md-audit.js +190 -0
  33. package/dist/core/feature-switch.js +20 -1
  34. package/dist/core/flags.js +28 -0
  35. package/dist/core/fs-atomic.js +8 -3
  36. package/dist/core/learning-tuning-config.js +8 -0
  37. package/dist/core/learning-variants.js +213 -0
  38. package/dist/core/manifest.js +62 -0
  39. package/dist/core/mds-variants.js +38 -1
  40. package/dist/core/plugins.js +78 -11
  41. package/{src/assets → dist/learning-off}/agents/code.md +77 -28
  42. package/dist/learning-off/agents/design.md +119 -0
  43. package/dist/learning-off/agents/diagnose.md +210 -0
  44. package/dist/learning-off/agents/knowledge.md +90 -0
  45. package/dist/learning-off/agents/research.md +149 -0
  46. package/dist/learning-off/agents/review.md +228 -0
  47. package/dist/learning-off/agents/scrutinize.md +117 -0
  48. package/{src/assets → dist/learning-off}/agents/skim.md +5 -10
  49. package/dist/learning-off/agents/triage.md +163 -0
  50. package/dist/learning-off/commands/bug-analysis.md +420 -0
  51. package/dist/learning-off/commands/code-review.md +525 -0
  52. package/dist/learning-off/commands/debug.md +294 -0
  53. package/dist/learning-off/commands/dynamic-build.md +1255 -0
  54. package/dist/learning-off/commands/dynamic-plan.md +424 -0
  55. package/dist/learning-off/commands/dynamic-profile.md +214 -0
  56. package/dist/learning-off/commands/dynamic-tickets.md +632 -0
  57. package/dist/learning-off/commands/explore.md +210 -0
  58. package/dist/learning-off/commands/implement.md +808 -0
  59. package/dist/learning-off/commands/plan.md +664 -0
  60. package/dist/learning-off/commands/release.md +310 -0
  61. package/dist/learning-off/commands/research.md +222 -0
  62. package/dist/learning-off/commands/resolve.md +837 -0
  63. package/dist/learning-off/commands/self-review.md +266 -0
  64. package/dist/skills/git/references/tracker/_contract.md +33 -0
  65. package/dist/skills/git/references/tracker/github/fetch-issue.md +2 -0
  66. package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +2 -0
  67. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +5 -1
  68. package/dist/skills/git/references/tracker/github/post-wave-report.md +2 -0
  69. package/dist/skills/git/references/tracker/github/setup-task.md +12 -0
  70. package/dist/skills/git/references/tracker/jira/associate-release.md +1 -1
  71. package/dist/skills/git/references/tracker/jira/fetch-issue.md +2 -0
  72. package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +2 -0
  73. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +5 -1
  74. package/dist/skills/git/references/tracker/jira/post-wave-report.md +2 -0
  75. package/dist/skills/git/references/tracker/jira/setup-task.md +14 -2
  76. package/dist/skills/git/references/tracker/linear/associate-release.md +1 -1
  77. package/dist/skills/git/references/tracker/linear/fetch-issue.md +2 -0
  78. package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +2 -0
  79. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +5 -1
  80. package/dist/skills/git/references/tracker/linear/post-wave-report.md +2 -0
  81. package/dist/skills/git/references/tracker/linear/setup-task.md +14 -2
  82. package/dist/targets/claude-code/installer.js +72 -36
  83. package/dist/targets/claude-code/language-stamp.js +185 -0
  84. package/dist/targets/claude-code/learning-install.js +489 -0
  85. package/package.json +1 -1
  86. package/src/assets/agents/code.mds +339 -0
  87. package/src/assets/agents/design.mds +149 -0
  88. package/src/assets/agents/diagnose.mds +225 -0
  89. package/src/assets/agents/evaluate.md +12 -25
  90. package/src/assets/agents/git.mds +29 -56
  91. package/src/assets/agents/knowledge.mds +125 -0
  92. package/src/assets/agents/learning.md +4 -6
  93. package/src/assets/agents/research.mds +176 -0
  94. package/src/assets/agents/review.mds +286 -0
  95. package/src/assets/agents/scrutinize.mds +132 -0
  96. package/src/assets/agents/simplify.md +20 -0
  97. package/src/assets/agents/skim.mds +161 -0
  98. package/src/assets/agents/synthesize.md +12 -0
  99. package/src/assets/agents/test.md +2 -2
  100. package/src/assets/agents/triage.mds +194 -0
  101. package/src/assets/agents/validate.md +15 -8
  102. package/src/assets/commands/_partials/_compliance.mds +5 -4
  103. package/src/assets/commands/_partials/_decisions.mds +31 -0
  104. package/src/assets/commands/_partials/_engine.mds +29 -24
  105. package/src/assets/commands/_partials/_knowledge.mds +26 -13
  106. package/src/assets/commands/_partials/_plan_contract.mds +1 -1
  107. package/src/assets/commands/_partials/_preamble.mds +33 -9
  108. package/src/assets/commands/_partials/_publication.mds +5 -4
  109. package/src/assets/commands/_partials/_settings.mds +13 -5
  110. package/src/assets/commands/_partials/_wave.mds +16 -6
  111. package/src/assets/commands/bug-analysis.mds +24 -2
  112. package/src/assets/commands/code-review.mds +148 -45
  113. package/src/assets/commands/debug.mds +17 -1
  114. package/src/assets/commands/dynamic-build.mds +144 -65
  115. package/src/assets/commands/dynamic-plan.mds +36 -6
  116. package/src/assets/commands/dynamic-profile.mds +9 -1
  117. package/src/assets/commands/dynamic-tickets.mds +16 -2
  118. package/src/assets/commands/explore.mds +27 -1
  119. package/src/assets/commands/implement.mds +171 -138
  120. package/src/assets/commands/plan.mds +47 -8
  121. package/src/assets/commands/{release.md → release.mds} +27 -24
  122. package/src/assets/commands/research.mds +28 -4
  123. package/src/assets/commands/resolve.mds +69 -17
  124. package/src/assets/commands/self-review.mds +43 -13
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_contract.mds +72 -0
  127. package/src/assets/mds/tracker/_github.mds +13 -2
  128. package/src/assets/mds/tracker/_jira.mds +17 -5
  129. package/src/assets/mds/tracker/_linear.mds +17 -5
  130. package/src/assets/mds/tracker/_mcp.mds +2 -2
  131. package/src/assets/mds/tracker/_steps.mds +97 -0
  132. package/src/assets/rules/context-economy.md +10 -0
  133. package/src/assets/rules/go.md +1 -0
  134. package/src/assets/rules/java.md +1 -0
  135. package/src/assets/rules/python.md +1 -0
  136. package/src/assets/rules/rust.md +1 -0
  137. package/src/assets/rules/typescript.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +636 -0
  139. package/src/assets/scripts/claude-md-audit.cjs +611 -0
  140. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +2 -3
  141. package/src/assets/scripts/hooks/background-memory-update +290 -9
  142. package/src/assets/scripts/hooks/json-helper.cjs +13 -5
  143. package/src/assets/scripts/hooks/json-parse +34 -10
  144. package/src/assets/scripts/hooks/session-start-context +315 -7
  145. package/src/assets/scripts/release-trace.cjs +27 -10
  146. package/src/assets/skills/accessibility/SKILL.md +1 -1
  147. package/src/assets/skills/apply-decisions/SKILL.md +12 -82
  148. package/src/assets/skills/apply-feature-knowledge/SKILL.md +9 -43
  149. package/src/assets/skills/architecture/SKILL.md +1 -1
  150. package/src/assets/skills/boundary-validation/SKILL.md +1 -1
  151. package/src/assets/skills/complexity/SKILL.md +1 -1
  152. package/src/assets/skills/compliance/SKILL.md +1 -1
  153. package/src/assets/skills/consistency/SKILL.md +1 -1
  154. package/src/assets/skills/database/SKILL.md +1 -1
  155. package/src/assets/skills/dependencies/SKILL.md +1 -1
  156. package/src/assets/skills/dependency-research/SKILL.md +3 -6
  157. package/src/assets/skills/design-review/SKILL.md +1 -1
  158. package/src/assets/skills/docs-framework/SKILL.md +1 -1
  159. package/src/assets/skills/documentation/SKILL.md +1 -1
  160. package/src/assets/skills/feature-knowledge/SKILL.md +43 -12
  161. package/src/assets/skills/gap-analysis/SKILL.md +1 -1
  162. package/src/assets/skills/git/SKILL.md +1 -1
  163. package/src/assets/skills/go/SKILL.md +1 -1
  164. package/src/assets/skills/java/SKILL.md +1 -1
  165. package/src/assets/skills/patterns/SKILL.md +1 -1
  166. package/src/assets/skills/performance/SKILL.md +1 -1
  167. package/src/assets/skills/python/SKILL.md +1 -1
  168. package/src/assets/skills/qa/SKILL.md +1 -3
  169. package/src/assets/skills/quality-gates/SKILL.md +10 -13
  170. package/src/assets/skills/quality-gates/references/report-template.md +20 -20
  171. package/src/assets/skills/react/SKILL.md +1 -1
  172. package/src/assets/skills/regression/SKILL.md +1 -1
  173. package/src/assets/skills/reliability/SKILL.md +1 -1
  174. package/src/assets/skills/research-codebase/SKILL.md +1 -1
  175. package/src/assets/skills/research-competitor/SKILL.md +1 -1
  176. package/src/assets/skills/research-external/SKILL.md +1 -1
  177. package/src/assets/skills/research-technology/SKILL.md +1 -1
  178. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  179. package/src/assets/skills/rust/SKILL.md +1 -1
  180. package/src/assets/skills/security/SKILL.md +1 -1
  181. package/src/assets/skills/software-design/SKILL.md +1 -1
  182. package/src/assets/skills/test-driven-development/SKILL.md +9 -29
  183. package/src/assets/skills/testing/SKILL.md +1 -1
  184. package/src/assets/skills/typescript/SKILL.md +1 -1
  185. package/src/assets/skills/ui-design/SKILL.md +1 -1
  186. package/src/assets/skills/worktree-support/SKILL.md +3 -55
  187. package/src/assets/skills/worktree-support/references/discovery.md +48 -0
  188. package/src/assets/skills/worktree-support/references/roots.md +2 -2
@@ -43,6 +43,8 @@ import { applyTrackerSentinel, parseTrackerId, rearmTrackerInference, DEFAULT_TR
43
43
  import { formatTrackerSummary, shouldRunTrackerStep, runTrackerStep, buildClackTrackerPrompts, } from './tracker-prompts.js';
44
44
  import { shouldRunAttributionStep, runAttributionStep, buildClackAttributionPrompts, applyAttributionAnswer, attributionSeedFrom, } from './attribution-prompts.js';
45
45
  import { convergeFromManifest } from '../../targets/claude-code/compliance-install.js';
46
+ import { convergeLearningVariants } from '../../targets/claude-code/learning-install.js';
47
+ import { auditAndRecordClaudeMd, formatClaudeMdAuditNote, formatClaudeMdAuditUnavailable, } from '../../core/claude-md-audit.js';
46
48
  import * as os from 'os';
47
49
  // Re-export pure functions for tests (canonical source is post-install.ts)
48
50
  export { substituteSettingsTemplate, mergeDenyList, discoverProjectGitRoots } from '../../targets/claude-code/post-install.js';
@@ -319,6 +321,24 @@ export function trackerOverrideMessage(provider) {
319
321
  text: `Tracker: ${formatTrackerSummary(provider)}`,
320
322
  };
321
323
  }
324
+ /**
325
+ * The audit's printable outcome, the ONE function both init paths use.
326
+ *
327
+ * D-CLAUDE-MD-IMPORT-AUDIT, D-INIT-REAL-OUTCOME: the Recommended summary note prints
328
+ * before the install runs, and the Advanced path prints no end-of-wizard summary
329
+ * (D-TRACKER-CLI-SURFACE), so the audit cannot be a row of either. It is decided after
330
+ * the install, from the CLAUDE.md files as they are, and printed as one note by the single
331
+ * call site in `run`, whichever path got there: the lines are the script's own formatter's,
332
+ * the ones the SessionStart hook shows in its systemMessage. Nothing is printed when
333
+ * nothing is flagged; a failure is at most one degraded line and never changes the exit code.
334
+ *
335
+ * Pure — returns text, prints nothing.
336
+ */
337
+ export function claudeMdAuditStep(outcome) {
338
+ if (!outcome.ok)
339
+ return { note: null, degraded: formatClaudeMdAuditUnavailable(outcome.error) };
340
+ return { note: formatClaudeMdAuditNote(outcome.value), degraded: null };
341
+ }
322
342
  /** The real adapter — the ONE binding of each owner into the init lifecycle. */
323
343
  export function buildTrackerLifecycleIO() {
324
344
  return {
@@ -1558,6 +1578,10 @@ export const initCommand = new Command('init')
1558
1578
  rulesMap,
1559
1579
  isPartialInstall: !!options.plugin,
1560
1580
  spinner: s,
1581
+ // The SETTLED switch, after the flag, the prompt and the seed have all had
1582
+ // their say (D-LEARNING-VARIANT-INSTALL): the files installed here and the
1583
+ // manifest written at the end of init cannot disagree about it.
1584
+ learning: learningEnabled,
1561
1585
  // Non-fatal install notices with no other channel (skipped symlinks in the
1562
1586
  // generated reference tree, mode-normalisation failures) reach the user rather
1563
1587
  // than the void. Collected now, emitted after the spinner stops.
@@ -1569,6 +1593,30 @@ export const initCommand = new Command('init')
1569
1593
  p.log.error(`${error}`);
1570
1594
  process.exit(1);
1571
1595
  }
1596
+ // D-LEARNING-VARIANT-INSTALL: converge the learning variants. The file copy above
1597
+ // installed the variant of everything IT installed; on a `--plugin` install the
1598
+ // other plugins' files, already on disk, may still be the other variant (this run
1599
+ // may have flipped the switch), and the apply-decisions skill is removed here when
1600
+ // the copy skipped it on a partial install. Rewrites only what is installed.
1601
+ // Warn-not-abort: either mismatch is safe (an on variant is still gated at run
1602
+ // time, an off variant loads no decisions). The agent mapping is reapplied below.
1603
+ try {
1604
+ const learningConverge = await convergeLearningVariants({
1605
+ claudeDir,
1606
+ devflowDir,
1607
+ learning: learningEnabled,
1608
+ plugins: effectivePlugins,
1609
+ warn: (msg) => installWarnings.push(msg),
1610
+ });
1611
+ if (verbose && learningConverge.converged) {
1612
+ p.log.info(`Learning ${learningEnabled ? 'on' : 'off'} variants: ` +
1613
+ `${learningConverge.commandsRewritten.length} command(s) and ${learningConverge.agentsRewritten.length} agent(s) ` +
1614
+ `rewritten, ${learningConverge.unchanged} already current`);
1615
+ }
1616
+ }
1617
+ catch (err) {
1618
+ installWarnings.push(`Learning variant convergence failed — ${err instanceof Error ? err.message : String(err)}`);
1619
+ }
1572
1620
  // Converge compliance artifacts (always converge, never short-circuit).
1573
1621
  // Called unconditionally so that enabling/disabling compliance during init
1574
1622
  // is reflected in the installed artifacts without a separate devflow compliance run.
@@ -2263,6 +2311,20 @@ export const initCommand = new Command('init')
2263
2311
  agent: trackerLifecycle.agent,
2264
2312
  });
2265
2313
  logSummaryLines(trackerLines);
2314
+ // CLAUDE.md import audit (D-CLAUDE-MD-IMPORT-AUDIT, D-AUDIT-STAMP): after the install, on
2315
+ // both paths, through claudeMdAuditStep. Global roots always; the project's roots when init
2316
+ // runs inside a git repository that is not HOME, at its toplevel. The stamp is written here,
2317
+ // now that the machine root exists, so the first SessionStart repeats nothing printed below.
2318
+ const auditStep = claudeMdAuditStep(await auditAndRecordClaudeMd({
2319
+ claudeDir,
2320
+ projectRoot: gitRoot,
2321
+ home: homeDir,
2322
+ devflowDir,
2323
+ }));
2324
+ if (auditStep.note !== null)
2325
+ p.note(auditStep.note, 'CLAUDE.md import audit');
2326
+ if (auditStep.degraded !== null)
2327
+ p.log.warn(auditStep.degraded);
2266
2328
  // External model routing status line (Advanced path / explicit --proxy flag only)
2267
2329
  if (proxyEnabled) {
2268
2330
  p.log.info(`External model routing: ${color.green('enabled')} — takes effect in new Claude Code sessions`);
@@ -4,9 +4,11 @@ import * as path from 'path';
4
4
  import * as p from '@clack/prompts';
5
5
  import color from 'picocolors';
6
6
  import { getLearningDir, getLearningTuningConfigPath, } from '../../core/project-paths.js';
7
+ import { loadShippedAgentDefaults } from '../../core/agent-models.js';
7
8
  import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
8
9
  import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
9
- import { getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
10
+ import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
11
+ import { applyLearningToggle, describeLearningConverge, readRunningVersion, } from '../../targets/claude-code/learning-install.js';
10
12
  import { getLedgerRoot } from '../../core/ledger-root.js';
11
13
  import { drainLearningQueue } from '../../core/learning-queue-cleanup.js';
12
14
  import { firstSymbolicLink } from '../../core/linked-path.js';
@@ -273,15 +275,34 @@ export const CONFIGURE_SCOPE_OPTIONS = [
273
275
  hint: 'All projects (~/.devflow/learning.json); a devflow agents Learning mapping takes precedence over it',
274
276
  },
275
277
  ];
278
+ /** The models `devflow learning --configure` offers, each with what it is good for. */
279
+ const LEARNING_MODEL_CHOICES = [
280
+ { value: 'opus', label: 'Opus', note: 'highest quality for detection + curation judgment' },
281
+ { value: 'sonnet', label: 'Sonnet', note: 'good balance of quality and speed' },
282
+ { value: 'haiku', label: 'Haiku', note: 'fastest, lowest cost' },
283
+ ];
284
+ /**
285
+ * D-LEARNING-SHIPPED-ROW: the model offered as "Recommended" is the one the Learning
286
+ * agent ships with, the `model:` line of its frontmatter. It is read from there and
287
+ * written nowhere else, so a change of the shipped tier moves the recommendation with
288
+ * it and this list never has to be edited. A shipped model outside the three offered
289
+ * (or none) recommends nothing.
290
+ */
291
+ export function learningModelOptions(shippedModel) {
292
+ return LEARNING_MODEL_CHOICES.map(choice => ({
293
+ value: choice.value,
294
+ label: choice.label,
295
+ hint: choice.value === shippedModel
296
+ ? `Recommended — ${choice.note}`
297
+ : choice.note.charAt(0).toUpperCase() + choice.note.slice(1),
298
+ }));
299
+ }
276
300
  async function handleConfigure() {
277
301
  p.intro(color.bgCyan(color.black(' Learning Configuration ')));
302
+ const shippedModel = (await loadShippedAgentDefaults()).learning?.model;
278
303
  const model = await p.select({
279
304
  message: 'Model for decision detection',
280
- options: [
281
- { value: 'opus', label: 'Opus', hint: 'Recommended — highest quality for detection + curation judgment' },
282
- { value: 'sonnet', label: 'Sonnet', hint: 'Good balance of quality and speed' },
283
- { value: 'haiku', label: 'Haiku', hint: 'Fastest, lowest cost' },
284
- ],
305
+ options: learningModelOptions(shippedModel),
285
306
  });
286
307
  if (p.isCancel(model)) {
287
308
  p.cancel('Configuration cancelled.');
@@ -406,11 +427,44 @@ async function handleClear() {
406
427
  if (!drain.drained)
407
428
  p.log.warn(formatRefusedDrain('learning', drain.linkedFolder));
408
429
  }
430
+ /**
431
+ * Make the installed prompts match the switch just written
432
+ * (D-LEARNING-VARIANT-INSTALL): the learning-on or learning-off commands and
433
+ * agents, and the apply-decisions skill. The same convergence `devflow init` runs,
434
+ * followed by reapplying the saved agent model mapping so `devflow agents`
435
+ * overrides survive.
436
+ *
437
+ * Warn-not-fail: the switch itself is already recorded, and either mismatch is
438
+ * safe (an on variant is still gated at run time, an off variant loads no
439
+ * decisions), so a problem here never changes the exit code.
440
+ */
441
+ async function convergeInstalledVariants(enabled) {
442
+ try {
443
+ const outcome = await applyLearningToggle({
444
+ claudeDir: getClaudeDirectory(),
445
+ devflowDir: getDevFlowDirectory(),
446
+ learning: enabled,
447
+ runningVersion: await readRunningVersion(),
448
+ warn: (msg) => p.log.warn(msg),
449
+ });
450
+ if (outcome.kind === 'skipped') {
451
+ p.log.warn(outcome.message);
452
+ return;
453
+ }
454
+ const changed = describeLearningConverge(outcome.result, enabled);
455
+ if (changed !== null)
456
+ p.log.info(color.dim(`Installed prompts: ${changed}`));
457
+ }
458
+ catch (err) {
459
+ p.log.warn(`Could not update the installed prompts for the new learning setting — run ${color.cyan('devflow init')}: ${String(err)}`);
460
+ }
461
+ }
409
462
  /**
410
463
  * `--enable` / `--disable`: the machine-wide switch (D-FEATURES-NARROW-ONLY),
411
464
  * converged exactly as `devflow init --learning / --no-learning` converges it —
412
- * the manifest value, and on disable a drained queue in the current project.
413
- * Never requires a git root: the switch is not a per-project setting.
465
+ * the manifest value, the installed prompt variants and skill
466
+ * (D-LEARNING-VARIANT-INSTALL), and on disable a drained queue in the current
467
+ * project. Never requires a git root: the switch is not a per-project setting.
414
468
  */
415
469
  async function handleToggle(enabled) {
416
470
  const recorded = await writeMachineFeature(getDevFlowDirectory(), 'learning', enabled);
@@ -419,6 +473,7 @@ async function handleToggle(enabled) {
419
473
  process.exitCode = 1;
420
474
  return;
421
475
  }
476
+ await convergeInstalledVariants(enabled);
422
477
  if (enabled) {
423
478
  p.log.success('Learning enabled in every project (a repository can opt out)');
424
479
  p.log.info(color.dim('Architectural decisions and pitfalls will be detected from your sessions'));
@@ -8,7 +8,7 @@ import { getInstallationPaths, getClaudeDirectory, getHomeDirectory, getManagedS
8
8
  import { getGitRoot } from '../../core/git.js';
9
9
  import { isSameLocation } from '../../core/same-location.js';
10
10
  import { DEVFLOW_PLUGINS, SKILL_NAMESPACE, getAllSkillNames, getAllAgentNames, getAllCommandNames, parsePluginSelection, resolveFeatureRedirect, prefixSkillName, unprefixSkillName, skillsOf, FEATURE_OWNED_SKILLS } from '../../core/plugins.js';
11
- import { readManifest } from '../../core/manifest.js';
11
+ import { readManifest, removeManifestPlugins } from '../../core/manifest.js';
12
12
  import { sweepOrphanedAssets, mdFileName, mdEntryName } from '../../core/orphan-sweep.js';
13
13
  import { LEGACY_SKILL_NAMES } from '../../targets/claude-code/legacy.js';
14
14
  import { removeAmbientHook } from './ambient.js';
@@ -21,6 +21,7 @@ import { applyProxyTeardownToSettings } from './proxy.js';
21
21
  import { readProxyState, proxyJsonExists } from '../../core/proxy-state.js';
22
22
  import { hudCacheDir } from '../../core/cache.js';
23
23
  import { TRACKER_ATTEMPTS_NAMES, TRACKER_CLAIM_FILE, TRACKER_CONVENTIONS_DIR, TRACKER_ENABLED_FILE, TRACKER_LEGACY_ATTEMPTS_FILE, TRACKER_LEGACY_CONVENTIONS_FILE, TRACKER_PROVIDER_IDS, TRACKER_STAGED_PREFIX, } from '../../core/tracker.js';
24
+ import { CLAUDE_MD_AUDIT_STAMP_FILE, CLAUDE_MD_AUDIT_STAMP_TMP_PREFIX } from '../../core/claude-md-audit.js';
24
25
  import { revertExternalAgents } from '../../core/agent-models.js';
25
26
  import { detectShell, getProfilePath } from '../../core/safe-delete.js';
26
27
  import { isAlreadyInstalled, removeFromProfile } from '../../core/safe-delete-install.js';
@@ -30,6 +31,7 @@ import { stripFlags } from '../../core/flags.js';
30
31
  import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-cleanup.js';
31
32
  import { getPackageRoot, isContainedIn } from '../../core/paths.js';
32
33
  import { firstSymbolicLink } from '../../core/linked-path.js';
34
+ import { restampInstalledCommands } from '../../targets/claude-code/language-stamp.js';
33
35
  /**
34
36
  * Where a retired repo-local install lives: `<gitRoot>/.claude` and `<gitRoot>/.devflow`.
35
37
  *
@@ -607,6 +609,12 @@ export function installArtifactPaths(devflowDir) {
607
609
  ...TRACKER_ATTEMPTS_NAMES.map(name => ({ relPath: name })),
608
610
  { relPath: TRACKER_LEGACY_ATTEMPTS_FILE },
609
611
  { relPath: TRACKER_ENABLED_FILE },
612
+ // The CLAUDE.md import audit's stamp (D-AUDIT-STAMP): machine state the SessionStart
613
+ // hook and `devflow init` write, holding paths, existence flags and finding keys and no
614
+ // user-authored content. Its sibling temp file, `<stamp>.tmp.<pid>`, is a prefix family:
615
+ // a SIGKILL between the write and the rename leaves one behind.
616
+ { relPath: CLAUDE_MD_AUDIT_STAMP_FILE },
617
+ { relPath: CLAUDE_MD_AUDIT_STAMP_TMP_PREFIX, isPrefix: true },
610
618
  // The agent's scrubbed staging file, one per invocation under a mktemp name
611
619
  // it removes from a trap — a SIGKILL outruns the trap and leaves it behind.
612
620
  // A prefix, because the names exist only on disk. Content is a scrubbed copy
@@ -912,6 +920,39 @@ export async function runSelectivePhaseForScope(opts) {
912
920
  catch { /* agents dir absent or revert failed — non-fatal */ }
913
921
  }
914
922
  await removeSelectedPlugins(claudeDir, selectedPlugins, verbose, installedPlugins, mayChange);
923
+ // D-LANGUAGE-FOCUS-STAMP: re-stamp the installed /code-review from the selection that REMAINS.
924
+ // A language plugin owns a skill and no command, so removing it deletes the skill while its
925
+ // name would stay on the stamped line, and /code-review would spawn a focus whose skill is gone.
926
+ // The remaining selection is the retained set the removal above used (installedPlugins minus the
927
+ // selected plugins, D-RETAIN-FROM-MANIFEST), so the stamp and the skills on disk cannot disagree.
928
+ // Narrowly scoped: only the stamped line of a command that is still installed is rewritten, and
929
+ // nothing else converges here. A damaged copy is a warning, never a failure.
930
+ {
931
+ const removed = new Set(selectedPlugins.map(sp => sp.name));
932
+ await restampInstalledCommands({
933
+ claudeDir,
934
+ effectivePlugins: installedPlugins.filter(plugin => !removed.has(plugin.name)),
935
+ warn: (msg) => p.log.warn(msg),
936
+ mayChange,
937
+ });
938
+ }
939
+ // D-UNINSTALL-DROPS-PLUGIN: the manifest is the install record `devflow init` seeds its plugin
940
+ // selection from (a re-init keeps the prior selection), so a plugin removed here but still listed
941
+ // would be installed again by the next plain init. Take the selected names off the list;
942
+ // knownPlugins and every other key stay.
943
+ // Behind the scope guard like every other write here. A failed write is a warning, never a failure.
944
+ {
945
+ const manifestPath = path.join(devflowDir, 'manifest.json');
946
+ if (await mayChange(manifestPath)) {
947
+ const recorded = await removeManifestPlugins(devflowDir, selectedPlugins.map(sp => sp.name));
948
+ if (!recorded.ok) {
949
+ p.log.warn(`Could not drop the plugin from manifest.json — a later devflow init would install it again: ${recorded.error}`);
950
+ }
951
+ else if (verbose && recorded.removed.length > 0) {
952
+ p.log.success(`Removed ${recorded.removed.join(', ')} from manifest.json`);
953
+ }
954
+ }
955
+ }
915
956
  // Clean up ambient hook if ambient plugin is being removed
916
957
  if (selectedPlugins.some(sp => sp.name === 'devflow-ambient')) {
917
958
  const settingsPath = path.join(claudeDir, 'settings.json');
@@ -177,12 +177,24 @@ If no tool produced findings: set `STATIC_FINDINGS` to `(none)`.
177
177
 
178
178
  ### Phase 3: Context Loading
179
179
 
180
- **Produces:** DECISIONS_CONTEXT, FEATURE_KNOWLEDGE, PLAN_CONTEXT, ACCEPTANCE_RULES
180
+ **Produces:** DECISIONS_CONTEXT, FEATURE_KNOWLEDGE, FEATURE_KNOWLEDGE_RULES, PLAN_CONTEXT, ACCEPTANCE_RULES
181
181
 
182
182
  #### Decisions Index
183
183
 
184
+ **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
185
+
186
+ ```bash
187
+ node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
188
+ ```
189
+
190
+ Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off` instead.
191
+
192
+ The accepted line is the only source of these values: the script alone folds the committed `.devflow/project.json`, the personal `.devflow/config.json` and the machine manifest.
193
+
184
194
  ### Load DECISIONS_CONTEXT
185
195
 
196
+ When the settings line says `LEARNING=off`, set `DECISIONS_CONTEXT` to `(none)` and skip this step, locating no ledger and reading no index.
197
+
186
198
  The decisions ledger belongs to the repository, not to one checkout: in a linked worktree it lives in the main worktree, and a session started in a subdirectory reads the copy at the repository root. Locate it with ONE git call, run from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`):
187
199
 
188
200
  ```bash
@@ -240,22 +252,32 @@ Glob `{worktree}/.devflow/features/*/KNOWLEDGE.md`. For each file found, read on
240
252
 
241
253
  Match the current task area and description against each index line (or frontmatter `description` + `directories` on fallback). Select entries whose documented area overlaps the current task. This is a relevance judgment — prefer specificity over breadth.
242
254
 
243
- **Step 4 — Read selected KBs:**
255
+ **Step 4 — Read each selected KB's Rules:**
256
+
257
+ For each selected entry, `{kb}` is `{worktree}/.devflow/features/{slug}/KNOWLEDGE.md`:
258
+
259
+ 1. List its `##` headings with line numbers through Bash: `command grep -n '^## ' "{kb}"`. This only locates sections; the text of a KB comes from the Read view alone.
260
+ 2. Read the `## Rules` range (its line to the next heading) with the Read tool, using `offset` and `limit`, and choose the one to three bullets most relevant to the current task. The choice is yours, made per KB.
261
+ 3. If the KB has no `## Rules` section, choose one to three entries from its `## Anti-Patterns` or `## Gotchas` range the same way, and label them by that section's name instead of an ID.
244
262
 
245
- For each selected entry, read `{worktree}/.devflow/features/{slug}/KNOWLEDGE.md` in full. When the KB content contradicts the current code you observe, **trust the code** — the code is the freshness mechanism; the KB may lag behind.
263
+ When a KB contradicts the code you observe, **trust the code** — the code is the freshness mechanism; the KB may lag behind. A missing Rules section, a missing heading list and `(none)` are legitimate states, not errors.
246
264
 
247
- **Step 5 — Set FEATURE_KNOWLEDGE:**
265
+ **Step 5 — Set FEATURE_KNOWLEDGE and FEATURE_KNOWLEDGE_RULES:**
248
266
 
249
- Concatenate the selected KNOWLEDGE.md files under slug headers:
267
+ Write one block per selected KB. Paste each bullet verbatim from the Read view, never from a shell view. The path is relative to the checkout root; an agent resolves it under `WORKTREE_PATH` when one is provided.
250
268
 
251
269
  ```
252
270
  --- Feature knowledge: {slug} ---
253
- {full KNOWLEDGE.md content}
271
+ KB: .devflow/features/{slug}/KNOWLEDGE.md
272
+ Rules:
273
+ - **KB-AP-2** {bullet text, verbatim}
274
+ - **KB-INV-1** {bullet text, verbatim}
275
+ Headings: L5 Rules · L40 Overview · L62 Anti-Patterns · L118 Key Files
254
276
  ```
255
277
 
256
- If no KBs exist, no KBs are relevant, or `.devflow/features/` is absent, set `FEATURE_KNOWLEDGE` to `(none)`.
278
+ A KB with no Rules section labels its entries `Rules ({section name}):` and gives them no ID. `FEATURE_KNOWLEDGE` is these blocks; `FEATURE_KNOWLEDGE_RULES` is the same blocks without the `Headings:` line. Both come from this one selection, and each spawn names the variable its recipient takes. If no KBs exist, no KBs are relevant, or `.devflow/features/` is absent, set both to `(none)`.
257
279
 
258
- **One git call, then direct file reads — no `.cjs` script.** After resolving `{worktree}`, this step is 1 index read (or N frontmatter reads on fallback), bounded by KB count.
280
+ **One git call, then direct reads — no `.cjs` script.** After resolving `{worktree}`, this step is 1 index read (or N frontmatter reads on fallback), plus one heading listing and one Rules read per selected KB, bounded by KB count.
259
281
 
260
282
  #### Plan Artifact
261
283