yadflow 1.0.1
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 +50 -0
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/bin/sdlc.mjs +135 -0
- package/cli/commit.mjs +81 -0
- package/cli/epic-state.mjs +220 -0
- package/cli/gate.mjs +456 -0
- package/cli/lib.mjs +142 -0
- package/cli/manifest.mjs +119 -0
- package/cli/openpr.mjs +65 -0
- package/cli/plan.mjs +127 -0
- package/cli/platform.mjs +151 -0
- package/cli/reconcile.mjs +83 -0
- package/cli/repo.mjs +61 -0
- package/cli/setup.mjs +208 -0
- package/package.json +51 -0
- package/skills/sdlc/config.yaml +156 -0
- package/skills/sdlc/install.sh +51 -0
- package/skills/sdlc/module-help.csv +17 -0
- package/skills/sdlc-author-analysis/SKILL.md +136 -0
- package/skills/sdlc-author-architecture/SKILL.md +180 -0
- package/skills/sdlc-author-architecture/references/contract-format.md +72 -0
- package/skills/sdlc-author-epic/SKILL.md +154 -0
- package/skills/sdlc-author-epic/references/state-schema.md +187 -0
- package/skills/sdlc-author-stories/SKILL.md +109 -0
- package/skills/sdlc-author-stories/references/story-schema.md +46 -0
- package/skills/sdlc-author-ui/SKILL.md +113 -0
- package/skills/sdlc-backfill/SKILL.md +91 -0
- package/skills/sdlc-backfill/references/backfill.md +66 -0
- package/skills/sdlc-backfill/templates/checks/backfill-check.sh +42 -0
- package/skills/sdlc-checks/SKILL.md +138 -0
- package/skills/sdlc-checks/references/check-gates.md +168 -0
- package/skills/sdlc-checks/templates/checks/build-test-lint.sh +14 -0
- package/skills/sdlc-checks/templates/checks/contract-check.sh +62 -0
- package/skills/sdlc-checks/templates/checks/spec-link.sh +38 -0
- package/skills/sdlc-checks/templates/checks/verified-commits.sh +120 -0
- package/skills/sdlc-checks/templates/github/sdlc-checks.yml +45 -0
- package/skills/sdlc-checks/templates/github/sdlc-verified-commits.yml +22 -0
- package/skills/sdlc-checks/templates/gitlab/.gitlab-ci.yml +40 -0
- package/skills/sdlc-checks/templates/gitlab/gitlab-ci.include-root.yml +7 -0
- package/skills/sdlc-checks/templates/gitlab/sdlc-checks.gitlab-ci.yml +47 -0
- package/skills/sdlc-checks/templates/gitlab/sdlc-verified-commits.gitlab-ci.yml +21 -0
- package/skills/sdlc-connect-repos/SKILL.md +159 -0
- package/skills/sdlc-connect-repos/references/code-context.md +92 -0
- package/skills/sdlc-connect-repos/references/hub-config.md +77 -0
- package/skills/sdlc-connect-repos/references/repos-registry.md +62 -0
- package/skills/sdlc-hub-bridge/SKILL.md +119 -0
- package/skills/sdlc-hub-bridge/references/bridge.md +136 -0
- package/skills/sdlc-hub-bridge/references/login-roster.md +42 -0
- package/skills/sdlc-hub-bridge/templates/checks/hub-route.sh +50 -0
- package/skills/sdlc-hub-bridge/templates/github/sdlc-gate-sync.yml +63 -0
- package/skills/sdlc-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +7 -0
- package/skills/sdlc-hub-bridge/templates/gitlab/sdlc-gate-sync.gitlab-ci.yml +64 -0
- package/skills/sdlc-implement/SKILL.md +143 -0
- package/skills/sdlc-implement/references/implement-conventions.md +103 -0
- package/skills/sdlc-implement/templates/.gitmessage +17 -0
- package/skills/sdlc-pr-template/SKILL.md +86 -0
- package/skills/sdlc-pr-template/references/risk-routing.md +54 -0
- package/skills/sdlc-pr-template/templates/checks/risk-route.sh +44 -0
- package/skills/sdlc-pr-template/templates/github/pull_request_template.md +30 -0
- package/skills/sdlc-pr-template/templates/gitlab/merge_request_templates/Default.md +32 -0
- package/skills/sdlc-pr-template/templates/hub/github/pull_request_template.md +36 -0
- package/skills/sdlc-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +37 -0
- package/skills/sdlc-review-comments/SKILL.md +63 -0
- package/skills/sdlc-review-comments/references/comment-conventions.md +55 -0
- package/skills/sdlc-review-comments/templates/github/REVIEW_COMMENTS.md +49 -0
- package/skills/sdlc-review-comments/templates/gitlab/REVIEW_COMMENTS.md +49 -0
- package/skills/sdlc-review-gate/SKILL.md +196 -0
- package/skills/sdlc-review-gate/references/gating.md +79 -0
- package/skills/sdlc-run/SKILL.md +109 -0
- package/skills/sdlc-run/references/run-loop.md +121 -0
- package/skills/sdlc-ship/SKILL.md +86 -0
- package/skills/sdlc-ship/references/ship-and-record.md +67 -0
- package/skills/sdlc-ship/templates/.coderabbit.yaml +19 -0
- package/skills/sdlc-spec/SKILL.md +119 -0
- package/skills/sdlc-spec/references/spec-handoff.md +101 -0
- package/skills/sdlc-status/SKILL.md +92 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Login roster — schema, resolution, per-repo routing
|
|
2
|
+
|
|
3
|
+
The roster lives in `.sdlc/hub.json` (`roster: [...]`) and is the only thing that turns a platform
|
|
4
|
+
**login** into an SDLC **name + role** for the ledger. Schema and the no-tokens rule are documented once
|
|
5
|
+
in `../../sdlc-connect-repos/references/hub-config.md`; this file covers how the bridge *uses* it.
|
|
6
|
+
|
|
7
|
+
## Entry
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{ "login": "abdelrahmannasr", "name": "alice", "role": "owner" }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `login` — the GitHub/GitLab username whose review/approval is being mapped.
|
|
14
|
+
- `name` — the SDLC name written to `approvals.json` / `comments.json` (the same names as `epic.md`
|
|
15
|
+
`owner` and `repos.json` `domain_owner`). Keep it stable.
|
|
16
|
+
- `role` — the person's default role: `owner` or `reviewer`. **Not** `domain-owner` — that is derived.
|
|
17
|
+
|
|
18
|
+
## Resolution
|
|
19
|
+
|
|
20
|
+
1. **login → name + role** from the roster.
|
|
21
|
+
2. **domain-owner is derived:** if the resolved `name` equals a repo's `domain_owner` in `repos.json`,
|
|
22
|
+
and that repo is a **touched domain** for the step under review, the bridge also emits a
|
|
23
|
+
`domain-owner` approval scoped to that repo (`domain: <repo>`). One person owning several repos yields
|
|
24
|
+
several `domain-owner` records with different `domain` values — exactly what the gate predicate allows.
|
|
25
|
+
3. **Unmapped login → reviewer (flagged).** A login not in the roster maps to `name: <login>`,
|
|
26
|
+
`role: reviewer`, with `<!-- unverified login: <login> -->` in the review record. It counts as a
|
|
27
|
+
reviewer but is **never** auto-promoted to owner/domain-owner, so a stranger can never satisfy the
|
|
28
|
+
owner/domain-owner requirement. The marker prompts a human to add the login to the roster.
|
|
29
|
+
|
|
30
|
+
## Per-repo routing (stories review, and any escalated step)
|
|
31
|
+
|
|
32
|
+
The stories review needs a `domain-owner` per repo in the **union of every story's `repos`**. On the
|
|
33
|
+
review PR the bridge makes this legible and enforceable:
|
|
34
|
+
|
|
35
|
+
- Add a `domain:<repo>` **label** per touched repo.
|
|
36
|
+
- **Request** each touched repo's `domain_owner` login as a reviewer (resolved via the roster).
|
|
37
|
+
- On `sync`, an approval from login *L* maps to `domain-owner` for repo *R* **iff**
|
|
38
|
+
`repos.json[R].domain_owner == roster[L].name`. So a domain owner's approval is scoped to exactly the
|
|
39
|
+
repos they own, and a repo with no approving owner shows up as still-required in the gate report.
|
|
40
|
+
|
|
41
|
+
This is the same touched-domains computation the gate uses (`../sdlc-review-gate/references/gating.md`):
|
|
42
|
+
architecture+contract → `epic.repos`; stories → union of story `repos`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Hub review routing — the front-half analogue of sdlc-pr-template's risk-route.sh. Reads a hub review
|
|
3
|
+
# PR/MR description's "Impact & Risk (front-half)" block and prints the required reviewers, reusing
|
|
4
|
+
# sdlc-review-gate's rule: base = owner + 1 reviewer; if a risk tag (contract|auth|payments) is set OR
|
|
5
|
+
# the artifact is the stories set, ALSO a domain-owner per touched repo. Advisory: it ROUTES the human
|
|
6
|
+
# review; it does not approve or merge.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
BODY="${1:?usage: hub-route.sh <hub-pr-description-file>}"
|
|
10
|
+
[ -f "$BODY" ] || { echo "hub-route: file not found: $BODY" >&2; exit 2; }
|
|
11
|
+
|
|
12
|
+
# Value side of the FIRST line matching a label regex, comments + markdown markers stripped. Tolerant:
|
|
13
|
+
# a missing label yields empty (never aborts) — an advisory helper must still print for a half-filled body.
|
|
14
|
+
value_of() {
|
|
15
|
+
grep -iE "$1" "$BODY" 2>/dev/null | head -1 \
|
|
16
|
+
| sed -E 's/<!--.*$//; s/^[^:]*://; s/[*`]//g; s/^[[:space:]]*//; s/[[:space:]]*$//' || true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
risk_tags="$(printf '%s' "$(value_of 'Risk tags:')" | tr 'A-Z' 'a-z')"
|
|
20
|
+
repos="$(value_of 'Domains.*touched:')"
|
|
21
|
+
artifact="$(value_of 'Artifact:')"
|
|
22
|
+
|
|
23
|
+
echo "Risk tags: ${risk_tags:-none}"
|
|
24
|
+
echo "Repos touched: ${repos:-unspecified}"
|
|
25
|
+
|
|
26
|
+
escalate=no
|
|
27
|
+
why=""
|
|
28
|
+
case "$risk_tags" in
|
|
29
|
+
*contract*) escalate=yes; why="risk tag: contract" ;;
|
|
30
|
+
esac
|
|
31
|
+
case "$risk_tags" in *auth*) escalate=yes; why="${why:+$why, }risk tag: auth" ;; esac
|
|
32
|
+
case "$risk_tags" in *payments*) escalate=yes; why="${why:+$why, }risk tag: payments" ;; esac
|
|
33
|
+
# The stories review routes per-repo even with no risk tag.
|
|
34
|
+
case "$artifact" in *stories*) escalate=yes; why="${why:+$why, }stories per-repo routing" ;; esac
|
|
35
|
+
|
|
36
|
+
if [ "$escalate" = "yes" ]; then
|
|
37
|
+
echo "ROUTE: ESCALATED (${why}) -> owner + 1 reviewer PLUS one domain-owner approval per touched repo"
|
|
38
|
+
echo " (same escalation as sdlc-review-gate; map each repo to its domain_owner in repos.json)."
|
|
39
|
+
case "$repos" in
|
|
40
|
+
""|*"<"*|*"…"*|*"|"*)
|
|
41
|
+
echo " (Repos line not filled in — list each touched repo to route the domain owners.)" ;;
|
|
42
|
+
*)
|
|
43
|
+
printf '%s\n' "$repos" | tr ',' '\n' | while IFS= read -r r; do
|
|
44
|
+
r="$(printf '%s' "$r" | sed -E 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
45
|
+
[ -n "$r" ] && echo " - domain-owner: $r"
|
|
46
|
+
done ;;
|
|
47
|
+
esac
|
|
48
|
+
else
|
|
49
|
+
echo "ROUTE: base rule -> owner + 1 reviewer (no domain-owner escalation)."
|
|
50
|
+
fi
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# sdlc-managed: sdlc-hub-bridge
|
|
2
|
+
# Event-driven gate sync for the PRODUCT HUB. When a reviewer approves, requests changes, or a
|
|
3
|
+
# human merges a review/EP-* PR, this workflow runs `sdlc gate ci`, which maps the platform state
|
|
4
|
+
# into the file ledger (epics/<epic>/.sdlc/*.json + reviews/*.md) and commits ONLY those ledger
|
|
5
|
+
# files to the default branch. It never approves and never merges — the merge click remains the
|
|
6
|
+
# human approval act; the gate predicate is the same one `sdlc gate sync` runs locally.
|
|
7
|
+
#
|
|
8
|
+
# Loop prevention: neither `pull_request_review` nor `pull_request` fires on a push to the default
|
|
9
|
+
# branch, and the ledger commit carries [skip ci] to guard every other workflow.
|
|
10
|
+
#
|
|
11
|
+
# Protected default branch? The github.token push will fail (visibly — the run goes red and manual
|
|
12
|
+
# `sdlc gate sync` still works). Options, in order of preference:
|
|
13
|
+
# a) add a ruleset bypass so GitHub Actions may push to the default branch, or
|
|
14
|
+
# b) store a fine-grained PAT as the SDLC_GATE_TOKEN secret and pass it to actions/checkout via
|
|
15
|
+
# `with: { token: ${{ secrets.SDLC_GATE_TOKEN }} }` — the one exception to the bridge's
|
|
16
|
+
# no-stored-tokens rule; see skills/sdlc-hub-bridge/references/bridge.md.
|
|
17
|
+
name: sdlc-gate-sync
|
|
18
|
+
on:
|
|
19
|
+
pull_request_review:
|
|
20
|
+
types: [submitted, dismissed]
|
|
21
|
+
pull_request:
|
|
22
|
+
types: [closed, synchronize] # closed -> merged advance; synchronize -> prompt revoke-on-change
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write # push the ledger commit
|
|
26
|
+
pull-requests: read # gh pr view + reviewThreads GraphQL
|
|
27
|
+
|
|
28
|
+
# Serialize runs repo-wide so ledger pushes never race; sync reads the FULL platform state each
|
|
29
|
+
# time, so a queued superseded run loses nothing.
|
|
30
|
+
concurrency:
|
|
31
|
+
group: sdlc-gate-sync
|
|
32
|
+
cancel-in-progress: false
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
sync:
|
|
36
|
+
if: >
|
|
37
|
+
startsWith(github.event.pull_request.head.ref, 'review/EP-') &&
|
|
38
|
+
(github.event_name == 'pull_request_review' ||
|
|
39
|
+
github.event.action == 'synchronize' ||
|
|
40
|
+
github.event.pull_request.merged == true)
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
env:
|
|
43
|
+
GH_TOKEN: ${{ github.token }}
|
|
44
|
+
steps:
|
|
45
|
+
# Check out the BASE branch (not the PR merge ref): the ledger lives there, and `gate ci`
|
|
46
|
+
# overlays the artifact from the head ref itself so approvals bind to the reviewed content.
|
|
47
|
+
# A head branch auto-deleted on merge is fine: the event payload still carries head.ref (the
|
|
48
|
+
# string `gate ci` parses), and a failed fetch just skips the overlay — by then the artifact
|
|
49
|
+
# is already on the base branch via the merge.
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
with:
|
|
52
|
+
ref: ${{ github.event.pull_request.base.ref }}
|
|
53
|
+
fetch-depth: 0
|
|
54
|
+
- uses: actions/setup-node@v4
|
|
55
|
+
with:
|
|
56
|
+
node-version: "20"
|
|
57
|
+
- name: Sync the gate ledger
|
|
58
|
+
run: |
|
|
59
|
+
git config user.name "sdlc-gate-sync[bot]"
|
|
60
|
+
git config user.email "sdlc-gate-sync[bot]@users.noreply.github.com"
|
|
61
|
+
npx -y -p yadflow@1 sdlc gate ci \
|
|
62
|
+
--branch "${{ github.event.pull_request.head.ref }}" \
|
|
63
|
+
--pr "${{ github.event.pull_request.number }}"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SDLC gate sync — minimal root pipeline for the PRODUCT HUB.
|
|
2
|
+
# Written by the sdlc-hub-bridge `wire` action ONLY when the hub has no existing root
|
|
3
|
+
# `.gitlab-ci.yml`. The job itself lives in the included fragment so the same single source is
|
|
4
|
+
# reused whether or not a root pipeline already exists. If a root later grows real jobs, they
|
|
5
|
+
# coexist with the included sdlc-* job (which carries `needs: []` and no `stage:`).
|
|
6
|
+
include:
|
|
7
|
+
- local: '.gitlab/ci/sdlc-gate-sync.yml'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# sdlc-managed-include: sdlc-hub-bridge
|
|
2
|
+
# Event-driven gate sync for the PRODUCT HUB, as an INCLUDABLE fragment. Pulled into the hub's
|
|
3
|
+
# root .gitlab-ci.yml via:
|
|
4
|
+
# include:
|
|
5
|
+
# - local: '.gitlab/ci/sdlc-gate-sync.yml'
|
|
6
|
+
# so wiring never edits the foreign root pipeline beyond that one include line. The job carries
|
|
7
|
+
# `needs: []` and no `stage:` (same merge-safety as the sdlc-checks fragment).
|
|
8
|
+
#
|
|
9
|
+
# What it does: run `sdlc gate ci`, which maps MR approvals / change-request discussions / the
|
|
10
|
+
# merge into the file ledger (epics/<epic>/.sdlc/*.json + reviews/*.md) and commits ONLY those
|
|
11
|
+
# ledger files to the default branch. It never approves and never merges — the merge click remains
|
|
12
|
+
# the human approval act.
|
|
13
|
+
#
|
|
14
|
+
# GitLab is the DEGRADED path, stated honestly: GitLab fires no pipeline on an approval alone.
|
|
15
|
+
# - MR events (open / push to the review branch) and the merge are picked up near-immediately.
|
|
16
|
+
# - Approvals are only seen by the SCHEDULED sweep. Create a pipeline schedule (one-time, cannot
|
|
17
|
+
# be committed as code): cron `*/15 * * * *` with variable SDLC_GATE_SYNC=true — so an approval
|
|
18
|
+
# can take up to ~15 minutes to reach the ledger. UI: CI/CD > Schedules, or:
|
|
19
|
+
# glab api projects/:id/pipeline_schedules -X POST \
|
|
20
|
+
# -f description='sdlc gate sync' -f ref=main -f cron='*/15 * * * *'
|
|
21
|
+
# glab api "projects/:id/pipeline_schedules/<id>/variables" -X POST \
|
|
22
|
+
# -f key=SDLC_GATE_SYNC -f value=true
|
|
23
|
+
#
|
|
24
|
+
# Token (the one documented bend of the bridge's no-stored-tokens rule): CI_JOB_TOKEN can neither
|
|
25
|
+
# read the approvals API nor push to a protected branch. Create a PROJECT ACCESS TOKEN with
|
|
26
|
+
# `read_api` + `write_repository` (role Developer+, allowed to push to the default branch) and
|
|
27
|
+
# store it as a masked CI/CD variable SDLC_GATE_TOKEN. Without it the job fails visibly and
|
|
28
|
+
# manual `sdlc gate sync` remains the fallback.
|
|
29
|
+
variables:
|
|
30
|
+
GIT_DEPTH: "0" # full history: gate ci fetches the review branch and pushes the ledger
|
|
31
|
+
|
|
32
|
+
sdlc-gate-sync:
|
|
33
|
+
needs: []
|
|
34
|
+
image: node:20
|
|
35
|
+
rules:
|
|
36
|
+
# MR-event path: fires on MR open / push to the review branch — NOT on approval (see header).
|
|
37
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^review\/EP-/
|
|
38
|
+
# Merge path: branch pipeline on the default branch whose merge commit names a review branch.
|
|
39
|
+
# Squash/fast-forward merges may omit the branch name — those advance on the next scheduled sweep.
|
|
40
|
+
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_MESSAGE =~ /review\/EP-/
|
|
41
|
+
# Scheduled sweep: the only path that picks up approvals.
|
|
42
|
+
- if: $CI_PIPELINE_SOURCE == "schedule" && $SDLC_GATE_SYNC == "true"
|
|
43
|
+
script:
|
|
44
|
+
# Pinned glab binary (node:20 has no glab). Alternative: image registry.gitlab.com/gitlab-org/cli.
|
|
45
|
+
- GLAB_VERSION=1.55.0
|
|
46
|
+
- curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/glab_${GLAB_VERSION}_linux_amd64.deb" -o /tmp/glab.deb && dpkg -i /tmp/glab.deb
|
|
47
|
+
# The ledger lives on the default branch; gate ci overlays the artifact from the review branch itself.
|
|
48
|
+
- git fetch origin "$CI_DEFAULT_BRANCH"
|
|
49
|
+
- git checkout -B "$CI_DEFAULT_BRANCH" "origin/$CI_DEFAULT_BRANCH"
|
|
50
|
+
- git config user.name "sdlc-gate-sync" && git config user.email "sdlc-gate-sync@noreply.${CI_SERVER_HOST}"
|
|
51
|
+
- git remote set-url origin "https://oauth2:${SDLC_GATE_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
|
52
|
+
- export GITLAB_TOKEN="$SDLC_GATE_TOKEN" GITLAB_HOST="$CI_SERVER_URL"
|
|
53
|
+
- |
|
|
54
|
+
# Merge pushes have no CI_MERGE_REQUEST_IID — derive the just-merged review branch from the
|
|
55
|
+
# merge commit message so the merge advances in event mode; the scheduled sweep stays the
|
|
56
|
+
# catch-all (squash/FF merges whose message omits the branch land there).
|
|
57
|
+
REVIEW_BRANCH="$(printf '%s' "$CI_COMMIT_MESSAGE" | grep -oE 'review/EP-[A-Za-z0-9._/-]+' | head -n1 || true)"
|
|
58
|
+
if [ -n "$CI_MERGE_REQUEST_IID" ]; then
|
|
59
|
+
npx -y -p yadflow@1 sdlc gate ci --branch "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" --pr "$CI_MERGE_REQUEST_IID"
|
|
60
|
+
elif [ -n "$REVIEW_BRANCH" ]; then
|
|
61
|
+
npx -y -p yadflow@1 sdlc gate ci --branch "$REVIEW_BRANCH"
|
|
62
|
+
else
|
|
63
|
+
npx -y -p yadflow@1 sdlc gate ci
|
|
64
|
+
fi
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdlc-implement
|
|
3
|
+
description: 'Build-half Step B of the gated SDLC. With the dev lens, implement ONE atomic task from a story''s Spec Kit tasks.md as a small diff (≤3 files) on its own branch in the code repo. The diff stays inside the files the task declared — flag and STOP if it would grow beyond them. Commit per convention, ending with the task ID; add Contract-Change: yes only if the diff touches the locked contract surface (which routes back to the architecture gate). The step never advances itself; it produces a committed branch and hands off to the check gates, which the orchestrator (sdlc-run) may auto-run once `implement` is earned to machine_advance (Phase 4b Step D) — the merge still needs the gates and the engineer review. Use when the user says "implement task <id>" or after a story is spec''d.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SDLC — Implement Task (build-half Step B)
|
|
7
|
+
|
|
8
|
+
**Goal:** Turn ONE atomic task from a story's `tasks.md` (produced by Step A `sdlc-spec`) into a small,
|
|
9
|
+
reviewable diff on its own branch in the code repo. **One atomic task = one branch = one PR/MR**
|
|
10
|
+
(build plan §B). This is the **light per-task loop**: it runs once per task; the heavy spec ceremony
|
|
11
|
+
already ran once for the story.
|
|
12
|
+
|
|
13
|
+
This step **never auto-advances**. When the task is implemented and committed, control passes to the
|
|
14
|
+
**check gates** (Step C — `sdlc-checks`: spec-link, contract-check, build/test/lint) and then human/AI
|
|
15
|
+
review (Steps D–E, not built yet). Implementation here produces a branch + commit and stops.
|
|
16
|
+
|
|
17
|
+
The implementing lens is **`dev`** (`bmad-agent-dev`, Amelia). The dev writes only what the task
|
|
18
|
+
declares; it does not redesign, does not widen the contract, and does not pick up sibling tasks.
|
|
19
|
+
|
|
20
|
+
## Conventions
|
|
21
|
+
|
|
22
|
+
- `{project-root}` resolves from the project working directory — the **product** repo.
|
|
23
|
+
- The work happens **inside the code repo** (a separate git repo) at
|
|
24
|
+
`{project-root}/demo-repos/<repo>/` (`config.yaml` `build.code_repos_root`). Use absolute paths.
|
|
25
|
+
- **Branch name:** `feat/<story-id>-<task-id>-<short-slug>` (e.g.
|
|
26
|
+
`feat/EP-istifta-inquiries-S01-T01-create-inquiry`). Branched off the code repo's default branch.
|
|
27
|
+
- **Commit message:** a conventional subject, body describing the change, and a **required `Task:`
|
|
28
|
+
trailer** (e.g. `Task: EP-istifta-inquiries-S01-T01`) in the trailer block. Add `Contract-Change: yes`
|
|
29
|
+
**only** if the diff touches the locked contract surface (see Step 5), and a per-commit
|
|
30
|
+
`Co-Authored-By:` for any AI tool that helped author the diff (the human author owns the commit;
|
|
31
|
+
trailer order `Task:` → `Contract-Change:` → `Co-Authored-By:`). The skill installs a `.gitmessage`
|
|
32
|
+
template that scaffolds these (Step 2).
|
|
33
|
+
- Speak in the configured `communication_language`; write code/comments in `document_output_language`.
|
|
34
|
+
|
|
35
|
+
## Inputs
|
|
36
|
+
|
|
37
|
+
- `epic` — the `EP-<slug>` (ask if not provided).
|
|
38
|
+
- `story` — the `EP-<slug>-S0N` whose spec is being implemented (ask if not provided).
|
|
39
|
+
- `repo` — the code repo the story's spec lives in (one of the story's `repos`).
|
|
40
|
+
- `task` — the ONE atomic task ID to implement, e.g. `T01` (ask if not provided).
|
|
41
|
+
|
|
42
|
+
## On Activation
|
|
43
|
+
|
|
44
|
+
### Step 1 — Resolve the spec and the task
|
|
45
|
+
Read `demo-repos/<repo>/specs/<story>/tasks.md`. Find the block for `<task>` (`## <task> — …`). Read
|
|
46
|
+
its one-line goal and its **Files:** list (the declared file boundary) and the acceptance criterion it
|
|
47
|
+
satisfies. If the task ID is not in `tasks.md`, STOP. Confirm the spec exists (Step A ran); if not,
|
|
48
|
+
STOP and point at `sdlc-spec`.
|
|
49
|
+
|
|
50
|
+
### Step 2 — Resolve the code repo and branch
|
|
51
|
+
Confirm `demo-repos/<repo>/` is its own git repo (`.git` present). From its default branch, create the
|
|
52
|
+
task branch `feat/<story>-<task>-<short-slug>`. If a branch for this task already exists, reuse it
|
|
53
|
+
rather than forking a second one (one task = one branch).
|
|
54
|
+
|
|
55
|
+
**On the first implement in a repo, install the commit template** (idempotent): copy this skill's
|
|
56
|
+
`templates/.gitmessage` to `<repo>/.gitmessage` and run `git -C <repo> config commit.template .gitmessage`
|
|
57
|
+
so every commit is pre-scaffolded with the `Task:` trailer and the commented per-commit `Co-Authored-By:`
|
|
58
|
+
choices (`config.yaml` `build.ai_coauthor.allowed`). Do the same at the hub root for hub commits. Skip if
|
|
59
|
+
already configured.
|
|
60
|
+
|
|
61
|
+
### Step 3 — Read the spec inputs (do NOT re-derive the contract)
|
|
62
|
+
Read the story's `spec.md`, `plan.md`, `data-model.md`, and `contracts/` for this task's context. The
|
|
63
|
+
contract slice is **quoted** from the product repo's locked `contract.md` — implement to it, never
|
|
64
|
+
change it here.
|
|
65
|
+
|
|
66
|
+
### Step 4 — Implement the task (dev lens), inside the declared files ONLY
|
|
67
|
+
Adopt the **dev** lens. Implement the task's goal so it satisfies its acceptance criterion, touching
|
|
68
|
+
**only the files in the task's `Files:` list** (≤3 where possible). Write real, working code — tests
|
|
69
|
+
must exercise behavior, not just pass (build plan §C). **Before committing, run a smoke or test that
|
|
70
|
+
actually exercises the task's acceptance criterion** — the task is not done until the criterion is
|
|
71
|
+
demonstrably met (record the result in the Step 7 report).
|
|
72
|
+
|
|
73
|
+
**File-boundary rule (hard):** if the implementation genuinely needs a file **not** in the declared
|
|
74
|
+
list, **flag and STOP** — do not silently widen the diff. Report the extra file(s) needed so the
|
|
75
|
+
task's spec can be corrected (re-run `sdlc-spec` / re-scope the task) before implementing. A task whose
|
|
76
|
+
declared files are wrong is a spec bug, not an implementation decision.
|
|
77
|
+
|
|
78
|
+
This stop is a **scope overrun** — a halt condition that pulls in a human regardless of any automation
|
|
79
|
+
dial. When this step is driven by the orchestrator (`sdlc-run`, Phase 4), the stop must be legible to
|
|
80
|
+
it: mark the `implement` step `status: blocked` in `build-state/<story>.json` and surface
|
|
81
|
+
`scope_overrun: true` so the run records a `rejected` trust entry and halts (it never advances past a
|
|
82
|
+
boundary breach). The same applies to the Step 5 contract-surface stop (`contract_touch: true`).
|
|
83
|
+
|
|
84
|
+
### Step 5 — Contract-surface check (local pre-flight for Step C)
|
|
85
|
+
Determine whether the diff touches the **locked contract surface** (the API/event/data-model shapes in
|
|
86
|
+
`epics/<epic>/contract.md`'s `CONTRACT-SURFACE` block). Normal implementation **consumes** the
|
|
87
|
+
contract (e.g. implementing `POST /inquiries` to the agreed shape) — that is **not** a contract change.
|
|
88
|
+
A contract change means the diff alters the agreed cross-repo shape itself.
|
|
89
|
+
|
|
90
|
+
- If the diff does **not** change the surface: proceed; no `Contract-Change` trailer.
|
|
91
|
+
- If the task **requires** changing the surface: **flag and STOP**. The contract is owned upstream;
|
|
92
|
+
route back to the **architecture gate** to amend and re-lock `contract.md` first. Only then return
|
|
93
|
+
here, and record `Contract-Change: yes` in the commit body (the Step C contract-check will require a
|
|
94
|
+
matching, already-updated contract).
|
|
95
|
+
|
|
96
|
+
### Step 6 — Commit on the task branch
|
|
97
|
+
Stage only the declared files. Commit with the convention: a conventional subject, a short body, and a
|
|
98
|
+
`Task: <story>-<task>` trailer (plus `Contract-Change: yes` if Step 5 applies). The human author owns the
|
|
99
|
+
commit; if an AI tool helped author this diff, add its `Co-Authored-By:` line from
|
|
100
|
+
`config.yaml` `build.ai_coauthor.allowed` (the `.gitmessage` template scaffolds the choices). Keep all
|
|
101
|
+
trailers in one contiguous block. Do not commit sibling tasks' work.
|
|
102
|
+
|
|
103
|
+
### Step 7 — Report; the advance decision belongs to the dial (Phase 4)
|
|
104
|
+
Report: the branch name, the files changed, how the change satisfies the task's acceptance criterion,
|
|
105
|
+
the result of any test/smoke run, and the next action — the **check gates** (Step C — `sdlc-checks`)
|
|
106
|
+
then the PR and review (Steps D–E). Do **not** open a PR, merge, or hand-edit the epic's front-half
|
|
107
|
+
`state.json`. Step B ends at a committed task branch.
|
|
108
|
+
|
|
109
|
+
- **Run standalone:** stop here; a human triggers the gates.
|
|
110
|
+
- **Run by the orchestrator** (`sdlc-run`): this skill still just produces the committed branch and
|
|
111
|
+
signals success (or a scope/contract halt). The orchestrator records the `implement` step's status
|
|
112
|
+
and trust entry and, when `implement` is earned to `machine_advance` (Step D, Phase 4b), **auto-runs
|
|
113
|
+
the check gates** instead of waiting for a manual nudge. The diff still cannot merge without the
|
|
114
|
+
gates passing and the engineer review — Step D removes only the "now run the gates" hand-off.
|
|
115
|
+
|
|
116
|
+
### Step 8 — Record the `tasks` trust signal on first consume (Phase 4b)
|
|
117
|
+
Resolving a task from `tasks.md` (Step 1) is the moment the generated task list "survives contact" —
|
|
118
|
+
the evidence that could later earn the `tasks` step a `machine_advance`. When driven by `sdlc-run`,
|
|
119
|
+
finalize a `tasks` trust entry, anchored to what the human/dev actually did with the list:
|
|
120
|
+
- the task is implemented with its declared `Files:`/scope **as generated** → `approved-unchanged`;
|
|
121
|
+
- the task is **re-scoped** first (its `Files:`/boundary edited) → `approved-with-edits`
|
|
122
|
+
(signal `task_rescoped: true`);
|
|
123
|
+
- the task list is discarded / regenerated → `rejected`.
|
|
124
|
+
Append the entry to `epics/<epic>/.sdlc/trust-log.json` (schema:
|
|
125
|
+
`../sdlc-author-epic/references/state-schema.md`). `tasks` stays `human_approve` until its slice clears
|
|
126
|
+
the threshold — this only *gathers* evidence. (The `implement` step's own verdict is finalized later,
|
|
127
|
+
at the engineer review in `sdlc-ship`: merged as authored → `approved-unchanged`; edited first →
|
|
128
|
+
`approved-with-edits`; scope/contract/checks halt → `rejected`.)
|
|
129
|
+
|
|
130
|
+
## Hard rules (build plan §B, Cross-cutting)
|
|
131
|
+
|
|
132
|
+
- **One atomic task = one branch = one PR/MR.** Never bundle tasks; never exceed the declared files.
|
|
133
|
+
- **Light loop per task.** Do not re-run the heavy spec ceremony; that already ran once in Step A.
|
|
134
|
+
- **Never widen the contract here.** Surface changes go back to the architecture gate (Step 5).
|
|
135
|
+
- **The step never advances itself.** A scope overrun or contract touch always halts. The
|
|
136
|
+
`implement → checks` hand-off advances only when the orchestrator's `implement` dial is
|
|
137
|
+
`machine_advance` (earned, Step D) — and never past the engineer review, which is always human.
|
|
138
|
+
Standalone, the step stops at a committed branch.
|
|
139
|
+
|
|
140
|
+
## Reference
|
|
141
|
+
- Branch/commit conventions, the file-boundary rule, the Contract-Change rule: `references/implement-conventions.md`.
|
|
142
|
+
- The task list this step consumes: Step A's `references/spec-handoff.md` (`../sdlc-spec/...`).
|
|
143
|
+
- Contract surface + hash recipe: `../sdlc-author-architecture/references/contract-format.md`.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Implement conventions — branch, commit, file boundary, contract change
|
|
2
|
+
|
|
3
|
+
Step B (`sdlc-implement`) turns ONE atomic task into ONE branch and ONE commit in the code repo. These
|
|
4
|
+
conventions are what the later steps (check gates §C, PR template §D, review §E) rely on to trace a
|
|
5
|
+
diff back to its task, story, and contract.
|
|
6
|
+
|
|
7
|
+
## Branch naming
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
feat/<story-id>-<task-id>-<short-slug>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `<story-id>` — the permanent story ID, e.g. `EP-istifta-inquiries-S01`.
|
|
14
|
+
- `<task-id>` — the atomic task ID from `tasks.md`, e.g. `T01`.
|
|
15
|
+
- `<short-slug>` — 2–4 hyphenated words naming the change, e.g. `create-inquiry`.
|
|
16
|
+
|
|
17
|
+
Example: `feat/EP-istifta-inquiries-S01-T01-create-inquiry`. Branched off the code repo's default
|
|
18
|
+
branch. One task = one branch; never reuse a branch for a different task, never fork a second branch
|
|
19
|
+
for the same task.
|
|
20
|
+
|
|
21
|
+
## Commit message
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<type>: <subject ending with what changed>
|
|
25
|
+
|
|
26
|
+
<body — what and why, 1–3 lines>
|
|
27
|
+
|
|
28
|
+
Task: <story-id>-<task-id>
|
|
29
|
+
[Contract-Change: yes]
|
|
30
|
+
[Co-Authored-By: <AI name> <email>]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- The **`<type>` is lowercase** (`feat`, `fix`, `docs`, `refactor`, `test`, `perf`, `build`, `ci`,
|
|
34
|
+
`chore`, `revert`) and the **`<subject>` starts lowercase**, is **imperative**, and has **no trailing
|
|
35
|
+
period** — Conventional Commits (see `CONTRIBUTING.md` and `config.yaml` `build.commit_subject_style`).
|
|
36
|
+
Proper nouns/acronyms keep their case (`fix: refresh OAuth token`). e.g. `feat: add POST /inquiries
|
|
37
|
+
create path`, not `feat: Add POST /inquiries create path.`
|
|
38
|
+
- The **`Task:` trailer is required** (`Task: EP-istifta-inquiries-S01-T01`) — the anchor the spec-link
|
|
39
|
+
check (§C) and the PR (§D) read to connect the diff to its spec and story. It need not be the *last*
|
|
40
|
+
line: the spec-link gate finds it with git's native trailer parser
|
|
41
|
+
(`%(trailers:key=Task)`), which is order-independent. All trailers must sit in **one contiguous block**
|
|
42
|
+
in the last paragraph (no blank lines between them) so git parses them as trailers.
|
|
43
|
+
- **Trailer order:** `Task:` → `Contract-Change:` (if any) → `Co-Authored-By:` (if any), last.
|
|
44
|
+
- `Contract-Change: yes` appears **only** when the diff alters the locked contract surface (see below).
|
|
45
|
+
Omit it for normal implementation.
|
|
46
|
+
|
|
47
|
+
## Commit ownership & AI co-authors
|
|
48
|
+
|
|
49
|
+
The **human git author owns the commit** (`config.yaml` `build.commit_owner: git_author`). When an AI
|
|
50
|
+
tool assisted, record it **per commit** as a `Co-Authored-By` trailer — the AI is a co-author, **never**
|
|
51
|
+
the author. The owner picks the tool from `config.yaml` `build.ai_coauthor.allowed`:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
55
|
+
Co-Authored-By: GitHub Copilot <copilot@users.noreply.github.com>
|
|
56
|
+
Co-Authored-By: Cursor <noreply@cursor.com>
|
|
57
|
+
Co-Authored-By: CodeRabbit <noreply@coderabbit.ai>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Choose the entry whose `id` matches the tool that actually helped author the diff; add more than one
|
|
61
|
+
line if several did. CodeRabbit is a co-author only when it **contributed code**, not when it merely
|
|
62
|
+
reviewed (that is `ai_review` in `sdlc-ship`).
|
|
63
|
+
- For a fully human-authored commit, pick `id: none` — i.e. **omit** the trailer. `ai_coauthor.required`
|
|
64
|
+
is `false`, so a missing trailer is valid and no gate fails on it.
|
|
65
|
+
- `sdlc-implement` installs the `.gitmessage` template (`templates/.gitmessage`) and sets
|
|
66
|
+
`git config commit.template .gitmessage` in the repo, so these lines are pre-scaffolded (commented) for
|
|
67
|
+
the owner to uncomment.
|
|
68
|
+
|
|
69
|
+
## File-boundary rule (hard stop)
|
|
70
|
+
|
|
71
|
+
Each task in `tasks.md` declares a `Files:` list (≤3 where possible). The implementation diff must stay
|
|
72
|
+
**inside that list**. If the task genuinely needs a file not listed:
|
|
73
|
+
|
|
74
|
+
1. **Stop.** Do not widen the diff silently.
|
|
75
|
+
2. Report the extra file(s) needed.
|
|
76
|
+
3. Treat it as a **spec bug**: the task's declared files were wrong. Correct the task (re-run
|
|
77
|
+
`sdlc-spec` / re-scope) so the boundary is right, then implement.
|
|
78
|
+
|
|
79
|
+
A diff that quietly spreads beyond the declared files is the single easiest way to smuggle unreviewed
|
|
80
|
+
scope past the gates — hence the hard stop.
|
|
81
|
+
|
|
82
|
+
## Contract-change rule
|
|
83
|
+
|
|
84
|
+
The **locked contract surface** is the cross-repo agreement in `epics/<epic>/contract.md` (the
|
|
85
|
+
`CONTRACT-SURFACE` block, hash-locked at `.sdlc/contract-lock.json`). Distinguish:
|
|
86
|
+
|
|
87
|
+
- **Consuming the contract** (normal) — implementing an endpoint/event/entity to the shape the contract
|
|
88
|
+
already agreed (e.g. building `POST /inquiries` to its agreed request/response). **Not** a contract
|
|
89
|
+
change; no trailer.
|
|
90
|
+
- **Changing the contract** (exceptional) — altering the agreed shape itself (new field crossing repos,
|
|
91
|
+
changed status enum, new shared endpoint). This is **not** an implementation decision. Stop, go back
|
|
92
|
+
to the **architecture gate**, amend and re-lock `contract.md` (which re-escalates that review per the
|
|
93
|
+
contract `risk_tags`), and only then implement — recording `Contract-Change: yes` so the §C
|
|
94
|
+
contract-check finds the matching, already-updated contract.
|
|
95
|
+
|
|
96
|
+
This keeps the contract singular and owned upstream: a code repo can never widen the shared surface
|
|
97
|
+
from inside an implementation branch.
|
|
98
|
+
|
|
99
|
+
## Why one task at a time
|
|
100
|
+
|
|
101
|
+
Small diffs scoped to declared files are reviewable, revertable, and traceable. The heavy spec ceremony
|
|
102
|
+
(specify→tasks) ran once for the whole story in Step A; Step B is the **light loop** — repeat per task,
|
|
103
|
+
each its own branch and PR, each passing the gates independently.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# <type>: <subject — lowercase, imperative, no trailing period>
|
|
2
|
+
# types: feat fix docs refactor test perf build ci chore revert
|
|
3
|
+
#
|
|
4
|
+
# <body — what changed and why, 1–3 lines. Wrap at ~72 cols.>
|
|
5
|
+
#
|
|
6
|
+
# --- Trailers (keep them in ONE contiguous block below, no blank lines between) ---
|
|
7
|
+
# Task: is REQUIRED — it anchors the spec-link gate. Order: Task -> Contract-Change -> Co-Authored-By.
|
|
8
|
+
Task: <EP-slug-S0N-T0N>
|
|
9
|
+
# Contract-Change: yes # ONLY if this diff alters the locked contract surface (routes to architecture gate)
|
|
10
|
+
#
|
|
11
|
+
# Per-commit AI co-author (the HUMAN git author owns the commit; AI is only a co-author).
|
|
12
|
+
# Uncomment the line for the tool that actually helped author this diff (add more than one if needed).
|
|
13
|
+
# Leave all commented for a fully human-authored commit (id: none — the trailer is optional).
|
|
14
|
+
# Co-Authored-By: Claude <noreply@anthropic.com>
|
|
15
|
+
# Co-Authored-By: GitHub Copilot <copilot@users.noreply.github.com>
|
|
16
|
+
# Co-Authored-By: Cursor <noreply@cursor.com>
|
|
17
|
+
# Co-Authored-By: CodeRabbit <noreply@coderabbit.ai>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdlc-pr-template
|
|
3
|
+
description: 'Build-half Step D of the gated SDLC. Detect a code repo''s platform and commit the matching PR/MR template — .github/pull_request_template.md (GitHub) or .gitlab/merge_request_templates/Default.md (GitLab). The template carries an Impact & Risk block; a high risk level (or a touched contract/auth/payments surface) routes the review to domain owners, reusing sdlc-review-gate''s escalation. Includes risk-route.sh to print the required reviewers from a PR body. Never auto-advances. Use when the user says "add the PR template" or "set up the MR template" for a repo.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SDLC — PR/MR Template (build-half Step D)
|
|
7
|
+
|
|
8
|
+
**Goal:** Commit the platform-correct PR/MR template into a code repo so every PR/MR carries an
|
|
9
|
+
**Impact & Risk** block and a checklist tied to the check gates. A **high** risk level (or a touched
|
|
10
|
+
contract/auth/payments surface) **routes the review to domain owners** — the same escalation
|
|
11
|
+
`sdlc-review-gate` applies on the front-half gates (owner + 1 reviewer, plus one domain-owner per
|
|
12
|
+
touched domain). This step **never auto-advances**; it sets up the template and the routing helper.
|
|
13
|
+
|
|
14
|
+
## Conventions
|
|
15
|
+
|
|
16
|
+
- `{project-root}` resolves from the project working directory — the **product** repo (holds the
|
|
17
|
+
canonical templates under this skill).
|
|
18
|
+
- Code repos are separate git repos under `{project-root}/demo-repos/<repo>/`.
|
|
19
|
+
- Canonical sources live in this skill's `templates/`:
|
|
20
|
+
- `templates/github/pull_request_template.md` → installs to `<repo>/.github/pull_request_template.md`
|
|
21
|
+
- `templates/gitlab/merge_request_templates/Default.md` → installs to
|
|
22
|
+
`<repo>/.gitlab/merge_request_templates/Default.md`
|
|
23
|
+
- `templates/checks/risk-route.sh` → installs to `<repo>/checks/risk-route.sh` (advisory routing helper)
|
|
24
|
+
- **Hub variants** (`repo: hub`) — front-half artifact-review PR/MR bodies:
|
|
25
|
+
`templates/hub/github/pull_request_template.md` → `{project-root}/.github/pull_request_template.md`;
|
|
26
|
+
`templates/hub/gitlab/merge_request_templates/Default.md` →
|
|
27
|
+
`{project-root}/.gitlab/merge_request_templates/Default.md`. The hub body carries no `Task:` trailer
|
|
28
|
+
(hub PRs change artifacts, not code); its routing helper is `sdlc-hub-bridge`'s `hub-route.sh`.
|
|
29
|
+
- The Impact & Risk block reuses the conventions of earlier steps: the `Task: <story>-<task>` trailer
|
|
30
|
+
(`sdlc-implement`), the contract surface (`sdlc-author-architecture` / contract-check), and the
|
|
31
|
+
domain-owner escalation (`sdlc-review-gate`).
|
|
32
|
+
- **PR/MR title.** One atomic task = one branch = one PR/MR, so the title **defaults to that task's
|
|
33
|
+
commit subject** and follows the same Conventional Commits style — `<type>: <lowercase imperative
|
|
34
|
+
description, no trailing period>`, proper nouns/acronyms keep their case (`config.yaml`
|
|
35
|
+
`build.pr_title_style`; see `CONTRIBUTING.md`). Because PRs are squash-merged, the title becomes the
|
|
36
|
+
merge commit subject — so it must be the clean, lowercase-after-the-type form, not `Fix: ...` or a
|
|
37
|
+
trailing period.
|
|
38
|
+
|
|
39
|
+
## Inputs
|
|
40
|
+
|
|
41
|
+
- `repo` — the code repo to add the template to (one of an epic's repos), or `hub` for the product hub.
|
|
42
|
+
- `action` — `wire` (commit the matching template + helper) | `route` (print required reviewers from a
|
|
43
|
+
PR body). Default `wire`.
|
|
44
|
+
- `body` — for `route`: a file holding the PR/MR description to evaluate.
|
|
45
|
+
|
|
46
|
+
## On Activation
|
|
47
|
+
|
|
48
|
+
### Step 1 — Resolve the repo and detect the platform
|
|
49
|
+
Map `repo` → `{project-root}/demo-repos/<repo>/` (or the registry `path`). Detect the platform: a GitHub
|
|
50
|
+
remote or `.github/` → GitHub; a GitLab remote or `.gitlab/` → GitLab. If ambiguous, ask. For
|
|
51
|
+
`repo: hub`, the target is `{project-root}` itself and the platform comes from `.sdlc/hub.json`.
|
|
52
|
+
|
|
53
|
+
### Step 2 — `wire` (drop only the matching template)
|
|
54
|
+
Copy from this skill's `templates/`:
|
|
55
|
+
- GitHub → `templates/github/pull_request_template.md` to `<repo>/.github/pull_request_template.md`.
|
|
56
|
+
- GitLab → `templates/gitlab/merge_request_templates/Default.md` to
|
|
57
|
+
`<repo>/.gitlab/merge_request_templates/Default.md`.
|
|
58
|
+
- **`repo: hub`** → use the `templates/hub/<platform>/…` variants, installed into `{project-root}`'s own
|
|
59
|
+
`.github/`/`.gitlab/`. The hub's routing helper (`hub-route.sh`) is installed by `sdlc-hub-bridge`.
|
|
60
|
+
Drop **only the matching** template (drop both only if the repo genuinely uses both). For code repos also
|
|
61
|
+
install `templates/checks/risk-route.sh` to `<repo>/checks/` (`chmod +x`). If the target already has a
|
|
62
|
+
non-SDLC PR/MR template, do not clobber it — back it up / ask. Commit the template on the repo's default
|
|
63
|
+
branch (shared infrastructure, not a task diff).
|
|
64
|
+
|
|
65
|
+
### Step 3 — `route` (show who must review)
|
|
66
|
+
Run `bash checks/risk-route.sh <body>` to parse the PR description's Impact & Risk block and print the
|
|
67
|
+
required reviewers:
|
|
68
|
+
- **low | medium** risk → base rule: owner + 1 reviewer.
|
|
69
|
+
- **high** risk (or a contract/auth/payments surface touched) → base rule **plus** one domain-owner
|
|
70
|
+
approval per touched domain — identical to `sdlc-review-gate`'s escalation. The actual approvals are
|
|
71
|
+
recorded by the engineer review (Step E), via `sdlc-review-gate`.
|
|
72
|
+
|
|
73
|
+
### Step 4 — Stop (no auto-advance)
|
|
74
|
+
Report what was committed (or the routing result). The template and routing are advisory inputs to the
|
|
75
|
+
human review (Step E); they do not approve or merge. Do not touch the epic's `.sdlc/` state.
|
|
76
|
+
|
|
77
|
+
## Hard rules (build plan §D, Cross-cutting)
|
|
78
|
+
|
|
79
|
+
- **Drop only the matching template** for the detected platform.
|
|
80
|
+
- **High risk routes to domain owners** — the same escalation as the gate; never a separate rule.
|
|
81
|
+
- **Nothing auto-advances.** The template sets up review; the human owns the merge.
|
|
82
|
+
|
|
83
|
+
## Reference
|
|
84
|
+
- The Impact & Risk block, the risk levels, and the routing rule: `references/risk-routing.md`.
|
|
85
|
+
- The escalation this reuses: `../sdlc-review-gate/SKILL.md` and its `references/gating.md`.
|
|
86
|
+
- The check gates the checklist references: `../sdlc-checks/references/check-gates.md`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Impact & Risk block and review routing
|
|
2
|
+
|
|
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 changes pull in the right reviewers automatically —
|
|
5
|
+
reusing the escalation `sdlc-review-gate` already applies on the front-half gates.
|
|
6
|
+
|
|
7
|
+
## The Impact & Risk block
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
## Impact & Risk
|
|
11
|
+
- **Domains / repos touched:** <backend | mobile | …>
|
|
12
|
+
- **Contract surface touched:** no
|
|
13
|
+
- **Risk level:** low
|
|
14
|
+
- **Rollback plan:** <how to revert>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **Domains / repos touched** — the domains this change affects; these are the candidate domain owners
|
|
18
|
+
when the change escalates.
|
|
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 escalates
|
|
21
|
+
the review (contract is a `sdlc-review-gate` risk tag).
|
|
22
|
+
- **Risk level** — `low | medium | high`. The author's assessment of blast radius.
|
|
23
|
+
- **Rollback plan** — how to revert safely.
|
|
24
|
+
|
|
25
|
+
## Routing rule (reuses the gate's escalation)
|
|
26
|
+
|
|
27
|
+
| Condition | Required reviewers |
|
|
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** |
|
|
31
|
+
|
|
32
|
+
This is exactly `sdlc-review-gate`'s rule (`references/gating.md`): the base rule is owner + 1
|
|
33
|
+
reviewer; escalation adds a domain-owner per touched domain. The PR template applies the same logic at
|
|
34
|
+
the code-review boundary so a risky PR cannot be approved by just any two people. The **approvals are
|
|
35
|
+
recorded by the engineer review (Step E) through `sdlc-review-gate`** — the template and `risk-route.sh`
|
|
36
|
+
only *route* (advisory); they never approve or merge.
|
|
37
|
+
|
|
38
|
+
## risk-route.sh
|
|
39
|
+
|
|
40
|
+
`bash checks/risk-route.sh <pr-description-file>` parses the Impact & Risk block and prints the required
|
|
41
|
+
reviewers. Example (high risk, two domains):
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Risk level: high
|
|
45
|
+
Contract surface touched: no
|
|
46
|
+
Domains touched: backend, mobile
|
|
47
|
+
ROUTE: ESCALATED (risk: high) -> owner + 1 reviewer PLUS one domain-owner approval per touched domain
|
|
48
|
+
(same escalation as sdlc-review-gate). Required domain owners:
|
|
49
|
+
- domain-owner: backend
|
|
50
|
+
- domain-owner: mobile
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
It is **advisory** — not a blocking gate. CI may run it to comment the required reviewers; the human
|
|
54
|
+
review (Step E) still owns the merge.
|