yadflow 3.19.0-next.2 → 4.0.0-next.3
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/CHANGELOG.md +371 -0
- package/README.md +160 -22
- package/bin/commands.mjs +46 -0
- package/bin/yad.mjs +506 -117
- package/cli/artifact-status.mjs +30 -11
- package/cli/capture.mjs +417 -0
- package/cli/checkpoint.mjs +47 -27
- package/cli/claims.mjs +165 -0
- package/cli/codeowners-command.mjs +170 -0
- package/cli/codeowners.mjs +397 -0
- package/cli/commit.mjs +13 -9
- package/cli/dial.mjs +183 -0
- package/cli/docs.mjs +88 -32
- package/cli/doctor.mjs +1682 -90
- package/cli/epic-state.mjs +3460 -211
- package/cli/epic.mjs +506 -0
- package/cli/errors.mjs +3 -0
- package/cli/fold.mjs +247 -0
- package/cli/gate-local.mjs +296 -0
- package/cli/gate.mjs +977 -183
- package/cli/history.mjs +556 -0
- package/cli/hook.mjs +264 -53
- package/cli/hubcommit.mjs +9 -19
- package/cli/index-command.mjs +87 -0
- package/cli/ledger.mjs +54 -4
- package/cli/lib.mjs +161 -9
- package/cli/manifest.mjs +376 -38
- package/cli/migrate.mjs +683 -52
- package/cli/mode.mjs +170 -0
- package/cli/next.mjs +365 -86
- package/cli/openpr.mjs +193 -34
- package/cli/owners.mjs +266 -0
- package/cli/people.mjs +657 -0
- package/cli/plan.mjs +466 -125
- package/cli/platform.mjs +109 -128
- package/cli/product-index.mjs +287 -0
- package/cli/protection.mjs +706 -0
- package/cli/reconcile.mjs +40 -12
- package/cli/repo-publish.mjs +24 -26
- package/cli/repo.mjs +23 -14
- package/cli/report.mjs +24 -14
- package/cli/review.mjs +19 -22
- package/cli/riskmap-command.mjs +289 -0
- package/cli/riskmap.mjs +373 -0
- package/cli/setup.mjs +127 -279
- package/cli/ship.mjs +6 -5
- package/cli/skill.mjs +180 -0
- package/cli/skip.mjs +211 -30
- package/cli/thread.mjs +41 -16
- package/cli/tidy.mjs +18 -18
- package/cli/update-commit.mjs +21 -21
- package/cli/usage.mjs +115 -109
- package/package.json +3 -3
- package/skills/sdlc/config.yaml +149 -70
- package/skills/sdlc/module-help.csv +24 -24
- package/skills/yad-analysis/SKILL.md +121 -59
- package/skills/yad-architecture/SKILL.md +30 -18
- package/skills/yad-architecture/references/contract-format.md +8 -5
- package/skills/yad-backfill/SKILL.md +10 -4
- package/skills/yad-backfill/references/backfill.md +8 -1
- package/skills/yad-backfill/templates/checks/backfill-check.sh +10 -2
- package/skills/yad-change/SKILL.md +119 -42
- package/skills/yad-change/references/triage.md +42 -27
- package/skills/yad-checks/SKILL.md +68 -35
- package/skills/yad-checks/references/check-gates.md +310 -85
- package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
- package/skills/yad-checks/templates/checks/contract-check.sh +130 -9
- package/skills/yad-checks/templates/checks/epic-open.sh +2 -2
- package/skills/yad-checks/templates/checks/ledger-guard.sh +55 -8
- package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +1 -1
- package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
- package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
- package/skills/yad-checks/templates/github/yad-checks.yml +13 -2
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
- package/skills/yad-checks/templates/github/yad-update-guard.yml +7 -5
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +10 -1
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +3 -3
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +37 -6
- package/skills/yad-checks/templates/hooks/yad-capture.sh +53 -0
- package/skills/yad-commit/SKILL.md +4 -4
- package/skills/yad-connect-design/SKILL.md +5 -5
- package/skills/yad-connect-design/references/design-context.md +1 -1
- package/skills/yad-connect-design/references/design-registry.md +2 -2
- package/skills/yad-connect-docs/SKILL.md +11 -11
- package/skills/yad-connect-docs/references/docs-registry.md +1 -1
- package/skills/yad-connect-learning/SKILL.md +5 -5
- package/skills/yad-connect-learning/references/learning-registry.md +2 -2
- package/skills/yad-connect-repos/SKILL.md +66 -45
- package/skills/yad-connect-repos/references/code-context.md +4 -4
- package/skills/yad-connect-repos/references/hub-config.md +47 -51
- package/skills/yad-connect-repos/references/repos-registry.md +8 -7
- package/skills/yad-connect-repos/references/risk-map.md +81 -0
- package/skills/yad-connect-testing/SKILL.md +5 -5
- package/skills/yad-connect-testing/references/testing-context.md +3 -4
- package/skills/yad-connect-testing/references/testing-registry.md +2 -2
- package/skills/yad-defects/SKILL.md +7 -7
- package/skills/yad-discovery/SKILL.md +131 -94
- package/skills/yad-discovery/references/discovery-schema.md +22 -6
- package/skills/yad-discovery/references/foundation-schema.md +374 -0
- package/skills/yad-docs/SKILL.md +13 -8
- package/skills/yad-docs/references/data-mapping.md +9 -7
- package/skills/yad-docs/templates/app/package-lock.json +3 -3
- package/skills/yad-docs-overview/SKILL.md +30 -15
- package/skills/yad-docs-overview/references/pipeline-model.md +33 -20
- package/skills/yad-docs-sync/SKILL.md +10 -5
- package/skills/yad-docs-sync/references/staleness.md +8 -7
- package/skills/yad-engineer-review/SKILL.md +81 -17
- package/skills/yad-engineer-review/references/ship-and-record.md +17 -8
- package/skills/yad-epic/SKILL.md +173 -92
- package/skills/yad-epic/references/state-schema.md +610 -94
- package/skills/yad-hub-bridge/SKILL.md +57 -38
- package/skills/yad-hub-bridge/references/bridge.md +94 -67
- package/skills/yad-hub-bridge/references/login-roster.md +163 -70
- package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
- package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
- package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +21 -11
- package/skills/yad-implement/SKILL.md +27 -13
- package/skills/yad-implement/references/implement-conventions.md +2 -2
- package/skills/yad-learn/SKILL.md +9 -9
- package/skills/yad-learn/references/learning-state.md +2 -2
- package/skills/yad-open-pr/SKILL.md +61 -26
- package/skills/yad-pair-review/SKILL.md +7 -5
- package/skills/yad-pair-review/references/session-state.md +1 -1
- package/skills/yad-pr-template/SKILL.md +47 -26
- package/skills/yad-pr-template/references/risk-routing.md +97 -24
- package/skills/yad-pr-template/templates/checks/pr-template.sh +34 -11
- package/skills/yad-pr-template/templates/checks/pr-title.sh +32 -9
- package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
- package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
- package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
- package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +14 -13
- package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +14 -12
- package/skills/yad-reconcile/SKILL.md +3 -3
- package/skills/yad-report/SKILL.md +5 -5
- package/skills/yad-review-companion/SKILL.md +6 -3
- package/skills/yad-review-gate/SKILL.md +203 -93
- package/skills/yad-review-gate/references/gating.md +232 -54
- package/skills/yad-run/SKILL.md +81 -51
- package/skills/yad-run/references/run-loop.md +61 -39
- package/skills/yad-ship/SKILL.md +15 -11
- package/skills/yad-spec/SKILL.md +21 -6
- package/skills/yad-spec/references/spec-handoff.md +17 -5
- package/skills/yad-status/SKILL.md +115 -50
- package/skills/yad-stories/SKILL.md +37 -21
- package/skills/yad-stories/references/story-schema.md +7 -6
- package/skills/yad-stub/SKILL.md +58 -47
- package/skills/yad-sync-repos/SKILL.md +2 -2
- package/skills/yad-test-cases/SKILL.md +32 -23
- package/skills/yad-test-cases/references/test-cases-schema.md +7 -4
- package/skills/yad-timeline/SKILL.md +8 -7
- package/skills/yad-ui/SKILL.md +41 -18
- package/cli/roster.mjs +0 -164
- package/skills/sdlc/install.sh +0 -68
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Impact & Risk block and review routing
|
|
2
2
|
|
|
3
3
|
The PR/MR template (Phase 3 build plan §D) carries an **Impact & Risk** block so every change states
|
|
4
|
-
its blast radius before review, and so high-risk
|
|
5
|
-
|
|
4
|
+
its blast radius before review, and so a high-risk change shows how many approvers it asks for —
|
|
5
|
+
the same count `yad-review-gate` prints on the Shape gates.
|
|
6
6
|
|
|
7
7
|
## The Impact & Risk block
|
|
8
8
|
|
|
@@ -14,41 +14,114 @@ reusing the escalation `yad-review-gate` already applies on the Shape gates.
|
|
|
14
14
|
- **Rollback plan:** <how to revert>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
- **Domains / repos touched** — the domains this change affects
|
|
18
|
-
|
|
17
|
+
- **Domains / repos touched** — the domains this change affects. When the count is raised,
|
|
18
|
+
`risk-route.sh` lists them as a hint for whom to ask. yadflow keeps no list of people, so no names come from it.
|
|
19
19
|
- **Contract surface touched** — `yes` means the diff changes the shared contract surface. That path is
|
|
20
|
-
governed by `contract-check` (needs `Contract-Change: yes` + a re-locked contract) AND it
|
|
21
|
-
|
|
20
|
+
governed by `contract-check` (needs `Contract-Change: yes` + a re-locked contract) AND it raises the
|
|
21
|
+
approver count (contract is a `yad-review-gate` risk tag).
|
|
22
22
|
- **Risk level** — `low | medium | high`. The author's assessment of blast radius.
|
|
23
23
|
- **Rollback plan** — how to revert safely.
|
|
24
24
|
|
|
25
|
-
## Routing rule (reuses the gate's
|
|
25
|
+
## Routing rule (reuses the gate's count)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|-----------|--------------------|
|
|
29
|
-
| `low` / `medium`, no sensitive surface | **base rule:** owner + 1 reviewer |
|
|
30
|
-
| `high`, OR a touched contract/auth/payments surface | base rule **plus one domain-owner approval per touched domain** |
|
|
27
|
+
The count is `base 1 + risk step`:
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
| Condition | Risk step | Approvers |
|
|
30
|
+
|-----------|-----------|-----------|
|
|
31
|
+
| `low` / `medium`, no contract surface | +0 | 1 (the base) |
|
|
32
|
+
| `high` | +1 | 2 |
|
|
33
|
+
| contract surface touched | +2 | 3 |
|
|
34
|
+
|
|
35
|
+
`high` risk and a touched contract surface together take the **larger** step (contract, +2), never the
|
|
36
|
+
sum. Approvers are counted as distinct people, and the base approver should not be the author.
|
|
37
|
+
|
|
38
|
+
**The count is reported, not enforced, on a code PR.** Branch protection holds the merge — at least 1
|
|
39
|
+
approval (GitHub blocks self-approval; GitLab only if its settings do). The risk step is **advisory**
|
|
40
|
+
here. The capacity cap (roadmap E72) caps a count at the active people less one. It is reported, not
|
|
41
|
+
enforced, on the Shape gates for now, and on the Build half it stays reported even once the Shape cap
|
|
42
|
+
is enforced: `yad open-pr` prints the count capped by the active people, while `risk-route.sh` cannot cap, because a code repo's CI has no Product to count people
|
|
43
|
+
from. There are no roles: no owner, no reviewer, no
|
|
44
|
+
domain owner. This is `yad-review-gate`'s rule (`references/gating.md`) applied at the code-review
|
|
45
|
+
boundary. The **approvals are recorded by the engineer review (Step E) through `yad-review-gate`** — the
|
|
46
|
+
template and `risk-route.sh` only *route* (advisory); they never approve or merge.
|
|
47
|
+
|
|
48
|
+
**The risk map counts too (E66).** A code repo's `.sdlc/risk-map` (E65) gives each directory a level —
|
|
49
|
+
`high`, `medium` or `low`. A change that touches a `high` directory adds the high step (+1), the same
|
|
50
|
+
step a body saying `Risk level: high` adds. The rules:
|
|
51
|
+
|
|
52
|
+
- The map is read from the **base branch**, never the PR's own copy, so a PR cannot lower its count by
|
|
53
|
+
editing or deleting the map.
|
|
54
|
+
- The **larger** step wins. A body saying `low` on a change to a `high` directory still counts +1, and
|
|
55
|
+
the output says the two disagree. A body saying `high` still counts +1 when the map says `low`.
|
|
56
|
+
- A `guessed` level counts as a `confirmed` one does: a level only raises the count, never lowers it.
|
|
57
|
+
- `medium` is printed and adds nothing. An `unset` line, or a directory no line covers, adds nothing.
|
|
58
|
+
- A deleted or moved file counts where it was.
|
|
59
|
+
- If the map cannot be read (no base, a shallow clone, a newer map version, or a `checks/risk-map-check.sh`
|
|
60
|
+
older than this count, which `yad update` refreshes), the output says so and counts the body alone. It
|
|
61
|
+
never guesses a level.
|
|
37
62
|
|
|
38
63
|
## risk-route.sh
|
|
39
64
|
|
|
40
|
-
`bash checks/risk-route.sh <pr-description-file
|
|
41
|
-
|
|
65
|
+
`bash checks/risk-route.sh <pr-description-file> [<base>]` parses the Impact & Risk block, asks
|
|
66
|
+
`checks/risk-map-check.sh --level` for the level the change takes from the base branch's risk map, and
|
|
67
|
+
prints the approver count. **Run it inside the code repo, on the PR's branch.** On the base branch itself
|
|
68
|
+
the change is empty, and it says so. `<base>` defaults the way the checks resolve it (the configured
|
|
69
|
+
`default_branch`, then `origin/HEAD`, then `origin/main`). Example (high risk, two domains):
|
|
42
70
|
|
|
43
71
|
```
|
|
44
72
|
Risk level: high
|
|
45
73
|
Contract surface touched: no
|
|
46
74
|
Domains touched: backend, mobile
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
75
|
+
Risk map (origin/main): low — nothing this change touches is high
|
|
76
|
+
ROUTE: 2 approvers = base 1 + high risk 1 (risk: high)
|
|
77
|
+
Branch protection (when configured) holds the merge, not this count: at least 1 approval (GitHub blocks self-approval; GitLab only if its settings do).
|
|
78
|
+
The risk step is advisory, and not capped here — `yad open-pr` shows the count capped by the active people.
|
|
79
|
+
Ask reviewers who know the touched domains:
|
|
80
|
+
- backend
|
|
81
|
+
- mobile
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
With a touched contract surface the line reads `ROUTE: 3 approvers = base 1 + contract risk 2 (contract
|
|
85
|
+
surface touched)`. With neither it reads `ROUTE: 1 approver = base 1 (no risk step).` When the map raises
|
|
86
|
+
a body that says `low`:
|
|
87
|
+
|
|
51
88
|
```
|
|
89
|
+
Risk map (origin/main): high — src/payments/ (guessed)
|
|
90
|
+
worked there in the last 30 days: Alice (@alice)
|
|
91
|
+
ROUTE: 2 approvers = base 1 + high risk 1 (high on the risk map: src/payments/ (guessed))
|
|
92
|
+
The body says Risk level: low, but the risk map on origin/main marks src/payments/ (guessed) high — the larger counts.
|
|
93
|
+
Branch protection (when configured) holds the merge, not this count: at least 1 approval (GitHub blocks self-approval; GitLab only if its settings do).
|
|
94
|
+
The risk step is advisory, and not capped here — `yad open-pr` shows the count capped by the active people.
|
|
95
|
+
This change touches src/payments/ (guessed), so it asks for an approval from someone who has
|
|
96
|
+
committed there in the last 30 days (its own authors left out):
|
|
97
|
+
- Alice (@alice)
|
|
98
|
+
Ask reviewers who know the touched domains:
|
|
99
|
+
(Domains line not filled in — list each touched domain so the right reviewers can be asked.)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Proven history (E67).** When a `high` directory is touched, the script also names the people who have
|
|
103
|
+
committed there in the **last 30 days**, read from the base branch's history, with this change's own
|
|
104
|
+
authors left out (an approval has to come from someone else). A robot is never listed. A person is shown
|
|
105
|
+
as their git name, plus `(@login)` only when their commit address is a platform `noreply` one — yadflow
|
|
106
|
+
prints no e-mail addresses. A name holding an `@` is shown as the bare login (`- @carol`), else as `a name
|
|
107
|
+
that is an e-mail address` or `a name written like a login`, so a printed `@word` is always a real login. "Recent" is git's `--since`, which uses the **committer** date, so a rebased
|
|
108
|
+
or squashed commit counts from when it landed. Work in a `low` directory *inside* a `high` one is not
|
|
109
|
+
history for the `high` one: the deepest map line decides, and git applies that itself — one query per
|
|
110
|
+
directory, excluding the listed directories below it, so no file name is ever read back out of git.
|
|
111
|
+
|
|
112
|
+
When a change touches more than one `high` directory, the people are printed as ONE list: someone who
|
|
113
|
+
has worked in any of them meets the ask, and the output does not say who worked where.
|
|
114
|
+
|
|
115
|
+
Two answers are not a list of people, and both are said plainly:
|
|
116
|
+
|
|
117
|
+
- `nobody else has committed there in the last 30 days` — the count stands on its own.
|
|
118
|
+
- `who has worked there lately: not read — …` — a shallow clone, or git could not read the history. The
|
|
119
|
+
ask is unknown, never "nobody".
|
|
120
|
+
|
|
121
|
+
The CI `risk-map` job prints the map's part on every PR as a `COUNT [risk-map]:` line. It does not read
|
|
122
|
+
the body, so `risk-route.sh` is where the two meet. `yad open-pr` prints the same joined count once the
|
|
123
|
+
PR is open, and leaves the body's level as the author wrote it.
|
|
52
124
|
|
|
53
|
-
It is **advisory** — not a blocking gate
|
|
54
|
-
review (Step E)
|
|
125
|
+
It is **advisory** — not a blocking gate, and not a CI job (the user's decision, 2026-09-18): the CI
|
|
126
|
+
`risk-map` job prints the map's half, and the human review (Step E), which runs this script, owns the
|
|
127
|
+
merge. No reviewers are requested automatically: ask them on the PR/MR itself.
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
# requires `## Summary`, `## Impact & Risk`, `## Checklist`, and a filled `Risk level:` (low|medium|high).
|
|
8
8
|
# --profile hub — the Shape artifact-review template (templates/hub/<platform>/):
|
|
9
9
|
# requires `## Artifact under review`, `## Impact & Risk (front-half)` (or `(Shape)`), `## Checklist`, and a `Risk tags:` line.
|
|
10
|
-
# BUT only for review/EP-* head branches. Every other
|
|
10
|
+
# BUT only for review/EP-* head branches. Every other Product PR is a tooling/code change to the Product
|
|
11
11
|
# itself and uses the code task template instead; pass the head ref via --head so the gate knows
|
|
12
|
-
# which template to require. With no --head, the
|
|
12
|
+
# which template to require. With no --head, the Product profile stays strict (artifact-review template).
|
|
13
13
|
# Branch name is not enough on its own: a non-review head that actually changes Shape
|
|
14
14
|
# artifacts (epics/**) would otherwise slip past the review workflow with only the code template.
|
|
15
15
|
# Pass the PR's changed paths via --changed <file> (one path per line); when they touch epics/**
|
|
16
16
|
# on a non-review head the gate FAILS — artifact changes must go through a review/EP-* PR.
|
|
17
|
+
# Build the list with `git -c core.quotePath=false diff --no-renames --name-only`: without
|
|
18
|
+
# --no-renames a rename lists only its new path, and the removed artifact is never seen.
|
|
17
19
|
# The body is passed as a FILE path (single positional arg); CI writes the event body to a temp file
|
|
18
20
|
# (GitHub: github.event.pull_request.body; GitLab: $CI_MERGE_REQUEST_DESCRIPTION).
|
|
19
21
|
set -euo pipefail
|
|
@@ -33,11 +35,32 @@ while [ $# -gt 0 ]; do
|
|
|
33
35
|
*) ARGS+=("$1"); shift ;;
|
|
34
36
|
esac
|
|
35
37
|
done
|
|
36
|
-
case "$PROFILE" in code|hub) ;; *) echo "FAIL [pr-template]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
|
|
38
|
+
case "$PROFILE" in code|hub|product) ;; *) echo "FAIL [pr-template]: unknown --profile '$PROFILE' (code|hub|product)."; exit 1 ;; esac
|
|
39
|
+
# `product` is the new name for the `hub` profile and BOTH are accepted.
|
|
40
|
+
#
|
|
41
|
+
# Not because the two sides update separately — they do not: this script and the workflow that passes
|
|
42
|
+
# the flag are both in PRODUCT_WIRING (cli/manifest.mjs) and land together on one `yad update`. It is
|
|
43
|
+
# the plain add-before-remove ladder instead: accept the new spelling now, switch the workflow
|
|
44
|
+
# templates to emit it in a later release, drop the old one after that. Every shipped template still
|
|
45
|
+
# passes `--profile hub` today, so this arm is dead weight until that switch — which is the point.
|
|
46
|
+
#
|
|
47
|
+
# Normalised to `hub` immediately, so nothing below has to know there are two spellings. That is
|
|
48
|
+
# load-bearing in pr-title.sh and pr-template.sh: leave `$PROFILE` as `product` and the `= hub`
|
|
49
|
+
# branch is skipped, taking the whole review-branch arm with it — including the guard that stops a
|
|
50
|
+
# plain code title carrying an artifact change past its review.
|
|
51
|
+
[ "$PROFILE" = product ] && PROFILE=hub
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
|
|
54
|
+
# True when the PR changes a Shape artifact (anything under epics/** — or foundation/**, the Product level, E75). Reads the --changed list
|
|
39
55
|
# of paths CI computed from the PR diff; with no list (direct caller / test) it reports false.
|
|
40
|
-
|
|
56
|
+
# A step owner file (`yad assign`, E47) is not an artifact: an assignment is advice, reviewed by nobody, and
|
|
57
|
+
# a review/EP-* PR would advance the step on merge. So a PR of owner files alone is not an artifact change.
|
|
58
|
+
# No `-q` on the second grep: under pipefail an early exit would SIGPIPE the first and read as "no artifact".
|
|
59
|
+
# `"?`: git still C-quotes a path holding `"`, `\`, a tab or a newline, even with core.quotePath=false. Such a
|
|
60
|
+
# line counts as an artifact, and the owner exemption never matches it — so it fails closed.
|
|
61
|
+
# `LC_ALL=C grep -a` on both: with quotePath off a path's high bytes arrive raw, and GNU grep under a UTF-8
|
|
62
|
+
# locale drops a line that is not valid UTF-8 from its output — the pipe would then read as "no artifact".
|
|
63
|
+
artifact_changed() { [ -n "$CHANGED" ] && [ -f "$CHANGED" ] && LC_ALL=C grep -aE '^"?(epics|foundation)/' "$CHANGED" | LC_ALL=C grep -avE '^(epics/EP-[^/]+|foundation)/\.sdlc/owners/[^/]+\.json$' >/dev/null; }
|
|
41
64
|
|
|
42
65
|
BODY="${ARGS[0]:-}"
|
|
43
66
|
if [ -z "$BODY" ] || [ ! -f "$BODY" ]; then
|
|
@@ -75,7 +98,7 @@ require_heading() {
|
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
# The code task template: `## Summary`, `## Impact & Risk`, `## Checklist`, and a filled `Risk level:`.
|
|
78
|
-
# Used by the code profile and by
|
|
101
|
+
# Used by the code profile and by Product tooling PRs (any head branch that is not review/EP-*).
|
|
79
102
|
check_code_body() {
|
|
80
103
|
require_heading '## Summary' '## Summary'
|
|
81
104
|
require_heading '## Impact & Risk' '## Impact & Risk'
|
|
@@ -95,11 +118,11 @@ check_code_body() {
|
|
|
95
118
|
check_hub_body() {
|
|
96
119
|
require_heading '## Artifact under review' '## Artifact under review'
|
|
97
120
|
# Add before you remove (change-safety rule 3). `front-half` is the ORIGINAL spelling and it is
|
|
98
|
-
# still what `yad gate open` emits and what the shipped
|
|
121
|
+
# still what `yad gate open` emits and what the shipped Product template carries; `Shape` is the
|
|
99
122
|
# replacement, accepted here first so that this script is lenient BEFORE anything starts writing
|
|
100
123
|
# the new wording. That ordering is the whole point: this file is refreshed by `yad update`
|
|
101
|
-
# (
|
|
102
|
-
# the `yad-pr-template repo:hub action: wire` skill, which nothing runs automatically. So a
|
|
124
|
+
# (PRODUCT_WIRING, cli/manifest.mjs), but the PR template beside it is NOT — it is installed only by
|
|
125
|
+
# the `yad-pr-template repo:hub action: wire` skill, which nothing runs automatically. So a Product
|
|
103
126
|
# WILL sit with a refreshed checker next to an old template, and the checker has to accept both.
|
|
104
127
|
# The emitter flips to `(Shape)` in the next major; only after that may `front-half` be dropped.
|
|
105
128
|
require_heading '## Impact & Risk \((front-half|Shape)\)' '## Impact & Risk (front-half)'
|
|
@@ -115,11 +138,11 @@ if [ "$PROFILE" = hub ]; then
|
|
|
115
138
|
case "$HEADREF" in
|
|
116
139
|
review/EP-*|"") check_hub_body ;; # artifact-review PR (or unknown head — stay strict)
|
|
117
140
|
*)
|
|
118
|
-
# tooling/code change to the
|
|
141
|
+
# tooling/code change to the Product itself — UNLESS it changes Shape artifacts (epics/**),
|
|
119
142
|
# which must go through a review/EP-* PR. Without this guard a non-review head could carry an
|
|
120
143
|
# artifact change past the Shape review with only the code template.
|
|
121
144
|
if artifact_changed; then
|
|
122
|
-
echo "FAIL [pr-template]: head '${HEADREF}' changes Shape artifacts (epics/**) but is not a review/EP-* branch — artifact changes must go through a review PR."
|
|
145
|
+
echo "FAIL [pr-template]: head '${HEADREF}' changes Shape artifacts (epics/** or foundation/**) but is not a review/EP-* branch — artifact changes must go through a review PR."
|
|
123
146
|
rc=1
|
|
124
147
|
else
|
|
125
148
|
check_code_body; KIND="hub-tooling"
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
# the squash-merge subject). Keep <type> in sync with cli/manifest.mjs COMMIT_TYPES.
|
|
7
7
|
# --profile hub — a Shape artifact-review title "review: <artifact> (EP-<slug>)", the shape
|
|
8
8
|
# `yad gate open` creates (cli/gate.mjs) — BUT only for review/EP-* head branches. Every other
|
|
9
|
-
#
|
|
9
|
+
# Product PR is a tooling/code change to the Product itself and follows the code convention; pass the
|
|
10
10
|
# head ref via --head so the gate can tell the two apart (a tooling PR has no EP artifact to
|
|
11
|
-
# review). With no --head, the
|
|
11
|
+
# review). With no --head, the Product profile stays strict (requires the review shape).
|
|
12
12
|
# Branch name is not enough on its own: a non-review head that actually changes Shape
|
|
13
13
|
# artifacts (epics/**) would otherwise slip past the review workflow with a plain code title.
|
|
14
14
|
# Pass the PR's changed paths via --changed <file> (one path per line); when they touch epics/**
|
|
15
15
|
# on a non-review head the gate FAILS — artifact changes must go through a review/EP-* PR.
|
|
16
|
+
# Build the list with `git -c core.quotePath=false diff --no-renames --name-only`: without
|
|
17
|
+
# --no-renames a rename lists only its new path, and the removed artifact is never seen.
|
|
16
18
|
# The title is passed as the (single) positional arg; CI injects it from the event payload
|
|
17
19
|
# (GitHub: github.event.pull_request.title; GitLab: $CI_MERGE_REQUEST_TITLE).
|
|
18
20
|
set -euo pipefail
|
|
@@ -32,11 +34,32 @@ while [ $# -gt 0 ]; do
|
|
|
32
34
|
*) ARGS+=("$1"); shift ;;
|
|
33
35
|
esac
|
|
34
36
|
done
|
|
35
|
-
case "$PROFILE" in code|hub) ;; *) echo "FAIL [pr-title]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
|
|
37
|
+
case "$PROFILE" in code|hub|product) ;; *) echo "FAIL [pr-title]: unknown --profile '$PROFILE' (code|hub|product)."; exit 1 ;; esac
|
|
38
|
+
# `product` is the new name for the `hub` profile and BOTH are accepted.
|
|
39
|
+
#
|
|
40
|
+
# Not because the two sides update separately — they do not: this script and the workflow that passes
|
|
41
|
+
# the flag are both in PRODUCT_WIRING (cli/manifest.mjs) and land together on one `yad update`. It is
|
|
42
|
+
# the plain add-before-remove ladder instead: accept the new spelling now, switch the workflow
|
|
43
|
+
# templates to emit it in a later release, drop the old one after that. Every shipped template still
|
|
44
|
+
# passes `--profile hub` today, so this arm is dead weight until that switch — which is the point.
|
|
45
|
+
#
|
|
46
|
+
# Normalised to `hub` immediately, so nothing below has to know there are two spellings. That is
|
|
47
|
+
# load-bearing in pr-title.sh and pr-template.sh: leave `$PROFILE` as `product` and the `= hub`
|
|
48
|
+
# branch is skipped, taking the whole review-branch arm with it — including the guard that stops a
|
|
49
|
+
# plain code title carrying an artifact change past its review.
|
|
50
|
+
[ "$PROFILE" = product ] && PROFILE=hub
|
|
36
51
|
|
|
37
|
-
|
|
52
|
+
|
|
53
|
+
# True when the PR changes a Shape artifact (anything under epics/** — or foundation/**, the Product level, E75). Reads the --changed list
|
|
38
54
|
# of paths CI computed from the PR diff; with no list (direct caller / test) it reports false.
|
|
39
|
-
|
|
55
|
+
# A step owner file (`yad assign`, E47) is not an artifact: an assignment is advice, reviewed by nobody, and
|
|
56
|
+
# a review/EP-* PR would advance the step on merge. So a PR of owner files alone is not an artifact change.
|
|
57
|
+
# No `-q` on the second grep: under pipefail an early exit would SIGPIPE the first and read as "no artifact".
|
|
58
|
+
# `"?`: git still C-quotes a path holding `"`, `\`, a tab or a newline, even with core.quotePath=false. Such a
|
|
59
|
+
# line counts as an artifact, and the owner exemption never matches it — so it fails closed.
|
|
60
|
+
# `LC_ALL=C grep -a` on both: with quotePath off a path's high bytes arrive raw, and GNU grep under a UTF-8
|
|
61
|
+
# locale drops a line that is not valid UTF-8 from its output — the pipe would then read as "no artifact".
|
|
62
|
+
artifact_changed() { [ -n "$CHANGED" ] && [ -f "$CHANGED" ] && LC_ALL=C grep -aE '^"?(epics|foundation)/' "$CHANGED" | LC_ALL=C grep -avE '^(epics/EP-[^/]+|foundation)/\.sdlc/owners/[^/]+\.json$' >/dev/null; }
|
|
40
63
|
|
|
41
64
|
TITLE="${ARGS[0]:-}"
|
|
42
65
|
if [ -z "$TITLE" ]; then
|
|
@@ -47,7 +70,7 @@ fi
|
|
|
47
70
|
TYPES='feat|fix|docs|refactor|test|perf|build|ci|chore|revert'
|
|
48
71
|
|
|
49
72
|
# Conventional-Commits subject (optional scope + breaking `!`), no trailing period. Used by the code
|
|
50
|
-
# profile and by
|
|
73
|
+
# profile and by Product tooling PRs (any head branch that is not review/EP-*).
|
|
51
74
|
check_code_title() {
|
|
52
75
|
if ! printf '%s' "$TITLE" | grep -qE "^(${TYPES})(\([a-z0-9._-]+\))?!?: .+"; then
|
|
53
76
|
echo "FAIL [pr-title]: '${TITLE}' is not '<type>(<scope>)?!?: <description>' (type one of: ${TYPES//|/, })."
|
|
@@ -69,15 +92,15 @@ if [ "$PROFILE" = hub ]; then
|
|
|
69
92
|
echo "PASS [pr-title]: '${TITLE}' (profile: hub, artifact-review)"
|
|
70
93
|
exit 0
|
|
71
94
|
fi
|
|
72
|
-
echo "FAIL [pr-title]: '${TITLE}' is not a
|
|
95
|
+
echo "FAIL [pr-title]: '${TITLE}' is not a Product review title 'review: <artifact> (EP-<slug>)'."
|
|
73
96
|
exit 1
|
|
74
97
|
;;
|
|
75
98
|
*)
|
|
76
|
-
# Any other
|
|
99
|
+
# Any other Product PR is a tooling/code change to the Product itself — UNLESS it changes Shape
|
|
77
100
|
# artifacts (epics/**), which must go through a review/EP-* PR. Without this guard a non-review
|
|
78
101
|
# head could carry an artifact change past the Shape review with only a code title.
|
|
79
102
|
if artifact_changed; then
|
|
80
|
-
echo "FAIL [pr-title]: head '${HEADREF}' changes Shape artifacts (epics/**) but is not a review/EP-* branch — artifact changes must go through a review PR."
|
|
103
|
+
echo "FAIL [pr-title]: head '${HEADREF}' changes Shape artifacts (epics/** or foundation/**) but is not a review/EP-* branch — artifact changes must go through a review PR."
|
|
81
104
|
exit 1
|
|
82
105
|
fi
|
|
83
106
|
# tooling only — fall through to the code convention.
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Risk routing (Phase 3 build plan §D). Reads a PR/MR description's Impact & Risk block and prints
|
|
3
|
-
#
|
|
4
|
-
# contract
|
|
5
|
-
#
|
|
2
|
+
# Risk routing (Phase 3 build plan §D). Reads a PR/MR description's Impact & Risk block and prints how
|
|
3
|
+
# many approvers the change asks for, as the same sum the review gate prints: base 1, plus a risk step —
|
|
4
|
+
# `high` risk +1, a touched contract surface +2, the larger of the two and never their sum. The count is
|
|
5
|
+
# advisory on the Build half: branch protection holds the merge, and this script cannot apply the capacity
|
|
6
|
+
# cap (E72), which needs the Product's count of active people — `yad open-pr` shows the capped count.
|
|
7
|
+
# There are no roles and no named owners: yadflow keeps no list of people (E62), so when a risk step
|
|
8
|
+
# raises the count the touched domains are printed as a hint for whom to ask (with no risk step it prints
|
|
9
|
+
# the base line alone). Advisory: it ROUTES the human review; it does not approve or merge.
|
|
10
|
+
#
|
|
11
|
+
# E66 — the risk map counts too. Run inside the code repo, ON THE PR'S BRANCH, it also asks
|
|
12
|
+
# `checks/risk-map-check.sh --level` which directories the change touches on the BASE branch's
|
|
13
|
+
# `.sdlc/risk-map`: a `high` one adds the high step (+1). The largest step wins, so the body can raise
|
|
14
|
+
# the count and never lower it — a body saying `low` on a change to a `high` directory still counts +1,
|
|
15
|
+
# and the output says the two disagree. When the map cannot be read, it says so and counts the body
|
|
16
|
+
# alone; it never guesses a level.
|
|
17
|
+
#
|
|
18
|
+
# risk-route.sh <pr-description-file> [<base>] <base> defaults the way the checks resolve it
|
|
6
19
|
set -euo pipefail
|
|
7
20
|
|
|
8
|
-
BODY="${1:?usage: risk-route.sh <pr-description-file>}"
|
|
21
|
+
BODY="${1:?usage: risk-route.sh <pr-description-file> [<base>]}"
|
|
9
22
|
[ -f "$BODY" ] || { echo "risk-route: file not found: $BODY" >&2; exit 2; }
|
|
10
23
|
|
|
11
24
|
# Value side of the FIRST line matching a label regex, with any HTML comment + markdown markers
|
|
@@ -24,21 +37,101 @@ echo "Risk level: ${risk:-unspecified}"
|
|
|
24
37
|
echo "Contract surface touched: ${contract:-unspecified}"
|
|
25
38
|
echo "Domains touched: ${domains:-unspecified}"
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
# The map's level for this change, from the risk-map check beside this script (wired together as
|
|
41
|
+
# checks/), or from the repo's checks/ when this copy runs from elsewhere. Machine lines, see that file.
|
|
42
|
+
here="$(cd "$(dirname "$0")" 2>/dev/null && pwd)" || here=""
|
|
43
|
+
checker=""
|
|
44
|
+
for c in "${here:+$here/}risk-map-check.sh" checks/risk-map-check.sh; do
|
|
45
|
+
if [ -f "$c" ]; then checker="$c"; break; fi
|
|
46
|
+
done
|
|
47
|
+
if [ -z "$checker" ]; then
|
|
48
|
+
lv="UNKNOWN checks/risk-map-check.sh is not here — wire it with \`yad update\`"
|
|
49
|
+
else
|
|
50
|
+
lv="$(bash "$checker" --level ${2:+"$2"} 2>/dev/null)" || lv="UNKNOWN checks/risk-map-check.sh failed"
|
|
51
|
+
fi
|
|
52
|
+
line_of() { printf '%s\n' "$lv" | sed -n "s/^$1 //p" | head -1; }
|
|
53
|
+
dirs_of() { printf '%s\n' "$lv" | awk -v want="$1" '$1 == "DIR" && $3 == want { printf "%s%s%s", sep, $2, ($4 == "guessed") ? " (guessed)" : ""; sep = ", " }'; }
|
|
54
|
+
# A risk-map check from before E66 has no --level mode: it reads the flag as a base and prints warnings,
|
|
55
|
+
# none of the lines above. Reading that as "nothing is high" would be a guess, so it is unknown instead.
|
|
56
|
+
if [ -z "$(line_of UNKNOWN)$(line_of NOMAP)$(line_of LEVEL)" ]; then
|
|
57
|
+
lv="UNKNOWN checks/risk-map-check.sh cannot count the risk map yet (it predates E66) — update it with \`yad update\`"
|
|
58
|
+
fi
|
|
59
|
+
map_base="$(line_of BASE)"
|
|
60
|
+
map_high=""
|
|
61
|
+
# E67 — the people the check found with recent commits in those `high` directories: `WHO <login|-> <name>`.
|
|
62
|
+
# `$1 = ""` would rebuild the line with one space between fields and squash a name's own spacing.
|
|
63
|
+
who_list() { printf '%s\n' "$lv" | sed -n 's/^WHO //p' | awk '{ login = $1; name = $0; sub(/^[^ ]* /, "", name); printf "%s%s%s", sep, name, (login == "-" || name == "@" login) ? "" : " (@" login ")"; sep = ", " }'; }
|
|
64
|
+
if [ -n "$(line_of UNKNOWN)" ]; then
|
|
65
|
+
echo "Risk map: not counted — $(line_of UNKNOWN). Only the body is counted."
|
|
66
|
+
elif [ -n "$(line_of NOMAP)" ]; then
|
|
67
|
+
echo "Risk map: none — $(line_of NOMAP), so no directory adds a step."
|
|
68
|
+
else
|
|
69
|
+
map_high="$(dirs_of high)"
|
|
70
|
+
map_medium="$(dirs_of medium)"
|
|
71
|
+
if [ "$(line_of FILES)" = "0" ]; then
|
|
72
|
+
echo "Risk map (${map_base}): this checkout changes no file against ${map_base} — run it on the PR's branch."
|
|
73
|
+
elif [ -n "$map_high" ]; then
|
|
74
|
+
echo "Risk map (${map_base}): high — ${map_high}"
|
|
75
|
+
else
|
|
76
|
+
echo "Risk map (${map_base}): $(line_of LEVEL) — nothing this change touches is high"
|
|
77
|
+
fi
|
|
78
|
+
[ -z "$map_medium" ] || echo " medium (reported only, adds nothing): ${map_medium}"
|
|
79
|
+
if [ -n "$map_high" ]; then
|
|
80
|
+
hist_unknown="$(line_of HISTUNKNOWN)"; who="$(who_list)"
|
|
81
|
+
# A check older than E67 answers nothing about history: no WHO, and no HISTNONE saying it looked.
|
|
82
|
+
# Reading that silence as "nobody" would drop the ask — the same mistake as reading a failure as zero.
|
|
83
|
+
if [ -z "$hist_unknown" ] && [ -z "$who" ] && [ -z "$(printf '%s\n' "$lv" | sed -n 's/^HISTNONE$/x/p')" ]; then
|
|
84
|
+
hist_unknown="checks/risk-map-check.sh cannot answer this yet (it predates E67) — update it with \`yad update\`"
|
|
85
|
+
fi
|
|
86
|
+
if [ -n "$hist_unknown" ]; then
|
|
87
|
+
echo " who has worked there lately: not read — ${hist_unknown}"
|
|
88
|
+
elif [ -n "$who" ]; then
|
|
89
|
+
echo " worked there in the last 30 days: ${who}"
|
|
90
|
+
else
|
|
91
|
+
echo " nobody else has committed there in the last 30 days"
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
step=0
|
|
97
|
+
tier=""
|
|
98
|
+
why=""
|
|
99
|
+
if [ "$risk" = "high" ]; then step=1; tier=high; why="risk: high"; fi
|
|
100
|
+
if [ -n "$map_high" ]; then
|
|
101
|
+
[ "$step" -ge 1 ] || { step=1; tier=high; }
|
|
102
|
+
why="${why:+$why, }high on the risk map: ${map_high}"
|
|
103
|
+
fi
|
|
104
|
+
if [ "$contract" = "yes" ]; then step=2; tier=contract; why="${why:+$why, }contract surface touched"; fi
|
|
105
|
+
|
|
106
|
+
if [ "$step" -gt 0 ]; then
|
|
107
|
+
echo "ROUTE: $((1 + step)) approvers = base 1 + ${tier} risk ${step} (${why})"
|
|
108
|
+
# The body can raise the count and never lower it: say so when it tried to.
|
|
109
|
+
if [ -n "$map_high" ] && [ "$risk" != "high" ]; then
|
|
110
|
+
echo " The body says Risk level: ${risk:-unspecified}, but the risk map on ${map_base} marks ${map_high} high — the larger counts."
|
|
111
|
+
fi
|
|
112
|
+
echo " Branch protection (when configured) holds the merge, not this count: at least 1 approval (GitHub blocks self-approval; GitLab only if its settings do)."
|
|
113
|
+
echo " The risk step is advisory, and not capped here — \`yad open-pr\` shows the count capped by the active people."
|
|
114
|
+
# E67 — a change to a `high` directory asks for an approval from someone who has worked there in the
|
|
115
|
+
# last 30 days. Reported, like the count: it names who can meet it and blocks nothing. With nobody to
|
|
116
|
+
# name (or a history that could not be read), the touched domains stay the only hint.
|
|
117
|
+
if [ -n "$map_high" ] && [ -n "$(who_list)" ]; then
|
|
118
|
+
echo " This change touches ${map_high}, so it asks for an approval from someone who has"
|
|
119
|
+
echo " committed there in the last 30 days (its own authors left out):"
|
|
120
|
+
printf '%s\n' "$lv" | sed -n 's/^WHO //p' | while IFS= read -r row; do
|
|
121
|
+
login="${row%% *}"; name="${row#* }"
|
|
122
|
+
if [ "$login" = "-" ] || [ "$name" = "@${login}" ]; then echo " - ${name}"; else echo " - ${name} (@${login})"; fi
|
|
123
|
+
done
|
|
124
|
+
fi
|
|
125
|
+
echo " Ask reviewers who know the touched domains:"
|
|
33
126
|
case "$domains" in
|
|
34
127
|
""|*"<"*|*"…"*|*"|"*)
|
|
35
|
-
echo " (Domains line not filled in — list each touched domain
|
|
128
|
+
echo " (Domains line not filled in — list each touched domain so the right reviewers can be asked.)" ;;
|
|
36
129
|
*)
|
|
37
130
|
printf '%s\n' "$domains" | tr ',' '\n' | while IFS= read -r d; do
|
|
38
131
|
d="$(printf '%s' "$d" | sed -E 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
39
|
-
[ -n "$d" ]
|
|
132
|
+
if [ -n "$d" ]; then echo " - $d"; fi # not `&&`: an empty last item (`a, b,`) would exit 1
|
|
40
133
|
done ;;
|
|
41
134
|
esac
|
|
42
135
|
else
|
|
43
|
-
echo "ROUTE:
|
|
136
|
+
echo "ROUTE: 1 approver = base 1 (no risk step)."
|
|
44
137
|
fi
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
<!-- Fill every field. risk-route.sh + the engineer review read this block. -->
|
|
13
13
|
- **Domains / repos touched:** <backend | mobile | …>
|
|
14
14
|
- **Contract surface touched:** no <!-- yes => needs Contract-Change + a re-locked contract (contract-check) -->
|
|
15
|
-
- **Risk level:** low <!-- low | medium | high — high (or a contract
|
|
15
|
+
- **Risk level:** low <!-- low | medium | high — high (or a touched contract surface) raises the advisory approver count -->
|
|
16
16
|
- **Rollback plan:** <how to revert if this misbehaves>
|
|
17
17
|
|
|
18
|
-
> **Routing:**
|
|
19
|
-
> contract
|
|
20
|
-
>
|
|
18
|
+
> **Routing:** the merge needs **1 approval, which should not be the author’s own**. `high` risk adds 1
|
|
19
|
+
> and a touched contract surface adds 2 to the count (the larger, never the sum); that step is advisory
|
|
20
|
+
> (`yad open-pr` shows it capped by the active people). A change to a directory the base branch's `.sdlc/risk-map` marks `high` adds 1
|
|
21
|
+
> too, even when this says `low`. Run `bash checks/risk-route.sh <this-description>` on this branch to
|
|
22
|
+
> print the count.
|
|
21
23
|
|
|
22
24
|
## Testing
|
|
23
25
|
<!-- How the acceptance criteria were exercised. Tests must exercise behavior, not just pass. -->
|
|
@@ -27,4 +29,4 @@
|
|
|
27
29
|
- [ ] No contract-surface change without `Contract-Change: yes` + a re-locked contract (contract-check gate)
|
|
28
30
|
- [ ] Lint, build, and tests pass (build/test/lint gate)
|
|
29
31
|
- [ ] Diff stays inside the files the task's spec declared (≤3 where possible)
|
|
30
|
-
- [ ] Impact & Risk filled; `high` risk
|
|
32
|
+
- [ ] Impact & Risk filled; for `high` risk, ask reviewers who know the touched domains
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
<!-- Fill every field. risk-route.sh + the engineer review read this block. -->
|
|
17
17
|
- **Domains / repos touched:** <backend | mobile | …>
|
|
18
18
|
- **Contract surface touched:** no <!-- yes => needs Contract-Change + a re-locked contract (contract-check) -->
|
|
19
|
-
- **Risk level:** low <!-- low | medium | high — high (or a contract
|
|
19
|
+
- **Risk level:** low <!-- low | medium | high — high (or a touched contract surface) raises the advisory approver count -->
|
|
20
20
|
- **Rollback plan:** <how to revert if this misbehaves>
|
|
21
21
|
|
|
22
|
-
> **Routing:**
|
|
23
|
-
> contract
|
|
24
|
-
>
|
|
22
|
+
> **Routing:** the merge needs **1 approval, which should not be the author’s own**. `high` risk adds 1
|
|
23
|
+
> and a touched contract surface adds 2 to the count (the larger, never the sum); that step is advisory
|
|
24
|
+
> (`yad open-pr` shows it capped by the active people). A change to a directory the base branch's `.sdlc/risk-map` marks `high` adds 1
|
|
25
|
+
> too, even when this says `low`. Run `bash checks/risk-route.sh <this-description>` on this branch to
|
|
26
|
+
> print the count.
|
|
25
27
|
|
|
26
28
|
## Testing
|
|
27
29
|
<!-- How the acceptance criteria were exercised. Tests must exercise behavior, not just pass. -->
|
|
@@ -31,6 +33,6 @@
|
|
|
31
33
|
- [ ] No contract-surface change without `Contract-Change: yes` + a re-locked contract (contract-check gate)
|
|
32
34
|
- [ ] Lint, build, and tests pass (build/test/lint gate)
|
|
33
35
|
- [ ] Diff stays inside the files the task's spec declared (≤3 where possible)
|
|
34
|
-
- [ ] Impact & Risk filled; `high` risk
|
|
36
|
+
- [ ] Impact & Risk filled; for `high` risk, ask reviewers who know the touched domains
|
|
35
37
|
|
|
36
38
|
/assign me
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- SDLC HUB PR template — Shape artifact review (epic / architecture+contract / ui-design / stories). -->
|
|
2
|
-
<!-- This PR is a REVIEW VEHICLE on the
|
|
3
|
-
advances the step
|
|
4
|
-
`yad
|
|
2
|
+
<!-- This PR is a REVIEW VEHICLE on the Product, not a code merge. The file gate (yad-review-gate)
|
|
3
|
+
advances the step when this PR is MERGED with the approvals met and every thread resolved. Reviewers
|
|
4
|
+
approve/comment here; `yad gate sync` (or CI at merge, with a verified ledger) pulls that into the file ledger. -->
|
|
5
5
|
|
|
6
6
|
## Artifact under review
|
|
7
7
|
- Epic: `EP-<slug>`
|
|
@@ -14,23 +14,24 @@
|
|
|
14
14
|
|
|
15
15
|
## Impact & Risk (front-half)
|
|
16
16
|
- **Domains / repos touched:** <epic.repos, e.g. backend, mobile>
|
|
17
|
-
- **Risk tags:** <none | contract | auth | payments> <!-- contract/auth/payments =>
|
|
17
|
+
- **Risk tags:** <none | contract | auth | payments> <!-- contract/auth/payments => raises the advisory approver count -->
|
|
18
18
|
- **Contract surface:** <n/a | locked @ sha256:…> <!-- architecture only; a re-lock invalidates prior approvals -->
|
|
19
19
|
|
|
20
20
|
## Required approvals (yad-review-gate rule)
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
- Full count, in **distinct people**: base 1 + the step's risk step — 3 on a `contract` step, 2 on
|
|
22
|
+
`auth`/`payments`, 1 otherwise (the largest step, never the sum). No approval should be the author’s own.
|
|
23
|
+
- Enforced: **1 approval** (the base). The risk step is **advisory**. `yad gate status <epic>`
|
|
24
|
+
prints the count capped at the active people less one (one seat is left for the author), and
|
|
25
|
+
`yad-hub-bridge`'s `hub-route.sh` run on this description prints the full count.
|
|
26
|
+
- No reviewers are requested automatically — ask them on this PR. The `domain:<repo>` labels show the touched repos.
|
|
25
27
|
|
|
26
28
|
## How to review (this drives the gate)
|
|
27
|
-
- **Approve** this PR to record
|
|
28
|
-
(your platform login maps to your SDLC name + role via `.sdlc/hub.json`'s roster).
|
|
29
|
+
- **Approve** this PR to record your approval in the file ledger, under your platform login (no role).
|
|
29
30
|
- **Comment / request changes** to record review comments (synced into `reviews/<artifact>--<date>--comments.md`).
|
|
30
|
-
- **
|
|
31
|
+
- **Merge to advance** — once the approvals are met and every thread is resolved, merging this PR moves the step (CI records it with a verified ledger; otherwise `yad gate sync`).
|
|
31
32
|
|
|
32
33
|
## Checklist
|
|
33
34
|
- [ ] `owner` set in the artifact frontmatter (inherited from `epic.md`)
|
|
34
|
-
- [ ] Contract re-locked (`.sdlc/contract-lock.json`) if the surface changed (architecture only)
|
|
35
|
-
- [ ] Risk tags reflect the real surface touched (contract/auth/payments
|
|
35
|
+
- [ ] Contract re-locked (`.sdlc/contract-lock.json`) if the surface changed (architecture only — a short-lane epic has no architecture step and no lock, so tick this only if your route has one)
|
|
36
|
+
- [ ] Risk tags reflect the real surface touched (contract/auth/payments raise the approval count)
|
|
36
37
|
- [ ] No secrets or tokens in the artifact or this description
|