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.
- package/.agents/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/ci-remediation.md +44 -1
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +49 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* module mechanizes it.
|
|
20
20
|
*
|
|
21
21
|
* Contract (pure where possible — the only side effect is an injectable
|
|
22
|
-
* `git log` read
|
|
22
|
+
* `git log` read):
|
|
23
23
|
*
|
|
24
24
|
* - If `storyTitle` is **already** a parseable Conventional Commit
|
|
25
25
|
* subject, it is preserved verbatim and suffixed with `(#<storyId>)`.
|
|
@@ -28,116 +28,48 @@
|
|
|
28
28
|
* `<type>: <descriptive text> (#<storyId>)`. The `type` is derived
|
|
29
29
|
* from the branch's own (already-conventional) commit subjects when
|
|
30
30
|
* available, falling back to a safe configured default (`chore`).
|
|
31
|
+
* - Either way, a branch (or Story) that declares a breaking change gets
|
|
32
|
+
* the `!` marker and a `BREAKING CHANGE:` footer on the PR body.
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
34
|
-
*
|
|
34
|
+
* The rules that decide *what* the subject says now live in
|
|
35
|
+
* `conventional-subject.js` — type precedence, acronym-safe casing, and
|
|
36
|
+
* breaking-change collection are pure and unit-tested there. What is left
|
|
37
|
+
* here is the git read those rules consume and the assembly of the two
|
|
38
|
+
* strings `gh pr create` needs.
|
|
35
39
|
*/
|
|
36
40
|
|
|
37
41
|
import { gitSpawn as defaultGitSpawn } from '../../../git-utils.js';
|
|
38
42
|
import { Logger as DefaultLogger } from '../../../Logger.js';
|
|
43
|
+
import {
|
|
44
|
+
collectBreakingNotes,
|
|
45
|
+
isConventionalSubject,
|
|
46
|
+
markBreaking,
|
|
47
|
+
pickDominantType,
|
|
48
|
+
shapeDescription,
|
|
49
|
+
} from './conventional-subject.js';
|
|
39
50
|
|
|
40
51
|
/** Safe default Conventional-Commit type when none can be derived. */
|
|
41
52
|
const DEFAULT_CONVENTIONAL_TYPE = 'chore';
|
|
42
53
|
|
|
43
54
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
55
|
+
* Record separator between whole commit messages in the `git log` read. A NUL
|
|
56
|
+
* cannot occur inside a commit message, so splitting on it is unambiguous —
|
|
57
|
+
* unlike a blank-line or subject-prefix heuristic, which a commit body can
|
|
58
|
+
* forge.
|
|
48
59
|
*/
|
|
49
|
-
const
|
|
50
|
-
'feat',
|
|
51
|
-
'fix',
|
|
52
|
-
'perf',
|
|
53
|
-
'refactor',
|
|
54
|
-
'revert',
|
|
55
|
-
'docs',
|
|
56
|
-
'style',
|
|
57
|
-
'chore',
|
|
58
|
-
'test',
|
|
59
|
-
'build',
|
|
60
|
-
'ci',
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
// Precedence used when a branch carries a mix of conventional types: pick
|
|
64
|
-
// the most release-significant one so the squash subject communicates the
|
|
65
|
-
// branch's headline impact (and release-please bumps appropriately).
|
|
66
|
-
const TYPE_PRECEDENCE = Object.freeze([
|
|
67
|
-
'feat',
|
|
68
|
-
'fix',
|
|
69
|
-
'perf',
|
|
70
|
-
'refactor',
|
|
71
|
-
'revert',
|
|
72
|
-
'docs',
|
|
73
|
-
'style',
|
|
74
|
-
'test',
|
|
75
|
-
'build',
|
|
76
|
-
'ci',
|
|
77
|
-
'chore',
|
|
78
|
-
]);
|
|
79
|
-
|
|
80
|
-
const TYPE_GROUP = CONVENTIONAL_TYPES.join('|');
|
|
81
|
-
// Anchored Conventional-Commit header matcher:
|
|
82
|
-
// <type>(<optional scope>)<optional !>: <non-empty description>
|
|
83
|
-
// Mirrors the shape `@commitlint/config-conventional` enforces (a known
|
|
84
|
-
// type, an optional parenthesised scope, an optional breaking `!`, a
|
|
85
|
-
// colon-space separator, and a non-empty subject). Used for the pure
|
|
86
|
-
// "is this already conventional?" check and to pull the type off a branch
|
|
87
|
-
// commit subject without spawning commitlint per call.
|
|
88
|
-
const CONVENTIONAL_HEADER_RE = new RegExp(
|
|
89
|
-
`^(?:${TYPE_GROUP})(?:\\([^()\\r\\n]+\\))?!?: \\S.*$`,
|
|
90
|
-
);
|
|
91
|
-
const LEADING_TYPE_RE = new RegExp(
|
|
92
|
-
`^(${TYPE_GROUP})(?:\\([^()\\r\\n]+\\))?!?:`,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* True iff `subject` is a parseable Conventional Commit subject under the
|
|
97
|
-
* repo's type vocabulary. Pure.
|
|
98
|
-
*
|
|
99
|
-
* @param {string} subject
|
|
100
|
-
* @returns {boolean}
|
|
101
|
-
*/
|
|
102
|
-
function isConventionalSubject(subject) {
|
|
103
|
-
if (typeof subject !== 'string') return false;
|
|
104
|
-
return CONVENTIONAL_HEADER_RE.test(subject.trim());
|
|
105
|
-
}
|
|
60
|
+
const RECORD_SEP = '\u0000';
|
|
106
61
|
|
|
107
62
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
63
|
+
* Read the branch's own commits (those unique to the Story branch relative to
|
|
64
|
+
* the base branch) as whole messages — subject AND body, because the body is
|
|
65
|
+
* where a `BREAKING CHANGE:` footer lives.
|
|
110
66
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
function parseConventionalType(subject) {
|
|
115
|
-
if (typeof subject !== 'string') return null;
|
|
116
|
-
const match = subject.trim().match(LEADING_TYPE_RE);
|
|
117
|
-
return match ? match[1] : null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Pick the most release-significant type from a list of conventional
|
|
122
|
-
* types, honouring `TYPE_PRECEDENCE`. Returns `null` for an empty list.
|
|
123
|
-
* Pure.
|
|
67
|
+
* Returns `[]` when the read fails, which degrades every downstream rule to
|
|
68
|
+
* its safe default (type `chore`, no breaking marker) rather than throwing a
|
|
69
|
+
* close that is otherwise healthy.
|
|
124
70
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*/
|
|
128
|
-
function pickDominantType(types) {
|
|
129
|
-
const present = new Set(types.filter(Boolean));
|
|
130
|
-
for (const candidate of TYPE_PRECEDENCE) {
|
|
131
|
-
if (present.has(candidate)) return candidate;
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Read the branch's own commit subjects (commits unique to the Story
|
|
138
|
-
* branch relative to the base branch) and derive the dominant
|
|
139
|
-
* Conventional-Commit type. Returns `DEFAULT_CONVENTIONAL_TYPE` when no
|
|
140
|
-
* conventional subject is found or the git read fails.
|
|
71
|
+
* Oldest-first (`--reverse`) is load-bearing: `pickDominantType` breaks a tie
|
|
72
|
+
* on the Story's primary commit, which is the first one authored.
|
|
141
73
|
*
|
|
142
74
|
* @param {{
|
|
143
75
|
* storyBranch: string,
|
|
@@ -146,96 +78,174 @@ function pickDominantType(types) {
|
|
|
146
78
|
* gitSpawn?: typeof defaultGitSpawn,
|
|
147
79
|
* logger?: { warn?: Function },
|
|
148
80
|
* }} args
|
|
149
|
-
* @returns {string}
|
|
81
|
+
* @returns {string[]} Whole commit messages, oldest first.
|
|
150
82
|
*/
|
|
151
|
-
function
|
|
83
|
+
function readBranchCommits({
|
|
152
84
|
storyBranch,
|
|
153
85
|
baseBranch,
|
|
154
86
|
cwd = process.cwd(),
|
|
155
87
|
gitSpawn = defaultGitSpawn,
|
|
156
88
|
logger = DefaultLogger,
|
|
157
89
|
}) {
|
|
90
|
+
if (!storyBranch || !baseBranch) return [];
|
|
91
|
+
const range = `${baseBranch}..${storyBranch}`;
|
|
158
92
|
try {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
93
|
+
const result = gitSpawn(
|
|
94
|
+
cwd,
|
|
95
|
+
'log',
|
|
96
|
+
'--no-merges',
|
|
97
|
+
'--reverse',
|
|
98
|
+
'--format=%B%x00',
|
|
99
|
+
range,
|
|
100
|
+
);
|
|
101
|
+
if (result?.status !== 0) {
|
|
162
102
|
logger?.warn?.(
|
|
163
103
|
`[normalize-pr-title] git log ${range} failed (status=${result?.status ?? 'n/a'}); ` +
|
|
164
|
-
`defaulting type to "${DEFAULT_CONVENTIONAL_TYPE}".`,
|
|
104
|
+
`defaulting type to "${DEFAULT_CONVENTIONAL_TYPE}" and assuming no breaking change.`,
|
|
165
105
|
);
|
|
166
|
-
return
|
|
106
|
+
return [];
|
|
167
107
|
}
|
|
168
|
-
|
|
169
|
-
.split(
|
|
170
|
-
.map((
|
|
171
|
-
.filter(
|
|
172
|
-
return pickDominantType(types) ?? DEFAULT_CONVENTIONAL_TYPE;
|
|
108
|
+
return String(result.stdout ?? '')
|
|
109
|
+
.split(RECORD_SEP)
|
|
110
|
+
.map((message) => message.trim())
|
|
111
|
+
.filter((message) => message.length > 0);
|
|
173
112
|
} catch (err) {
|
|
174
113
|
logger?.warn?.(
|
|
175
|
-
`[normalize-pr-title] could not
|
|
176
|
-
`(defaulting to "${DEFAULT_CONVENTIONAL_TYPE}"): ${err?.message ?? err}`,
|
|
114
|
+
`[normalize-pr-title] could not read branch commits ` +
|
|
115
|
+
`(defaulting to "${DEFAULT_CONVENTIONAL_TYPE}", no breaking change): ${err?.message ?? err}`,
|
|
177
116
|
);
|
|
178
|
-
return
|
|
117
|
+
return [];
|
|
179
118
|
}
|
|
180
119
|
}
|
|
181
120
|
|
|
182
121
|
/**
|
|
183
|
-
* Produce
|
|
122
|
+
* Produce the PR title and the breaking-change notes that belong with it.
|
|
184
123
|
*
|
|
185
124
|
* - Already-conventional `storyTitle` → preserved verbatim + `(#<id>)`.
|
|
186
|
-
* - Otherwise → `<derivedType>: <storyTitle> (#<id>)`.
|
|
187
|
-
* - Empty / missing `storyTitle` → `<derivedType>:
|
|
125
|
+
* - Otherwise → `<derivedType>: <shaped storyTitle> (#<id>)`.
|
|
126
|
+
* - Empty / missing `storyTitle` → `<derivedType>: story #<id>`.
|
|
127
|
+
* - Breaking → `!` inserted before the colon in either shape.
|
|
188
128
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
129
|
+
* `commitMessages` is the branch read (`readBranchCommits`); passing `[]`
|
|
130
|
+
* yields the safe default type and no breaking marker. `storyBody` is the
|
|
131
|
+
* Story issue's body, scanned for a declared `BREAKING CHANGE:` footer.
|
|
191
132
|
*
|
|
192
133
|
* @param {{
|
|
193
134
|
* storyTitle: string,
|
|
194
135
|
* storyId: number|string,
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* cwd?: string,
|
|
198
|
-
* gitSpawn?: typeof defaultGitSpawn,
|
|
199
|
-
* logger?: { warn?: Function },
|
|
136
|
+
* commitMessages?: string[],
|
|
137
|
+
* storyBody?: string,
|
|
200
138
|
* }} args
|
|
139
|
+
* @returns {{ title: string, breaking: boolean, breakingNotes: string[] }}
|
|
140
|
+
*/
|
|
141
|
+
function normalizePrTitle({
|
|
142
|
+
storyTitle,
|
|
143
|
+
storyId,
|
|
144
|
+
commitMessages = [],
|
|
145
|
+
storyBody = '',
|
|
146
|
+
}) {
|
|
147
|
+
const idSuffix = `(#${storyId})`;
|
|
148
|
+
const trimmed = typeof storyTitle === 'string' ? storyTitle.trim() : '';
|
|
149
|
+
const { breaking, notes } = collectBreakingNotes({
|
|
150
|
+
commitMessages,
|
|
151
|
+
storyBody,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Already conventional → preserve verbatim (the maker's own casing and
|
|
155
|
+
// scope survive), append the id reference. Only the breaking marker may be
|
|
156
|
+
// added, and only when it is not already there.
|
|
157
|
+
const subject = isConventionalSubject(trimmed)
|
|
158
|
+
? trimmed
|
|
159
|
+
: synthesizeSubject({ description: trimmed, storyId, commitMessages });
|
|
160
|
+
|
|
161
|
+
const marked = breaking ? markBreaking(subject) : subject;
|
|
162
|
+
return { title: `${marked} ${idSuffix}`, breaking, breakingNotes: notes };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build a conventional subject for a Story whose title is plain prose.
|
|
167
|
+
*
|
|
168
|
+
* @param {{ description: string, storyId: number|string, commitMessages: string[] }} args
|
|
169
|
+
* @returns {string}
|
|
170
|
+
*/
|
|
171
|
+
function synthesizeSubject({ description, storyId, commitMessages }) {
|
|
172
|
+
const subjects = commitMessages.map((message) => message.split('\n')[0]);
|
|
173
|
+
const type = pickDominantType(subjects) ?? DEFAULT_CONVENTIONAL_TYPE;
|
|
174
|
+
const raw = description.length > 0 ? description : `Story #${storyId}`;
|
|
175
|
+
return `${type}: ${shapeDescription(raw)}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Build the PR body.
|
|
180
|
+
*
|
|
181
|
+
* The `Closes #<id>` footer is what auto-closes the Story on merge. A
|
|
182
|
+
* `BREAKING CHANGE:` footer goes LAST, as the spec requires, so that a repo
|
|
183
|
+
* configured to use the PR body as the squash-commit message hands
|
|
184
|
+
* release-please a parseable note rather than prose. When the squash body is
|
|
185
|
+
* built from the constituent commit messages instead (GitHub's default, and
|
|
186
|
+
* this repo's setting), the note still reaches `main` through whichever
|
|
187
|
+
* commit carried the footer — and the `!` in the subject carries the signal
|
|
188
|
+
* either way.
|
|
189
|
+
*
|
|
190
|
+
* @param {{ storyId: number|string, breakingNotes?: string[] }} args
|
|
201
191
|
* @returns {string}
|
|
202
192
|
*/
|
|
203
|
-
|
|
193
|
+
function buildPrBody({ storyId, breakingNotes }) {
|
|
194
|
+
const lines = [`Closes #${storyId}`, '', '_Auto-opened by `/deliver`._'];
|
|
195
|
+
if (breakingNotes.length > 0) {
|
|
196
|
+
lines.push('', `BREAKING CHANGE: ${breakingNotes.join(' ')}`);
|
|
197
|
+
}
|
|
198
|
+
return lines.join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Derive the two strings `gh pr create` needs. One `git log` read serves both
|
|
203
|
+
* halves: the commit SUBJECTS decide the type, and the commit BODIES — plus
|
|
204
|
+
* the Story body — decide whether this is a breaking change.
|
|
205
|
+
*
|
|
206
|
+
* A declared break is announced on the progress channel, because a `!` the
|
|
207
|
+
* operator did not expect in the squash subject should be visible while the
|
|
208
|
+
* close is running rather than discovered in the release notes.
|
|
209
|
+
*
|
|
210
|
+
* @param {{ storyTitle: string, storyId: number|string, storyBody?: string,
|
|
211
|
+
* storyBranch: string, baseBranch: string, cwd?: string,
|
|
212
|
+
* gitSpawn?: typeof defaultGitSpawn,
|
|
213
|
+
* progress?: (tag: string, msg: string) => void }} args
|
|
214
|
+
* @returns {{ title: string, body: string, breaking: boolean, breakingNotes: string[] }}
|
|
215
|
+
*/
|
|
216
|
+
export function buildPullRequestFields({
|
|
204
217
|
storyTitle,
|
|
205
218
|
storyId,
|
|
219
|
+
storyBody = '',
|
|
206
220
|
storyBranch,
|
|
207
221
|
baseBranch,
|
|
208
222
|
cwd = process.cwd(),
|
|
209
223
|
gitSpawn = defaultGitSpawn,
|
|
210
|
-
|
|
224
|
+
progress = () => {},
|
|
211
225
|
}) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
226
|
+
const commitMessages = readBranchCommits({
|
|
227
|
+
storyBranch,
|
|
228
|
+
baseBranch,
|
|
229
|
+
cwd,
|
|
230
|
+
gitSpawn,
|
|
231
|
+
});
|
|
232
|
+
const { title, breaking, breakingNotes } = normalizePrTitle({
|
|
233
|
+
storyTitle,
|
|
234
|
+
storyId,
|
|
235
|
+
commitMessages,
|
|
236
|
+
storyBody,
|
|
237
|
+
});
|
|
238
|
+
if (breaking) {
|
|
239
|
+
progress(
|
|
240
|
+
'PR',
|
|
241
|
+
'⚠️ Breaking change declared — the PR title carries `!` and the body a ' +
|
|
242
|
+
`BREAKING CHANGE footer: ${breakingNotes.join(' ') || '(no note text)'}`,
|
|
243
|
+
);
|
|
218
244
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
baseBranch,
|
|
226
|
-
cwd,
|
|
227
|
-
gitSpawn,
|
|
228
|
-
logger,
|
|
229
|
-
})
|
|
230
|
-
: DEFAULT_CONVENTIONAL_TYPE;
|
|
231
|
-
|
|
232
|
-
// Lowercase the leading character of a synthesized description so the
|
|
233
|
-
// subject satisfies commitlint's `subject-case` rule (matching the
|
|
234
|
-
// `shapeMergeSubject` behaviour). An already-conventional title is left
|
|
235
|
-
// untouched (it was preserved verbatim above). The empty-title fallback
|
|
236
|
-
// uses a lowercased `story #<id>` for the same reason.
|
|
237
|
-
const rawDescription = trimmed.length > 0 ? trimmed : `Story #${storyId}`;
|
|
238
|
-
const description =
|
|
239
|
-
rawDescription.charAt(0).toLowerCase() + rawDescription.slice(1);
|
|
240
|
-
return `${type}: ${description} ${idSuffix}`;
|
|
245
|
+
return {
|
|
246
|
+
title,
|
|
247
|
+
body: buildPrBody({ storyId, breakingNotes }),
|
|
248
|
+
breaking,
|
|
249
|
+
breakingNotes,
|
|
250
|
+
};
|
|
241
251
|
}
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import path from 'node:path';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
parseMergeWatchMode,
|
|
14
|
+
parseOverrideReviewBlock,
|
|
15
|
+
parseSprintArgs,
|
|
16
|
+
} from '../../../cli-args.js';
|
|
13
17
|
import { getDeliveryRouting } from '../../../config/delivery-routing.js';
|
|
14
18
|
import { PROJECT_ROOT } from '../../../project-root.js';
|
|
15
19
|
import { isOperatorMergeReason } from './auto-merge.js';
|
|
@@ -91,8 +95,8 @@ export function resolveWaitForMerge({
|
|
|
91
95
|
* (`waitForMergeExplicit` / `noWaitForMerge`) for the runner to resolve once
|
|
92
96
|
* the config and the arm outcome exist.
|
|
93
97
|
*
|
|
94
|
-
* @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, waitForMergeParam, noWaitForMergeParam, maxWaitSecondsParam, mergeWatchModeParam }} raw
|
|
95
|
-
* @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, waitForMergeExplicit, noWaitForMerge, maxWaitSeconds, mergeWatchMode }}
|
|
98
|
+
* @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, waitForMergeParam, noWaitForMergeParam, maxWaitSecondsParam, mergeWatchModeParam, overrideReviewBlockParam }} raw
|
|
99
|
+
* @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, waitForMergeExplicit, noWaitForMerge, maxWaitSeconds, mergeWatchMode, overrideReviewBlock }}
|
|
96
100
|
*/
|
|
97
101
|
export function parseCloseOptions({
|
|
98
102
|
storyIdParam,
|
|
@@ -104,6 +108,7 @@ export function parseCloseOptions({
|
|
|
104
108
|
noWaitForMergeParam,
|
|
105
109
|
maxWaitSecondsParam,
|
|
106
110
|
mergeWatchModeParam,
|
|
111
|
+
overrideReviewBlockParam,
|
|
107
112
|
}) {
|
|
108
113
|
// An injecting caller (`storyIdParam` supplied) is not reading argv at all,
|
|
109
114
|
// so there is nothing to parse and `parsed` stays empty. This used to build a
|
|
@@ -148,5 +153,12 @@ export function parseCloseOptions({
|
|
|
148
153
|
? waitForMergeExplicit
|
|
149
154
|
: undefined,
|
|
150
155
|
noWaitForMerge: !!resolveFlag(noWaitForMergeParam, parsed.noWaitForMerge),
|
|
156
|
+
// `undefined` when unsupplied, which is what keeps a review
|
|
157
|
+
// critical blocker blocking. An injecting caller passes the reason string
|
|
158
|
+
// directly; both doors run the same validating parser, so neither can arm
|
|
159
|
+
// a reasonless override.
|
|
160
|
+
overrideReviewBlock: parseOverrideReviewBlock(
|
|
161
|
+
resolveFlag(overrideReviewBlockParam, parsed.overrideReviewBlock),
|
|
162
|
+
),
|
|
151
163
|
};
|
|
152
164
|
}
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
import { gh as defaultGh } from '../../../gh-exec.js';
|
|
45
45
|
import { Logger } from '../../../Logger.js';
|
|
46
46
|
import { computeChangeSet as defaultComputeChangeSet } from '../../change-set.js';
|
|
47
|
-
import {
|
|
47
|
+
import { buildPullRequestFields } from './normalize-pr-title.js';
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Pick the PR this head branch should resolve to from a `gh pr list
|
|
@@ -112,6 +112,7 @@ function probeEmptyDiff({ cwd, baseBranch, storyBranch, computeChangeSet }) {
|
|
|
112
112
|
* cwd: string,
|
|
113
113
|
* storyId: number,
|
|
114
114
|
* storyTitle: string,
|
|
115
|
+
* storyBody?: string,
|
|
115
116
|
* storyBranch: string,
|
|
116
117
|
* baseBranch: string,
|
|
117
118
|
* gh?: ReturnType<typeof import('../../../gh-exec.js').createGh>,
|
|
@@ -124,6 +125,7 @@ export async function ensurePullRequestWith({
|
|
|
124
125
|
cwd: _cwd,
|
|
125
126
|
storyId,
|
|
126
127
|
storyTitle,
|
|
128
|
+
storyBody = '',
|
|
127
129
|
storyBranch,
|
|
128
130
|
baseBranch,
|
|
129
131
|
gh = defaultGh,
|
|
@@ -185,26 +187,19 @@ export async function ensurePullRequestWith({
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
progress('PR', `Opening PR for ${storyBranch} → ${baseBranch}...`);
|
|
188
|
-
// The repo squash-merges and GitHub uses the PR title as the squash
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
// (
|
|
192
|
-
|
|
193
|
-
// type derived from the branch's own commit subjects (default `chore`).
|
|
194
|
-
// `gh-exec` spawns `gh` against the current process cwd (the worktree),
|
|
195
|
-
// so the branch-commit read uses the same cwd.
|
|
196
|
-
const title = normalizePrTitle({
|
|
190
|
+
// The repo squash-merges and GitHub uses the PR title as the squash subject
|
|
191
|
+
// on `main`, so both fields are derived rather than typed — see
|
|
192
|
+
// `normalize-pr-title.js`. `gh-exec` spawns `gh` against the current process
|
|
193
|
+
// cwd (the worktree), so the branch read uses the same cwd.
|
|
194
|
+
const { title, body } = buildPullRequestFields({
|
|
197
195
|
storyTitle,
|
|
198
196
|
storyId,
|
|
197
|
+
storyBody,
|
|
199
198
|
storyBranch,
|
|
200
199
|
baseBranch,
|
|
201
200
|
cwd: _cwd ?? process.cwd(),
|
|
201
|
+
progress,
|
|
202
202
|
});
|
|
203
|
-
const body = [
|
|
204
|
-
`Closes #${storyId}`,
|
|
205
|
-
'',
|
|
206
|
-
`_Auto-opened by \`/deliver\`._`,
|
|
207
|
-
].join('\n');
|
|
208
203
|
try {
|
|
209
204
|
const createResult = await gh.pr.create([
|
|
210
205
|
'--base',
|
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|
* added because it would re-ask a question the push exit code already
|
|
16
16
|
* answered authoritatively.
|
|
17
17
|
*
|
|
18
|
+
* Issue #4990 — the push runs in the Story worktree, not the main
|
|
19
|
+
* checkout. `core.hooksPath` is the relative `.husky/_`, so the
|
|
20
|
+
* invocation directory is what selects which tree `pre-push` resolves
|
|
21
|
+
* and measures. Pushing from the main checkout therefore validated a
|
|
22
|
+
* tree other than the one being sent — false red when that checkout sat
|
|
23
|
+
* on unrelated work, and worse, false green whenever it happened to be
|
|
24
|
+
* clean. `worktreePath` falls back to `cwd` because single-tree mode and
|
|
25
|
+
* a disabled `delivery.worktreeIsolation` both legitimately have no
|
|
26
|
+
* worktree, and there the main checkout IS the tree being pushed. The
|
|
27
|
+
* two sibling phases resolve their tree the same way
|
|
28
|
+
* (`base-sync.js`, `close-validation.js`).
|
|
29
|
+
*
|
|
18
30
|
* `gitSync` is accepted as an injected dependency rather than statically
|
|
19
31
|
* imported so the caller's (cache-busted) binding wins. The
|
|
20
32
|
* `single-story-close.js` orchestrator owns the static import; test
|
|
@@ -29,6 +41,7 @@ import { gitSync as defaultGitSync } from '../../../git-utils.js';
|
|
|
29
41
|
*
|
|
30
42
|
* @param {{
|
|
31
43
|
* cwd: string,
|
|
44
|
+
* worktreePath?: string|null,
|
|
32
45
|
* storyBranch: string,
|
|
33
46
|
* gitSync?: typeof defaultGitSync,
|
|
34
47
|
* progress: (tag: string, msg: string) => void,
|
|
@@ -36,6 +49,7 @@ import { gitSync as defaultGitSync } from '../../../git-utils.js';
|
|
|
36
49
|
*/
|
|
37
50
|
export function pushStoryBranch({
|
|
38
51
|
cwd,
|
|
52
|
+
worktreePath = null,
|
|
39
53
|
storyBranch,
|
|
40
54
|
gitSync = defaultGitSync,
|
|
41
55
|
progress,
|
|
@@ -46,8 +60,9 @@ export function pushStoryBranch({
|
|
|
46
60
|
// before this point, but `--skip-validation` skips that chain, and the
|
|
47
61
|
// bypass then left nothing running at all. `pre-push` is the backstop,
|
|
48
62
|
// and it only became reachable once hooks were materialized into
|
|
49
|
-
// worktrees — which is where every Story branch is built
|
|
50
|
-
|
|
63
|
+
// worktrees — which is where every Story branch is built, and where the
|
|
64
|
+
// cwd below now resolves so the hook reads that tree.
|
|
65
|
+
gitSync(worktreePath ?? cwd, 'push', '-u', 'origin', storyBranch);
|
|
51
66
|
progress('GIT', `✅ Pushed ${storyBranch}.`);
|
|
52
67
|
} catch (err) {
|
|
53
68
|
throw new Error(
|
|
@@ -20,6 +20,11 @@ export async function handleCriticalReviewBlock({
|
|
|
20
20
|
'',
|
|
21
21
|
`The Story-scope review reported **${criticalCount} critical blocker(s)** on ${prUrl}.`,
|
|
22
22
|
'Remediate the posted findings, then re-run `/deliver`.',
|
|
23
|
+
'',
|
|
24
|
+
'If you have reviewed a finding and judged it wrong, re-run close with',
|
|
25
|
+
'`--override-review-block "<reason>"` rather than merging by hand — see',
|
|
26
|
+
'`phases/review-override.js`. The override is recorded on the Story, on the',
|
|
27
|
+
'PR, and as friction telemetry.',
|
|
23
28
|
].join('\n');
|
|
24
29
|
try {
|
|
25
30
|
await upsertStructuredComment(provider, storyId, 'friction', body);
|