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,16 +1,16 @@
1
1
  /**
2
- * GitHub Provider — error classifier + sub-issues GraphQL shapes.
2
+ * GitHub Provider — error classifier + sub-issues GraphQL shape.
3
3
  *
4
4
  * `classifyGithubError` buckets `gh-exec`-thrown errors into 4 categories
5
5
  * (`feature-disabled` / `permission` / `transient` / `permanent`) so the
6
- * sub-issues fallback and the addSubIssue retry loop have a deterministic
6
+ * sub-issues fallback and the shared retry loop have a deterministic
7
7
  * switch. Rate-limit detection wins over the 401/403 → permission rule
8
8
  * because GitHub's secondary rate limit is delivered as HTTP 403 with a
9
9
  * known message; if we bucketed it as 'permission' it would never be
10
10
  * retried.
11
11
  *
12
- * `SUB_ISSUES_QUERY` / `ADD_SUB_ISSUE_MUTATION` / `REMOVE_SUB_ISSUE_MUTATION`
13
- * are the three GraphQL shapes the sub-issues feature reads/writes.
12
+ * `SUB_ISSUES_QUERY` is the GraphQL shape the sub-issues read path uses.
13
+ * Story #5008 removed the add/remove mutations with the write surface.
14
14
  *
15
15
  * Extracted from `../github.js` in Story #1846 / Task #1857.
16
16
  */
@@ -153,11 +153,11 @@ export function classifyGithubError(err) {
153
153
  // transient network/connectivity errors — so every former consumer of either
154
154
  // module keeps (or gains) its prior retry coverage with no shim.
155
155
  //
156
- // Mirrors the addSubIssue retry contract in `sub-issues.js` so read-path
157
- // callers (paginateRest, getTicket, getNativeSubIssues, …) absorb the same
158
- // jittered exponential backoff on transient GitHub errors instead of
159
- // bubbling a one-shot 502/429/ECONNRESET that kills a longer pipeline
160
- // (e.g. the /deliver Phase E retro). The network consumers repointed here
156
+ // Read-path callers (paginateRest, getTicket, getNativeSubIssues, …) all
157
+ // absorb the same jittered exponential backoff on transient GitHub errors
158
+ // through this one primitive, instead of bubbling a one-shot
159
+ // 502/429/ECONNRESET that kills a longer pipeline (e.g. the /deliver
160
+ // Phase E retro). The network consumers repointed here
161
161
  // (branch-protection, labels, projects-v2-graphql) call with no opts, so
162
162
  // they adopt these defaults; their retry *classes* (the network blips) are
163
163
  // preserved via the unified classifier above.
@@ -174,8 +174,7 @@ export const TRANSIENT_RETRY_DEFAULTS = Object.freeze({
174
174
  * `classify` (defaults to `classifyGithubError`) decides whether each
175
175
  * failure is retry-eligible: only `'transient'` retries; every other
176
176
  * bucket (`feature-disabled` / `permission` / `permanent`) bubbles on the
177
- * first failure. The retry shape matches `addSubIssue` in
178
- * `sub-issues.js:119-167`.
177
+ * first failure.
179
178
  *
180
179
  * `sleep` and `random` are injectable so tests can drive deterministic
181
180
  * retry paths without real-world timing.
@@ -256,19 +255,3 @@ export const SUB_ISSUES_QUERY = `query($id: ID!, $cursor: String) {
256
255
  }
257
256
  }
258
257
  }`;
259
-
260
- export const ADD_SUB_ISSUE_MUTATION = `
261
- mutation($parentId: ID!, $subIssueId: ID!, $replaceParent: Boolean) {
262
- addSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId, replaceParent: $replaceParent }) {
263
- issue { number }
264
- subIssue { number }
265
- }
266
- }`;
267
-
268
- export const REMOVE_SUB_ISSUE_MUTATION = `
269
- mutation($parentId: ID!, $subIssueId: ID!) {
270
- removeSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {
271
- issue { number }
272
- subIssue { number }
273
- }
274
- }`;
@@ -9,8 +9,7 @@
9
9
  * 1. **Transient retry** — each underlying `gh.api({...})` call inside
10
10
  * `paginateRest` is wrapped in `withTransientRetry` (from `./errors.js`),
11
11
  * so a single 502 / 429 / ECONNRESET on page N of M does not lose
12
- * pages 1..N-1 of work. The retry shape matches the existing
13
- * `addSubIssue` mutation retry contract in `sub-issues.js`.
12
+ * pages 1..N-1 of work.
14
13
  * 2. **Page cap** — `paginateRest` enforces a hard ceiling (default
15
14
  * `DEFAULT_PAGE_CAP = 50` → 5000 items at `per_page=100`). Exceeding
16
15
  * the cap throws a clear error naming the endpoint, cap, and items
@@ -1,21 +1,19 @@
1
1
  /**
2
2
  * GitHub Provider — SubIssueGateway.
3
3
  *
4
- * Owns the native GitHub Sub-Issues link surface (the GraphQL `subIssues`
5
- * field on an Issue, plus the add/remove mutations) along with the
6
- * `reconcileSubIssueLinks` walker that backfills missing parent/child
7
- * links across an Epic's child set.
4
+ * Owns the **read** side of the native GitHub Sub-Issues surface: the
5
+ * GraphQL `subIssues` field on an Issue, paginated into a child-number
6
+ * list. Story #5008 removed the write side (`addSubIssue`,
7
+ * `removeSubIssue`) and the `reconcileSubIssueLinks` walker along with the
8
+ * rest of the Epic-hierarchy provider surface — v2 delivery is Story-only,
9
+ * so nothing establishes or repairs parent/child links any more.
8
10
  *
9
- * Extracted from `../github.js` in Story #2462 / Task #2480. Public
10
- * surface on `GitHubProvider` is unchanged — `addSubIssue`,
11
- * `removeSubIssue`, and `reconcileSubIssueLinks` all delegate here.
11
+ * Extracted from `../github.js` in Story #2462 / Task #2480. The read path
12
+ * still reaches `GitHubProvider` as `_getNativeSubIssues`, and
13
+ * `IssuesGateway` calls it directly through its `getNativeSubIssues` hook.
12
14
  *
13
- * Constructed with `{ ghGraphql, hooks, classifyGithubError, cache, queries }`:
15
+ * Constructed with `{ ghGraphql, cache, classifyGithubError }`:
14
16
  * - `ghGraphql(query, variables, opts)` — bound `_ghGraphql` from the parent.
15
- * - `hooks.getTicket(id, opts)` — bound to the TicketGateway.
16
- * - `hooks.getTickets(parentId)` — bound to the TicketGateway (for the reconciler).
17
- * - `hooks.primeTicketCache(tickets)` — bound to the TicketGateway (reconciler priming).
18
- * - `hooks.invalidateTicket(id)` — bound to the TicketGateway (post-mutation invalidate).
19
17
  * - `cache.primeIfAbsent(ticket)` — bound to the shared cache (native-walk priming).
20
18
  *
21
19
  * The gateway holds **no** transport state of its own — every call goes
@@ -26,25 +24,14 @@
26
24
  */
27
25
 
28
26
  import { Logger } from '../../lib/Logger.js';
29
- import { concurrentMap } from '../../lib/util/concurrent-map.js';
30
27
  import {
31
- ADD_SUB_ISSUE_MUTATION,
32
28
  classifyGithubError as defaultClassifyGithubError,
33
- REMOVE_SUB_ISSUE_MUTATION,
34
29
  SUB_ISSUES_QUERY,
35
30
  withTransientRetry,
36
31
  } from './errors.js';
37
32
  import { subIssueNodeToTicket } from './mappers.js';
38
33
  import { defaultRetryWarn } from './request-helpers.js';
39
34
 
40
- // Concurrency + retry budgets — preserved from `../github.js` so dispatch
41
- // fan-out and sub-issue reconciliation keep their established shapes.
42
- const SUB_ISSUE_RECONCILE_CONCURRENCY = 4;
43
- const SUB_ISSUE_RETRY_MAX_ATTEMPTS = 6;
44
- const SUB_ISSUE_RETRY_BASE_DELAY_MS = 1000;
45
- const SUB_ISSUE_RETRY_MAX_DELAY_MS = 30000;
46
- const SUB_ISSUE_RETRY_JITTER_MS = 500;
47
-
48
35
  // Story #2852: cap the native sub-issue cursor walk so a runaway pagination
49
36
  // (e.g. an upstream API regression that never sets `hasNextPage = false`)
50
37
  // fails fast instead of looping forever. 50 cursor pages × 100 nodes per
@@ -55,24 +42,16 @@ export class SubIssueGateway {
55
42
  /**
56
43
  * @param {{
57
44
  * ghGraphql: (query: string, variables?: object, opts?: object) => Promise<object>,
58
- * hooks?: {
59
- * getTicket?: (id: number, opts?: object) => Promise<object>,
60
- * getTickets?: (parentId: number) => Promise<object[]>,
61
- * primeTicketCache?: (tickets: object[]) => void,
62
- * invalidateTicket?: (id: number) => void,
63
- * },
64
45
  * cache?: { primeIfAbsent: (ticket: object) => void },
65
46
  * classifyGithubError?: (err: unknown) => string,
66
47
  * }} deps
67
48
  */
68
49
  constructor({
69
50
  ghGraphql,
70
- hooks = {},
71
51
  cache,
72
52
  classifyGithubError = defaultClassifyGithubError,
73
53
  } = {}) {
74
54
  this._ghGraphql = ghGraphql;
75
- this._hooks = hooks;
76
55
  this._cache = cache;
77
56
  this._classify = classifyGithubError;
78
57
  }
@@ -132,191 +111,4 @@ export class SubIssueGateway {
132
111
  }
133
112
  return childIds;
134
113
  }
135
-
136
- /**
137
- * Establish the native sub-issue link between `parentNumber` and the
138
- * child identified by `childNodeId`. Retries on transient errors with
139
- * jittered exponential backoff before re-throwing.
140
- */
141
- async addSubIssue(
142
- parentNumber,
143
- childNodeId,
144
- opts = { replaceParent: false },
145
- ) {
146
- const getTicket = this._hooks.getTicket;
147
- if (typeof getTicket !== 'function') {
148
- throw new Error(
149
- '[SubIssueGateway] addSubIssue requires a getTicket hook',
150
- );
151
- }
152
- const parentTicket = await getTicket(parentNumber);
153
- let lastErr;
154
- for (let attempt = 0; attempt < SUB_ISSUE_RETRY_MAX_ATTEMPTS; attempt++) {
155
- try {
156
- const result = await this._ghGraphql(
157
- ADD_SUB_ISSUE_MUTATION,
158
- {
159
- parentId: parentTicket.nodeId,
160
- subIssueId: childNodeId,
161
- replaceParent: opts.replaceParent,
162
- },
163
- { headers: { 'GraphQL-Features': 'sub_issues' } },
164
- );
165
- // Sub-issue link mutates the parent's sub-issue list. Invalidate so
166
- // the next `getTicket(parentNumber)` re-fetches a coherent view.
167
- if (typeof this._hooks.invalidateTicket === 'function') {
168
- this._hooks.invalidateTicket(parentNumber);
169
- }
170
- return result;
171
- } catch (err) {
172
- lastErr = err;
173
- const category = this._classify(err);
174
- const isFinalAttempt = attempt === SUB_ISSUE_RETRY_MAX_ATTEMPTS - 1;
175
- if (category !== 'transient' || isFinalAttempt) throw err;
176
- const base = Math.min(
177
- SUB_ISSUE_RETRY_MAX_DELAY_MS,
178
- SUB_ISSUE_RETRY_BASE_DELAY_MS * 2 ** attempt,
179
- );
180
- const delay =
181
- base + Math.floor(Math.random() * SUB_ISSUE_RETRY_JITTER_MS);
182
- Logger.warn(
183
- `[GitHubProvider] sub-issue link transient error for parent #${parentNumber} (attempt ${attempt + 1}/${SUB_ISSUE_RETRY_MAX_ATTEMPTS}); retrying in ${delay}ms: ${err.message}`,
184
- );
185
- await new Promise((r) => setTimeout(r, delay));
186
- }
187
- }
188
- throw lastErr;
189
- }
190
-
191
- async removeSubIssue(parentNumber, subIssueNumber) {
192
- const getTicket = this._hooks.getTicket;
193
- if (typeof getTicket !== 'function') {
194
- throw new Error(
195
- '[SubIssueGateway] removeSubIssue requires a getTicket hook',
196
- );
197
- }
198
- const parentTicket = await getTicket(parentNumber);
199
- const childTicket = await getTicket(subIssueNumber);
200
- const result = await this._ghGraphql(
201
- REMOVE_SUB_ISSUE_MUTATION,
202
- { parentId: parentTicket.nodeId, subIssueId: childTicket.nodeId },
203
- { headers: { 'GraphQL-Features': 'sub_issues' } },
204
- );
205
- if (typeof this._hooks.invalidateTicket === 'function') {
206
- this._hooks.invalidateTicket(parentNumber);
207
- this._hooks.invalidateTicket(subIssueNumber);
208
- }
209
- return result;
210
- }
211
-
212
- /**
213
- * Walk every child of `epicId` whose body footer carries `parent: #N` and
214
- * verify the native sub-issue link is present. Re-establish missing links
215
- * via `addSubIssue` (which retries internally). Idempotent.
216
- */
217
- async reconcileSubIssueLinks(epicId) {
218
- const PARENT_RE = /(?:^|\n)parent:\s*#(\d+)/;
219
- const getTicket = this._hooks.getTicket;
220
- const getTickets = this._hooks.getTickets;
221
- if (typeof getTicket !== 'function' || typeof getTickets !== 'function') {
222
- throw new Error(
223
- '[SubIssueGateway] reconcileSubIssueLinks requires getTicket + getTickets hooks',
224
- );
225
- }
226
- const allChildren = await getTickets(epicId);
227
- const parentByChild = new Map();
228
- for (const child of allChildren) {
229
- const match = (child.body ?? '').match(PARENT_RE);
230
- if (!match) continue;
231
- parentByChild.set(child.id, Number.parseInt(match[1], 10));
232
- }
233
-
234
- const childrenByParent = new Map();
235
- for (const [childId, parentId] of parentByChild) {
236
- if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
237
- childrenByParent.get(parentId).push(childId);
238
- }
239
-
240
- let alreadyLinked = 0;
241
- let reconciled = 0;
242
- let failed = 0;
243
- const failures = [];
244
-
245
- const parentEntries = Array.from(childrenByParent.entries());
246
-
247
- await concurrentMap(
248
- parentEntries,
249
- async ([parentId, childIds]) => {
250
- let parent;
251
- try {
252
- parent = await getTicket(parentId);
253
- } catch (err) {
254
- for (const childId of childIds) {
255
- failures.push({ parentId, childId, reason: err.message });
256
- }
257
- failed += childIds.length;
258
- return;
259
- }
260
- if (!parent?.nodeId) {
261
- for (const childId of childIds) {
262
- failures.push({
263
- parentId,
264
- childId,
265
- reason: 'parent missing nodeId',
266
- });
267
- }
268
- failed += childIds.length;
269
- return;
270
- }
271
-
272
- const linked = new Set(
273
- await this.getNativeSubIssues(parent.nodeId, parentId),
274
- );
275
-
276
- await concurrentMap(
277
- childIds,
278
- async (childId) => {
279
- if (linked.has(childId)) {
280
- alreadyLinked++;
281
- return;
282
- }
283
- let childTicket;
284
- try {
285
- childTicket = await getTicket(childId);
286
- } catch (err) {
287
- failures.push({ parentId, childId, reason: err.message });
288
- failed++;
289
- return;
290
- }
291
- if (!childTicket?.nodeId) {
292
- failures.push({
293
- parentId,
294
- childId,
295
- reason: 'child missing nodeId',
296
- });
297
- failed++;
298
- return;
299
- }
300
- try {
301
- await this.addSubIssue(parentId, childTicket.nodeId);
302
- reconciled++;
303
- } catch (err) {
304
- failures.push({ parentId, childId, reason: err.message });
305
- failed++;
306
- }
307
- },
308
- { concurrency: SUB_ISSUE_RECONCILE_CONCURRENCY },
309
- );
310
- },
311
- { concurrency: SUB_ISSUE_RECONCILE_CONCURRENCY },
312
- );
313
-
314
- return {
315
- totalExpected: parentByChild.size,
316
- alreadyLinked,
317
- reconciled,
318
- failed,
319
- failures,
320
- };
321
- }
322
114
  }
@@ -19,8 +19,10 @@
19
19
  * module.
20
20
  *
21
21
  * Gateway map: tickets, sub-issues, comments, labels, branch-protection,
22
- * merge-methods, prs, project-board, issues (epics + sub-tickets +
23
- * branch probe + raw GraphQL).
22
+ * merge-methods, project-board, issues (epics + sub-tickets + branch probe
23
+ * + raw GraphQL). Story #5008 retired the `prs` gateway — `single-story-close`
24
+ * drives `gh pr create` directly — along with the sub-issue write surface and
25
+ * the repo-wide recent-comments feed.
24
26
  */
25
27
 
26
28
  import { createGh, gh as defaultGh } from '../lib/gh-exec.js';
@@ -125,16 +127,11 @@ const DELEGATIONS = [
125
127
  ['updateTicket', 'tickets.updateTicket'],
126
128
  ['_applyLabelMutations', 'tickets._applyLabelMutations'],
127
129
  ['_getNativeSubIssues', 'subIssues.getNativeSubIssues'],
128
- ['addSubIssue', 'subIssues.addSubIssue'],
129
- ['removeSubIssue', 'subIssues.removeSubIssue'],
130
- ['reconcileSubIssueLinks', 'subIssues.reconcileSubIssueLinks'],
131
- ['getRecentComments', 'comments.getRecentComments'],
132
130
  ['getTicketComments', 'comments.getTicketComments'],
133
131
  ['deleteComment', 'comments.deleteComment'],
134
132
  ['postComment', 'comments.postComment'],
135
133
  ['getBranchProtection', 'branchProtection.getBranchProtection'],
136
134
  ['setBranchProtection', 'branchProtection.setBranchProtection'],
137
- ['createPullRequest', 'pullRequests.createPullRequest'],
138
135
  ['ensureLabels', 'labels.ensureLabels'],
139
136
  ['_reconcileLabelsPresence', 'labels._reconcileLabelsPresence'],
140
137
  ['getMergeMethods', 'mergeMethods.getMergeMethods'],
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+
3
+ // .agents/scripts/prune-baseline-orphans.js — Story #5012.
4
+ //
5
+ // Delete the baseline rows that are provably inert, and nothing else.
6
+ //
7
+ // This is the cheap remedy that makes `check-baseline-scope.js` fair. A gate
8
+ // that hard-fails on a stale row is only defensible while clearing one costs a
9
+ // command rather than a full re-score: the honest alternative — re-derive the
10
+ // whole baseline — spends a coverage run or a full-tree MI pass to express a
11
+ // deletion, which is why stale rows accumulate instead.
12
+ //
13
+ // Two row classes qualify, both decidable without measuring anything:
14
+ //
15
+ // - `absent` — the row's file is gone from disk.
16
+ // - `out-of-scope` — the file is still there, but the gate's own
17
+ // `targetDirs` / `ignoreGlobs` no longer match it.
18
+ //
19
+ // The pruner never adds a row, never restamps `generatedAt` (a fresh stamp
20
+ // over unmeasured rows is the exact failure an age check exists to catch), and
21
+ // recomputes `rollup` through the kind's own arithmetic so the pruned envelope
22
+ // still validates against its schema. An unreadable scope config degrades to
23
+ // orphan-only pruning rather than treating unknown scope as empty scope.
24
+ //
25
+ // Exit codes:
26
+ // 0 nothing to prune (or, without `--check`, the prune was written)
27
+ // 1 `--check` found rows that would be pruned
28
+ // 2 the pruner could not run
29
+
30
+ // Fail-fast if the framework's runtime deps are not installed — must be the
31
+ // first import so the check runs before any third-party-importing sibling
32
+ // module is evaluated (Story #3432).
33
+ import './lib/runtime-deps/ensure-installed.js';
34
+ import { PRUNABLE_KINDS, runPrune } from './lib/baselines/orphan-pruner.js';
35
+ import { runAsCli } from './lib/cli-utils.js';
36
+ import { getQuality } from './lib/config/quality.js';
37
+ import { resolveConfig } from './lib/config-resolver.js';
38
+
39
+ const EXIT_CLEAN = 0;
40
+ const EXIT_WOULD_PRUNE = 1;
41
+ const EXIT_CANNOT_RUN = 2;
42
+
43
+ const HELP = {
44
+ invocation:
45
+ 'node .agents/scripts/prune-baseline-orphans.js [--check] [--kind <kind>] [--json]',
46
+ summary:
47
+ "Remove baseline rows whose file is gone from disk or has left the gate's scope. Never adds a row, never restamps generatedAt, never re-scores.",
48
+ flags: [
49
+ ['--check', 'Report what would be pruned, write nothing, exit 1 if any.'],
50
+ ['--kind <kind>', 'Prune one kind only (repeatable). Default: all.'],
51
+ ['--json', 'Emit the report as JSON instead of text.'],
52
+ ['--cwd <dir>', 'Repository root to prune. Default: process.cwd().'],
53
+ ],
54
+ notes: [
55
+ 'Exit codes:\n 0 clean, or pruned\n 1 --check found prunable rows\n 2 the pruner could not run',
56
+ ],
57
+ };
58
+
59
+ /**
60
+ * Parse argv into an options bag. An unknown flag is an error, never a silent
61
+ * no-op — a typo'd `--dry-run` must not read as "write the files".
62
+ *
63
+ * @param {string[]} argv
64
+ * @returns {{ kinds: string[], check: boolean, json: boolean, cwd: string }}
65
+ */
66
+ export function parseArgs(argv = []) {
67
+ const out = { kinds: [], check: false, json: false, cwd: null };
68
+ let i = 0;
69
+ while (i < argv.length) {
70
+ const arg = argv[i];
71
+ const value = argv[i + 1];
72
+ i += 1;
73
+ if (arg === '--check') out.check = true;
74
+ else if (arg === '--json') out.json = true;
75
+ else if (arg === '--kind') {
76
+ out.kinds.push(value);
77
+ i += 1;
78
+ } else if (arg === '--cwd') {
79
+ out.cwd = value;
80
+ i += 1;
81
+ } else throw new Error(`unknown flag "${arg}" (try --help)`);
82
+ }
83
+ const unknown = out.kinds.filter((k) => !PRUNABLE_KINDS.includes(k));
84
+ if (unknown.length > 0) {
85
+ throw new Error(
86
+ `unknown --kind ${unknown.join(', ')}; expected one of ${PRUNABLE_KINDS.join(', ')}`,
87
+ );
88
+ }
89
+ if (out.kinds.length === 0) out.kinds = [...PRUNABLE_KINDS];
90
+ out.cwd = out.cwd ?? process.cwd();
91
+ return out;
92
+ }
93
+
94
+ /**
95
+ * Render one kind's outcome as indented text lines.
96
+ *
97
+ * @param {object} entry
98
+ * @param {boolean} check
99
+ * @returns {string[]}
100
+ */
101
+ function renderKind(entry, check) {
102
+ if (!entry.present) return [];
103
+ if (entry.skipped)
104
+ return [` - ${entry.kind}: skipped (${entry.skipReason})`];
105
+ const suffix = entry.degraded
106
+ ? ` [orphan-only: ${entry.degradedReason}]`
107
+ : '';
108
+ if (entry.removed.length === 0) {
109
+ return [` - ${entry.kind}: clean${suffix}`];
110
+ }
111
+ const verb = check ? 'would prune' : 'pruned';
112
+ return [
113
+ ` - ${entry.kind}: ${verb} ${entry.removed.length} row(s)${suffix}`,
114
+ ...entry.removed.map((row) => ` · ${row.reason}: ${row.path}`),
115
+ ];
116
+ }
117
+
118
+ /**
119
+ * Render the whole report as text.
120
+ *
121
+ * @param {object} report
122
+ * @returns {string}
123
+ */
124
+ export function formatReport(report) {
125
+ const verb = report.check ? 'would prune' : 'pruned';
126
+ const lines = [
127
+ `[prune-baseline-orphans] ${verb} ${report.removedCount} row(s) across ` +
128
+ `${report.writtenCount} file(s)`,
129
+ ...report.kinds.flatMap((entry) => renderKind(entry, report.check)),
130
+ ];
131
+ if (report.check && report.removedCount > 0) {
132
+ lines.push(
133
+ '',
134
+ 'Run without --check to write the prune:',
135
+ ' node .agents/scripts/prune-baseline-orphans.js',
136
+ );
137
+ }
138
+ return lines.join('\n');
139
+ }
140
+
141
+ /**
142
+ * CLI entry point. Returns its exit code rather than calling `process.exit()`,
143
+ * so `runAsCli`'s `propagateExitCode` path settles it through `flushStdio` and
144
+ * an unbounded report is not truncated at a pipe boundary (Story #4783).
145
+ *
146
+ * @returns {Promise<number>}
147
+ */
148
+ async function main() {
149
+ let opts;
150
+ let report;
151
+ try {
152
+ opts = parseArgs(process.argv.slice(2));
153
+ const config = resolveConfig({ cwd: opts.cwd });
154
+ report = runPrune({
155
+ cwd: opts.cwd,
156
+ kinds: opts.kinds,
157
+ check: opts.check,
158
+ quality: getQuality(config) ?? { gates: {} },
159
+ });
160
+ } catch (err) {
161
+ const message = err?.message ?? String(err);
162
+ process.stdout.write(
163
+ `${JSON.stringify({ schemaVersion: '1', error: message }, null, 2)}\n`,
164
+ );
165
+ return EXIT_CANNOT_RUN;
166
+ }
167
+ process.stdout.write(
168
+ opts.json
169
+ ? `${JSON.stringify(report, null, 2)}\n`
170
+ : `${formatReport(report)}\n`,
171
+ );
172
+ return report.check && report.removedCount > 0
173
+ ? EXIT_WOULD_PRUNE
174
+ : EXIT_CLEAN;
175
+ }
176
+
177
+ runAsCli(import.meta.url, main, {
178
+ source: 'prune-baseline-orphans',
179
+ usage: HELP,
180
+ propagateExitCode: true,
181
+ });
@@ -197,7 +197,6 @@ export async function runResolveStories(
197
197
  stories,
198
198
  nativeEdges,
199
199
  warn: (m) => Logger.warn(m),
200
- config,
201
200
  });
202
201
  const foreignDone = await resolveForeignDone({
203
202
  provider,
@@ -209,7 +208,6 @@ export async function runResolveStories(
209
208
  nativeEdges,
210
209
  foreignDone,
211
210
  warn: () => {},
212
- config,
213
211
  });
214
212
 
215
213
  stdout.write(