mandrel 1.90.0 → 1.92.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 (115) hide show
  1. package/.agents/README.md +5 -5
  2. package/.agents/docs/SDLC.md +75 -107
  3. package/.agents/docs/configuration.md +1 -1
  4. package/.agents/docs/quality-gates.md +8 -7
  5. package/.agents/docs/workflows.md +4 -10
  6. package/.agents/instructions.md +2 -2
  7. package/.agents/schemas/agentrc.schema.json +1 -1
  8. package/.agents/schemas/lifecycle/README.md +2 -1
  9. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  10. package/.agents/schemas/risk-verdict.schema.json +14 -1
  11. package/.agents/scripts/README.md +0 -46
  12. package/.agents/scripts/analyze-execution.js +76 -1
  13. package/.agents/scripts/check-doc-links.js +3 -0
  14. package/.agents/scripts/check-workflow-cli-lint.js +208 -0
  15. package/.agents/scripts/epic-audit-recheck.js +1 -1
  16. package/.agents/scripts/epic-deliver-preflight.js +37 -1
  17. package/.agents/scripts/epic-plan-clarity.js +16 -6
  18. package/.agents/scripts/epic-plan-decompose.js +37 -59
  19. package/.agents/scripts/epic-plan-healthcheck.js +39 -6
  20. package/.agents/scripts/epic-plan-spec.js +41 -168
  21. package/.agents/scripts/epic-reconcile.js +7 -1
  22. package/.agents/scripts/lib/Logger.js +4 -4
  23. package/.agents/scripts/lib/audit-suite/index.js +4 -5
  24. package/.agents/scripts/lib/audit-suite/runner.js +6 -4
  25. package/.agents/scripts/lib/audit-suite/selector.js +2 -3
  26. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  27. package/.agents/scripts/lib/command-header.js +20 -0
  28. package/.agents/scripts/lib/config/github.js +1 -3
  29. package/.agents/scripts/lib/config-settings-schema.js +4 -3
  30. package/.agents/scripts/lib/degraded-mode.js +1 -1
  31. package/.agents/scripts/lib/label-constants.js +18 -3
  32. package/.agents/scripts/lib/label-taxonomy.js +14 -3
  33. package/.agents/scripts/lib/mutation/baseline-snapshot.js +7 -6
  34. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +18 -8
  35. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -4
  36. package/.agents/scripts/lib/orchestration/docs-digest.js +2 -2
  37. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +4 -0
  38. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +22 -12
  39. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +6 -1
  40. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +18 -2
  41. package/.agents/scripts/{lifecycle-diff.js → lib/orchestration/lifecycle/ledger-diff.js} +10 -76
  42. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +1 -1
  43. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +32 -0
  44. package/.agents/scripts/lib/orchestration/plan-context.js +510 -0
  45. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +177 -0
  46. package/.agents/scripts/lib/orchestration/plan-metrics.js +445 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +359 -0
  48. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +127 -0
  49. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +978 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +191 -0
  51. package/.agents/scripts/lib/orchestration/plan-reachability.js +160 -0
  52. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +3 -3
  53. package/.agents/scripts/lib/orchestration/planning-risk.js +1 -1
  54. package/.agents/scripts/lib/orchestration/remote-verifier.js +165 -0
  55. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +10 -0
  56. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  57. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +1 -1
  58. package/.agents/scripts/lib/orchestration/ticketing/reads.js +11 -2
  59. package/.agents/scripts/lib/plan-phase-cleanup.js +16 -0
  60. package/.agents/scripts/lib/presentation/manifest-persistence.js +2 -3
  61. package/.agents/scripts/lib/templates/spec-author-prompts.js +74 -0
  62. package/.agents/scripts/plan-context.js +186 -0
  63. package/.agents/scripts/plan-critics.js +227 -0
  64. package/.agents/scripts/plan-persist.js +383 -0
  65. package/.agents/scripts/pr-watch-with-update.js +8 -7
  66. package/.agents/scripts/run-lint.js +10 -11
  67. package/.agents/scripts/single-story-init.js +22 -0
  68. package/.agents/scripts/story-plan.js +19 -7
  69. package/.agents/scripts/sync-claude-commands.js +10 -5
  70. package/.agents/skills/core/knowledge-transfer/SKILL.md +11 -7
  71. package/.agents/workflows/audit-documentation.md +5 -7
  72. package/.agents/workflows/audit-lighthouse.md +1 -0
  73. package/.agents/workflows/audit-security.md +1 -0
  74. package/.agents/workflows/audit-to-stories.md +16 -5
  75. package/.agents/workflows/deliver.md +8 -0
  76. package/.agents/workflows/git-deliver.md +76 -243
  77. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  78. package/.agents/workflows/helpers/code-review.md +7 -6
  79. package/.agents/workflows/helpers/deliver-epic.md +16 -4
  80. package/.agents/workflows/helpers/plan-epic-reference.md +109 -96
  81. package/.agents/workflows/helpers/plan-epic.md +246 -999
  82. package/.agents/workflows/helpers/plan-story.md +14 -14
  83. package/.agents/workflows/helpers/scope-triage-gate.md +6 -4
  84. package/.agents/workflows/helpers/single-story-deliver.md +13 -4
  85. package/.agents/workflows/helpers/worktree-lifecycle.md +9 -7
  86. package/.agents/workflows/mandrel-update.md +144 -466
  87. package/.agents/workflows/plan.md +73 -143
  88. package/docs/CHANGELOG.md +20 -0
  89. package/lib/cli/registry.js +21 -3
  90. package/package.json +1 -1
  91. package/.agents/schemas/loop-unit.schema.json +0 -70
  92. package/.agents/scripts/assert-branch.js +0 -81
  93. package/.agents/scripts/check-loop-units.js +0 -204
  94. package/.agents/scripts/detect-merges.js +0 -111
  95. package/.agents/scripts/git-pr-quality-gate.js +0 -205
  96. package/.agents/scripts/git-rebase-and-resolve.js +0 -234
  97. package/.agents/scripts/hierarchy-gate.js +0 -192
  98. package/.agents/scripts/hydrate-context.js +0 -179
  99. package/.agents/scripts/lib/loop-units/validate-loop-unit.js +0 -197
  100. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +0 -167
  101. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +0 -69
  102. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +0 -86
  103. package/.agents/scripts/loc-delta.js +0 -205
  104. package/.agents/scripts/render-manifest.js +0 -143
  105. package/.agents/scripts/run-audit-suite.js +0 -97
  106. package/.agents/scripts/select-audits.js +0 -155
  107. package/.agents/scripts/update-mutation-baseline.js +0 -189
  108. package/.agents/workflows/explain.md +0 -118
  109. package/.agents/workflows/git-merge-pr.md +0 -377
  110. package/.agents/workflows/helpers/epic-plan-decompose.md +0 -22
  111. package/.agents/workflows/helpers/epic-plan-spec.md +0 -22
  112. package/.agents/workflows/loops/README.md +0 -65
  113. package/.agents/workflows/loops/fix-failing-tests.md +0 -74
  114. package/.agents/workflows/loops/nightly-audit.md +0 -81
  115. package/.agents/workflows/loops/watch-ci.md +0 -68
@@ -1,189 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * update-mutation-baseline.js — Refresh `baselines/mutation.json` from a
4
- * fresh Stryker run (Story #1736, Task #1752 + #1753).
5
- *
6
- * Reads the configured mutation gate (`delivery.quality.gates.mutation`),
7
- * invokes the Stryker runner from `lib/mutation/stryker-runner.js`, and
8
- * atomically rewrites the baseline file with the new per-workspace
9
- * mutation scores. Preserves the configured `tolerancePct` (from the
10
- * gate's `tolerance.value`) so the file is self-contained — consumers
11
- * don't need to read `.agentrc.json` to interpret the baseline.
12
- *
13
- * Exit codes:
14
- * 0 — baseline refreshed (didChange true) or no change (didChange false)
15
- * 0 — Stryker skipped (no config) with an explanatory line on stderr
16
- * 1 — Stryker invocation failed
17
- *
18
- * The refresh is intentionally non-fatal when Stryker is not configured:
19
- * the operator can run `npx stryker init` and re-invoke this script.
20
- *
21
- * DORMANT BY DESIGN (Story #3665). This script and the rest of the
22
- * mutation-testing surface ship built-but-unwired: Mandrel declares no
23
- * `delivery.quality.gates.mutation` block and no `stryker.conf.*`, so the
24
- * gate never enters the `check-baselines` pipeline and this refresh
25
- * self-skips (no Stryker config detected). The dormancy is an evaluated,
26
- * intentional opt-in — not an oversight. A spike (Story #3665) concluded
27
- * DEFER on activation. The essential rationale, preserved here so it does
28
- * not depend on a point-in-time writeup:
29
- *
30
- * - Cost model: a full `.agents/scripts` Stryker baseline is a
31
- * tens-of-minutes-to-multi-hour job even under `coverageAnalysis:
32
- * 'perTest'` at `concurrency: 8`. Stryker reruns the affected test set
33
- * once per mutant (~5,000–6,500 mutants over ~19k effective LOC), and
34
- * Mandrel's tests live in a sibling `tests/` tree (not colocated with
35
- * source), so there is no cheap per-source-file partition for `perTest`
36
- * to exploit. Activation therefore belongs on a nightly `schedule`
37
- * (never per-PR), with PR-time `check-baselines` consuming the
38
- * nightly-produced baseline as a read-only ratchet.
39
- * - Baseline-shape fit: the kernel/check side keys mutation rows by
40
- * `path` (`lib/baselines/kinds/mutation.js` — per-file rows + per-
41
- * component rollup, the right fit for this single-package repo), but
42
- * this refresh writer (`lib/mutation/baseline-snapshot.js`) emits a
43
- * workspace-keyed `{ workspaces: { '*': score } }` snapshot that
44
- * collapses to one repo-wide number with zero per-file resolution.
45
- * The two shapes do not agree; activation must first reconcile this
46
- * writer to emit the kernel's path-keyed `rows[]`/`rollup` envelope.
47
- * - Prerequisites: a `node --test` Stryker runner integration (no
48
- * first-party `@stryker-mutator` plugin exists for the Node built-in
49
- * runner), the shape reconciliation above, and nightly CI plumbing —
50
- * together an Epic-sized effort, not a config flip.
51
- *
52
- * Re-evaluate (promote to an activation Epic) when a coverage-gamed
53
- * regression ships, the project migrates to a test runner with first-party
54
- * Stryker support, or an operator opts in for a specific high-risk subtree.
55
- */
56
-
57
- import path from 'node:path';
58
-
59
- import { resolveDiffScope } from './lib/baselines/diff-scope-cli.js';
60
- import { runAsCli } from './lib/cli-utils.js';
61
- import {
62
- getQuality,
63
- PROJECT_ROOT,
64
- resolveConfig,
65
- } from './lib/config-resolver.js';
66
- import { Logger } from './lib/Logger.js';
67
- import {
68
- DEFAULT_BASELINE_PATH,
69
- DEFAULT_TOLERANCE_PCT,
70
- writeBaseline,
71
- } from './lib/mutation/baseline-snapshot.js';
72
- import { runStryker } from './lib/mutation/stryker-runner.js';
73
-
74
- /**
75
- * Resolve the mutation gate config relevant to baseline refresh.
76
- *
77
- * @param {object} config Canonical resolved config (`resolveConfig()` output).
78
- * @returns {{ baselinePath: string, tolerancePct: number, strykerConfigPath: string | null }}
79
- */
80
- export function resolveMutationGate(config) {
81
- const quality = getQuality(config);
82
- const gate = quality.gates?.mutation ?? {};
83
- const baselinePath =
84
- typeof gate.baselinePath === 'string' && gate.baselinePath.length > 0
85
- ? gate.baselinePath
86
- : DEFAULT_BASELINE_PATH;
87
- const tol = gate.tolerance;
88
- const tolerancePct =
89
- tol &&
90
- typeof tol === 'object' &&
91
- Number.isFinite(tol.value) &&
92
- tol.value >= 0
93
- ? tol.value
94
- : DEFAULT_TOLERANCE_PCT;
95
- const strykerConfigPath =
96
- typeof gate.strykerConfigPath === 'string' &&
97
- gate.strykerConfigPath.length > 0
98
- ? gate.strykerConfigPath
99
- : null;
100
- return { baselinePath, tolerancePct, strykerConfigPath };
101
- }
102
-
103
- /**
104
- * @param {{
105
- * cwd?: string,
106
- * runStrykerFn?: typeof runStryker,
107
- * writeBaselineFn?: typeof writeBaseline,
108
- * resolveConfigFn?: typeof resolveConfig,
109
- * logger?: { info?: (m: string) => void, warn?: (m: string) => void, error?: (m: string) => void },
110
- * }} [opts]
111
- * @returns {Promise<{ status: 0 | 1, didChange: boolean, skipped: boolean, reason?: string, baselinePath: string }>}
112
- */
113
- export async function refreshMutationBaseline({
114
- cwd = PROJECT_ROOT,
115
- runStrykerFn = runStryker,
116
- writeBaselineFn = writeBaseline,
117
- resolveConfigFn = resolveConfig,
118
- logger = Logger,
119
- } = {}) {
120
- const config = resolveConfigFn({ cwd });
121
- const gate = resolveMutationGate(config);
122
- const absBaseline = path.isAbsolute(gate.baselinePath)
123
- ? gate.baselinePath
124
- : path.resolve(cwd, gate.baselinePath);
125
-
126
- logger.info?.(`[mutation] refreshing baseline → ${gate.baselinePath}`);
127
- const runResult = await runStrykerFn({
128
- cwd,
129
- configPath: gate.strykerConfigPath,
130
- });
131
- if (runResult.skipped) {
132
- logger.info?.(
133
- `[mutation] skipped — ${runResult.reason ?? 'runner reported skip'}`,
134
- );
135
- return {
136
- status: 0,
137
- didChange: false,
138
- skipped: true,
139
- reason: runResult.reason ?? 'runner-skip',
140
- baselinePath: absBaseline,
141
- };
142
- }
143
- if (!runResult.ok) {
144
- logger.error?.(
145
- `[mutation] Stryker invocation failed: ${runResult.error ?? 'unknown error'}`,
146
- );
147
- return {
148
- status: 1,
149
- didChange: false,
150
- skipped: false,
151
- reason: runResult.error ?? 'stryker-failed',
152
- baselinePath: absBaseline,
153
- };
154
- }
155
-
156
- const writeResult = writeBaselineFn(absBaseline, {
157
- tolerancePct: gate.tolerancePct,
158
- workspaces: runResult.byWorkspace,
159
- });
160
-
161
- if (writeResult.didChange) {
162
- logger.info?.(`[mutation] baseline updated at ${gate.baselinePath}`);
163
- } else {
164
- logger.info?.(`[mutation] baseline unchanged at ${gate.baselinePath}`);
165
- }
166
-
167
- return {
168
- status: 0,
169
- didChange: writeResult.didChange,
170
- skipped: false,
171
- baselinePath: absBaseline,
172
- };
173
- }
174
-
175
- async function main() {
176
- // Story #1974 — `--diff-scope` accepted for CLI parity with other
177
- // update-*-baseline scripts; no row-narrowing effect because mutation
178
- // baseline is workspace-keyed (not file-keyed).
179
- const diffScope = resolveDiffScope({ argv: process.argv.slice(2) });
180
- if (diffScope) {
181
- Logger.info(
182
- `[mutation] --diff-scope ${diffScope.ref}: noted (informational only).`,
183
- );
184
- }
185
- const result = await refreshMutationBaseline();
186
- if (result.status !== 0) process.exit(result.status);
187
- }
188
-
189
- runAsCli(import.meta.url, main, { source: 'update-mutation-baseline' });
@@ -1,118 +0,0 @@
1
- ---
2
- description:
3
- Walk the operator through a code change until they genuinely understand it.
4
- Targets a PR, a branch, or the working-tree diff, then drives the
5
- `core/knowledge-transfer` skill (restate-first, why-ladder, mastery gates,
6
- persistent checklist) with an operator-controlled stop at every checkpoint.
7
- ---
8
-
9
- # /explain [PR# | branch | --staged | --diff <ref>]
10
-
11
- ## Overview
12
-
13
- `/explain` is the **operator-facing comprehension command**. Use it when code
14
- landed (or is about to) and you want to be sure you actually understand it —
15
- the problem it solves, why it was solved this way, the design decisions, the
16
- edge cases, and the blast radius. It is the after-the-fact counterpart to
17
- [`/plan`](helpers/plan-epic.md) Phase 11 (which walks the operator through a
18
- *plan* before delivery); both drive the same engine.
19
-
20
- ```text
21
- /explain 1234 → walk through merged/open PR #1234
22
- /explain story-104 → walk through the diff of branch story-104 vs main
23
- /explain --staged → walk through the currently staged changes
24
- /explain --diff HEAD~3 → walk through the diff from HEAD~3 to working tree
25
- /explain → no subject given: ask what to explain, then proceed
26
- ```
27
-
28
- This command **delegates the method** to the
29
- [`core/knowledge-transfer`](../skills/core/knowledge-transfer/SKILL.md) skill.
30
- This file owns only **subject resolution** (which diff) and the handoff into
31
- that skill. Read the skill before running — it defines the loop, the
32
- why-ladder, the depth levels, the quizzing rules, and the operator-stop
33
- contract.
34
-
35
- ## When to use `/explain`
36
-
37
- | Scenario | Command |
38
- | --- | --- |
39
- | Understand a change that already merged | `/explain <PR#>` |
40
- | Understand a branch before merging it | `/explain <branch>` |
41
- | Understand what you are about to commit | `/explain --staged` |
42
- | Understand a freshly planned Epic backlog | `/plan` Phase 11 (automatic) |
43
-
44
- ## Step 1 — Resolve the subject
45
-
46
- Determine the change to explain from the argument:
47
-
48
- - **PR number** (`/explain 1234`) — read the PR metadata and diff:
49
-
50
- ```bash
51
- gh pr view 1234 --json title,body,state,headRefName,baseRefName
52
- gh pr diff 1234
53
- ```
54
-
55
- - **Branch** (`/explain story-104`) — diff the branch against the base
56
- branch (`project.baseBranch`, default `main`):
57
-
58
- ```bash
59
- git diff main...story-104
60
- ```
61
-
62
- - **`--staged`** — the staged working-tree changes:
63
-
64
- ```bash
65
- git diff --staged
66
- ```
67
-
68
- - **`--diff <ref>`** — an explicit diff range:
69
-
70
- ```bash
71
- git diff <ref>
72
- ```
73
-
74
- - **No argument** — ask the operator which PR, branch, or range they want
75
- walked through, then resolve as above. Do not guess.
76
-
77
- Read the resolved diff **and** the surrounding code it touches before
78
- explaining anything — ground every explanation in the real artifact, never
79
- the title alone.
80
-
81
- ## Step 2 — Drive the comprehension loop
82
-
83
- Activate [`core/knowledge-transfer`](../skills/core/knowledge-transfer/SKILL.md)
84
- with the resolved change as the subject. Follow the skill exactly:
85
-
86
- 1. **Frame & gather** — state the change in one sentence; write the checklist
87
- to `temp/comprehension-<subject>.md`.
88
- 2. **Restate-first** — ask the operator what they already understand before
89
- explaining.
90
- 3. **Fill gaps one layer at a time** along the why-ladder
91
- (problem → why → branches → solution → why this solution → design
92
- decisions → edge cases → broader impact), confirming mastery at each layer
93
- and offering the stop exit before advancing.
94
- 4. **Close** — summarize coverage, note any unchecked items, leave the
95
- checklist artifact in place.
96
-
97
- Honor depth requests (ELI5 / intern / peer) and quiz via the host's
98
- structured-question mechanism when it sharpens understanding.
99
-
100
- ## Constraints
101
-
102
- - **Operator-invoked only.** Never auto-fire `/explain`, and never run it
103
- inside a non-interactive delivery sub-agent — there is no operator to
104
- teach.
105
- - **Operator controls the exit.** Stop the moment the operator says they are
106
- satisfied; this command never blocks or gates anything.
107
- - **Read-only.** `/explain` teaches; it does not modify code, tickets, or
108
- branches. The only file it writes is the `temp/` checklist artifact.
109
-
110
- ## See also
111
-
112
- - [`core/knowledge-transfer`](../skills/core/knowledge-transfer/SKILL.md) —
113
- the comprehension engine this command drives.
114
- - [`/plan`](helpers/plan-epic.md) — Phase 11 runs the same engine over a plan
115
- before delivery.
116
- - `/code-review` (Claude Code built-in) — correctness review of a diff. A
117
- different concern: `/explain` builds *operator* understanding, not a defect
118
- list.
@@ -1,377 +0,0 @@
1
- ---
2
- description: >-
3
- Analyze, validate, resolve conflicts, and merge a given pull request by
4
- number.
5
- ---
6
-
7
- # /git-merge-pr [#PR_LIST]
8
-
9
- This workflow performs a full end-to-end merge of one or more pull requests: it
10
- analyzes each PR diff, validates linting and tests, resolves any merge
11
- conflicts, and completes the merge into the target base branch.
12
-
13
- > **When to run**: Any time one or more PRs are ready for merge review and you
14
- > want an automated merge with conflict resolution and quality gates enforced.
15
- >
16
- > **Persona**: `devops-engineer` · **Skills**:
17
- > `core/git-workflow-and-versioning`
18
-
19
- ---
20
-
21
- ## Step 0 — Resolve Context
22
-
23
- 1. Resolve one or more `[PR_NUMBER]` values from the slash-command argument
24
- (e.g. `/git-merge-pr 42 43 45` → `PR_LIST=[42, 43, 45]`).
25
- 2. **Sequential Loop**: Steps 1 through 7 must be performed **sequentially** for
26
- each PR in the `PR_LIST`. Complete the full merge and cleanup for one PR
27
- before starting the next.
28
- 3. For the current `[PR_NUMBER]`, fetch metadata from GitHub by calling
29
- `mcp__github__pull_request_read` (method `get`) with the repo's
30
- `owner` / `repo` and `pullNumber: [PR_NUMBER]`. From the response, read
31
- `number`, `title`, `headRefName`, `baseRefName`, `state`, `mergeable`,
32
- and `mergeStateStatus`.
33
-
34
- 4. From the output, resolve:
35
- - `[PR_TITLE]` — the PR title.
36
- - `[HEAD_BRANCH]` — the source branch (`headRefName`).
37
- - `[BASE_BRANCH]` — the merge target (`baseRefName`).
38
- - `[PR_STATE]` — must be `OPEN`. If `CLOSED` or `MERGED`, **SKIP** this PR
39
- and proceed to the next one in the list.
40
- - `[MERGEABLE]` — initial GitHub mergeability signal (`MERGEABLE`,
41
- `CONFLICTING`, or `UNKNOWN`).
42
-
43
- ---
44
-
45
- ## Step 1 — PR Analysis
46
-
47
- Fetch the full diff and review the scope of changes:
48
-
49
- ```powershell
50
- gh pr diff [PR_NUMBER]
51
- ```
52
-
53
- > **Why `gh pr diff` and not MCP?** `mcp__github__pull_request_read`'s
54
- > `get_diff` method returns a structured response rather than the raw
55
- > unified diff this step renders to the operator. `gh pr diff` stays
56
- > until MCP exposes a raw-diff equivalent.
57
-
58
- Summarize the following to the operator before proceeding:
59
-
60
- - **Files changed** (count and list).
61
- - **Lines added / removed**.
62
- - **Areas of concern** — any files that touch shared utilities, schemas,
63
- migrations, or critical infrastructure.
64
- - **Initial mergeability status** from Step 0.
65
-
66
- > This is a read-only analysis step. No files are modified yet.
67
-
68
- ---
69
-
70
- ## Step 2 — Checkout & Sync
71
-
72
- Delegate the rebase orchestration to `git-rebase-and-resolve.js`. It fetches
73
- `origin`, checks out the head branch, and rebases it onto the base — then
74
- reports the outcome in structured form so this skill routes on outcome
75
- instead of re-implementing the retry loop.
76
-
77
- ```powershell
78
- node .agents/scripts/git-rebase-and-resolve.js --onto origin/[BASE_BRANCH] --head [HEAD_BRANCH] --json
79
- ```
80
-
81
- Parse the JSON result. Route on `outcome`:
82
-
83
- - `clean` → rebase landed with no conflicts. Proceed to Step 3.
84
- - `conflict` → `conflictedFiles[]` lists the unmerged paths. Proceed to
85
- Step 2.5.
86
- - `error` → git returned an error unrelated to conflicts. **STOP** and
87
- surface `stderr` to the operator.
88
-
89
- ### Step 2.5 — Conflict Resolution
90
-
91
- Follow the shared conflict-resolution procedure in
92
- [`helpers/_merge-conflict-template.md`](helpers/_merge-conflict-template.md):
93
- read both sides
94
- of each file in `conflictedFiles[]`, apply both when compatible (or choose a
95
- side with an explicit rationale), never silently drop code, then stage the
96
- resolutions.
97
-
98
- Continue the rebase via the script — it calls `git rebase --continue` and
99
- reports whether the rebase is now clean, still has conflicts (cascading
100
- conflict), or hit a different error:
101
-
102
- ```powershell
103
- node .agents/scripts/git-rebase-and-resolve.js --continue --json
104
- ```
105
-
106
- Loop until `outcome` is `continued`. If you need to bail out entirely:
107
-
108
- ```powershell
109
- node .agents/scripts/git-rebase-and-resolve.js --abort --json
110
- ```
111
-
112
- Once the rebase completes cleanly, force-push the rebased branch:
113
-
114
- ```powershell
115
- git push --force-with-lease origin [HEAD_BRANCH]
116
- ```
117
-
118
- ---
119
-
120
- ## Step 3 — Quality Gate (Lint + Format + Test)
121
-
122
- Run the full lint / format / test suite via the gate wrapper. The wrapper
123
- owns the command list so this skill doesn't rot when a project renames
124
- `lint` → `lint:ci` or swaps Biome for ESLint. The default check set is
125
- `lint`, `format:check`, `test`; override via `.agentrc.json → github.branchProtection`.
126
-
127
- ```powershell
128
- node .agents/scripts/git-pr-quality-gate.js --json
129
- ```
130
-
131
- The script emits a JSON result `{ ok, checks: [...], failed: [...] }`.
132
- Exit code 0 means every check passed. On failure:
133
-
134
- 1. Read the `failed[]` entries (each has `name` and `reason`).
135
- 2. For **format** failures, run `npx biome format --write .` to auto-fix,
136
- then re-run the gate.
137
- 3. For **lint** failures, apply the minimal manual fix.
138
- 4. For **test** failures, classify before fixing:
139
- - **Pre-existing failures** (unrelated to this PR's diff): alert the
140
- operator and ask whether to proceed or block.
141
- - **Regression introduced by this PR**: apply the fix.
142
- 5. Commit the fixes and re-push:
143
-
144
- ```powershell
145
- git add .
146
- # justification: post-CI remediation; CI lint+test gate ran upstream and produced the failure being fixed here. Local hook would re-run the same gate.
147
- git commit --no-verify -m "fix(ci): resolve quality-gate failures on [HEAD_BRANCH] for PR #[PR_NUMBER]"
148
- git push origin [HEAD_BRANCH]
149
- ```
150
-
151
- 6. Re-run the gate until it exits 0 before continuing to Step 3.5.
152
-
153
- > If a failure cannot be resolved after exhausting reasonable remediation
154
- > attempts, **STOP** and escalate to the operator with a detailed summary.
155
-
156
- ---
157
-
158
- ## Step 3.5 — Unified Baselines Gate (`check-baselines`)
159
-
160
- `check-baselines.js` is the single canonical floor + tolerance + schema
161
- gate for every baseline kind (coverage, crap, maintainability, mutation).
162
- It owns absolute-floor enforcement, schema validation, and kernel-mismatch
163
- surfacing. The per-kind regression CLIs that previously layered on top of
164
- this gate were removed in Epic #1943 — `check-baselines.js` is now the
165
- sole source of truth for baseline regressions at merge time.
166
-
167
- ```powershell
168
- node .agents/scripts/check-baselines.js --format text
169
- ```
170
-
171
- Exit codes:
172
-
173
- - `0` — every enabled gate's floors are met and no schema errors.
174
- - `1` — any floor breach. Inspect the JSON output
175
- (`--format json`, the default) to see which kind / component / axis
176
- fell below floor.
177
- - `2` — any baseline failed schema validation. Regenerate the offending
178
- baseline through its per-kind update script.
179
- - `3` — config resolution error (typically a malformed `.agentrc.json`).
180
-
181
- Treat any non-zero exit as a hard merge block before proceeding to Step 4.
182
-
183
- ---
184
-
185
- ## Step 4 — Final Mergeability Check
186
-
187
- Re-query GitHub to confirm the PR is now clean and ready to merge by
188
- calling `mcp__github__pull_request_read` (method `get`) with the repo's
189
- `owner` / `repo` and `pullNumber: [PR_NUMBER]`. Read `mergeable`,
190
- `mergeStateStatus`, `reviewDecision`, and `statusCheckRollup` from the
191
- response (the same fields the prior `gh pr view --json` shape exposed).
192
-
193
- Verify:
194
-
195
- - `mergeable` is `MERGEABLE`.
196
- - `mergeStateStatus` is `CLEAN` or `HAS_HOOKS`.
197
- - Required CI checks (if any) are passing (`statusCheckRollup` → all `SUCCESS`
198
- or `NEUTRAL`).
199
-
200
- If any blocking condition remains, resolve it before proceeding to the merge
201
- step.
202
-
203
- ---
204
-
205
- ## Step 5 — Merge
206
-
207
- Merge the PR as a squash commit and delete the head branch. Call
208
- `mcp__github__merge_pull_request` with the repo's `owner` / `repo`,
209
- `pullNumber: [PR_NUMBER]`, `merge_method: "squash"`, and
210
- `delete_branch: true`.
211
-
212
- > **MCP coverage gap — auto-merge queueing.** `mcp__github__merge_pull_request`
213
- > performs an **immediate** merge; it does not enable GitHub's native
214
- > auto-merge queue. When the workflow needs to queue the merge behind
215
- > required-check completion (the default `--auto` posture), fall back to
216
- > the shell form `gh pr merge [PR_NUMBER] --auto --squash --delete-branch`.
217
- > Use the MCP call when CI is already green and the merge can fire
218
- > synchronously (the common case after a clean Step 4 verdict).
219
- >
220
- > **Merge strategy guidance** (override with operator instruction):
221
- >
222
- > - `mcp__github__merge_pull_request` with `merge_method: "squash"` — the
223
- > default for an already-green PR; clean history, single squash commit.
224
- > - `gh pr merge [PR_NUMBER] --auto --squash --delete-branch` — auto-merge
225
- > queueing fallback when required checks are still pending. Requires
226
- > `allow_auto_merge=true` on the repo (Story #1239 turns this on).
227
- > - `gh pr merge [PR_NUMBER] --squash` (no `--auto`) — synchronous merge;
228
- > use only when bypassing auto-merge is intentional (e.g. CI is broken
229
- > and a hotfix is going in under admin override).
230
- > - `--merge` — preserves the full commit history from `[HEAD_BRANCH]` (use for
231
- > Epic branches with meaningful commit granularity); pass `merge_method:
232
- > "merge"` to the MCP call or `--merge` to `gh pr merge`.
233
- > - `--rebase` — linear history; ideal for small, atomic PRs; pass
234
- > `merge_method: "rebase"` to the MCP call or `--rebase` to `gh pr merge`.
235
-
236
- After the merge command returns, perform a conflict marker scan to confirm no
237
- stray markers entered the base branch. Delegate to `detect-merges.js` — it
238
- owns the scan logic and is the same script used by `/deliver` Phase 5.3.
239
-
240
- ```powershell
241
- git checkout [BASE_BRANCH]
242
- git pull origin [BASE_BRANCH]
243
- node .agents/scripts/detect-merges.js
244
- ```
245
-
246
- If the script exits non-zero: **STOP**, alert the operator immediately, and
247
- do not proceed until the conflict markers are resolved.
248
-
249
- ---
250
-
251
- ## Step 6 — Post-Merge Verification & Cleanup
252
-
253
- Confirm the merge landed correctly on the base branch:
254
-
255
- ```powershell
256
- git log origin/[BASE_BRANCH] -5 --oneline
257
- ```
258
-
259
- Verify that the top commit corresponds to the merged PR.
260
-
261
- Explicitly delete the remote head branch. This is **mandatory** and must always
262
- succeed — even if the Husky pre-push hook blocks `git push origin --delete`. Use
263
- the **two-stage** approach below:
264
-
265
- **Stage 1 — git push (fast path):**
266
-
267
- ```powershell
268
- # Attempt standard deletion first (fast, uses existing auth)
269
- git push origin --delete [HEAD_BRANCH] 2>$null
270
- $gitDeleteOk = $LASTEXITCODE -eq 0
271
- ```
272
-
273
- **Stage 2 — REST API fallback (always run if Stage 1 failed):**
274
-
275
- If Stage 1 fails (exit code ≠ 0, e.g., due to Husky hook blocking the push),
276
- fall back to the GitHub REST API using the token from the git credential store:
277
-
278
- ```powershell
279
- if (-not $gitDeleteOk) {
280
- # Retrieve token from git's native credential manager
281
- $creds = "protocol=https`nhost=github.com`n" | git credential fill 2>$null
282
- $token = ($creds | Select-String 'password=(.+)').Matches[0].Groups[1].Value
283
-
284
- if ($token) {
285
- $url = "https://api.github.com/repos/[OWNER]/[REPO]/git/refs/heads/[HEAD_BRANCH]"
286
- $headers = @{ Authorization = "token $token"; Accept = "application/vnd.github.v3+json" }
287
- try {
288
- Invoke-RestMethod -Method DELETE -Uri $url -Headers $headers -ErrorAction Stop
289
- Write-Host "Remote branch deleted via REST API: [HEAD_BRANCH]"
290
- } catch {
291
- $status = $_.Exception.Response.StatusCode.value__
292
- if ($status -eq 422 -or $status -eq 404) {
293
- Write-Host "Branch already gone (HTTP $status) — skipping."
294
- } else {
295
- Write-Warning "Failed to delete remote branch via API (HTTP $status): [HEAD_BRANCH]"
296
- }
297
- }
298
- } else {
299
- Write-Warning "No GitHub token found in credential store — remote branch may not be deleted."
300
- }
301
- }
302
- ```
303
-
304
- Prune stale remote-tracking refs and delete the local branch:
305
-
306
- ```powershell
307
- git fetch --prune
308
- git branch -D [HEAD_BRANCH] 2>$null
309
- ```
310
-
311
- > **Note:** `git branch -D` is safe to ignore if the local branch does not
312
- > exist. `git fetch --prune` must always run to keep the local ref list clean.
313
-
314
- Explicitly close the GitHub PR object. Because this workflow squash-merges
315
- directly into the base branch (bypassing GitHub's native merge flow), GitHub
316
- **will not** auto-close the PR — it must be closed explicitly.
317
-
318
- Call `mcp__github__update_pull_request` with the repo's `owner` / `repo`,
319
- `pullNumber: [PR_NUMBER]`, and `state: "closed"`.
320
-
321
- > **Note:** This is a hard requirement — leaving the PR open after merging
322
- > pollutes the repository's open PR list and causes confusion for reviewers.
323
-
324
- Optionally, run the test suite one final time on the base branch to confirm no
325
- regressions were introduced by the merge:
326
-
327
- ```powershell
328
- npm test
329
- ```
330
-
331
- ---
332
-
333
- ## Step 7 — Summary Report
334
-
335
- Post a structured summary comment to the PR (now closed) for traceability.
336
- Call `mcp__github__add_issue_comment` with the repo's `owner` / `repo`,
337
- `issue_number: [PR_NUMBER]` (PR comments use the issues comments endpoint),
338
- and `body` set to:
339
-
340
- ```markdown
341
- ✅ **Merged by agent** via `/git-merge-pr`
342
-
343
- - **Branch**: `[HEAD_BRANCH]` → `[BASE_BRANCH]`
344
- - **Conflicts resolved**: [YES/NO — list files if YES]
345
- - **Lint fixes applied**: [YES/NO]
346
- - **Test fixes applied**: [YES/NO]
347
- - **Merge strategy**: squash
348
- ```
349
-
350
- ---
351
-
352
- ## Constraint
353
-
354
- - **Never** merge a PR that has unresolved lint errors or failing tests. Running
355
- a passing quality gate is mandatory before the merge commit.
356
- - **Never** silently drop code when resolving merge conflicts. When in doubt,
357
- ask the operator.
358
- - **Never** bypass required GitHub branch protection checks (required reviewers,
359
- required status checks). If these are blocking, surface them to the operator
360
- rather than attempting to force-merge.
361
- - **Always** explicitly delete the remote head branch in Step 6 with
362
- `git push origin --delete [HEAD_BRANCH]`. Do **not** rely solely on the
363
- Step 5 merge call's `delete_branch` flag (whether passed via
364
- `mcp__github__merge_pull_request` or `gh pr merge --delete-branch`) — that
365
- flag is silently skipped when a PR auto-closes without a normal merge
366
- commit (e.g., duplicate rebase scenarios).
367
- - **Always** treat a "remote ref not found" error from the delete command as a
368
- non-fatal, idempotent success — the branch is already gone.
369
- - **Always** use `--force-with-lease` (never bare `--force`) when pushing
370
- rebased branches to avoid overwriting concurrent pushes.
371
- - **Always** explicitly close the GitHub PR via
372
- `mcp__github__update_pull_request` with `state: "closed"` in Step 6 after
373
- branch cleanup. Because this workflow pushes directly to the base branch,
374
- GitHub will **never** auto-close the PR — it must be closed manually
375
- every time.
376
- - **Always** post a Step 7 summary comment for auditability, even if no fixes
377
- were required.