devflow-kit 2.4.0 → 3.0.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 (213) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +111 -18
  3. package/dist/agents/git.md +822 -0
  4. package/dist/cli/commands/agents.js +6 -1
  5. package/dist/cli/commands/ambient.js +160 -145
  6. package/dist/cli/commands/attribution-prompts.js +1 -1
  7. package/dist/cli/commands/capture.js +29 -55
  8. package/dist/cli/commands/compliance-prompts.js +1 -1
  9. package/dist/cli/commands/compliance.js +48 -55
  10. package/dist/cli/commands/context.js +17 -32
  11. package/dist/cli/commands/debug.js +65 -26
  12. package/dist/cli/commands/flags.js +3 -3
  13. package/dist/cli/commands/hud.js +34 -10
  14. package/dist/cli/commands/init-seed.js +61 -27
  15. package/dist/cli/commands/init.js +649 -240
  16. package/dist/cli/commands/install-report.js +200 -0
  17. package/dist/cli/commands/knowledge/index.js +2 -2
  18. package/dist/cli/commands/knowledge/toggle.js +35 -37
  19. package/dist/cli/commands/learning.js +79 -57
  20. package/dist/cli/commands/legacy-hooks.js +11 -14
  21. package/dist/cli/commands/memory.js +134 -135
  22. package/dist/cli/commands/prompt-io.js +4 -4
  23. package/dist/cli/commands/proxy.js +23 -41
  24. package/dist/cli/commands/security.js +81 -29
  25. package/dist/cli/commands/skills.js +71 -7
  26. package/dist/cli/commands/tracker-prompts.js +145 -0
  27. package/dist/cli/commands/tracker.js +277 -0
  28. package/dist/cli/commands/uninstall.js +520 -169
  29. package/dist/cli.js +2 -0
  30. package/dist/commands/bug-analysis.md +58 -14
  31. package/dist/commands/code-review.md +110 -32
  32. package/dist/commands/debug.md +55 -11
  33. package/dist/commands/dynamic-build.md +344 -73
  34. package/dist/commands/dynamic-plan.md +77 -27
  35. package/dist/commands/dynamic-profile.md +25 -11
  36. package/dist/commands/dynamic-tickets.md +76 -15
  37. package/dist/commands/explore.md +37 -7
  38. package/dist/commands/implement.md +314 -62
  39. package/dist/commands/plan.md +146 -32
  40. package/dist/commands/release.md +64 -17
  41. package/dist/commands/research.md +34 -8
  42. package/dist/commands/resolve.md +196 -68
  43. package/dist/commands/self-review.md +45 -9
  44. package/dist/core/agent-models.js +55 -12
  45. package/dist/core/assets.js +58 -2
  46. package/dist/core/compliance-compose.js +27 -27
  47. package/dist/core/evidence-policy.js +363 -0
  48. package/dist/core/feature-config.js +200 -65
  49. package/dist/core/feature-switch.js +112 -0
  50. package/dist/core/flags.js +34 -6
  51. package/dist/core/fs-atomic.js +27 -0
  52. package/dist/core/hook-log-dirs.js +104 -0
  53. package/dist/core/learning-tuning-config.js +5 -3
  54. package/dist/core/ledger-root.js +102 -0
  55. package/dist/core/manifest.js +38 -10
  56. package/dist/core/mds-variants.js +798 -0
  57. package/dist/core/migrations.js +49 -23
  58. package/dist/core/model-discovery.js +12 -1
  59. package/dist/core/plugins.js +361 -12
  60. package/dist/core/project-paths.js +1 -18
  61. package/dist/core/proxy-log.js +8 -6
  62. package/dist/core/proxy-state.js +11 -8
  63. package/dist/core/reference-sweep.js +136 -0
  64. package/dist/core/same-location.js +25 -0
  65. package/dist/core/tracker.js +494 -0
  66. package/dist/hud/components/config-counts.js +15 -4
  67. package/dist/hud/components/learning-counts.js +14 -0
  68. package/dist/hud/config.js +2 -1
  69. package/dist/hud/cost-history.js +2 -4
  70. package/dist/hud/git.js +52 -7
  71. package/dist/hud/index.js +7 -9
  72. package/dist/skills/git/references/decision-markers.md +19 -0
  73. package/dist/skills/git/references/learn-conventions.md +56 -0
  74. package/dist/skills/git/references/pr/check-ci-status.md +14 -0
  75. package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
  76. package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
  77. package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
  78. package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
  79. package/dist/skills/git/references/pr/post-review-summary.md +42 -0
  80. package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
  81. package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
  82. package/dist/skills/git/references/pr/validate-branch.md +18 -0
  83. package/dist/skills/git/references/publication-gate.md +13 -0
  84. package/dist/skills/git/references/tracker/_mcp.md +153 -0
  85. package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
  86. package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
  87. package/dist/skills/git/references/tracker/github/create-release.md +11 -0
  88. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
  89. package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
  90. package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
  91. package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
  92. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
  93. package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
  94. package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
  95. package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
  96. package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
  97. package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
  98. package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
  99. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
  100. package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
  101. package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
  102. package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
  103. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
  104. package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
  105. package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
  106. package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
  107. package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
  108. package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
  109. package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
  110. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
  111. package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
  112. package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
  113. package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
  114. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
  115. package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
  116. package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
  117. package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
  118. package/dist/skills/git/references/trust-rule.md +7 -0
  119. package/dist/targets/claude-code/claude-paths.js +59 -57
  120. package/dist/targets/claude-code/compliance-install.js +49 -65
  121. package/dist/targets/claude-code/hooks.js +108 -3
  122. package/dist/targets/claude-code/installer.js +1187 -32
  123. package/dist/targets/claude-code/legacy.js +5 -0
  124. package/dist/targets/claude-code/post-install.js +366 -151
  125. package/dist/targets/claude-code/tracker-install.js +134 -0
  126. package/package.json +8 -6
  127. package/src/assets/agents/code.md +45 -6
  128. package/src/assets/agents/design.md +2 -1
  129. package/src/assets/agents/git.mds +825 -0
  130. package/src/assets/agents/knowledge.md +3 -3
  131. package/src/assets/agents/learning.md +11 -0
  132. package/src/assets/agents/review.md +3 -1
  133. package/src/assets/agents/synthesize.md +1 -1
  134. package/src/assets/agents/test.md +16 -5
  135. package/src/assets/agents/tracker.md +474 -0
  136. package/src/assets/agents/validate.md +7 -5
  137. package/src/assets/commands/_partials/_compliance.mds +19 -1
  138. package/src/assets/commands/_partials/_decisions.mds +15 -3
  139. package/src/assets/commands/_partials/_docs_root.mds +35 -0
  140. package/src/assets/commands/_partials/_engine.mds +13 -11
  141. package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
  142. package/src/assets/commands/_partials/_factory.mds +1 -1
  143. package/src/assets/commands/_partials/_knowledge.mds +27 -9
  144. package/src/assets/commands/_partials/_plan_contract.mds +22 -7
  145. package/src/assets/commands/_partials/_preamble.mds +2 -2
  146. package/src/assets/commands/_partials/_publication.mds +8 -2
  147. package/src/assets/commands/_partials/_settings.mds +28 -0
  148. package/src/assets/commands/_partials/_ticket_template.mds +3 -2
  149. package/src/assets/commands/_partials/_tracker.mds +18 -0
  150. package/src/assets/commands/_partials/_wave.mds +16 -10
  151. package/src/assets/commands/bug-analysis.mds +31 -19
  152. package/src/assets/commands/code-review.mds +67 -41
  153. package/src/assets/commands/debug.mds +13 -7
  154. package/src/assets/commands/dynamic-build.mds +274 -66
  155. package/src/assets/commands/dynamic-plan.mds +50 -23
  156. package/src/assets/commands/dynamic-profile.mds +24 -11
  157. package/src/assets/commands/dynamic-tickets.mds +63 -16
  158. package/src/assets/commands/explore.mds +4 -5
  159. package/src/assets/commands/implement.mds +234 -67
  160. package/src/assets/commands/plan.mds +91 -33
  161. package/src/assets/commands/release.md +64 -17
  162. package/src/assets/commands/research.mds +11 -9
  163. package/src/assets/commands/resolve.mds +150 -78
  164. package/src/assets/commands/self-review.mds +24 -25
  165. package/src/assets/mds/git/_pr.mds +331 -0
  166. package/src/assets/mds/git/_references.mds +135 -0
  167. package/src/assets/mds/tracker/_common.mds +156 -0
  168. package/src/assets/mds/tracker/_github.mds +472 -0
  169. package/src/assets/mds/tracker/_jira.mds +407 -0
  170. package/src/assets/mds/tracker/_linear.mds +449 -0
  171. package/src/assets/mds/tracker/_mcp.mds +305 -0
  172. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
  173. package/src/assets/scripts/hooks/background-memory-update +40 -19
  174. package/src/assets/scripts/hooks/capture-prompt +18 -8
  175. package/src/assets/scripts/hooks/capture-question +18 -8
  176. package/src/assets/scripts/hooks/capture-turn +27 -13
  177. package/src/assets/scripts/hooks/debug-trace +11 -6
  178. package/src/assets/scripts/hooks/ensure-devflow-init +33 -6
  179. package/src/assets/scripts/hooks/ensure-proxy +9 -8
  180. package/src/assets/scripts/hooks/ensure-root-gitignore +236 -60
  181. package/src/assets/scripts/hooks/git-marker +48 -0
  182. package/src/assets/scripts/hooks/hook-log-init +3 -1
  183. package/src/assets/scripts/hooks/json-helper.cjs +228 -5
  184. package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -20
  185. package/src/assets/scripts/hooks/log-paths +80 -0
  186. package/src/assets/scripts/hooks/memory-worker +22 -13
  187. package/src/assets/scripts/hooks/pre-compact-memory +44 -15
  188. package/src/assets/scripts/hooks/preamble +1 -4
  189. package/src/assets/scripts/hooks/queue-append +146 -28
  190. package/src/assets/scripts/hooks/resolve-project-root +101 -7
  191. package/src/assets/scripts/hooks/session-start-context +534 -20
  192. package/src/assets/scripts/hooks/session-start-memory +38 -15
  193. package/src/assets/scripts/lib/project-config.cjs +633 -0
  194. package/src/assets/scripts/pr-evidence.cjs +1961 -0
  195. package/src/assets/scripts/redact-secrets.cjs +490 -62
  196. package/src/assets/scripts/release-trace.cjs +1143 -0
  197. package/src/assets/scripts/resolve-evidence-policy.cjs +1145 -0
  198. package/src/assets/scripts/resolve-settings.cjs +1054 -0
  199. package/src/assets/scripts/verify-evidence.cjs +1822 -0
  200. package/src/assets/skills/compliance/SKILL.md +4 -2
  201. package/src/assets/skills/docs-framework/SKILL.md +11 -10
  202. package/src/assets/skills/docs-framework/references/patterns.md +10 -17
  203. package/src/assets/skills/gap-analysis/SKILL.md +2 -2
  204. package/src/assets/skills/git/SKILL.md +8 -78
  205. package/src/assets/skills/git/references/github-api.md +179 -141
  206. package/src/assets/skills/git/references/patterns.md +11 -6
  207. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  208. package/src/assets/skills/review-methodology/references/patterns.md +6 -61
  209. package/src/assets/skills/review-methodology/references/violations.md +14 -22
  210. package/src/assets/skills/worktree-support/SKILL.md +1 -1
  211. package/src/assets/skills/worktree-support/references/roots.md +29 -0
  212. package/src/targets/claude-code/templates/managed-settings.json +25 -9
  213. package/src/assets/agents/git.md +0 -938
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @file ledger-root.ts
3
+ *
4
+ * Where the learning ledger lives for a working directory — the TypeScript twin of
5
+ * `df_resolve_roots`' DF_LEDGER_ROOT in src/assets/scripts/hooks/resolve-project-root.
6
+ *
7
+ * D-LEDGER-MAIN-WORKTREE: the ledger is one per REPOSITORY, not per checkout. In a
8
+ * linked worktree the hooks queue and render into the main worktree's
9
+ * `.devflow/learning/`, so `devflow learning` and the HUD must read and clear that
10
+ * same directory — resolving the worktree's own toplevel (getGitRoot) would show an
11
+ * empty ledger and drain a queue nothing writes to. The rule, from ONE git call:
12
+ *
13
+ * git rev-parse --path-format=absolute --show-toplevel --git-common-dir
14
+ *
15
+ * - exactly two absolute lines, the common dir ends in `/.git`, `<main>/.devflow`
16
+ * is a directory and `<main>` is not HOME (physical paths, like the hooks'
17
+ * df_is_project_root) → `<main>`;
18
+ * - two absolute lines otherwise → the toplevel;
19
+ * - git before 2.31 echoes `--path-format=absolute` back as its own line → the
20
+ * toplevel it printed after it (the hooks fall back to the toplevel too);
21
+ * - git failed (not a work tree) → null: the caller keeps its non-git fallback;
22
+ * - any other output → getGitRoot(), the hooks' df_resolve_root fallback.
23
+ *
24
+ * Memory is NOT resolved here: working memory stays per checkout (getGitRoot).
25
+ * Parity with the shell helper is pinned by tests/core/ledger-root.test.ts, which
26
+ * runs df_resolve_roots on the same fixtures.
27
+ */
28
+ import { execFile } from 'child_process';
29
+ import { promises as fs } from 'fs';
30
+ import * as os from 'os';
31
+ import * as path from 'path';
32
+ import { getGitRoot } from './git.js';
33
+ /** The one git call, as argv (no shell). */
34
+ const LEDGER_ROOT_GIT_ARGS = ['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir'];
35
+ /** What git before 2.31 prints first: the flag it does not know, echoed back. */
36
+ const PATH_FORMAT_ECHO = '--path-format=absolute';
37
+ /** Classify the git call's stdout. Pure. */
38
+ export function parseLedgerRootOutput(stdout) {
39
+ const lines = stdout.replace(/\n$/, '').split('\n');
40
+ if (lines.length === 2 && lines.every(l => path.isAbsolute(l))) {
41
+ return { kind: 'roots', toplevel: lines[0], commonDir: lines[1] };
42
+ }
43
+ if (lines.length === 3 && lines[0] === PATH_FORMAT_ECHO && path.isAbsolute(lines[1])) {
44
+ return { kind: 'toplevel', toplevel: lines[1] };
45
+ }
46
+ return { kind: 'unrecognised' };
47
+ }
48
+ /** stdout of the one git call from `cwd`, or null when git failed. */
49
+ function runRevParse(cwd, timeoutMs) {
50
+ return new Promise((resolve) => {
51
+ execFile('git', LEDGER_ROOT_GIT_ARGS, { cwd, timeout: timeoutMs, encoding: 'utf-8' }, (err, stdout) => {
52
+ resolve(err ? null : stdout);
53
+ });
54
+ });
55
+ }
56
+ /** A path's physical location, or null when it cannot be resolved. */
57
+ async function physical(target) {
58
+ try {
59
+ return await fs.realpath(target);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * Whether the main worktree `main` holds the ledger: it has a `.devflow/` directory
67
+ * and is not HOME. An unresolvable `main` is refused (the ledger stays in this
68
+ * checkout); an unresolvable HOME cannot equal it, so it refuses nothing — both as
69
+ * df_is_project_root decides.
70
+ */
71
+ async function mainHoldsLedger(main, home) {
72
+ const stat = await fs.stat(path.join(main, '.devflow')).catch(() => null);
73
+ if (!stat?.isDirectory())
74
+ return false;
75
+ const [mainReal, homeReal] = await Promise.all([physical(main), physical(home)]);
76
+ return mainReal !== null && mainReal !== homeReal;
77
+ }
78
+ /**
79
+ * The learning ledger root for `cwd` — the main worktree or this checkout's
80
+ * toplevel — or null outside a git work tree.
81
+ */
82
+ export async function getLedgerRoot(cwd = process.cwd(), options = {}) {
83
+ const stdout = await runRevParse(cwd, options.timeoutMs ?? 0);
84
+ if (stdout === null)
85
+ return null;
86
+ const parsed = parseLedgerRootOutput(stdout);
87
+ switch (parsed.kind) {
88
+ case 'toplevel':
89
+ return parsed.toplevel;
90
+ case 'unrecognised':
91
+ return getGitRoot(cwd);
92
+ case 'roots': {
93
+ if (!parsed.commonDir.endsWith('/.git'))
94
+ return parsed.toplevel;
95
+ const main = parsed.commonDir.slice(0, -'/.git'.length);
96
+ if (main && await mainHoldsLedger(main, options.home ?? os.homedir()))
97
+ return main;
98
+ return parsed.toplevel;
99
+ }
100
+ }
101
+ }
102
+ //# sourceMappingURL=ledger-root.js.map
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import { LEGACY_PLUGIN_NAMES, DELETED_PLUGIN_NAMES } from './plugins.js';
4
4
  import { VIEW_MODES, migrateLegacyFlagsToRecord, sanitizeFlagsRecord, } from './flags.js';
5
5
  import { normalizeComplianceFeature } from './compliance.js';
6
+ import { normalizeTrackerFeature } from './tracker.js';
6
7
  import { writeFileAtomicExclusive } from './fs-atomic.js';
7
8
  /**
8
9
  * Parse features.flags across the three on-disk shapes; reports whether a heal is owed.
@@ -54,13 +55,14 @@ function parseManifestFlags(features, knownFlags) {
54
55
  * Read and parse the manifest file. Returns null if missing or corrupt.
55
56
  *
56
57
  * Self-heals the following on-disk inconsistencies (applies ADR-014):
57
- * - features.kb → features.knowledge rename
58
- * - features.decisions → features.learning rename
58
+ * - features.kb → features.knowledge rename (both absent → true, D-FEATURES-ABSENT-ON)
59
+ * - features.decisions → features.learning rename (both absent → true, D-FEATURES-ABSENT-ON)
59
60
  * - features.flags as string[] → FlagsRecord (via migrateLegacyFlagsToRecord)
60
61
  * - features.viewMode folded into flags['view-mode'] and stripped from result
61
62
  * - features.knownFlags stripped from result (folded into FlagsRecord key-presence)
62
63
  * - features.proxy absent → false
63
64
  * - features.compliance absent/malformed → {enabled:false, frameworks:[]}
65
+ * - features.tracker absent/malformed → {provider:'github'}
64
66
  *
65
67
  * D39: heal-write failure returns the migrated in-memory manifest (not null).
66
68
  * The on-disk format remains unhealed; next read triggers another attempt.
@@ -73,7 +75,6 @@ export async function readManifest(devflowDir) {
73
75
  const features = data.features;
74
76
  if (!data.version ||
75
77
  !Array.isArray(data.plugins) ||
76
- !data.scope ||
77
78
  typeof features !== 'object' ||
78
79
  features === null ||
79
80
  typeof features.ambient !== 'boolean' ||
@@ -82,15 +83,33 @@ export async function readManifest(devflowDir) {
82
83
  typeof data.updatedAt !== 'string') {
83
84
  return null;
84
85
  }
85
- // Self-heal: rename features.kb → features.knowledge on disk
86
+ // D-FEATURES-ABSENT-ON (a sub-decision of D-FEATURES-NARROW-ONLY,
87
+ // src/core/feature-switch.ts): knowledge and learning are machine-wide
88
+ // switches, and every runtime gate — queue_read_gates in the hooks,
89
+ // isMachineFeatureOn in the CLI, the knowledge write-back prose gate — reads
90
+ // an ABSENT key as ON. This reader must agree: init seeds from it and every
91
+ // syncManifestFeature toggle writes its result back, so a `false` default
92
+ // here would persist "off" for a feature the runtime is running — a plain
93
+ // re-init or `devflow hud --enable` on an older manifest silently switching
94
+ // it off machine-wide. Explicit booleans (and their legacy names) are kept
95
+ // as they are. memory needs no default: it is in the hard-null set above,
96
+ // so a manifest without it reads as no prior install and init seeds
97
+ // FEATURE_DEFAULTS.memory (true).
98
+ //
99
+ // Self-heal: rename features.kb → features.knowledge on disk.
100
+ // Coalesce: features.knowledge wins; fall back to features.kb; default ON.
101
+ // D-KNOWLEDGE-LEGACY-KB: isMachineFeatureOn applies this exact precedence,
102
+ // so the legacy key is honoured before the heal lands.
86
103
  const knowledge = typeof features.knowledge === 'boolean' ? features.knowledge
87
104
  : typeof features.kb === 'boolean' ? features.kb
88
- : false;
89
- // Self-heal: rename features.decisions → features.learning on disk
90
- // Coalesce: features.learning wins; fall back to features.decisions; default false.
105
+ : true;
106
+ // Self-heal: rename features.decisions → features.learning on disk (ADR-011).
107
+ // Coalesce: features.learning wins; fall back to features.decisions; default ON.
108
+ // D-LEARNING-LEGACY-DECISIONS: isMachineFeatureOn and queue_read_gates apply
109
+ // this exact precedence, so the legacy key is honoured before the heal lands.
91
110
  const learning = typeof features.learning === 'boolean' ? features.learning
92
111
  : typeof features.decisions === 'boolean' ? features.decisions
93
- : false;
112
+ : true;
94
113
  // Self-heal: non-string-array or absent knownFlags/knownPlugins → undefined (never partial/garbage)
95
114
  const asStringArray = (val) => Array.isArray(val) && val.every(e => typeof e === 'string')
96
115
  ? val
@@ -115,7 +134,8 @@ export async function readManifest(devflowDir) {
115
134
  const manifest = {
116
135
  version: data.version,
117
136
  plugins: data.plugins,
118
- scope: data.scope,
137
+ // D-MANIFEST-SCOPE-PINNED: the on-disk value is not consulted.
138
+ scope: 'user',
119
139
  knownPlugins,
120
140
  features: {
121
141
  ambient: features.ambient,
@@ -138,6 +158,12 @@ export async function readManifest(devflowDir) {
138
158
  proxy: typeof features.proxy === 'boolean' ? features.proxy : false,
139
159
  // Self-heal: absent/malformed compliance → {enabled:false, frameworks:[]}
140
160
  compliance: normalizeComplianceFeature(features.compliance),
161
+ // Self-heal: absent/malformed/unknown tracker → {provider:'github'} (AC-3.21).
162
+ // Deliberately NOT in the hard-null set above — see the field's doc comment.
163
+ // Healing here is silent and emits no DEGRADED: that is the correct
164
+ // ADR-014 behaviour, and a different condition from a per-repo config
165
+ // value outside the domain (which does emit `unknown tracker provider`).
166
+ tracker: normalizeTrackerFeature(features.tracker),
141
167
  },
142
168
  installedAt: data.installedAt,
143
169
  updatedAt: data.updatedAt,
@@ -168,7 +194,9 @@ export async function readManifest(devflowDir) {
168
194
  export async function writeManifest(devflowDir, data) {
169
195
  await fs.mkdir(devflowDir, { recursive: true });
170
196
  const manifestPath = path.join(devflowDir, 'manifest.json');
171
- await writeFileAtomicExclusive(manifestPath, JSON.stringify(data, null, 2) + '\n');
197
+ // D-MANIFEST-SCOPE-PINNED: every write records 'user', whatever the caller holds.
198
+ const pinned = { ...data, scope: 'user' };
199
+ await writeFileAtomicExclusive(manifestPath, JSON.stringify(pinned, null, 2) + '\n');
172
200
  }
173
201
  /**
174
202
  * Update a single feature field in the manifest. No-op when no manifest exists.