mandrel 2.31.0 → 2.32.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 (250) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +4 -4
  4. package/.agents/docs/agentrc-reference.json +61 -57
  5. package/.agents/docs/configuration.md +274 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +0 -1
  14. package/.agents/schemas/agentrc.schema.json +1939 -1400
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +7 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/bootstrap.js +28 -26
  21. package/.agents/scripts/check-baseline-drift.js +73 -13
  22. package/.agents/scripts/check-baseline-scope.js +362 -0
  23. package/.agents/scripts/check-dead-exports.js +9 -1
  24. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  25. package/.agents/scripts/check-knip-entries.js +136 -0
  26. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  27. package/.agents/scripts/check-schema-references.js +1 -1
  28. package/.agents/scripts/diagnose-friction.js +7 -4
  29. package/.agents/scripts/generate-config-docs.js +263 -171
  30. package/.agents/scripts/install-matrix-assert.js +0 -1
  31. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  32. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  33. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  34. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  35. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  36. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  37. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  38. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  39. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  40. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  41. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  42. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  43. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  44. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  45. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  46. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  47. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  48. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  49. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  50. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  51. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  52. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  53. package/.agents/scripts/lib/child-exec.js +193 -0
  54. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  55. package/.agents/scripts/lib/cli-args.js +64 -0
  56. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  57. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  58. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  59. package/.agents/scripts/lib/config/commands.js +3 -5
  60. package/.agents/scripts/lib/config/explain.js +5 -7
  61. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  62. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  63. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  64. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  65. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  66. package/.agents/scripts/lib/config/gates/index.js +5 -2
  67. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  68. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  69. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  70. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  71. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  72. package/.agents/scripts/lib/config/github.js +0 -1
  73. package/.agents/scripts/lib/config/limits.js +3 -48
  74. package/.agents/scripts/lib/config/qa.js +105 -0
  75. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  76. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  77. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  78. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  79. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  80. package/.agents/scripts/lib/crap-utils.js +84 -520
  81. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  82. package/.agents/scripts/lib/degraded-mode.js +2 -2
  83. package/.agents/scripts/lib/doc-tiers.js +3 -3
  84. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  85. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  86. package/.agents/scripts/lib/fs-walk.js +52 -0
  87. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  88. package/.agents/scripts/lib/git-utils.js +16 -36
  89. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  90. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  91. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  92. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  93. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  94. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  95. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  96. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  97. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  98. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  99. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  100. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  101. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  102. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  103. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  104. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  105. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  106. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  107. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  108. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  109. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  110. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  111. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  112. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  122. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  123. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  124. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  125. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  126. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  127. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  128. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  129. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  130. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  131. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  132. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  133. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  134. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  135. package/.agents/scripts/lib/signals/index.js +8 -6
  136. package/.agents/scripts/lib/signals/schema.js +20 -25
  137. package/.agents/scripts/lib/signals/write.js +8 -8
  138. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  139. package/.agents/scripts/lib/temp-retention.js +1 -1
  140. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  141. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  142. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  143. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  144. package/.agents/scripts/lib/worktree-manager.js +2 -3
  145. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  146. package/.agents/scripts/pr-watch-with-update.js +7 -5
  147. package/.agents/scripts/providers/github/cache.js +2 -2
  148. package/.agents/scripts/providers/github/comments.js +6 -28
  149. package/.agents/scripts/providers/github/compose.js +0 -15
  150. package/.agents/scripts/providers/github/errors.js +10 -27
  151. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  152. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  153. package/.agents/scripts/providers/github.js +4 -7
  154. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  155. package/.agents/scripts/resolve-stories.js +0 -2
  156. package/.agents/scripts/run-lint.js +61 -61
  157. package/.agents/scripts/run-test-profile.js +6 -6
  158. package/.agents/scripts/run-verify.js +48 -30
  159. package/.agents/scripts/single-story-close.js +20 -0
  160. package/.agents/scripts/single-story-init.js +12 -35
  161. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  162. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  163. package/.agents/skills/skills.index.json +1 -11
  164. package/.agents/workflows/audit-documentation.md +5 -6
  165. package/.agents/workflows/audit-to-stories.md +2 -2
  166. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  167. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  168. package/.agents/workflows/helpers/code-review.md +3 -8
  169. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  170. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  171. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  172. package/.agents/workflows/mandrel-update.md +10 -10
  173. package/.agents/workflows/qa-assist.md +15 -20
  174. package/.agents/workflows/qa-explore.md +9 -8
  175. package/README.md +1 -1
  176. package/docs/CHANGELOG.md +42 -0
  177. package/lib/migrations/index.js +2 -0
  178. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  179. package/package.json +12 -3
  180. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  181. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  182. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  183. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  184. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  185. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  186. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  187. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  188. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  189. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  190. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  191. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  192. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  193. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  194. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  195. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  196. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  197. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  198. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  199. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  200. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  201. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  202. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  203. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  204. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  205. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  206. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  207. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  208. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  209. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  210. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  211. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  212. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  213. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  214. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  215. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  216. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  217. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  218. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  219. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  220. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  221. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  222. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  223. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  224. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  225. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  226. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  227. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  228. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  229. package/.agents/scripts/lib/planning-corpus.js +0 -37
  230. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  231. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  232. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  233. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  234. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  235. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  236. package/.agents/scripts/lib/signals/read.js +0 -268
  237. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  238. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  239. package/.agents/scripts/lib/story-plan.js +0 -379
  240. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  241. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  242. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  243. package/.agents/scripts/lint-baseline.js +0 -507
  244. package/.agents/scripts/providers/github/prs.js +0 -103
  245. package/.agents/scripts/signals-view.js +0 -309
  246. package/.agents/scripts/story-plan.js +0 -370
  247. package/.agents/scripts/sync-branch-from-base.js +0 -149
  248. package/.agents/scripts/validate-docs-freshness.js +0 -314
  249. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  250. package/.agents/workflows/helpers/signals.md +0 -112
@@ -1,46 +0,0 @@
1
- /**
2
- * crap-baseline-index.js — per-file method-identity indexing over a CRAP
3
- * baseline's rows (Story #4981).
4
- *
5
- * Split out of `crap-engine.js` (rather than added inline) so the join
6
- * support lands as a new file, not a same-file expansion of the module the
7
- * scoring kernel already lives in. Deliberately dependency-free: both
8
- * `crap-engine.js` (which needs `methodIdentityKey` inside
9
- * `finalizeMethodRowsWithBaseline`) and `baselines/kinds/crap.js` (which
10
- * needs the file-scoped index) import FROM here, and this module imports
11
- * from neither — the only shape that keeps the edge one-directional.
12
- */
13
-
14
- /**
15
- * Per-file half of the method-identity key `baselines/kinds/crap.js`'s
16
- * `crapRowKey` composes with the file path (`${path}::${method}@${startLine}`).
17
- * The path component is redundant once a row set is already narrowed to one
18
- * file, which is exactly what `indexBaselineRowsByFile` does below.
19
- *
20
- * @param {{method: string, startLine: number}} row
21
- * @returns {string}
22
- */
23
- export function methodIdentityKey(row) {
24
- return `${row.method}@${row.startLine}`;
25
- }
26
-
27
- /**
28
- * Index baseline rows (accepts either the `{file, method, startLine, crap}`
29
- * legacy shape `compareCrap`/`scanAndScore` use, or the on-disk `{path, ...}`
30
- * shape) by file, then by `methodIdentityKey`, for O(1) per-method lookup —
31
- * exactly the shape `crap-engine.js#finalizeMethodRowsWithBaseline`'s
32
- * `baselineByKey` expects.
33
- *
34
- * @param {Array<{file?: string, path?: string, method: string, startLine: number, crap: number}>} baselineRows
35
- * @returns {Map<string, Map<string, {crap: number}>>} file → (method@startLine → row)
36
- */
37
- export function indexBaselineRowsByFile(baselineRows) {
38
- const byFile = new Map();
39
- for (const row of baselineRows ?? []) {
40
- const file = row?.file ?? row?.path;
41
- if (typeof file !== 'string' || file.length === 0) continue;
42
- if (!byFile.has(file)) byFile.set(file, new Map());
43
- byFile.get(file).set(methodIdentityKey(row), row);
44
- }
45
- return byFile;
46
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * crap-utils-incremental.js — small pure helpers that wire the incremental
3
- * CRAP join (Story #4981) into `crap-utils.js#scanAndScore`'s per-file work
4
- * queue. Split into their own file so the wiring lands as new code rather
5
- * than a same-file expansion of `scanAndScore` / `scoreFileSerial`.
6
- */
7
- import { indexBaselineRowsByFile } from './crap-baseline-index.js';
8
-
9
- /**
10
- * Resolve `scanAndScore`'s `incremental` option into the two lookup
11
- * structures the per-file queue build needs. Both are `null` when
12
- * `incremental` is absent (full-scope, the default) — every downstream
13
- * consumer treats a `null` context as "not incremental".
14
- *
15
- * @param {{ touchedFiles?: Set<string>|string[], baselineRows?: Array<object> } | null} incremental
16
- * @returns {{ touchedFiles: Set<string>|null, baselineByFile: Map<string, Map<string, object>>|null }}
17
- */
18
- export function resolveIncrementalContext(incremental) {
19
- const touchedFiles = incremental?.touchedFiles
20
- ? incremental.touchedFiles instanceof Set
21
- ? incremental.touchedFiles
22
- : new Set(incremental.touchedFiles)
23
- : null;
24
- const baselineByFile = incremental
25
- ? indexBaselineRowsByFile(incremental.baselineRows)
26
- : null;
27
- return { touchedFiles, baselineByFile };
28
- }
29
-
30
- /**
31
- * Merge one queued file's `touched` flag and per-file `baselineByKey` map
32
- * (resolved from the `resolveIncrementalContext` output) onto its base queue
33
- * item. `touched` defaults to `true` (every file is "touched" outside
34
- * incremental mode, matching
35
- * `crap-baseline-join.js#finalizeMethodRowsWithBaseline`'s own default).
36
- *
37
- * @param {object} item Base queue item (`{ abs, relPath, requireCoverage, coverageAvailable }`).
38
- * @param {{ touchedFiles: Set<string>|null, baselineByFile: Map<string, Map<string, object>>|null }} ctx
39
- * @returns {object} `item` plus `{ touched, baselineByKey }`.
40
- */
41
- export function resolveQueueIncrementalFields(
42
- item,
43
- { touchedFiles, baselineByFile },
44
- ) {
45
- const touched = touchedFiles ? touchedFiles.has(item.relPath) : true;
46
- const baselineByKey = baselineByFile
47
- ? (baselineByFile.get(item.relPath) ?? new Map())
48
- : null;
49
- return { ...item, touched, baselineByKey };
50
- }
51
-
52
- /**
53
- * True when `scoreFileSerial` should resolve a file's methods from the
54
- * baseline rather than requiring a fresh coverage entry — an untouched file
55
- * with at least one indexed baseline row.
56
- *
57
- * @param {boolean} touched
58
- * @param {Map<string, object>|null} baselineByKey
59
- * @returns {boolean}
60
- */
61
- function isIncrementalJoinActive(touched, baselineByKey) {
62
- return !touched && baselineByKey != null && baselineByKey.size > 0;
63
- }
64
-
65
- /**
66
- * `scoreFileSerial`'s file-level skip decision, factored out whole so the
67
- * incremental exception lives with the rest of this Story's branching
68
- * rather than inflating the cyclomatic complexity of the pre-#4981 caller.
69
- *
70
- * @param {boolean} requireCoverage
71
- * @param {object|null} entry Istanbul coverage entry for this file.
72
- * @param {boolean} touched
73
- * @param {Map<string, object>|null} baselineByKey
74
- * @returns {boolean}
75
- */
76
- export function shouldSkipFileForNoCoverage(
77
- requireCoverage,
78
- entry,
79
- touched,
80
- baselineByKey,
81
- ) {
82
- return (
83
- requireCoverage &&
84
- entry === null &&
85
- !isIncrementalJoinActive(touched, baselineByKey)
86
- );
87
- }
88
-
89
- /**
90
- * `scanAndScore`'s serial-vs-pool routing decision. Incremental mode always
91
- * routes serial (see `isIncrementalJoinActive`'s doc for why the baseline
92
- * lookup Maps don't cross the worker boundary); otherwise unchanged from the
93
- * pre-#4981 queue-length cutover.
94
- *
95
- * @param {number} queueLength
96
- * @param {unknown} incremental
97
- * @param {number} serialThreshold
98
- * @returns {boolean}
99
- */
100
- export function shouldRunSerial(queueLength, incremental, serialThreshold) {
101
- return queueLength < serialThreshold || Boolean(incremental);
102
- }
103
-
104
- /**
105
- * Project the `resolvedFromBaseline` marker onto a `scanAndScore` result row
106
- * — present only when true, so a full-scope scan's rows are unaffected.
107
- *
108
- * @param {{ resolvedFromBaseline?: boolean }} mr
109
- * @returns {{ resolvedFromBaseline: true } | {}}
110
- */
111
- export function resolvedFromBaselineFlag(mr) {
112
- return mr.resolvedFromBaseline === true ? { resolvedFromBaseline: true } : {};
113
- }
@@ -1,396 +0,0 @@
1
- // .agents/scripts/lib/dynamic-workflow/capability.js
2
- /**
3
- * Dynamic-workflow capability detection + audit-lens strategy selection.
4
- *
5
- * Originated as the `audit-clean-code` dynamic-workflow pilot (Story #3278)
6
- * and generalized to five read-only, dimensionally-decomposable lenses
7
- * (`audit-clean-code`, `audit-security`, `audit-performance`,
8
- * `audit-architecture`, `audit-quality`) under Epic #3597. The one-shot pilot
9
- * doc has been retired; this module docstring is the **canonical home for the
10
- * capability-degradation rationale** (the "Why this is not a contract shim"
11
- * section below), and `docs/roadmap.md` (Part 3 — Dynamic-Workflow
12
- * Orchestration) holds the orchestrated-run evidence and per-lens
13
- * cost/precision gate verdicts.
14
- *
15
- * Claude Code's [dynamic workflows](https://code.claude.com/docs/en/workflows)
16
- * let a JS script orchestrate many subagents in the background. They are a
17
- * Claude Code-only, paid-plan, research-preview feature (CC >= 2.1.154). Each
18
- * generalized lens runs along **two execution paths**:
19
- *
20
- * 1. **orchestrated** — the per-lens dynamic-workflow script
21
- * (`.claude/workflows/<lens>.workflow.js`) fans the lens dimensions out
22
- * as parallel subagents with an adversarial cross-check stage, then emits
23
- * the report contract.
24
- * 2. **sequential** — the existing single-pass lens
25
- * (`.agents/workflows/<lens>.md`) followed turn-by-turn.
26
- *
27
- * Both paths MUST emit the identical per-lens report contract
28
- * (`{{auditOutputDir}}/<lens>-results.md`), so downstream consumers
29
- * (`/deliver` Phase 4 epic-audit, `audit-to-stories`) are agnostic to
30
- * which path produced it.
31
- *
32
- * ## Why this is capability-degradation, not a contract shim
33
- *
34
- * The No-Shim / hard-cutover rule in `.agents/rules/git-conventions.md`
35
- * governs *contract version* changes (config / schema / lifecycle shape) and
36
- * forbids running two shapes of the **same contract** side by side. This
37
- * module does the opposite: it keeps **one** report contract and selects an
38
- * **execution strategy** from a runtime capability snapshot — the same
39
- * pattern the protocol already endorses in `.agents/instructions.md`
40
- * §1.C/§1.D (live-docs → in-repo → web fallback order). There is no second
41
- * report shape, no version-windowed sunset, and no legacy reader to delete
42
- * later.
43
- *
44
- * This module is **pure** — `detectDynamicWorkflowCapability` reads an
45
- * injected environment snapshot (never `process` / `fs` directly) and
46
- * `selectAuditStrategy` is a pure function of that snapshot. The split keeps
47
- * strategy selection unit-testable without a live Claude Code runtime.
48
- *
49
- * @module dynamic-workflow/capability
50
- */
51
-
52
- // ---------------------------------------------------------------------------
53
- // Constants
54
- // ---------------------------------------------------------------------------
55
-
56
- /**
57
- * Minimum Claude Code version that ships the dynamic-workflow runtime
58
- * (research preview). Below this floor the orchestrated path is unavailable
59
- * even on a paid Claude Code runtime.
60
- *
61
- * Source: https://code.claude.com/docs/en/workflows ("require Claude Code
62
- * v2.1.154 or later").
63
- */
64
- export const DYNAMIC_WORKFLOW_VERSION_FLOOR = '2.1.154';
65
-
66
- /** The two execution strategies the lens can run under. */
67
- export const AUDIT_STRATEGY = Object.freeze({
68
- ORCHESTRATED: 'orchestrated',
69
- SEQUENTIAL: 'sequential',
70
- });
71
-
72
- /**
73
- * Environment-variable names consulted when a caller does not inject an
74
- * explicit snapshot. Kept as constants so the docs / tests reference the
75
- * exact keys rather than hard-coding the strings.
76
- */
77
- export const ENV_KEYS = Object.freeze({
78
- /** Hard kill-switch documented at code.claude.com/docs/en/workflows. */
79
- DISABLE: 'CLAUDE_CODE_DISABLE_WORKFLOWS',
80
- /** Test/operator force-override: `orchestrated` | `sequential`. */
81
- FORCE_STRATEGY: 'MANDREL_AUDIT_STRATEGY',
82
- /** Runtime identity (`claude-code` when running under Claude Code). */
83
- RUNTIME: 'CLAUDE_CODE_RUNTIME',
84
- /** Claude Code version string (e.g. `2.1.160`). */
85
- VERSION: 'CLAUDE_CODE_VERSION',
86
- /** Entitlement hint (`pro` | `max` | `team` | `enterprise` | `api`). */
87
- PLAN: 'CLAUDE_CODE_PLAN',
88
- });
89
-
90
- /** Reason codes attached to a strategy decision for observability. */
91
- export const DECISION_REASON = Object.freeze({
92
- FORCED_ORCHESTRATED: 'forced-orchestrated',
93
- FORCED_SEQUENTIAL: 'forced-sequential',
94
- CAPABILITY_PRESENT: 'capability-present',
95
- NOT_CLAUDE_RUNTIME: 'not-claude-runtime',
96
- DISABLED_SETTING: 'disabled-setting',
97
- DISABLED_ENV: 'disabled-env',
98
- VERSION_BELOW_FLOOR: 'version-below-floor',
99
- UNPAID_PLAN: 'unpaid-plan',
100
- RUNTIME_UNKNOWN: 'runtime-unknown',
101
- });
102
-
103
- // Truthy env-flag values (mirrors how Claude Code reads boolean env flags:
104
- // the var is "set" when present and not an explicit falsey token).
105
- const FALSEY_FLAG = new Set(['', '0', 'false', 'off', 'no']);
106
-
107
- // Paid plan/entitlement tokens that unlock dynamic workflows.
108
- const PAID_PLANS = new Set(['pro', 'max', 'team', 'enterprise', 'api']);
109
-
110
- // ---------------------------------------------------------------------------
111
- // Helpers
112
- // ---------------------------------------------------------------------------
113
-
114
- /**
115
- * Is an environment flag "set" in the truthy sense Claude Code uses for
116
- * `CLAUDE_CODE_DISABLE_WORKFLOWS`? Present and not an explicit falsey token.
117
- *
118
- * @param {string|undefined|null} raw
119
- * @returns {boolean}
120
- */
121
- export function isFlagSet(raw) {
122
- if (raw === undefined || raw === null) return false;
123
- return !FALSEY_FLAG.has(String(raw).trim().toLowerCase());
124
- }
125
-
126
- /**
127
- * Compare two dotted numeric version strings. Returns a negative number when
128
- * `a < b`, zero when equal, positive when `a > b`. Non-numeric / missing
129
- * segments are treated as 0. Pre-release suffixes (e.g. `-rc1`) are ignored.
130
- *
131
- * @param {string} a
132
- * @param {string} b
133
- * @returns {number}
134
- */
135
- export function compareVersions(a, b) {
136
- const norm = (v) =>
137
- String(v ?? '')
138
- .trim()
139
- .replace(/^v/i, '')
140
- .split('-')[0]
141
- .split('.')
142
- .map((seg) => {
143
- const n = Number.parseInt(seg, 10);
144
- return Number.isNaN(n) ? 0 : n;
145
- });
146
- const av = norm(a);
147
- const bv = norm(b);
148
- const len = Math.max(av.length, bv.length);
149
- for (let i = 0; i < len; i += 1) {
150
- const diff = (av[i] ?? 0) - (bv[i] ?? 0);
151
- if (diff !== 0) return diff;
152
- }
153
- return 0;
154
- }
155
-
156
- /**
157
- * Does `version` meet or exceed the dynamic-workflow floor?
158
- *
159
- * @param {string} version
160
- * @param {string} [floor=DYNAMIC_WORKFLOW_VERSION_FLOOR]
161
- * @returns {boolean}
162
- */
163
- export function meetsVersionFloor(
164
- version,
165
- floor = DYNAMIC_WORKFLOW_VERSION_FLOOR,
166
- ) {
167
- if (!version) return false;
168
- return compareVersions(version, floor) >= 0;
169
- }
170
-
171
- // ---------------------------------------------------------------------------
172
- // Capability detection
173
- // ---------------------------------------------------------------------------
174
-
175
- /**
176
- * @typedef {object} CapabilitySnapshot
177
- * @property {string|undefined} [runtime] - Runtime identity (`claude-code` …).
178
- * @property {string|undefined} [version] - Claude Code version string.
179
- * @property {string|undefined} [plan] - Plan/entitlement token.
180
- * @property {boolean} [disableWorkflowsSetting] - `disableWorkflows` from settings.json.
181
- * @property {string|undefined} [disableWorkflowsEnv] - Raw env value of CLAUDE_CODE_DISABLE_WORKFLOWS.
182
- */
183
-
184
- /**
185
- * @typedef {object} CapabilityResult
186
- * @property {boolean} available - Whether the orchestrated path can run.
187
- * @property {string} reason - One of {@link DECISION_REASON}.
188
- * @property {object} detail - Echoed snapshot facts for logging.
189
- */
190
-
191
- /**
192
- * Detect whether the dynamic-workflow orchestrated path is available, from an
193
- * **injected** environment snapshot. The function is pure: callers resolve
194
- * `process.env` / settings.json and pass the facts in, so the decision is
195
- * reproducible in tests without a live runtime.
196
- *
197
- * Detection accounts for, in priority order:
198
- * 1. Non-Claude runtime → unavailable (`not-claude-runtime`).
199
- * 2. `disableWorkflows: true` in settings → unavailable (`disabled-setting`).
200
- * 3. `CLAUDE_CODE_DISABLE_WORKFLOWS` set → unavailable (`disabled-env`).
201
- * 4. CC version below {@link DYNAMIC_WORKFLOW_VERSION_FLOOR} → unavailable.
202
- * 5. Non-paid plan (when the plan token is known) → unavailable.
203
- * 6. Otherwise → available.
204
- *
205
- * When the runtime identifies as Claude Code but supplies no version, the
206
- * function fails **closed** (`runtime-unknown`, unavailable): an undetectable
207
- * version cannot be assumed to clear the research-preview floor, and the
208
- * sequential lens is always a safe fallback.
209
- *
210
- * @param {CapabilitySnapshot} snapshot
211
- * @returns {CapabilityResult}
212
- */
213
- export function detectDynamicWorkflowCapability(snapshot = {}) {
214
- const {
215
- runtime,
216
- version,
217
- plan,
218
- disableWorkflowsSetting,
219
- disableWorkflowsEnv,
220
- } = snapshot;
221
-
222
- const detail = {
223
- runtime: runtime ?? null,
224
- version: version ?? null,
225
- plan: plan ?? null,
226
- disableWorkflowsSetting: Boolean(disableWorkflowsSetting),
227
- disableWorkflowsEnv: isFlagSet(disableWorkflowsEnv),
228
- };
229
-
230
- const isClaudeRuntime =
231
- typeof runtime === 'string' && /claude[-_ ]?code/i.test(runtime);
232
- if (!isClaudeRuntime) {
233
- return {
234
- available: false,
235
- reason: DECISION_REASON.NOT_CLAUDE_RUNTIME,
236
- detail,
237
- };
238
- }
239
-
240
- if (disableWorkflowsSetting === true) {
241
- return {
242
- available: false,
243
- reason: DECISION_REASON.DISABLED_SETTING,
244
- detail,
245
- };
246
- }
247
-
248
- if (isFlagSet(disableWorkflowsEnv)) {
249
- return { available: false, reason: DECISION_REASON.DISABLED_ENV, detail };
250
- }
251
-
252
- if (!version) {
253
- // Claude Code runtime but no resolvable version → fail closed.
254
- return {
255
- available: false,
256
- reason: DECISION_REASON.RUNTIME_UNKNOWN,
257
- detail,
258
- };
259
- }
260
-
261
- if (!meetsVersionFloor(version)) {
262
- return {
263
- available: false,
264
- reason: DECISION_REASON.VERSION_BELOW_FLOOR,
265
- detail,
266
- };
267
- }
268
-
269
- // Plan is only a gate when we actually know it. An absent plan token does
270
- // not block — the runtime will refuse the launch itself if unentitled, and
271
- // the sequential fallback covers that case.
272
- if (typeof plan === 'string' && plan.trim().length > 0) {
273
- if (!PAID_PLANS.has(plan.trim().toLowerCase())) {
274
- return { available: false, reason: DECISION_REASON.UNPAID_PLAN, detail };
275
- }
276
- }
277
-
278
- return {
279
- available: true,
280
- reason: DECISION_REASON.CAPABILITY_PRESENT,
281
- detail,
282
- };
283
- }
284
-
285
- // ---------------------------------------------------------------------------
286
- // Strategy selection
287
- // ---------------------------------------------------------------------------
288
-
289
- /**
290
- * @typedef {object} StrategyDecision
291
- * @property {('orchestrated'|'sequential')} strategy - The chosen execution path.
292
- * @property {string} reason - One of {@link DECISION_REASON}.
293
- * @property {boolean} forced - True when an explicit override drove the choice.
294
- * @property {object} capability - The {@link CapabilityResult} that informed it.
295
- */
296
-
297
- /**
298
- * Select the audit execution strategy for `audit-clean-code` from a capability
299
- * snapshot and an optional explicit force-override.
300
- *
301
- * Decision order:
302
- * 1. `forceStrategy === 'orchestrated'` → orchestrated (forced). Used to
303
- * pin the dynamic path for testing even if detection is conservative;
304
- * the runtime still refuses the launch if genuinely unentitled.
305
- * 2. `forceStrategy === 'sequential'` → sequential (forced). Used to verify
306
- * the fallback path (Acceptance: "running the lens with the feature
307
- * disabled") without mutating the real environment.
308
- * 3. Capability available → orchestrated.
309
- * 4. Otherwise → sequential (graceful degradation).
310
- *
311
- * Pure function — no I/O, no side effects.
312
- *
313
- * @param {object} input
314
- * @param {CapabilitySnapshot} [input.snapshot] - Environment facts.
315
- * @param {('orchestrated'|'sequential'|null|undefined)} [input.forceStrategy] - Explicit override.
316
- * @returns {StrategyDecision}
317
- */
318
- export function selectAuditStrategy({ snapshot = {}, forceStrategy } = {}) {
319
- const capability = detectDynamicWorkflowCapability(snapshot);
320
-
321
- const normalizedForce =
322
- typeof forceStrategy === 'string'
323
- ? forceStrategy.trim().toLowerCase()
324
- : null;
325
-
326
- if (normalizedForce === AUDIT_STRATEGY.ORCHESTRATED) {
327
- return {
328
- strategy: AUDIT_STRATEGY.ORCHESTRATED,
329
- reason: DECISION_REASON.FORCED_ORCHESTRATED,
330
- forced: true,
331
- capability,
332
- };
333
- }
334
- if (normalizedForce === AUDIT_STRATEGY.SEQUENTIAL) {
335
- return {
336
- strategy: AUDIT_STRATEGY.SEQUENTIAL,
337
- reason: DECISION_REASON.FORCED_SEQUENTIAL,
338
- forced: true,
339
- capability,
340
- };
341
- }
342
-
343
- if (capability.available) {
344
- return {
345
- strategy: AUDIT_STRATEGY.ORCHESTRATED,
346
- reason: capability.reason,
347
- forced: false,
348
- capability,
349
- };
350
- }
351
-
352
- return {
353
- strategy: AUDIT_STRATEGY.SEQUENTIAL,
354
- reason: capability.reason,
355
- forced: false,
356
- capability,
357
- };
358
- }
359
-
360
- /**
361
- * Build a {@link CapabilitySnapshot} from a raw environment bag (typically
362
- * `process.env`) plus a resolved `disableWorkflows` settings value. Kept
363
- * separate from `process.env` access so it stays testable; the caller decides
364
- * where the env comes from.
365
- *
366
- * @param {Record<string,string|undefined>} env
367
- * @param {{ disableWorkflows?: boolean }} [settings]
368
- * @returns {CapabilitySnapshot}
369
- */
370
- export function snapshotFromEnv(env = {}, settings = {}) {
371
- return {
372
- runtime: env[ENV_KEYS.RUNTIME],
373
- version: env[ENV_KEYS.VERSION],
374
- plan: env[ENV_KEYS.PLAN],
375
- disableWorkflowsSetting: settings.disableWorkflows === true,
376
- disableWorkflowsEnv: env[ENV_KEYS.DISABLE],
377
- };
378
- }
379
-
380
- /**
381
- * Read the operator/test force-override from a raw environment bag. Returns
382
- * `'orchestrated'` / `'sequential'` when the value is a recognised strategy,
383
- * else `null`.
384
- *
385
- * @param {Record<string,string|undefined>} env
386
- * @returns {('orchestrated'|'sequential'|null)}
387
- */
388
- export function forceStrategyFromEnv(env = {}) {
389
- const raw = env[ENV_KEYS.FORCE_STRATEGY];
390
- if (typeof raw !== 'string') return null;
391
- const v = raw.trim().toLowerCase();
392
- if (v === AUDIT_STRATEGY.ORCHESTRATED || v === AUDIT_STRATEGY.SEQUENTIAL) {
393
- return v;
394
- }
395
- return null;
396
- }