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,37 +0,0 @@
1
- /**
2
- * planning-corpus.js — corpus-aware context for the standalone-Story
3
- * planning path (Story #4432).
4
- *
5
- * `/plan --seed` drafts a standalone Story from the seed, the body
6
- * template, and a title-only duplicate scan. This module assembles the
7
- * inherited context (`corpusContext`) for `story-plan.js`'s
8
- * `--emit-context` envelope:
9
- *
10
- * 1. `docsDigest` — the same per-project docs digest
11
- * `orchestration/docs-digest.js` builds for `/deliver` Story
12
- * children, reused here so the standalone path gets the same
13
- * compact outline instead of re-reading the whole docs set.
14
- * `null` when `project.docsContextFiles` is not configured.
15
- * 2. `relevantSections` — always `[]`. This field previously carried
16
- * ranked Tech Spec excerpts mined from open Epics. v2.0.0 removed
17
- * the Epic tier, and the provider's Epic-list surface was reduced
18
- * to a `return []` stub, which made the entire ranking pipeline a
19
- * permanent no-op. The pipeline has been removed; the field is
20
- * retained so the envelope shape stays stable for its consumers.
21
- */
22
-
23
- import { buildDocsDigest } from './orchestration/docs-digest.js';
24
-
25
- /**
26
- * Assemble the `corpusContext` field of the story-plan context envelope.
27
- *
28
- * @param {{
29
- * docsContextFiles?: string[],
30
- * docsRoot?: string,
31
- * }} opts
32
- * @returns {Promise<{ docsDigest: string|null, relevantSections: Array<object> }>}
33
- */
34
- export async function buildCorpusContext({ docsContextFiles, docsRoot }) {
35
- const docsDigest = await buildDocsDigest({ docsContextFiles, docsRoot });
36
- return { docsDigest, relevantSections: [] };
37
- }
@@ -1,181 +0,0 @@
1
- // .agents/scripts/lib/qa/coverage-report.js
2
- //
3
- // Render an AC × test-tier coverage matrix to a human-readable markdown
4
- // report and persist it under `<tempRoot>/qa/`.
5
- //
6
- // The matrix shape is produced by `lib/qa/coverage-verdict.js#acceptanceMatrix`
7
- // — one row per acceptance criterion, each row carrying the per-tier
8
- // `coverageVerdict` (`{unit, contract, acceptance}` with `present`/`absent`
9
- // statuses + notes). This module is the presentation + persistence seam:
10
- // - {@link renderCoverageReport} — pure: matrix → markdown string.
11
- // - {@link reportPathFor} — the report path under `<tempRoot>/qa/`.
12
- // - {@link writeCoverageReport} — render + write the markdown to disk.
13
- //
14
- // `renderCoverageReport` does no I/O so it stays trivially unit-testable;
15
- // `writeCoverageReport` accepts an injectable `fsImpl` (default `node:fs`)
16
- // for the same reason, mirroring `lib/qa/qa-session.js`.
17
-
18
- import fs from 'node:fs';
19
- import path from 'node:path';
20
-
21
- import { tempRootFrom } from '../config/temp-paths.js';
22
- import { acceptanceMatrix, TIERS } from './coverage-verdict.js';
23
-
24
- /** Directory segment (under `tempRoot`) that holds QA coverage reports. */
25
- export const QA_REPORT_DIRNAME = 'qa';
26
-
27
- /** Cell glyph for each verdict status, used in the markdown matrix table. */
28
- const STATUS_GLYPH = Object.freeze({
29
- present: '✅ present',
30
- absent: '❌ absent',
31
- });
32
-
33
- /** Title-case a tier name for the table header (`unit` → `Unit`). */
34
- function tierHeader(tier) {
35
- return tier.charAt(0).toUpperCase() + tier.slice(1);
36
- }
37
-
38
- /**
39
- * Escape the pipe character so a label never breaks the markdown table, and
40
- * collapse newlines to spaces so a multi-line criterion stays on one row.
41
- */
42
- function cell(value) {
43
- return String(value ?? '')
44
- .replace(/\r?\n/g, ' ')
45
- .replace(/\|/g, '\\|')
46
- .trim();
47
- }
48
-
49
- /**
50
- * Resolve the matrix input. Accepts either a pre-built matrix
51
- * (`{tiers, rows}` from {@link acceptanceMatrix}) or raw criteria (array or
52
- * keyed object), in which case it is run through {@link acceptanceMatrix}.
53
- *
54
- * @param {object|Array} input
55
- * @returns {{tiers: ReadonlyArray<string>, rows: Array}}
56
- */
57
- function resolveMatrix(input) {
58
- if (
59
- input &&
60
- typeof input === 'object' &&
61
- !Array.isArray(input) &&
62
- Array.isArray(input.rows)
63
- ) {
64
- return { tiers: input.tiers ?? TIERS, rows: input.rows };
65
- }
66
- return acceptanceMatrix(input);
67
- }
68
-
69
- /**
70
- * Render an AC × test-tier coverage matrix to a markdown report string.
71
- *
72
- * The report has a title, a one-line summary of how many criteria are fully
73
- * covered, the matrix table (one row per AC, one column per tier), and a
74
- * per-criterion notes section explaining each absent tier.
75
- *
76
- * @param {{tiers?:ReadonlyArray<string>,rows:Array}|Array|object} input
77
- * Either a built matrix (`{tiers, rows}`) or raw criteria to build one from.
78
- * @param {{ title?: string, generatedAt?: string }} [opts]
79
- * @returns {string} The markdown report.
80
- */
81
- export function renderCoverageReport(input, opts = {}) {
82
- const { tiers, rows } = resolveMatrix(input);
83
- const title =
84
- typeof opts.title === 'string' && opts.title.trim() !== ''
85
- ? opts.title.trim()
86
- : 'QA Coverage — AC × Test-Tier Matrix';
87
-
88
- const fullyCovered = rows.filter((row) =>
89
- tiers.every((tier) => row.verdict[tier]?.status === 'present'),
90
- ).length;
91
-
92
- const lines = [];
93
- lines.push(`# ${title}`);
94
- lines.push('');
95
- if (typeof opts.generatedAt === 'string' && opts.generatedAt.trim() !== '') {
96
- lines.push(`_Generated: ${opts.generatedAt.trim()}_`);
97
- lines.push('');
98
- }
99
- lines.push(
100
- `${rows.length} acceptance criteri${rows.length === 1 ? 'on' : 'a'}, ` +
101
- `${fullyCovered} fully covered across all ${tiers.length} tiers.`,
102
- );
103
- lines.push('');
104
-
105
- // Matrix table.
106
- const headerCells = ['Acceptance Criterion', ...tiers.map(tierHeader)];
107
- lines.push(`| ${headerCells.join(' | ')} |`);
108
- lines.push(`| ${headerCells.map(() => '---').join(' | ')} |`);
109
- for (const row of rows) {
110
- const acCell = cell(row.label ? `${row.id}: ${row.label}` : row.id);
111
- const tierCells = tiers.map((tier) => {
112
- const status = row.verdict[tier]?.status ?? 'absent';
113
- return STATUS_GLYPH[status] ?? cell(status);
114
- });
115
- lines.push(`| ${[acCell, ...tierCells].join(' | ')} |`);
116
- }
117
- lines.push('');
118
-
119
- // Per-criterion notes.
120
- lines.push('## Notes');
121
- lines.push('');
122
- for (const row of rows) {
123
- lines.push(
124
- `### ${cell(row.id)}${row.label ? ` — ${cell(row.label)}` : ''}`,
125
- );
126
- for (const tier of tiers) {
127
- const v = row.verdict[tier];
128
- const status = v?.status ?? 'absent';
129
- const note = v?.note ?? '';
130
- lines.push(`- **${tierHeader(tier)}** (${status}): ${cell(note)}`);
131
- }
132
- lines.push('');
133
- }
134
-
135
- return `${lines.join('\n').trimEnd()}\n`;
136
- }
137
-
138
- /**
139
- * The report path under `<tempRoot>/qa/`.
140
- *
141
- * @param {string} [fileName] Report file name (default `coverage-report.md`).
142
- * Path separators are rejected so a caller cannot escape the `qa/` dir.
143
- * @param {object} [config] Resolved config bag (for `project.paths.tempRoot`).
144
- * @returns {string}
145
- */
146
- export function reportPathFor(fileName = 'coverage-report.md', config) {
147
- const name = String(fileName);
148
- if (name.includes('/') || name.includes('\\') || name === '..') {
149
- throw new Error(
150
- `[coverage-report] file name must not contain path separators; got ${JSON.stringify(name)}`,
151
- );
152
- }
153
- return path.join(tempRootFrom(config), QA_REPORT_DIRNAME, name);
154
- }
155
-
156
- /**
157
- * Render the matrix to markdown and write it under `<tempRoot>/qa/`, creating
158
- * the directory if needed. Returns the resolved path and the rendered body.
159
- *
160
- * @param {{tiers?:ReadonlyArray<string>,rows:Array}|Array|object} input
161
- * Either a built matrix or raw criteria.
162
- * @param {{
163
- * fileName?: string,
164
- * config?: object,
165
- * title?: string,
166
- * generatedAt?: string,
167
- * fsImpl?: typeof fs,
168
- * }} [opts]
169
- * @returns {{ path: string, markdown: string }}
170
- */
171
- export function writeCoverageReport(input, opts = {}) {
172
- const fsImpl = opts.fsImpl ?? fs;
173
- const markdown = renderCoverageReport(input, {
174
- title: opts.title,
175
- generatedAt: opts.generatedAt,
176
- });
177
- const reportPath = reportPathFor(opts.fileName, opts.config);
178
- fsImpl.mkdirSync(path.dirname(reportPath), { recursive: true });
179
- fsImpl.writeFileSync(reportPath, markdown, 'utf8');
180
- return { path: reportPath, markdown };
181
- }
@@ -1,95 +0,0 @@
1
- // .agents/scripts/lib/qa/propose-missing-test.js
2
- //
3
- // Deterministic missing-test proposal from a coverage verdict.
4
- //
5
- // Closes the loop on accuracy (f4-accuracy): given the per-tier coverage
6
- // verdict produced by `lib/qa/coverage-verdict.js`, name the single tier that
7
- // should have caught a finding and draft a one-line test suggestion for it.
8
- //
9
- // The verdict's pyramid order is unit < contract < acceptance. The cheapest
10
- // tier that is ABSENT is the one a finding "leaked through" — adding a test
11
- // there is the lowest-cost way to have caught it — so this helper proposes
12
- // the *lowest* absent tier. When every tier is present there is no gap to
13
- // fill, so no proposal is returned.
14
- //
15
- // Pure logic, no I/O: no network, no child processes, no filesystem or env
16
- // reads. The companion process skill is `core/qa-coverage-mapping`.
17
- //
18
- // Public API:
19
- //
20
- // proposeMissingTest(coverageVerdict) -> null | {
21
- // tier: 'unit' | 'contract' | 'acceptance',
22
- // description: string, // one-line test suggestion
23
- // }
24
-
25
- import { TIERS } from './coverage-verdict.js';
26
-
27
- const ABSENT = 'absent';
28
-
29
- /**
30
- * One-line test-suggestion templates, keyed by tier. Each takes the absent
31
- * tier's verdict note (which already explains *why* the tier is uncovered)
32
- * and frames it as an actionable suggestion.
33
- */
34
- const DESCRIPTION_BY_TIER = Object.freeze({
35
- unit: (note) =>
36
- `Add a colocated unit test exercising this surface in isolation — ${note}.`,
37
- contract: (note) =>
38
- `Add a contract test asserting this surface’s wire shape or boundary — ${note}.`,
39
- acceptance: (note) =>
40
- `Add an acceptance scenario covering the user-visible journey — ${note}.`,
41
- });
42
-
43
- /**
44
- * Read the absent-tier note off a verdict entry, falling back to a generic
45
- * phrase when the entry omits a usable note.
46
- *
47
- * @param {{note?:unknown}} entry
48
- * @returns {string}
49
- */
50
- function noteFor(entry) {
51
- return typeof entry.note === 'string' && entry.note.trim() !== ''
52
- ? entry.note.trim()
53
- : 'no test currently covers this tier';
54
- }
55
-
56
- /**
57
- * Return true when a verdict entry marks its tier ABSENT.
58
- *
59
- * @param {unknown} entry
60
- * @returns {boolean}
61
- */
62
- function isAbsent(entry) {
63
- return entry !== null && typeof entry === 'object' && entry.status === ABSENT;
64
- }
65
-
66
- /**
67
- * Propose the single missing test that should have caught a finding.
68
- *
69
- * Walks the tiers in pyramid order (unit → contract → acceptance) and returns
70
- * a proposal for the first ABSENT tier — the lowest-cost gap. Returns `null`
71
- * when every tier is present (full coverage, nothing to propose).
72
- *
73
- * @param {Record<string,{status?:string,note?:string}>} coverageVerdict - The
74
- * object produced by `coverageVerdict()` in `lib/qa/coverage-verdict.js`.
75
- * @returns {null | {tier:string, description:string}}
76
- */
77
- export function proposeMissingTest(coverageVerdict) {
78
- if (coverageVerdict === null || typeof coverageVerdict !== 'object') {
79
- throw new TypeError(
80
- 'proposeMissingTest: coverageVerdict must be an object',
81
- );
82
- }
83
-
84
- for (const tier of TIERS) {
85
- const entry = coverageVerdict[tier];
86
- if (isAbsent(entry)) {
87
- return {
88
- tier,
89
- description: DESCRIPTION_BY_TIER[tier](noteFor(entry)),
90
- };
91
- }
92
- }
93
-
94
- return null;
95
- }
@@ -1,217 +0,0 @@
1
- /**
2
- * QA context hydrator — Story #3805, Epic #3798 (f1-shared-qa-core).
3
- *
4
- * Both QA front-ends (`/qa-explore` and `/qa-run`) need to load the
5
- * *grounded* surface context for an Epic before they reason about what to test:
6
- * the Epic body (which carries the folded Tech Spec sections and
7
- * Acceptance Table — Story #4324 retired the separate context tickets),
8
- * the project's `.feature` files, the implementation files the surface
9
- * map points at, and a slice of recent git history. Today a front-end that
10
- * trusts in-code comments ("this handler lives at …") can be wrong — the path
11
- * may have moved, or never existed on the base branch at all. This hydrator
12
- * removes that guesswork: it assembles every surface into one context object
13
- * and **verifies every surface-map path against the base ref (`main`)**,
14
- * marking any path absent on `main` as *unverified* rather than trusting it.
15
- *
16
- * ## No-network seam
17
- *
18
- * Every GitHub and git access flows through an **injected port** so the unit
19
- * test runs with no network and no real repository:
20
- * - {@link GithubPort} — `fetchIssue(number) → { number, body, labels }`.
21
- * - {@link GitPort} — `existsOnRef(path, ref) → boolean` and
22
- * `recentLog({ maxCount }) → LogEntry[]`.
23
- * - `fsImpl` — only used to enumerate `.feature` files; defaults
24
- * to `node:fs`.
25
- *
26
- * The hydrator never reaches the network itself; a caller in production wires
27
- * ports backed by `gh` / `git`, while a test wires in-memory fakes. This
28
- * mirrors the injected-seam style already used across `lib/qa/` (see
29
- * `qa-session.js`'s `fsImpl` and `redact-evidence.js`).
30
- */
31
-
32
- import fs from 'node:fs';
33
- import path from 'node:path';
34
-
35
- /** The base ref every surface-map path is verified against. */
36
- export const DEFAULT_BASE_REF = 'main';
37
-
38
- /** How many recent commits the hydrator pulls into the context object. */
39
- export const DEFAULT_LOG_MAX_COUNT = 20;
40
-
41
- /**
42
- * @typedef {object} GithubPort
43
- * @property {(issueNumber: number) => Promise<{
44
- * number: number,
45
- * body: string,
46
- * labels?: string[],
47
- * }>} fetchIssue Fetch one issue's body + labels.
48
- */
49
-
50
- /**
51
- * @typedef {object} GitPort
52
- * @property {(filePath: string, ref: string) => boolean | Promise<boolean>}
53
- * existsOnRef True when `filePath` is tracked on `ref` (e.g. `main`).
54
- * @property {(opts: { maxCount: number }) =>
55
- * Array<{ sha: string, subject: string }>
56
- * | Promise<Array<{ sha: string, subject: string }>>}
57
- * recentLog Recent commit log, newest first.
58
- */
59
-
60
- /**
61
- * @typedef {object} SurfaceMapEntry
62
- * @property {string} path Repo-relative implementation file path.
63
- * @property {string} [note] Free-form provenance note (e.g. a code comment).
64
- */
65
-
66
- /**
67
- * Enumerate the `.feature` files under `featureRoot`, returning repo-relative
68
- * POSIX-style paths sorted for determinism. A missing root yields an empty
69
- * array (a project that has not authored features is not an error).
70
- *
71
- * @param {string | undefined} featureRoot
72
- * @param {{ fsImpl?: typeof fs }} [opts]
73
- * @returns {string[]}
74
- */
75
- export function collectFeatureFiles(featureRoot, opts = {}) {
76
- const fsImpl = opts.fsImpl ?? fs;
77
- if (!featureRoot || !fsImpl.existsSync(featureRoot)) return [];
78
-
79
- const found = [];
80
- const walk = (dir) => {
81
- const entries = fsImpl.readdirSync(dir, { withFileTypes: true });
82
- for (const entry of entries) {
83
- const full = path.join(dir, entry.name);
84
- if (entry.isDirectory()) {
85
- walk(full);
86
- } else if (entry.isFile() && entry.name.endsWith('.feature')) {
87
- found.push(full.split(path.sep).join('/'));
88
- }
89
- }
90
- };
91
- walk(featureRoot);
92
- return found.sort();
93
- }
94
-
95
- /**
96
- * Verify each surface-map entry against the base ref. An entry whose path is
97
- * tracked on `baseRef` is `verified: true`; an entry absent on the base ref is
98
- * `verified: false` — the surface map (or the code comment it came from) named
99
- * a path that does not exist on `main`, so the front-end must treat it as
100
- * unverified rather than trusting it.
101
- *
102
- * @param {SurfaceMapEntry[]} surfaceMap
103
- * @param {GitPort} gitPort
104
- * @param {string} baseRef
105
- * @returns {Promise<Array<{
106
- * path: string,
107
- * note: string | null,
108
- * verified: boolean,
109
- * }>>}
110
- */
111
- export async function verifySurfaceMap(surfaceMap, gitPort, baseRef) {
112
- const entries = Array.isArray(surfaceMap) ? surfaceMap : [];
113
- const verified = [];
114
- for (const entry of entries) {
115
- const filePath = typeof entry === 'string' ? entry : entry?.path;
116
- if (!filePath) continue;
117
- const exists = await gitPort.existsOnRef(filePath, baseRef);
118
- verified.push({
119
- path: filePath,
120
- note: typeof entry === 'object' && entry?.note ? entry.note : null,
121
- verified: Boolean(exists),
122
- });
123
- }
124
- return verified;
125
- }
126
-
127
- /**
128
- * Hydrate the QA context object for an Epic.
129
- *
130
- * Assembles, in one object:
131
- * - `epic` — the Epic's `{ number, body, labels }`. The body is
132
- * the single planning document (ideation sections +
133
- * folded Tech Spec sections + Acceptance Table).
134
- * - `featureFiles` — repo-relative paths of the project's `.feature` files.
135
- * - `implementation` — the verified surface map (each entry carries
136
- * `verified` against the base ref).
137
- * - `gitLog` — recent commits, newest first.
138
- * - `baseRef` — the ref every path was verified against.
139
- * - `unverifiedPaths` — the subset of surface-map paths absent on `baseRef`.
140
- *
141
- * Every GitHub and git access flows through the injected ports, so this runs
142
- * with no network when the ports are fakes.
143
- *
144
- * @param {{
145
- * ticketNumber: number,
146
- * githubPort: GithubPort,
147
- * gitPort: GitPort,
148
- * surfaceMap?: SurfaceMapEntry[],
149
- * featureRoot?: string,
150
- * baseRef?: string,
151
- * logMaxCount?: number,
152
- * fsImpl?: typeof fs,
153
- * }} opts
154
- * @returns {Promise<{
155
- * epic: { number: number, body: string, labels: string[] },
156
- * featureFiles: string[],
157
- * implementation: Array<{ path: string, note: string | null, verified: boolean }>,
158
- * gitLog: Array<{ sha: string, subject: string }>,
159
- * baseRef: string,
160
- * unverifiedPaths: string[],
161
- * }>}
162
- */
163
- export async function hydrateQaContext(opts) {
164
- const {
165
- ticketNumber,
166
- githubPort,
167
- gitPort,
168
- surfaceMap = [],
169
- featureRoot,
170
- baseRef = DEFAULT_BASE_REF,
171
- logMaxCount = DEFAULT_LOG_MAX_COUNT,
172
- fsImpl,
173
- } = opts ?? {};
174
-
175
- if (!Number.isInteger(ticketNumber)) {
176
- throw new Error(
177
- 'hydrateQaContext: `ticketNumber` is required and must be an integer',
178
- );
179
- }
180
- if (!githubPort || typeof githubPort.fetchIssue !== 'function') {
181
- throw new Error(
182
- 'hydrateQaContext: `githubPort.fetchIssue` is required (inject a port)',
183
- );
184
- }
185
- if (
186
- !gitPort ||
187
- typeof gitPort.existsOnRef !== 'function' ||
188
- typeof gitPort.recentLog !== 'function'
189
- ) {
190
- throw new Error(
191
- 'hydrateQaContext: `gitPort` must expose `existsOnRef` and `recentLog`',
192
- );
193
- }
194
-
195
- const epicIssue = await githubPort.fetchIssue(ticketNumber);
196
- const epic = {
197
- number: epicIssue.number,
198
- body: epicIssue.body ?? '',
199
- labels: Array.isArray(epicIssue.labels) ? [...epicIssue.labels] : [],
200
- };
201
-
202
- const featureFiles = collectFeatureFiles(featureRoot, { fsImpl });
203
- const implementation = await verifySurfaceMap(surfaceMap, gitPort, baseRef);
204
- const gitLog = await gitPort.recentLog({ maxCount: logMaxCount });
205
- const unverifiedPaths = implementation
206
- .filter((entry) => !entry.verified)
207
- .map((entry) => entry.path);
208
-
209
- return {
210
- epic,
211
- featureFiles,
212
- implementation,
213
- gitLog: Array.isArray(gitLog) ? gitLog : [],
214
- baseRef,
215
- unverifiedPaths,
216
- };
217
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Detectors barrel (Epic #1721 / Story #1771 / Task #1774).
3
- *
4
- * Single import surface for every signal detector. Detector Stories
5
- * (rework in #1771, retry in #1768) re-export from here so callers
6
- * (`lib/orchestration/detectors-phase.js`) only ever import from one
7
- * place. The Epic #1769 hotspot detector was retired in the Epic #4406
8
- * signal-contract cutover (no live emitter, no consumer).
9
- *
10
- * @module lib/signals/detectors
11
- */
12
-
13
- export { detectRetry } from './retry.js';
14
- export { detectRework } from './rework.js';