mandrel 2.30.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 (253) 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/ci-remediation.md +44 -1
  10. package/.agents/rules/git-conventions-reference.md +27 -27
  11. package/.agents/rules/git-conventions.md +4 -2
  12. package/.agents/rules/known-tooling-behavior.md +66 -30
  13. package/.agents/rules/testing-standards.md +35 -71
  14. package/.agents/runtime-deps.json +0 -1
  15. package/.agents/schemas/agentrc.schema.json +1939 -1400
  16. package/.agents/schemas/lifecycle/README.md +21 -14
  17. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  18. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  19. package/.agents/scripts/README.md +7 -29
  20. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  21. package/.agents/scripts/bootstrap.js +28 -26
  22. package/.agents/scripts/check-baseline-drift.js +73 -13
  23. package/.agents/scripts/check-baseline-scope.js +362 -0
  24. package/.agents/scripts/check-dead-exports.js +9 -1
  25. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  26. package/.agents/scripts/check-knip-entries.js +136 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  28. package/.agents/scripts/check-schema-references.js +1 -1
  29. package/.agents/scripts/diagnose-friction.js +7 -4
  30. package/.agents/scripts/generate-config-docs.js +263 -171
  31. package/.agents/scripts/install-matrix-assert.js +0 -1
  32. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  33. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  34. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  35. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  36. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  37. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  38. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  39. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  40. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  41. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  42. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  43. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  44. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  45. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  46. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  47. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  48. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  49. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  50. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  51. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  52. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  53. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  54. package/.agents/scripts/lib/child-exec.js +193 -0
  55. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  56. package/.agents/scripts/lib/cli-args.js +64 -0
  57. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  58. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  59. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  60. package/.agents/scripts/lib/config/commands.js +3 -5
  61. package/.agents/scripts/lib/config/explain.js +5 -7
  62. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  63. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  64. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  65. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  66. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  67. package/.agents/scripts/lib/config/gates/index.js +5 -2
  68. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  69. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  70. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  71. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  72. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  73. package/.agents/scripts/lib/config/github.js +0 -1
  74. package/.agents/scripts/lib/config/limits.js +3 -48
  75. package/.agents/scripts/lib/config/qa.js +105 -0
  76. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  77. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  78. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  79. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  80. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  81. package/.agents/scripts/lib/crap-utils.js +84 -520
  82. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  83. package/.agents/scripts/lib/degraded-mode.js +2 -2
  84. package/.agents/scripts/lib/doc-tiers.js +3 -3
  85. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  86. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  87. package/.agents/scripts/lib/fs-walk.js +52 -0
  88. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  89. package/.agents/scripts/lib/git-utils.js +16 -36
  90. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  91. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  92. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  93. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  94. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  95. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  96. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  97. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  98. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  99. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  100. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  101. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  102. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  103. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  104. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  105. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  106. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  107. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  108. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  109. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  110. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  111. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  112. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  113. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  124. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
  125. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  126. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  127. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  128. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  129. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  130. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  131. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  132. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  133. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  134. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  135. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  136. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  137. package/.agents/scripts/lib/signals/index.js +8 -6
  138. package/.agents/scripts/lib/signals/schema.js +20 -25
  139. package/.agents/scripts/lib/signals/write.js +8 -8
  140. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  141. package/.agents/scripts/lib/temp-retention.js +1 -1
  142. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  143. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  144. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  145. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  146. package/.agents/scripts/lib/worktree-manager.js +2 -3
  147. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  148. package/.agents/scripts/pr-watch-with-update.js +7 -5
  149. package/.agents/scripts/providers/github/cache.js +2 -2
  150. package/.agents/scripts/providers/github/comments.js +6 -28
  151. package/.agents/scripts/providers/github/compose.js +0 -15
  152. package/.agents/scripts/providers/github/errors.js +10 -27
  153. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  154. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  155. package/.agents/scripts/providers/github.js +4 -7
  156. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  157. package/.agents/scripts/resolve-stories.js +0 -2
  158. package/.agents/scripts/run-lint.js +61 -61
  159. package/.agents/scripts/run-test-profile.js +6 -6
  160. package/.agents/scripts/run-verify.js +48 -30
  161. package/.agents/scripts/single-story-close.js +20 -0
  162. package/.agents/scripts/single-story-init.js +12 -35
  163. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  164. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  165. package/.agents/skills/skills.index.json +2 -12
  166. package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
  167. package/.agents/workflows/audit-documentation.md +5 -6
  168. package/.agents/workflows/audit-to-stories.md +2 -2
  169. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  170. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  171. package/.agents/workflows/helpers/code-review.md +3 -8
  172. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  173. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  174. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  175. package/.agents/workflows/mandrel-update.md +10 -10
  176. package/.agents/workflows/qa-assist.md +15 -20
  177. package/.agents/workflows/qa-explore.md +9 -8
  178. package/README.md +1 -1
  179. package/docs/CHANGELOG.md +49 -0
  180. package/lib/migrations/index.js +2 -0
  181. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  182. package/package.json +12 -3
  183. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  184. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  185. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  186. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  187. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  188. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  189. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  190. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  191. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  192. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  193. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  194. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  195. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  196. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  197. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  198. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  199. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  200. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  201. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  202. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  203. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  204. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  205. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  206. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  207. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  208. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  209. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  210. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  211. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  212. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  213. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  214. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  215. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  216. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  217. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  218. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  219. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  220. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  221. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  222. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  223. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  224. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  225. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  226. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  227. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  228. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  229. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  230. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  231. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  232. package/.agents/scripts/lib/planning-corpus.js +0 -37
  233. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  234. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  235. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  236. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  237. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  238. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  239. package/.agents/scripts/lib/signals/read.js +0 -268
  240. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  241. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  242. package/.agents/scripts/lib/story-plan.js +0 -379
  243. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  244. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  245. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  246. package/.agents/scripts/lint-baseline.js +0 -507
  247. package/.agents/scripts/providers/github/prs.js +0 -103
  248. package/.agents/scripts/signals-view.js +0 -309
  249. package/.agents/scripts/story-plan.js +0 -370
  250. package/.agents/scripts/sync-branch-from-base.js +0 -149
  251. package/.agents/scripts/validate-docs-freshness.js +0 -314
  252. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  253. package/.agents/workflows/helpers/signals.md +0 -112
@@ -1,314 +0,0 @@
1
- #!/usr/bin/env node
2
- /* node:coverage ignore file -- top-level docs-freshness gate shell; the tested logic lives in lib/docs-freshness.js */
3
-
4
- /**
5
- * .agents/scripts/validate-docs-freshness.js — Documentation Freshness Gate
6
- *
7
- * For each doc in `delivery.docsFreshness.paths` + `project.docsContextFiles`, verify
8
- * that the file was meaningfully updated during this Epic's lifecycle. A
9
- * file passes when **either** of the following holds:
10
- *
11
- * 1. `git log --all --grep="#<epicId>" -- <file>` returns a commit —
12
- * the Epic ID was referenced in a commit message that touched the
13
- * file. This is the pass path for **every** doc.
14
- * 2. The file's current body contains `#<epicId>` — but this
15
- * body-annotation path is accepted **only for changelog-class files**
16
- * (basename matches `/changelog/i`), where an appended release note
17
- * keyed to the Epic is the legitimate, expected update. Any other doc
18
- * (architecture, decisions, README, …) MUST pass via condition 1: the
19
- * living doc has to be **rewritten in an Epic-referencing commit**, not
20
- * merely annotated with `#<epicId>`.
21
- *
22
- * The prior gate accepted any diff against the base branch — a stray
23
- * whitespace edit or a one-line unrelated cleanup passed, defeating the
24
- * purpose of the check. Requiring an Epic-ID reference makes "did you
25
- * update the docs for this Epic?" a falsifiable question instead of a
26
- * checkbox. The changelog-only restriction on condition 2 closes the
27
- * follow-on perverse incentive: without it, the gate rewarded appending
28
- * `#<epicId>` history into living docs (manufacturing fake provenance) to
29
- * satisfy the check. Restricting the annotation path to changelog files
30
- * makes the gate ask "was this doc rewritten for the Epic?" rather than
31
- * "does it mention the Epic?".
32
- *
33
- * Usage:
34
- * node .agents/scripts/validate-docs-freshness.js --epic <EPIC_ID> [--docs <comma-separated>] [--json]
35
- *
36
- * `--json` emits a single JSON object on stdout with
37
- * { ok, epicId, results: [{ file, pass, reason }, ...] }
38
- * and suppresses the human-readable log lines. Intended for LLM/tool consumers
39
- * that need to enumerate failing files without parsing log output.
40
- *
41
- * Exit codes:
42
- * 0 — every doc has an Epic-ID reference.
43
- * 1 — one or more docs have no reference.
44
- * 2 — configuration error.
45
- */
46
-
47
- import fs from 'node:fs';
48
- import path from 'node:path';
49
- import { parseArgs } from 'node:util';
50
- import { runAsCli } from './lib/cli-utils.js';
51
- import {
52
- getPaths,
53
- PROJECT_ROOT,
54
- resolveConfig,
55
- } from './lib/config-resolver.js';
56
- import { gitSpawn } from './lib/git-utils.js';
57
- import { Logger } from './lib/Logger.js';
58
-
59
- /**
60
- * Resolve the canonical doc list for a release: `delivery.docsFreshness.paths`
61
- * entries plus `project.docsContextFiles` prefixed by `project.paths.docsRoot`.
62
- * Expects the full resolved config (`{ project, delivery, ... }`).
63
- *
64
- * @param {object} config
65
- * @returns {string[]}
66
- */
67
- export function resolveDocList(config) {
68
- // Read from new shape first; fall back to the legacy shim/bag.
69
- const project = config?.project ?? config;
70
- const delivery = config?.delivery ?? null;
71
- const docsFreshnessPaths = Array.isArray(delivery?.docsFreshness?.paths)
72
- ? delivery.docsFreshness.paths
73
- : Array.isArray(config?.release?.docs)
74
- ? config.release.docs
75
- : [];
76
- const contextDocs = Array.isArray(project?.docsContextFiles)
77
- ? project.docsContextFiles
78
- : Array.isArray(config?.docsContextFiles)
79
- ? config.docsContextFiles
80
- : [];
81
- const docsRoot = getPaths(config).docsRoot ?? 'docs';
82
- const resolved = [
83
- ...docsFreshnessPaths,
84
- ...contextDocs.map((f) => path.posix.join(docsRoot, f)),
85
- ];
86
- return Array.from(new Set(resolved));
87
- }
88
-
89
- /**
90
- * A doc is "changelog-class" when its basename matches `/changelog/i`
91
- * (e.g. `CHANGELOG.md`, `docs/CHANGELOG.md`, `changelog.mdx`). Only these
92
- * files may satisfy the freshness gate via a body annotation (pass
93
- * condition 2); every other doc must pass via an Epic-referencing commit
94
- * (pass condition 1).
95
- *
96
- * @param {string} file
97
- * @returns {boolean}
98
- */
99
- export function isChangelogClass(file) {
100
- return /changelog/i.test(path.basename(file));
101
- }
102
-
103
- function epicRefMatcher(epicId) {
104
- // Match `#N` as a standalone token. `(?!\d)` prevents `#10` from
105
- // satisfying a search for `#1` — a subtle bug the prior diff-only gate
106
- // never had to guard against.
107
- return new RegExp(`#${epicId}(?!\\d)`);
108
- }
109
-
110
- /* node:coverage disable -- real `git log` shell-out; exercised via the
111
- injectable `commitsForFile` seam in runFreshnessGate, not directly. */
112
- function commitsMentioningEpic(docPath, epicId, cwd = PROJECT_ROOT) {
113
- const res = gitSpawn(
114
- cwd,
115
- 'log',
116
- '--all',
117
- `--grep=#${epicId}`,
118
- '--pretty=format:%H',
119
- '--',
120
- docPath,
121
- );
122
- if (res.status !== 0) return [];
123
- return (res.stdout ?? '')
124
- .split('\n')
125
- .map((s) => s.trim())
126
- .filter(Boolean);
127
- }
128
- /* node:coverage enable */
129
-
130
- function fileBodyMentionsEpic(
131
- docPath,
132
- epicId,
133
- cwd = PROJECT_ROOT,
134
- readFileImpl = fs.readFileSync,
135
- ) {
136
- const abs = path.isAbsolute(docPath) ? docPath : path.join(cwd, docPath);
137
- let body;
138
- try {
139
- body = readFileImpl(abs, 'utf8');
140
- } catch {
141
- return false;
142
- }
143
- return epicRefMatcher(epicId).test(body);
144
- }
145
-
146
- /**
147
- * Run the freshness gate against every resolved doc. Pure; takes
148
- * everything it needs as inputs so tests don't need a worktree.
149
- *
150
- * @param {{
151
- * epicId: number,
152
- * docs: string[],
153
- * cwd?: string,
154
- * readFileImpl?: typeof fs.readFileSync,
155
- * commitsForFile?: (doc: string, epicId: number, cwd: string) => string[],
156
- * }} opts
157
- * @returns {{ ok: boolean, results: Array<{ file: string, pass: boolean, reason: string }> }}
158
- */
159
- export function runFreshnessGate({
160
- epicId,
161
- docs,
162
- cwd = PROJECT_ROOT,
163
- readFileImpl = fs.readFileSync,
164
- commitsForFile = commitsMentioningEpic,
165
- }) {
166
- const results = docs.map((file) => {
167
- const commits = commitsForFile(file, epicId, cwd);
168
- if (commits.length > 0) {
169
- return {
170
- file,
171
- pass: true,
172
- reason: `${commits.length} commit(s) reference Epic #${epicId}`,
173
- };
174
- }
175
- // Pass condition 2 (body annotation) is restricted to changelog-class
176
- // files. For every other doc, an appended `#<epicId>` no longer passes —
177
- // the living doc must be rewritten in an Epic-referencing commit.
178
- const changelogClass = isChangelogClass(file);
179
- if (
180
- changelogClass &&
181
- fileBodyMentionsEpic(file, epicId, cwd, readFileImpl)
182
- ) {
183
- return {
184
- file,
185
- pass: true,
186
- reason: `changelog body annotation references #${epicId}`,
187
- };
188
- }
189
- return {
190
- file,
191
- pass: false,
192
- reason: changelogClass
193
- ? `no commit message or changelog body reference to #${epicId}`
194
- : `${file} was not rewritten in an Epic-referencing commit for #${epicId} — ` +
195
- `living docs must be REWRITTEN in a commit whose message references ` +
196
- `#${epicId} (not annotated with #${epicId}); the body-annotation path ` +
197
- `passes only for changelog-class files`,
198
- };
199
- });
200
- return { ok: results.every((r) => r.pass), results };
201
- }
202
-
203
- /**
204
- * Pure: parse argv into the normalized CLI option bag.
205
- *
206
- * @param {string[]} argv
207
- * @returns {{ epicId: number|null, json: boolean, docsList: string[]|null }}
208
- */
209
- export function parseFreshnessArgs(argv) {
210
- const { values } = parseArgs({
211
- args: argv,
212
- options: {
213
- epic: { type: 'string' },
214
- docs: { type: 'string' },
215
- json: { type: 'boolean', default: false },
216
- },
217
- strict: false,
218
- });
219
- const parsed = Number.parseInt(values.epic ?? '', 10);
220
- return {
221
- epicId: Number.isNaN(parsed) || parsed <= 0 ? null : parsed,
222
- json: values.json === true,
223
- docsList: values.docs
224
- ? values.docs
225
- .split(',')
226
- .map((s) => s.trim())
227
- .filter(Boolean)
228
- : null,
229
- };
230
- }
231
-
232
- /** Pure: render a per-doc "ok / fail" line. */
233
- export function renderFreshnessLine(result) {
234
- return `[docs-freshness] ${result.pass ? '✅' : '❌'} ${result.file} — ${result.reason}`;
235
- }
236
-
237
- /**
238
- * Pure: build the failure message for the operator. Names the failing
239
- * file(s) and states the rewrite-not-append contract explicitly.
240
- *
241
- * @param {number} epicId
242
- * @param {Array<{ file: string, pass: boolean }>} [results]
243
- */
244
- export function renderFreshnessFailureMessage(epicId, results = []) {
245
- const failing = results.filter((r) => !r.pass).map((r) => r.file);
246
- const fileList = failing.length > 0 ? failing.join(', ') : '(see rows above)';
247
- return (
248
- `[docs-freshness] ❌ Documentation freshness gate FAILED for Epic #${epicId}.\n\n` +
249
- `Failing file(s): ${fileList}\n\n` +
250
- `Living docs satisfy this gate by being REWRITTEN in an Epic-referencing ` +
251
- `commit — a commit whose message references #${epicId} and touches the ` +
252
- `file — NOT by appending a #${epicId} annotation to the body. The ` +
253
- `body-annotation path passes ONLY for changelog-class files (basename ` +
254
- `matches /changelog/i). Rewrite each failing file for the Epic, then ` +
255
- `re-run /deliver.`
256
- );
257
- }
258
-
259
- /** Pure: success message. */
260
- export function renderFreshnessSuccessMessage(epicId, count) {
261
- return `[docs-freshness] ✅ All ${count} doc(s) reference Epic #${epicId}.`;
262
- }
263
-
264
- /* node:coverage disable -- process I/O + real config/git wiring (stdout,
265
- process.exit, resolveConfig, runAsCli); the pure logic these thin wrappers
266
- call is covered directly above. */
267
- function reportEmptyDocs(epicId, json) {
268
- if (json) {
269
- process.stdout.write(
270
- `${JSON.stringify({ ok: true, epicId, results: [] })}\n`,
271
- );
272
- return;
273
- }
274
- Logger.info(
275
- `[docs-freshness] ⏭ No docs configured under delivery.docsFreshness.paths or ` +
276
- `project.docsContextFiles — nothing to check.`,
277
- );
278
- }
279
-
280
- function reportGateOutcome({ epicId, json, ok, results }) {
281
- if (json) {
282
- process.stdout.write(`${JSON.stringify({ ok, epicId, results })}\n`);
283
- if (!ok) process.exit(1);
284
- return;
285
- }
286
- for (const r of results) Logger.info(renderFreshnessLine(r));
287
- if (ok) {
288
- Logger.info(renderFreshnessSuccessMessage(epicId, results.length));
289
- return;
290
- }
291
- Logger.error(renderFreshnessFailureMessage(epicId, results));
292
- process.exit(1);
293
- }
294
-
295
- async function main() {
296
- const args = parseFreshnessArgs(process.argv.slice(2));
297
- if (args.epicId === null) {
298
- throw new Error(
299
- 'Usage: node validate-docs-freshness.js --epic <EPIC_ID> [--docs a.md,b.md] [--json]',
300
- );
301
- }
302
- const { epicId, json, docsList } = args;
303
- const config = resolveConfig();
304
- const docs = docsList ?? resolveDocList(config);
305
- if (docs.length === 0) {
306
- reportEmptyDocs(epicId, json);
307
- return;
308
- }
309
- const { ok, results } = runFreshnessGate({ epicId, docs });
310
- reportGateOutcome({ epicId, json, ok, results });
311
- }
312
-
313
- runAsCli(import.meta.url, main, { source: 'validate-docs-freshness' });
314
- /* node:coverage enable */
@@ -1,78 +0,0 @@
1
- ---
2
- name: diagnose-friction
3
- description: >-
4
- Wrap a shell command with diagnostic capture. On failure, print static
5
- suggestions and append a structured `friction` record to the per-Story
6
- signals.ndjson stream. Use whenever a script in the orchestration loop
7
- invokes a tool whose failure shape we want the analyzer to attribute.
8
- allowed_tools:
9
- - Bash
10
- - Read
11
- ---
12
-
13
- # diagnose-friction
14
-
15
- ## Policy Capsule
16
-
17
- - Invoke via the wrapping CLI `node .agents/scripts/diagnose-friction.js --story <id> [--epic <id>] --cmd <command args...>`; this is the single supported entry point.
18
- - `--cmd` takes the command as **separate argv words** — the CLI spawns them with no shell. Quoting the whole command as one string (`--cmd "npm run lint"`) makes the entire string the executable name, so the spawn fails ENOENT. That is a usage error in your own invocation, not friction: the CLI refuses it with a usage message and appends **no** ledger row. Re-run it unquoted.
19
- - Pass the wrapped command's stdout and stderr through **unchanged** — never reformat, redact, or buffer in a way that loses the original failure shape.
20
- - Never mutate the wrapped command's exit code. The Skill observes; the caller decides whether the failure is fatal.
21
- - Operate as **best-effort observation**: a write failure on the signals stream MUST NOT halt the runner. A missing signal is preferable to a stalled wave.
22
- - On non-zero exit append a `friction` NDJSON record (`kind`, `ts`, `category`, `detail`, `exitCode`) only through the signals writer helper — never open `signals.ndjson` directly.
23
- - Resolve Story context from `--story` (and `--epic` when a run id applies); there is no body-parsing fallback — pass the flags explicitly.
24
- - Do **not** post GitHub comments from this Skill. Friction is local NDJSON: the retro is what aggregates the stream and routes recurring friction into proposals (Story #4545 deleted `analyze-execution`, the perf-summary comment surface).
25
- - Categorize failures deterministically (rebase abort, test-suite name, lint category, etc.) so the retro can attribute friction without re-running the command.
26
-
27
- ## Role
28
-
29
- Diagnostic interceptor. Captures the failure shape of a wrapped command
30
- and persists it as a structured signal so the retro can attribute friction
31
- back to the Story without re-running the command.
32
-
33
- ## When to use
34
-
35
- Any orchestration call site whose failure mode is informative
36
- (rebase aborts, test failures with classifiable suite names, lint
37
- errors with stable categories). The wrapping script today is
38
- `diagnose-friction.js`; this Skill documents the contract for callers
39
- that want to dispatch via the Skill tool rather than spawn the CLI.
40
-
41
- ## Inputs
42
-
43
- - `--cmd <command args...>` — the command to invoke and observe, passed as
44
- separate argv words. A single whitespace-containing argument is rejected as
45
- a quoting mistake before anything is spawned or recorded.
46
- - `--story <id>` / `--epic <id>` (optional) — when resolved, the Skill
47
- appends a `friction` signal to
48
- `temp/run-<eid>/stories/story-<sid>/signals.ndjson` on non-zero exit
49
- (standalone Stories: `temp/standalone/stories/story-<sid>/`).
50
-
51
- ## Outputs
52
-
53
- - The wrapped command's stdout / stderr is passed through unchanged.
54
- - On non-zero exit: a `friction` NDJSON record (kind, ts, category,
55
- detail, exitCode) is appended via the signals writer.
56
- - No GitHub comments are posted — friction is a local NDJSON signal. The
57
- retro reads the stream out-of-band.
58
-
59
- ## Procedure
60
-
61
- ```bash
62
- node .agents/scripts/diagnose-friction.js \
63
- --story <id> [--epic <id>] \
64
- --cmd <command args...>
65
- ```
66
-
67
- The Skill's contract is "best-effort observation" — the wrapping
68
- script never halts the runner because of a write failure. A missing
69
- signal is preferable to a halted runner.
70
-
71
- ## Constraints
72
-
73
- - Do **not** post GitHub comments from this Skill. Friction is local
74
- NDJSON; the retro owns the aggregate surface.
75
- - Do **not** mutate the wrapped command's exit code. The Skill's job
76
- is observation; the caller decides whether the failure is fatal.
77
- - Do **not** open `signals.ndjson` directly — use the signals writer
78
- helper so the file format and warn-once policy stay consistent.
@@ -1,112 +0,0 @@
1
- ---
2
- description: >-
3
- Helper doc for the signals-view.js debug viewer. Renders the signals
4
- span-tree for a run (and optionally a single Story) to the terminal.
5
- Read-only over `lib/signals/` — no remote writes, no state mutation, no
6
- auto-fixes. Dumb-terminal-safe (`console.log` only). Not a slash command —
7
- invoke the script directly when needed.
8
- ---
9
-
10
- # signals span-tree viewer (helper)
11
-
12
- > **Helper, not a slash command.** Files under `workflows/helpers/` are not
13
- > projected into the mandrel plugin command tree. The signals subsystem itself
14
- > (`lib/signals/`, writer, schema, detectors, NDJSON listeners) runs as
15
- > part of the normal `/deliver` machinery — this viewer is for
16
- > ad-hoc debugging when you need to inspect the span-tree directly.
17
- > Invoke the backing script: `node .agents/scripts/signals-view.js <run-id> [--story <id>]`.
18
-
19
- ## Overview
20
-
21
- `signals-view.js` is the operator-facing viewer for the consolidated
22
- signals stream. It reads `temp/run-<id>/stories/story-<sid>/signals.ndjson` via
23
- [`lib/signals/read`](../../scripts/lib/signals/read.js), builds an
24
- in-memory span-tree via
25
- [`lib/signals/buildSpanTree`](../../scripts/lib/signals/span-tree.js), and
26
- prints a run → Story → events tree to stdout.
27
-
28
- It is distinct from `diagnose-friction.js` (per-Story signal capture that
29
- wraps a shell command) and from `diagnose.js` (read of the checks
30
- registry). This viewer is purely a formatter over an iterator — no
31
- GitHub I/O, no commit creation, no label transitions.
32
-
33
- ```text
34
- node .agents/scripts/signals-view.js <run-id> [--story <id>]
35
- → signals.read({ run, story? })
36
- → buildSpanTree(asyncIterator)
37
- → console.log lines (run → Story → events)
38
- → exit 0 (always; missing-file path prints a friendly message)
39
- ```
40
-
41
- ## Arguments
42
-
43
- | Argument | Required | Description |
44
- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
45
- | `<run-id>` | yes | Positive integer run ID. The viewer resolves `temp/run-<id>/` under the configured `project.paths.tempRoot`. |
46
- | `--story <id>` | no | Positive integer Story ID. When set, the printed tree is narrowed to a single Story subtree under the run. |
47
-
48
- ## Flags (test-only)
49
-
50
- | Flag | Description |
51
- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | `--temp-root <path>` | Override the resolved `tempRoot`. Reserved for fixture-driven tests (see `tests/signals-view.test.js`); production callers omit this flag. |
53
-
54
- ## Exit codes
55
-
56
- | Code | Meaning |
57
- | ---- | --------------------------------------------------------------------------------------------- |
58
- | `0` | Happy path **or** missing signals file. The missing-file path prints a friendly message ("No signals found for run #N"), never a stack trace. |
59
- | `1` | Bad arguments (non-integer `<run-id>`, missing positional, malformed `--story`). |
60
-
61
- ## Examples
62
-
63
- ```bash
64
- # Render every Story under run #1181.
65
- node .agents/scripts/signals-view.js 1181
66
-
67
- # Narrow to a single Story subtree.
68
- node .agents/scripts/signals-view.js 1181 --story 1438
69
-
70
- # No signals yet — friendly message, exit 0.
71
- node .agents/scripts/signals-view.js 9999
72
- # → No signals found for run #9999.
73
- ```
74
-
75
- ## Phase steps
76
-
77
- 1. Parse `<run-id>` and optional `--story <id>` from the CLI
78
- arguments. Reject non-integer or non-positive inputs with exit 1 and
79
- the canonical usage line.
80
- 2. Invoke `node .agents/scripts/signals-view.js <run-id> [--story <id>]`
81
- from the operator's working directory. The script resolves
82
- `tempRoot` from the project's `.agentrc.json` (or the framework
83
- default `'temp'`).
84
- 3. Read the Story-tree output as the canonical surface — no further
85
- parsing is needed; the script's stdout is the deliverable.
86
-
87
- ## Constraints
88
-
89
- - **Never** writes to GitHub state (no label transitions, no comments).
90
- The viewer is purely read-side.
91
- - **Never** mutates the working tree, including `temp/`. The on-disk
92
- signals files are owned by `signals-writer.js`; this viewer only
93
- reads them.
94
- - **Never** uses Ink, blessed, or terminal-control escape sequences.
95
- Output goes through `console.log` exclusively so the viewer works on
96
- Windows + bash hosts (see [`helpers/parallel-tooling.md`](parallel-tooling.md)).
97
- - **Always** honours the configured `project.paths.tempRoot`.
98
- Earlier post-merge work leaked to the real repo root regardless of
99
- test sandbox `tempRoot` (project memory: `phase_timings_uses_project_root`) —
100
- this viewer reads via [`lib/config/temp-paths.js`](../../scripts/lib/config/temp-paths.js)
101
- so the sandbox path always wins.
102
-
103
- ## See also
104
-
105
- - [`.agents/scripts/signals-view.js`](../../scripts/signals-view.js) — the
106
- CLI implementation backing this helper.
107
- - [`.agents/scripts/lib/signals/`](../../scripts/lib/signals/) — the
108
- shared reader + schema + span-tree barrel.
109
- - [`tests/signals-view.test.js`](https://github.com/dsj1984/mandrel/blob/main/tests/signals-view.test.js) —
110
- pinned output and tempRoot-honour contracts.
111
- - [`tests/lib/signals/span-tree.test.js`](https://github.com/dsj1984/mandrel/blob/main/tests/lib/signals/span-tree.test.js) —
112
- pure-function contract for the span-tree builder.