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
@@ -24,16 +24,28 @@ Detect changed files and build context:
24
24
  4. Build TASK_DESCRIPTION from recent commit messages or branch name
25
25
  ### Load DECISIONS_CONTEXT
26
26
 
27
- Resolve the worktree root using the `devflow:worktree-support` algorithm (use WORKTREE_PATH if provided, otherwise cwd). All paths below are relative to `{worktree}`.
27
+ 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`):
28
+
29
+ ```bash
30
+ git -C "{start}" rev-parse --path-format=absolute --show-toplevel --git-common-dir
31
+ ```
32
+
33
+ Line 1 is the checkout's toplevel, line 2 the repository's common git directory. A git older than 2.31 echoes `--path-format=absolute` back as a line of its own first. `{ledger}` is the first of these that applies:
34
+
35
+ 1. **The main worktree** — line 2 without its trailing `/.git`, when the output is exactly two lines each beginning with `/`, line 2 ends in `/.git`, and the directory left once it is removed is not your home directory and contains a `.devflow/` directory.
36
+ 2. **The toplevel** — line 1, or on an older git the line after the echoed flag.
37
+ 3. **The start directory itself** — when the command failed or printed no absolute toplevel (outside a git repository).
38
+
39
+ This is the rule the learning hooks apply (D-LEDGER-MAIN-WORKTREE, D-PROMPT-ROOT), so you read the index the Learning agent writes.
28
40
 
29
41
  **Step 1 — Read the pre-rendered index:**
30
42
 
31
- Attempt to read `{worktree}/.devflow/learning/index.md`.
43
+ Attempt to read `{ledger}/.devflow/learning/index.md`.
32
44
 
33
45
  - If the file exists and contains non-empty content: use that content as `DECISIONS_CONTEXT`.
34
46
  - If the file is absent or empty: set `DECISIONS_CONTEXT` to `(none)`.
35
47
 
36
- **No subprocess, no `.cjs` script.** This is a single direct file read — the index is written at render time by `render-decisions.cjs` alongside `decisions.md`/`pitfalls.md`.
48
+ The index is one direct file read, written at render time by `render-decisions.cjs` alongside `decisions.md`/`pitfalls.md` — no `.cjs` script runs here, and the index's own footer names the files that hold each entry's full body.
37
49
 
38
50
  **Step 2 — Apply decisions using `devflow:apply-decisions`:**
39
51
 
@@ -43,7 +55,13 @@ Pass `DECISIONS_CONTEXT` to Scrutinize agent — the compact index lists active
43
55
 
44
56
  ### Load Feature Knowledge
45
57
 
46
- Resolve the worktree root using the `devflow:worktree-support` algorithm (use WORKTREE_PATH if provided, otherwise cwd). All paths below are relative to `{worktree}`.
58
+ Resolve `{worktree}` as the checkout's toplevel, because feature knowledge bases are committed with the branch (D-PROMPT-ROOT): from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`) — run
59
+
60
+ ```bash
61
+ git -C "{start}" rev-parse --show-toplevel
62
+ ```
63
+
64
+ and use its one-line output. If the command fails (outside a git repository), `{worktree}` is the start directory itself. All paths below are relative to `{worktree}`.
47
65
 
48
66
  **Step 1 — Read the index cache:**
49
67
 
@@ -78,7 +96,7 @@ Concatenate the selected KNOWLEDGE.md files under slug headers:
78
96
 
79
97
  If no KBs exist, no KBs are relevant, or `.devflow/features/` is absent, set `FEATURE_KNOWLEDGE` to `(none)`.
80
98
 
81
- **No subprocess, no git calls, no `.cjs` script.** This entire step is direct file reads — 1 index read (or N frontmatter reads on fallback), bounded by KB count.
99
+ **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.
82
100
 
83
101
  Pass `FEATURE_KNOWLEDGE` to Scrutinize agent.
84
102
 
@@ -165,13 +183,27 @@ Display summary:
165
183
 
166
184
  ### Feature Knowledge Write-Back (Conditional)
167
185
 
168
- Resolve the worktree root using the `devflow:worktree-support` algorithm (use WORKTREE_PATH if provided, otherwise cwd). All paths below are relative to `{worktree}`.
186
+ Resolve `{worktree}` as the checkout's toplevel, because feature knowledge bases are committed with the branch (D-PROMPT-ROOT): from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`) — run
169
187
 
170
- **Step 1 — Check the opt-out gate:**
188
+ ```bash
189
+ git -C "{start}" rev-parse --show-toplevel
190
+ ```
171
191
 
172
- Read `{worktree}/.devflow/config.json`. If the `knowledge` field is `false`, skip write-back entirely — the user has disabled it.
192
+ and use its one-line output. If the command fails (outside a git repository), `{worktree}` is the start directory itself. All paths below are relative to `{worktree}`.
173
193
 
174
- If `.devflow/config.json` does not exist, proceed (default is enabled).
194
+ **Step 1 — Check the opt-out gate, with `{root}` = `{worktree}`:**
195
+
196
+ **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:
197
+
198
+ ```bash
199
+ node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
200
+ ```
201
+
202
+ 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.
203
+
204
+ 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.
205
+
206
+ If the settings line says `KNOWLEDGE=off`, skip write-back entirely. The machine switch (`devflow knowledge --disable`), the repository and the personal settings can each turn knowledge off, and none can turn it back on (D-FEATURES-NARROW-ONLY). The fail-closed line says `KNOWLEDGE=off` too, so an unresolvable line skips write-back.
175
207
 
176
208
  **Step 2 — Evaluate whether write-back is warranted:**
177
209
 
@@ -210,6 +242,10 @@ The frontmatter in KNOWLEDGE.md is the source of truth — index.md is only a ca
210
242
  After writing, commit the two files to the current worktree branch yourself by running git via your Bash tool (do not use a script). Stage ONLY .devflow/features/index.md and .devflow/features/{slug}/KNOWLEDGE.md, then commit just those paths with a docs(knowledge): message. Do NOT push, do NOT force, do NOT stage anything else. Follow your Commit Protocol — it is non-blocking, so if any git step fails, report KB_COMMIT and finish normally."
211
243
  ```
212
244
 
245
+ **Step 4 — Surface an uncommitted knowledge base:**
246
+
247
+ When the Knowledge agent reports `KB_COMMIT: skipped (detached HEAD)`, the files were written but deliberately not committed — a commit on a detached HEAD becomes unreachable once HEAD moves. Tell the user in the workflow's final report, in one line, that the knowledge base was written but not committed, and name the uncommitted paths the agent listed, so they can commit them on a branch before the worktree is removed. Never commit them yourself.
248
+
213
249
  **Failure handling**: Non-blocking. If the Knowledge agent fails, log the failure and continue — the workflow outcome is not affected by write-back success.
214
250
 
215
251
  ## Architecture
@@ -27,7 +27,7 @@ import * as path from 'path';
27
27
  import { writeFileAtomicExclusive } from './fs-atomic.js';
28
28
  import { isDormantExternalModel } from './external-models.js';
29
29
  import { rewriteAgentFrontmatter, readFrontmatterModel, isValidModelName } from './agent-frontmatter.js';
30
- import { agentsDir } from './assets.js';
30
+ import { agentSourceDirs } from './assets.js';
31
31
  import { getAllAgentNames } from './plugins.js';
32
32
  import { mdEntryName, mdFileName } from './orphan-sweep.js';
33
33
  import { isContainedIn } from './paths.js';
@@ -359,26 +359,27 @@ export function resolveEffective(agentName, mapping, shippedDefaults, proxyEnabl
359
359
  // loadShippedDefaults
360
360
  // ---------------------------------------------------------------------------
361
361
  /**
362
- * Load shipped default models from the source agent files.
363
- * Reads every file in agentsDir() and parses the frontmatter model field.
364
- * Unknown or malformed files are silently skipped.
362
+ * Parse the shipped model default out of every {name}.md in one directory.
363
+ *
364
+ * A missing or unreadable directory yields an empty map — dist/agents/ does not
365
+ * exist until a generator host does, and a source tree that produced no agents
366
+ * is caught by the registry-completeness guard rather than by a throw here.
367
+ * Unknown or malformed files are skipped individually.
365
368
  */
366
- export async function loadShippedDefaults() {
367
- const sourceDir = agentsDir();
368
- const defaults = {};
369
+ async function readDirDefaults(dir) {
369
370
  let entries;
370
371
  try {
371
- entries = await fs.readdir(sourceDir);
372
+ entries = await fs.readdir(dir);
372
373
  }
373
374
  catch {
374
- return defaults;
375
+ return {};
375
376
  }
376
377
  const pairs = await Promise.all(entries.map(async (file) => {
377
378
  const agentName = mdEntryName(file);
378
379
  if (agentName === null)
379
380
  return null;
380
381
  try {
381
- const content = await fs.readFile(path.join(sourceDir, file), 'utf-8');
382
+ const content = await fs.readFile(path.join(dir, file), 'utf-8');
382
383
  const result = readFrontmatterModel(content);
383
384
  if (result.ok && result.value) {
384
385
  return [agentName, result.value];
@@ -389,6 +390,7 @@ export async function loadShippedDefaults() {
389
390
  }
390
391
  return null;
391
392
  }));
393
+ const defaults = {};
392
394
  for (const pair of pairs) {
393
395
  if (pair !== null) {
394
396
  defaults[pair[0]] = pair[1];
@@ -396,11 +398,51 @@ export async function loadShippedDefaults() {
396
398
  }
397
399
  return defaults;
398
400
  }
401
+ /**
402
+ * Load shipped default models from the agent files.
403
+ *
404
+ * Directories are MOST-PREFERRED FIRST — the convention owned by
405
+ * agentSourceDirs() — and the first directory to supply a name wins, so once an
406
+ * agent is generated into dist/agents/ its frontmatter is the shipped default.
407
+ *
408
+ * A registry agent that no directory supplies is reported through `onWarning`
409
+ * as ONE aggregate message naming every missing agent and the build step. The
410
+ * installer throws on the same invariant; this is a read path whose callers must
411
+ * keep rendering (`devflow agents --list`), so it warns instead. Staying silent
412
+ * is what makes the gap dangerous: resolveEffective returns an undefined model,
413
+ * reapplyAgentMapping buckets the agent 'unchanged', and disabling the proxy
414
+ * leaves an externally-pinned agent unreverted with nothing said (PF-022).
415
+ *
416
+ * @param dirs - Agent directories, most-preferred first. Injectable so tests can
417
+ * prove the precedence against a temp tree; all real callers use the default.
418
+ * @param opts - Optional warning channel; the gap is silent without one.
419
+ */
420
+ export async function loadShippedDefaults(dirs = agentSourceDirs(), opts) {
421
+ const perDir = await Promise.all(dirs.map(readDirDefaults));
422
+ const defaults = {};
423
+ for (const dirDefaults of perDir) {
424
+ for (const [agentName, model] of Object.entries(dirDefaults)) {
425
+ if (!(agentName in defaults)) {
426
+ defaults[agentName] = model;
427
+ }
428
+ }
429
+ }
430
+ const missing = getAllAgentNames().filter(name => !(name in defaults));
431
+ if (missing.length > 0) {
432
+ opts?.onWarning?.(`No shipped default found for declared agent(s): ${missing.join(', ')}. ` +
433
+ `Run \`npm run build:mds\` if they are compiled from .mds generator hosts, otherwise ` +
434
+ `ensure the agent files exist in src/assets/agents/ (searched: ${dirs.join(', ')}).`);
435
+ }
436
+ return defaults;
437
+ }
399
438
  /**
400
439
  * Idempotent convergence function: walk every installed agent file and
401
440
  * rewrite frontmatter model/effort to match the effective mapping.
402
441
  *
403
- * - Reads shipped defaults LIVE from src/assets/agents/ sources.
442
+ * - Reads shipped defaults LIVE from the agent sources — agentSourceDirs(),
443
+ * dist/agents/ preferred over src/assets/agents/. An agent no source supplies
444
+ * is reported through the warning channel rather than passing as 'unchanged'
445
+ * with no explanation.
404
446
  * - Gets the agent name list from the registry (getAllAgentNames()) plus
405
447
  * any mapping entries for agents not in the registry.
406
448
  * - Missing installed files → skip silently (recorded in skippedMissing).
@@ -419,7 +461,7 @@ export async function reapplyAgentMapping(opts) {
419
461
  return { updated: [], unchanged: [], skippedMissing: [], invalidMapping: [], warnings };
420
462
  }
421
463
  const mapping = mappingResult.value;
422
- const shippedDefaults = await loadShippedDefaults();
464
+ const shippedDefaults = await loadShippedDefaults(opts.agentSourceDirs, { onWarning: warn });
423
465
  // Build the union of: all registered agent names + all names in the mapping
424
466
  // (so agents not yet in the registry but configured are also processed).
425
467
  const registryNames = new Set(getAllAgentNames());
@@ -525,6 +567,7 @@ export async function revertExternalAgents(opts) {
525
567
  installDir: opts.installDir,
526
568
  devflowDir: opts.devflowDir,
527
569
  proxyEnabled: false,
570
+ agentSourceDirs: opts.agentSourceDirs,
528
571
  onWarning: opts.onWarning,
529
572
  });
530
573
  }
@@ -1,5 +1,6 @@
1
1
  import { join } from 'path';
2
2
  import { getPackageRoot } from './paths.js';
3
+ import { SKILL_REFS_OUTPUT_DIR } from './mds-variants.js';
3
4
  /**
4
5
  * Flat skills source directory: src/assets/skills/{name}/
5
6
  * All plugins' skills live here directly (no per-plugin subdirectory).
@@ -10,9 +11,13 @@ export function skillsDir() {
10
11
  /**
11
12
  * Flat agents source directory: src/assets/agents/{name}.md
12
13
  * All plugins' agents live here directly.
14
+ *
15
+ * @param root - Package root to resolve against. Injectable so a caller working
16
+ * on a temp tree (the test harness) reads the layout from here rather than
17
+ * spelling the path itself.
13
18
  */
14
- export function agentsDir() {
15
- return join(getPackageRoot(), 'src', 'assets', 'agents');
19
+ export function agentsDir(root = getPackageRoot()) {
20
+ return join(root, 'src', 'assets', 'agents');
16
21
  }
17
22
  /**
18
23
  * Flat rules source directory: src/assets/rules/{name}.md
@@ -36,4 +41,55 @@ export function scriptsDir() {
36
41
  export function commandsDir() {
37
42
  return join(getPackageRoot(), 'dist', 'commands');
38
43
  }
44
+ /**
45
+ * Compiled agents directory: dist/agents/{name}.md
46
+ *
47
+ * Output of the .mds generator hosts. The directory is absent until at least
48
+ * one generator host exists, so every reader must tolerate its absence.
49
+ *
50
+ * @param root - Package root to resolve against (see agentsDir).
51
+ */
52
+ export function compiledAgentsDir(root = getPackageRoot()) {
53
+ return join(root, 'dist', 'agents');
54
+ }
55
+ /**
56
+ * Compiled skill-reference directory: dist/skills/git/references/
57
+ *
58
+ * Output of the `.mds` reference modules — the generated `devflow:git` mechanics
59
+ * files, one per (provider, operation) pair under `tracker/{provider}/`. Like
60
+ * compiledAgentsDir(), the directory is absent until the build has run, so every
61
+ * reader must tolerate its absence.
62
+ *
63
+ * The spelling comes from SKILL_REFS_OUTPUT_DIR in src/core/mds-variants.ts —
64
+ * the build's own allowlist table — rather than being retyped here, so the
65
+ * destination has exactly one definition.
66
+ *
67
+ * @param root - Package root to resolve against (see agentsDir).
68
+ */
69
+ export function compiledSkillRefsDir(root = getPackageRoot()) {
70
+ return join(root, ...SKILL_REFS_OUTPUT_DIR.split('/'));
71
+ }
72
+ /**
73
+ * Agent source directories, MOST-PREFERRED FIRST.
74
+ *
75
+ * The single owner of the dist-first agent-resolution policy: a generator
76
+ * host's compiled artifact in dist/agents/ supersedes a hand-authored file of
77
+ * the same name in src/assets/agents/. Every consumer reads the order from
78
+ * here — the installer's first-hit-wins resolve, loadShippedDefaults's
79
+ * first-wins merge, and the test harness's resolveAgentSource — so the
80
+ * convention is stated once and cannot drift apart between call sites.
81
+ *
82
+ * Order is invisible to the type system: a list spelled least-preferred-first
83
+ * still typechecks and silently inverts the answer. Consumers therefore take
84
+ * this list as-is and never re-spell it; tests/guards/agent-source-precedence
85
+ * pins that they agree.
86
+ *
87
+ * The non-empty tuple makes an empty list a compile error at every call site:
88
+ * an empty list would survive a `??` default and resolve to nothing.
89
+ *
90
+ * @param root - Package root to resolve against (see agentsDir).
91
+ */
92
+ export function agentSourceDirs(root = getPackageRoot()) {
93
+ return [compiledAgentsDir(root), agentsDir(root)];
94
+ }
39
95
  //# sourceMappingURL=assets.js.map
@@ -167,44 +167,44 @@ function collectMissingFragmentWarnings(fnName, activeFrameworks, fragments, omi
167
167
  }
168
168
  return warnings;
169
169
  }
170
+ /**
171
+ * How a consumer picks framework references at run time (D-COMPLIANCE-REPO-LENS).
172
+ *
173
+ * Every framework reference is installed on every machine
174
+ * (D-COMPLIANCE-INSTALL-ALWAYS), so file presence no longer says which
175
+ * frameworks apply. The caller hands the agent `COMPLIANCE_FRAMEWORKS` — the
176
+ * `COMPLIANCE` field of the settings line, which folds this machine's selection
177
+ * with the repository's `.devflow/project.json` — and those ids alone are in force.
178
+ */
179
+ const RUNTIME_SELECTION_LINES = [
180
+ 'Every `references/{id}.md` is installed, so presence decides nothing: the ids you were given',
181
+ "(`COMPLIANCE_FRAMEWORKS`, this machine's plus the repository's) are the frameworks in force.",
182
+ 'Load `references/{id}.md` for each given id and no other; `none` means generic controls only.',
183
+ 'Apply generic controls always.',
184
+ '',
185
+ 'NEVER fabricate framework-specific guidance for a framework you were not given.',
186
+ ];
170
187
  /**
171
188
  * Build the ${DEVFLOW_COMPLIANCE_ACTIVE} substitution — the body of the
172
189
  * Active Frameworks section.
173
190
  *
174
- * Active: lists frameworks + instructs loading reference files.
175
- * Zero: informs that generic controls only apply.
176
- *
177
- * File presence corroborates: note preserved so the agent keeps checking files.
191
+ * The stamp names the machine's own selection; zero frameworks is the neutral
192
+ * stamp a compliance-off machine's skill carries. Either way the section ends in
193
+ * RUNTIME_SELECTION_LINES, because the frameworks a run applies are the ids its
194
+ * caller passes, not the stamp.
178
195
  *
179
196
  * C5: a framework with no fragment still appears here — only its mapping row,
180
197
  * checklist item and reference row are omitted.
181
198
  *
182
- * Frameworks the registry cannot label are dropped entirely: neither the label nor the
183
- * `references/{id}.md` path is emitted (see resolveRegistryFrameworks).
199
+ * Frameworks the registry cannot label are dropped entirely: no label is emitted
200
+ * (see resolveRegistryFrameworks).
184
201
  */
185
202
  function buildActiveSection(activeFrameworks) {
186
203
  const resolved = resolveRegistryFrameworks(activeFrameworks);
187
- if (resolved.length === 0) {
188
- return [
189
- 'No framework-specific reference files are active. Apply generic controls only.',
190
- '',
191
- 'NEVER fabricate framework-specific guidance for absent `references/{id}.md` files.',
192
- 'If no reference file is present for a framework, apply generic controls only.',
193
- ].join('\n');
194
- }
195
- const labels = resolved.map(fw => fw.label);
196
- const refList = resolved.map(fw => `\`references/${fw.id}.md\``).join(' and ');
197
- return [
198
- `**Active: ${labels.join(', ')}.**`,
199
- '',
200
- `Load ${refList} for framework-specific controls. Apply generic controls always.`,
201
- '',
202
- 'File presence in the installed skill directory is the authoritative signal: if a',
203
- '`references/{id}.md` file is absent, treat that framework as inactive regardless of',
204
- 'this list.',
205
- '',
206
- 'NEVER fabricate framework-specific guidance for absent `references/{id}.md` files.',
207
- ].join('\n');
204
+ const stamp = resolved.length === 0
205
+ ? 'The machine declares no framework.'
206
+ : `**Machine frameworks: ${resolved.map(fw => fw.label).join(', ')}.**`;
207
+ return [stamp, '', ...RUNTIME_SELECTION_LINES].join('\n');
208
208
  }
209
209
  /**
210
210
  * Build the ${DEVFLOW_COMPLIANCE_MAPPING} substitution — the ENTIRE Framework