yadflow 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +11 -2
  2. package/README.md +44 -22
  3. package/bin/yad.mjs +5 -0
  4. package/cli/doctor.mjs +1 -0
  5. package/cli/manifest.mjs +18 -2
  6. package/cli/ship.mjs +37 -0
  7. package/docs/index.html +44 -13
  8. package/package.json +2 -2
  9. package/skills/sdlc/config.yaml +7 -2
  10. package/skills/sdlc/install.sh +1 -1
  11. package/skills/sdlc/module-help.csv +7 -4
  12. package/skills/yad-checks/references/check-gates.md +58 -2
  13. package/skills/yad-checks/templates/checks/commit-message.sh +82 -0
  14. package/skills/yad-checks/templates/github/yad-checks.yml +27 -0
  15. package/skills/yad-checks/templates/github/yad-hub-checks.yml +36 -0
  16. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +20 -0
  17. package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +39 -0
  18. package/skills/yad-commit/SKILL.md +66 -0
  19. package/skills/yad-engineer-review/SKILL.md +86 -0
  20. package/skills/{yad-ship → yad-engineer-review}/references/ship-and-record.md +2 -2
  21. package/skills/{yad-ship → yad-engineer-review}/templates/.coderabbit.yaml +1 -1
  22. package/skills/yad-epic/references/state-schema.md +1 -1
  23. package/skills/yad-implement/SKILL.md +1 -1
  24. package/skills/yad-implement/references/implement-conventions.md +1 -1
  25. package/skills/yad-open-pr/SKILL.md +72 -0
  26. package/skills/yad-pr-template/templates/checks/pr-template.sh +62 -0
  27. package/skills/yad-pr-template/templates/checks/pr-title.sh +51 -0
  28. package/skills/yad-run/SKILL.md +2 -2
  29. package/skills/yad-run/references/run-loop.md +4 -4
  30. package/skills/yad-ship/SKILL.md +44 -66
  31. package/skills/yad-spec/SKILL.md +1 -1
@@ -12,6 +12,9 @@ repo uses. Each reads conventions established by earlier steps — it invents no
12
12
  | contract-check | changed files under `specs/<story>/contracts/`; the `Contract-Change: yes` trailer; `link.md`'s pinned `contract-lock`; the product repo's `contract-lock.json` | `yad-architecture` (lock), `yad-spec` (slice + link), `yad-implement` (trailer) |
13
13
  | build/test/lint | the repo's `npm run lint` / `npm run build` / `npm test` | the repo |
14
14
  | verified-commits | each commit's platform signature-verification status; the author email vs `.sdlc/verified-authors` | hub roster `email` fields (`yad check --fix` generates the allowlist) |
15
+ | commit-message | each non-merge commit's subject + trailer block | `yad-commit` / `CONTRIBUTING.md` (`config.yaml build.commit_subject_style`) |
16
+ | pr-title | the PR/MR title (from the CI event payload) | `yad-pr-template` (`config.yaml build.pr_title_style`) |
17
+ | pr-template | the PR/MR body (from the CI event payload) | `yad-pr-template` (the committed PR/MR template) |
15
18
 
16
19
  ## 1. spec-link (`templates/checks/spec-link.sh`)
17
20
 
@@ -84,16 +87,54 @@ Note the deliberate split with the gate-sync bot: this gate runs on **PRs/MRs on
84
87
  not subject to it. Do **not** replace it with a platform-level "reject unsigned pushes" rule on the
85
88
  default branch — that would break the event-driven gate sync (and GitLab push rules are Premium-only).
86
89
 
90
+ ## 5. commit-message (`templates/checks/commit-message.sh`)
91
+
92
+ The commit *pattern* gate (the presence-only `Task:` check is spec-link's; this checks SHAPE). For each
93
+ non-merge commit in `<base>..HEAD`:
94
+
95
+ - **Subject** must be `<type>: <description>` with `<type>` a known Conventional-Commits type
96
+ (`feat|fix|docs|refactor|test|perf|build|ci|chore|revert` — keep in sync with `cli/manifest.mjs`
97
+ `COMMIT_TYPES`) and **no trailing period** — mirroring `cli/commit.mjs` `buildCommitMessage`.
98
+ - **Trailers**, when present, appear in the fixed order `Task → Contract-Change → Co-Authored-By`.
99
+ - Merge/squash commits (2+ parents) are skipped — their platform-generated subjects are not authored.
100
+ - **Profiles** (`--profile code|hub`): the subject rule is identical on both; the gate never requires
101
+ the `Task:` trailer (spec-link owns that on code repos; hub commits are not task-scoped).
102
+ - **Fails closed** when `<base>` can't be resolved.
103
+
104
+ ## 6. pr-title (`templates/checks/pr-title.sh`)
105
+
106
+ The PR/MR title must follow the convention for the repo kind (title passed as the arg, injected by CI
107
+ from the event payload):
108
+
109
+ - `--profile code` (default) → a Conventional-Commits subject `<type>: <description>`, no trailing
110
+ period (`config.yaml build.pr_title_style: same_as_commit_subject` — one task = one PR, the title is
111
+ the squash-merge subject).
112
+ - `--profile hub` → a front-half artifact-review title `review: <artifact> (EP-<slug>)`, the shape
113
+ `yad gate open` creates.
114
+
115
+ ## 7. pr-template (`templates/checks/pr-template.sh`)
116
+
117
+ The PR/MR body must actually USE the committed template (body passed as a file, injected by CI) — this
118
+ catches a free-form description that bypassed it:
119
+
120
+ - `--profile code` (default) → requires `## Summary`, `## Impact & Risk`, `## Checklist`, and a filled
121
+ `Risk level:` (`low|medium|high`).
122
+ - `--profile hub` → requires `## Artifact under review`, `## Impact & Risk (front-half)`, `## Checklist`,
123
+ and a `Risk tags:` line.
124
+
87
125
  ## CI wiring (both platforms)
88
126
 
89
127
  The gates run identically under either CI; the config just invokes the scripts with the PR/MR base.
90
128
 
91
129
  - **GitHub Actions** — `templates/github/yad-checks.yml` → `.github/workflows/yad-checks.yml`. The
92
130
  jobs run on `pull_request` with `fetch-depth: 0`, passing `origin/${{ github.base_ref }}` as base
93
- (verified-commits also gets a read-only `GH_TOKEN` for the Verified-badge lookup).
131
+ (verified-commits also gets a read-only `GH_TOKEN` for the Verified-badge lookup). The pattern jobs
132
+ read the title/body from the event payload: `pr-title` takes `${{ github.event.pull_request.title }}`
133
+ and `pr-template` writes `${{ github.event.pull_request.body }}` to a temp file. All `--profile code`.
94
134
  - **GitLab CI** — `templates/gitlab/yad-checks.gitlab-ci.yml` → `.gitlab/ci/yad-checks.yml`, pulled in
95
135
  by the root `.gitlab-ci.yml`'s `include:`. The jobs run on `merge_request_event` with `GIT_DEPTH: 0`,
96
- passing `origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`.
136
+ passing `origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`; the pattern jobs read `$CI_MERGE_REQUEST_TITLE`
137
+ and `$CI_MERGE_REQUEST_DESCRIPTION`. All `--profile code`.
97
138
 
98
139
  ## Sync with existing CI (merge, never clobber)
99
140
 
@@ -148,6 +189,14 @@ plus a standalone workflow (`templates/github/yad-verified-commits.yml` →
148
189
  its one include line) whenever `.sdlc/hub.json` has a platform with the bridge enabled. So the
149
190
  front-half review PRs are held to the same rule as code-repo PRs: signed, known authors only.
150
191
 
192
+ The hub **also** runs the three pattern gates (`commit-message`, `pr-title`, `pr-template`) with
193
+ `--profile hub`, so the front-half review PRs follow the hub conventions — Conventional-Commits commit
194
+ subjects, a `review: <artifact> (EP-<slug>)` title, and a body that uses the hub artifact-review
195
+ template. `yad check --fix` installs the same `checks/*.sh` scripts plus a standalone hub workflow
196
+ (`templates/github/yad-hub-checks.yml` → `.github/workflows/yad-hub-checks.yml`, or the GitLab fragment
197
+ `templates/gitlab/yad-hub-checks.gitlab-ci.yml` → `.gitlab/ci/yad-hub-checks.yml` + its one include
198
+ line). Code repos run the same three with `--profile code` inside the main `yad-checks` workflow.
199
+
151
200
  ## Running by hand (Phase 3 is manual)
152
201
 
153
202
  From inside the code repo, against the PR/MR base (e.g. `master`):
@@ -157,6 +206,13 @@ bash checks/spec-link.sh master
157
206
  bash checks/contract-check.sh master
158
207
  bash checks/build-test-lint.sh
159
208
  bash checks/verified-commits.sh master # uses your own gh/glab auth for the signature lookup
209
+ bash checks/commit-message.sh --profile code master
210
+ # pr-title / pr-template validate the actual PR/MR metadata (in CI they come from the event payload).
211
+ # By hand, pass the title, and a FILE holding the PR/MR description (the rendered/filled body, not the
212
+ # template source):
213
+ bash checks/pr-title.sh --profile code "feat: add the inquiry endpoint"
214
+ # save the PR/MR description to a file first (e.g. `gh pr view <n> --json body -q .body > /tmp/pr-body.md`)
215
+ bash checks/pr-template.sh --profile code /tmp/pr-body.md
160
216
  ```
161
217
 
162
218
  ## Proven behavior (demo: `demo-repos/backend`, story EP-istifta-inquiries-S01)
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # commit-message gate.
3
+ # Every non-merge commit in the range under review must follow the project commit convention
4
+ # (CONTRIBUTING.md / config.yaml build — mirrors cli/commit.mjs buildCommitMessage):
5
+ # - subject is "<type>: <description>" where <type> is a known Conventional-Commits type,
6
+ # - the subject does NOT end with a period,
7
+ # - any trailers appear in the fixed order Task -> Contract-Change -> Co-Authored-By.
8
+ # Keep the type list in sync with cli/manifest.mjs COMMIT_TYPES and config.yaml build.commit_subject_style.
9
+ #
10
+ # Profiles (--profile code|hub, default code): the subject rule is identical on the product hub and on
11
+ # code repos (both follow CONTRIBUTING). The Task trailer is NOT required here (the spec-link gate owns
12
+ # that on code repos; hub commits are not task-scoped) — this gate only checks SHAPE and ORDER.
13
+ #
14
+ # Merge/squash commits (2+ parents) are skipped: their platform-generated subjects are not authored.
15
+ set -euo pipefail
16
+
17
+ PROFILE=code
18
+ ARGS=()
19
+ while [ $# -gt 0 ]; do
20
+ case "$1" in
21
+ --profile) PROFILE="${2:-code}"; shift 2 ;;
22
+ --profile=*) PROFILE="${1#*=}"; shift ;;
23
+ *) ARGS+=("$1"); shift ;;
24
+ esac
25
+ done
26
+ case "$PROFILE" in code|hub) ;; *) echo "FAIL [commit-message]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
27
+
28
+ BASE="${ARGS[0]:-${SDLC_BASE:-origin/main}}"
29
+
30
+ # Fail closed if the base ref can't be resolved (shallow clone / wrong base branch / unfetched ref).
31
+ if ! git rev-parse --verify --quiet "${BASE}^{commit}" >/dev/null; then
32
+ echo "FAIL [commit-message]: base ref '${BASE}' not found — fetch full history / check the base branch."
33
+ exit 1
34
+ fi
35
+ RANGE="${BASE}..HEAD"
36
+
37
+ # Conventional-Commits types — keep in sync with cli/manifest.mjs COMMIT_TYPES.
38
+ TYPES='feat|fix|docs|refactor|test|perf|build|ci|chore|revert'
39
+
40
+ commits="$(git rev-list --no-merges "$RANGE")"
41
+ if [ -z "$commits" ]; then
42
+ echo "PASS [commit-message]: no non-merge commits in ${RANGE} (profile: ${PROFILE})"
43
+ exit 0
44
+ fi
45
+
46
+ rc=0
47
+ while IFS= read -r sha; do
48
+ [ -z "$sha" ] && continue
49
+ short="$(git log -1 --format=%h "$sha")"
50
+ subject="$(git log -1 --format=%s "$sha")"
51
+
52
+ # 1) subject shape: "<type>(<optional scope>)<optional !>: <non-empty description>"
53
+ # (scope + breaking-change `!` are allowed per CONTRIBUTING.md).
54
+ if ! printf '%s' "$subject" | grep -qE "^(${TYPES})(\([a-z0-9._-]+\))?!?: .+"; then
55
+ echo "FAIL [commit-message]: ${short} subject '${subject}' is not '<type>(<scope>)?!?: <description>' (type one of: ${TYPES//|/, })."
56
+ rc=1
57
+ # 2) no trailing period on the subject
58
+ elif printf '%s' "$subject" | grep -qE '\.$'; then
59
+ echo "FAIL [commit-message]: ${short} subject '${subject}' must not end with a period."
60
+ rc=1
61
+ else
62
+ echo "PASS [commit-message]: ${short} '${subject}'"
63
+ fi
64
+
65
+ # 3) trailer order Task -> Contract-Change -> Co-Authored-By (only among those present).
66
+ # Parse ONLY the trailing trailer block (git interpret-trailers) so a body prose line that
67
+ # happens to start with a trailer key is never mistaken for a trailer.
68
+ trailers="$(git log -1 --format=%B "$sha" | git interpret-trailers --parse 2>/dev/null || true)"
69
+ lt="$(printf '%s\n' "$trailers" | grep -niE '^Task:' | head -1 | cut -d: -f1 || true)"
70
+ lc="$(printf '%s\n' "$trailers" | grep -niE '^Contract-Change:' | head -1 | cut -d: -f1 || true)"
71
+ lo="$(printf '%s\n' "$trailers" | grep -niE '^Co-Authored-By:' | head -1 | cut -d: -f1 || true)"
72
+ if { [ -n "$lt" ] && [ -n "$lc" ] && [ "$lt" -gt "$lc" ]; } \
73
+ || { [ -n "$lt" ] && [ -n "$lo" ] && [ "$lt" -gt "$lo" ]; } \
74
+ || { [ -n "$lc" ] && [ -n "$lo" ] && [ "$lc" -gt "$lo" ]; }; then
75
+ echo "FAIL [commit-message]: ${short} trailers out of order — expected Task -> Contract-Change -> Co-Authored-By."
76
+ rc=1
77
+ fi
78
+ done <<EOF
79
+ $commits
80
+ EOF
81
+
82
+ exit "$rc"
@@ -32,6 +32,33 @@ jobs:
32
32
  with: { node-version: "20" }
33
33
  - run: bash checks/build-test-lint.sh
34
34
 
35
+ # Pattern gates: commit subject + PR title + PR body all follow the convention (profile: code).
36
+ commit-message:
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ with: { fetch-depth: 0 }
41
+ - run: bash checks/commit-message.sh --profile code "origin/${{ github.base_ref }}"
42
+
43
+ # Pass the title via env (never interpolate untrusted ${{ }} into a run line — injection-safe).
44
+ pr-title:
45
+ runs-on: ubuntu-latest
46
+ env:
47
+ PR_TITLE: ${{ github.event.pull_request.title }}
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - run: bash checks/pr-title.sh --profile code "$PR_TITLE"
51
+
52
+ pr-template:
53
+ runs-on: ubuntu-latest
54
+ env:
55
+ PR_BODY: ${{ github.event.pull_request.body }}
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - run: |
59
+ body="$(mktemp)"; printf '%s' "$PR_BODY" > "$body"
60
+ bash checks/pr-template.sh --profile code "$body"
61
+
35
62
  # No unverified commits from unverified users: platform-Verified signature + allowlisted author.
36
63
  verified-commits:
37
64
  runs-on: ubuntu-latest
@@ -0,0 +1,36 @@
1
+ # yad-managed: yad-checks
2
+ # Pattern gates for the PRODUCT HUB: every PR (including the front-half review/EP-* PRs) must follow
3
+ # the hub conventions — Conventional-Commits commit subjects, a `review: <artifact> (EP-<slug>)` PR
4
+ # title, and a PR body that uses the hub artifact-review template. They run with `--profile hub`.
5
+ # Standalone workflow so it never collides with the code-repo yad-checks workflow.
6
+ name: yad-hub-checks
7
+ on:
8
+ pull_request:
9
+ branches: ["**"]
10
+
11
+ jobs:
12
+ commit-message:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with: { fetch-depth: 0 }
17
+ - run: bash checks/commit-message.sh --profile hub "origin/${{ github.base_ref }}"
18
+
19
+ # Pass the title via env (never interpolate untrusted ${{ }} into a run line — injection-safe).
20
+ pr-title:
21
+ runs-on: ubuntu-latest
22
+ env:
23
+ PR_TITLE: ${{ github.event.pull_request.title }}
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - run: bash checks/pr-title.sh --profile hub "$PR_TITLE"
27
+
28
+ pr-template:
29
+ runs-on: ubuntu-latest
30
+ env:
31
+ PR_BODY: ${{ github.event.pull_request.body }}
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - run: |
35
+ body="$(mktemp)"; printf '%s' "$PR_BODY" > "$body"
36
+ bash checks/pr-template.sh --profile hub "$body"
@@ -45,6 +45,26 @@ yad-build-test-lint:
45
45
  script:
46
46
  - bash checks/build-test-lint.sh
47
47
 
48
+ # Pattern gates: commit subject + MR title + MR body all follow the convention (profile: code).
49
+ yad-commit-message:
50
+ extends: .sdlc_mr_only
51
+ needs: []
52
+ script:
53
+ - bash checks/commit-message.sh --profile code "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
54
+
55
+ yad-pr-title:
56
+ extends: .sdlc_mr_only
57
+ needs: []
58
+ script:
59
+ - bash checks/pr-title.sh --profile code "$CI_MERGE_REQUEST_TITLE"
60
+
61
+ yad-pr-template:
62
+ extends: .sdlc_mr_only
63
+ needs: []
64
+ script:
65
+ - body="$(mktemp)"; printf '%s' "$CI_MERGE_REQUEST_DESCRIPTION" > "$body"
66
+ - bash checks/pr-template.sh --profile code "$body"
67
+
48
68
  # No unverified commits from unverified users: platform-Verified signature + allowlisted author.
49
69
  # Needs a CI/CD variable GITLAB_TOKEN (or SDLC_API_TOKEN) with read_api scope — CI_JOB_TOKEN cannot
50
70
  # read the commit-signature API. Without it the job FAILS closed with guidance.
@@ -0,0 +1,39 @@
1
+ # yad-managed-include: yad-checks
2
+ # Pattern gates for the PRODUCT HUB, as an INCLUDABLE fragment. Pulled into the hub's root
3
+ # .gitlab-ci.yml via:
4
+ # include:
5
+ # - local: '.gitlab/ci/yad-hub-checks.yml'
6
+ # Every MR (including the front-half review/EP-* MRs) must follow the hub conventions — a
7
+ # Conventional-Commits commit subject, a `review: <artifact> (EP-<slug>)` MR title, and an MR body
8
+ # that uses the hub artifact-review template. They run with `--profile hub`.
9
+ # Job names are yad-hub-prefixed so they coexist with any code-repo fragment in one pipeline; jobs use
10
+ # `needs: []` and no `stage:` so a foreign `stages:` list can neither break nor reorder them.
11
+ default:
12
+ image: node:20
13
+ tags: [$YAD_RUNNER_TAGS]
14
+
15
+ .yad_hub_mr_only:
16
+ rules:
17
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
18
+
19
+ variables:
20
+ GIT_DEPTH: "0" # full history so the commit gate can diff against the target branch
21
+
22
+ yad-hub-commit-message:
23
+ extends: .yad_hub_mr_only
24
+ needs: []
25
+ script:
26
+ - bash checks/commit-message.sh --profile hub "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
27
+
28
+ yad-hub-pr-title:
29
+ extends: .yad_hub_mr_only
30
+ needs: []
31
+ script:
32
+ - bash checks/pr-title.sh --profile hub "$CI_MERGE_REQUEST_TITLE"
33
+
34
+ yad-hub-pr-template:
35
+ extends: .yad_hub_mr_only
36
+ needs: []
37
+ script:
38
+ - body="$(mktemp)"; printf '%s' "$CI_MERGE_REQUEST_DESCRIPTION" > "$body"
39
+ - bash checks/pr-template.sh --profile hub "$body"
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: yad-commit
3
+ description: 'Build-half helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer block (Task → Contract-Change → Co-Authored-By), and an atomic-file guard (≤3 files). The human git author OWNS the commit; an assisting AI is recorded only as a Co-Authored-By footer, chosen per-commit with --ai (claude|copilot|cursor|coderabbit|none — default none, human-only). Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
4
+ ---
5
+
6
+ # SDLC — Commit by Convention (build-half helper)
7
+
8
+ **Goal:** Turn ONE staged atomic change into a single commit that satisfies the project conventions
9
+ (`CONTRIBUTING.md` / `config.yaml` `build`): a Conventional-Commits subject, the fixed trailer order
10
+ `Task → Contract-Change → Co-Authored-By`, and the atomic-file guard. This is the standalone commit
11
+ step — the same engine `yad-implement` and `yad-ship` use. It **never auto-advances**; it just commits.
12
+
13
+ ## Conventions
14
+
15
+ - Run **inside the repo holding the staged change** — a code repo under
16
+ `{project-root}/demo-repos/<repo>/`, or the product hub itself. Use absolute paths.
17
+ - **Stage first.** Only the staged (`git add`) atomic change is committed. The guard refuses more than
18
+ `ATOMIC_FILE_LIMIT` (3) staged files unless `--force` — split the change instead.
19
+ - **Subject** — `<type>: <lowercase imperative description, no trailing period>`; types are
20
+ `feat|fix|docs|refactor|test|perf|build|ci|chore|revert`; proper nouns/acronyms keep their case.
21
+ - **Task trailer** — required on a code repo (anchors the `spec-link` + `commit-message` gates). Given
22
+ with `--task`, else derived from the branch (`feat/<story>-<task>-…`). Hub commits are not
23
+ task-scoped, so the trailer is optional there.
24
+ - **Contract-Change trailer** — `--contract-change` only when the diff touches the locked contract
25
+ surface; it routes the change back to the architecture gate.
26
+ - **AI co-author footer** — `--ai <id>` records the assisting tool as a `Co-Authored-By` trailer. The
27
+ human is always the author; `--ai none` (the default) is an explicit human-only commit.
28
+
29
+ ## Inputs
30
+
31
+ - `type` — Conventional-Commits type (required).
32
+ - `message` — the subject text (required), `-m "<subject>"`.
33
+ - `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none`).
34
+ - `task` — Task trailer (optional; derived from the branch when omitted).
35
+ - `contractChange` — flag; mark the contract surface touched.
36
+
37
+ ## On Activation
38
+
39
+ ### Step 1 — Confirm the atomic stage
40
+ Confirm the change is staged and stays within the file boundary (≤3 files where possible). If more is
41
+ staged, split it into separate commits rather than passing `--force`.
42
+
43
+ ### Step 2 — Commit by convention
44
+ Run the CLI from the repo root:
45
+ ```
46
+ yad commit --type <type> -m "<subject>" [--ai <id>] [--task <id>] [--contract-change] [--dry-run]
47
+ ```
48
+ Use `--dry-run` first to preview the exact message (subject + trailer block) without committing. The
49
+ CLI validates the type, rejects a trailing period, and emits the trailers in the fixed order.
50
+
51
+ ### Step 3 — Stop (no auto-advance)
52
+ Report what was committed (files + Task). If `--contract-change` was set, note that it routes back to
53
+ the architecture gate. To also open the PR/MR in the same step, use `yad-ship`.
54
+
55
+ ## Hard rules
56
+
57
+ - **One staged atomic change = one commit.** Never bundle; never exceed the file boundary silently.
58
+ - **The human author owns the commit.** The AI is only a `Co-Authored-By` footer, chosen per-commit.
59
+ - **Trailer order is fixed:** `Task → Contract-Change → Co-Authored-By`.
60
+ - **Never widen the contract here.** A contract touch is flagged (`--contract-change`), not hidden.
61
+
62
+ ## Reference
63
+ - Branch/commit conventions + the file-boundary rule: `../yad-implement/references/implement-conventions.md`.
64
+ - The full convention text: `CONTRIBUTING.md`; the config: `skills/sdlc/config.yaml` `build`.
65
+ - The gate that enforces the subject pattern: `../yad-checks/references/check-gates.md` (`commit-message`).
66
+ - Open the PR/MR after committing: `../yad-open-pr/SKILL.md`; both at once: `../yad-ship/SKILL.md`.
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: yad-engineer-review
3
+ description: 'Build-half Step E of the gated SDLC — AI review, engineer review, then merge. Wire an advisory AI first-pass (CodeRabbit) on the PR/MR; record the human engineer review with the same human_approve discipline as the front gates (owner + 1 reviewer, escalating to domain owners on high risk / contract / auth / payments — the Step D routing); and on merge, record the ship in the epic build-log and update the story state so the epic → story → task → PR chain is traceable. Never auto-advances — the human owns the merge. Use when the user says "record the engineer review", "merge this task", or "wire the AI review". (To commit + open the PR/MR, use yad-ship.)'
4
+ ---
5
+
6
+ # SDLC — Engineer Review & Merge (build-half Step E)
7
+
8
+ **Goal:** Take a task PR/MR that has passed the **check gates** (Step C) through two sets of eyes and
9
+ out to production: an **AI first-pass** (advisory) and a **human engineer review** (the authority),
10
+ then **ship** — merge, record the ship, and update the story state. This is the last build-half step
11
+ (build plan §E). It is a **human gate**, the same `human_approve` discipline as the front states:
12
+ **nothing auto-advances**; the engineer owns the merge.
13
+
14
+ ## Conventions
15
+
16
+ - `{project-root}` resolves from the project working directory — the **product** repo (the source of
17
+ truth: it holds the story and the build ledger).
18
+ - Code repos are separate git repos under `{project-root}/demo-repos/<repo>/`.
19
+ - The build ledger is `{project-root}/epics/<epic>/.sdlc/build-log.json` (append-only).
20
+ - The engineer-review rule reuses `yad-review-gate`: base = at least one `owner` AND one distinct
21
+ `reviewer`; **escalated** (the PR's Impact & Risk is `high`, or it touches contract/auth/payments) =
22
+ base PLUS one `domain-owner` per touched domain — the same routing `yad-pr-template`'s
23
+ `risk-route.sh` prints.
24
+ - AI review wiring: `templates/.coderabbit.yaml` → `<repo>/.coderabbit.yaml`.
25
+
26
+ ## Inputs
27
+
28
+ - `epic` / `story` / `task` / `repo` — the PR under review (the task branch `feat/<story>-<task>-…`).
29
+ - `action` — `ai-review` | `approve` | `ship` (default `ai-review`).
30
+ - For `approve`: the reviewer `name` and `role` (`owner` | `reviewer` | `domain-owner`), and for a
31
+ domain owner the `domain`.
32
+
33
+ ## On Activation
34
+
35
+ ### Step 1 — `ai-review` (advisory first pass)
36
+ Ensure the AI reviewer is wired: copy `templates/.coderabbit.yaml` to `<repo>/.coderabbit.yaml` (commit
37
+ on the default branch if missing). CodeRabbit reviews each PR automatically and posts comments — it is
38
+ a **second set of eyes, never the authority**: it cannot approve or merge. Where CodeRabbit can't run
39
+ (no remote), run an equivalent AI first-pass by hand and capture its notes. Record that the AI review
40
+ ran; surface its findings to the engineer. Do **not** treat AI approval as a gate.
41
+
42
+ ### Step 2 — `approve` (the engineer review — the human gate)
43
+ A human engineer reads the diff **against the spec** (`specs/<story>/`) and the acceptance criteria,
44
+ and records an approval. Determine the rule from the PR's Impact & Risk block (run
45
+ `../yad-pr-template/templates/checks/risk-route.sh` on the PR body): base, or escalated to a
46
+ domain-owner per touched domain. Record each approval; re-evaluate whether the rule is satisfied.
47
+ Recording an approval does **not** ship — shipping is a separate, explicit step. Front-half discipline:
48
+ the gate talks only through files; refuse to treat AI review as a human approval.
49
+
50
+ ### Step 3 — `ship` (merge + record + update state)
51
+ Ship **iff ALL hold**: the check gates pass (Step C), the AI review has run (advisory), and the
52
+ engineer-review rule is satisfied (Step 2). Then:
53
+ - **Merge** the task branch into the repo's default branch (the human performs/authorises the merge).
54
+ - **Record the ship** — append to `epics/<epic>/.sdlc/build-log.json`:
55
+ ```json
56
+ { "story": "<story>", "task": "<task>", "repo": "<repo>", "branch": "feat/<story>-<task>-…",
57
+ "pr": "<url|#>", "mergeCommit": "<sha>", "gates": ["spec-link","contract-check","build-test-lint"],
58
+ "ai_review": "coderabbit (advisory)", "engineer_review": [{"approver":"<name>","role":"<role>","domain":"<opt>"}],
59
+ "risk": "<low|medium|high>", "shippedAt": "<YYYY-MM-DD>" }
60
+ ```
61
+ - **Update the story state** — when **every** task in `specs/<story>/tasks.md` has a ship record, set
62
+ the story frontmatter `status: shipped`; otherwise `status: in-build`. The chain
63
+ **epic → story → task → PR → mergeCommit** is now traceable end to end.
64
+ - **Finalize the trust verdict (Phase 4).** If this story has a `build-state/<story>.json` (it ran
65
+ through `yad-run`), the engineer **confirms or overrides** the provisional trust verdict that the
66
+ orchestrator derived for this run, and the final verdict is written to
67
+ `epics/<epic>/.sdlc/trust-log.json`. The human has the last word on the trust signal: a diff merged
68
+ as authored is `approved-unchanged`; one the engineer edited before merge is `approved-with-edits`;
69
+ a rejected one is `rejected`. This is the evidence that later earns a step its `machine_advance`
70
+ (it never weakens the merge gate — the engineer still owns the merge).
71
+
72
+ ### Step 4 — Stop
73
+ Report what shipped and the story's state. Do not advance anything else; the front-half `state.json`
74
+ stays as it was (`ready-for-build`). The build half is recorded in `build-log.json` + the story status.
75
+
76
+ ## Hard rules (build plan §E, Cross-cutting)
77
+
78
+ - **AI review is advisory, never the authority.** Only a human engineer approval counts toward the gate.
79
+ - **High risk routes to domain owners** — the same escalation as `yad-review-gate` / `risk-route.sh`.
80
+ - **Ship only after gates + engineer review.** No gate skipped; the human owns the merge.
81
+ - **Nothing auto-advances.** Step E records human decisions in files; it never machine-advances.
82
+
83
+ ## Reference
84
+ - The build ledger + story-state rules: `references/ship-and-record.md`.
85
+ - The escalation reused: `../yad-review-gate/SKILL.md`; the routing helper: `../yad-pr-template/`.
86
+ - The gates that must pass first: `../yad-checks/references/check-gates.md`.
@@ -1,6 +1,6 @@
1
1
  # Ship — the build ledger and the story state
2
2
 
3
- Step E (`yad-ship`) closes the build half: AI review (advisory) → engineer review (the human gate) →
3
+ Step E (`yad-engineer-review`) closes the build half: AI review (advisory) → engineer review (the human gate) →
4
4
  ship. Shipping records the merge and updates the story state so the whole chain is traceable.
5
5
 
6
6
  ## Two sets of eyes
@@ -64,4 +64,4 @@ trailer → story → epic).
64
64
  2. The **AI review** has run (advisory; findings surfaced to the engineer).
65
65
  3. The **engineer review** rule is satisfied (base or escalated per the Impact & Risk block).
66
66
 
67
- Only then does the human merge and `yad-ship` record it. Nothing auto-advances.
67
+ Only then does the human merge and `yad-engineer-review` record it. Nothing auto-advances.
@@ -1,6 +1,6 @@
1
1
  # CodeRabbit — AI first-pass review (Phase 3 build plan §E).
2
2
  # Advisory only: a second set of eyes that COMMENTS on every PR. It never approves or merges — the
3
- # human engineer review (yad-review-gate discipline) owns that decision. See skills/yad-ship.
3
+ # human engineer review (yad-review-gate discipline) owns that decision. See skills/yad-engineer-review.
4
4
  language: en
5
5
  reviews:
6
6
  request_changes_workflow: false # never block the merge — the engineer review is the gate
@@ -216,7 +216,7 @@ base** that decides when a step is safe to automate (build plan Step A). One ent
216
216
  |------|---------|-------------------------------|
217
217
  | `spec` | `human_edited_spec` | the human who accepts `specs/<story>/` (`yad-spec` Step 8) |
218
218
  | `tasks` | `task_rescoped` | first consume by `yad-implement` (Step 8) |
219
- | `implement` | `human_edited_diff`, `scope_overrun`, `contract_touch` | engineer review at `yad-ship` |
219
+ | `implement` | `human_edited_diff`, `scope_overrun`, `contract_touch` | engineer review at `yad-engineer-review` |
220
220
  | `checks` | `checks` (`pass`\|`fail`) | the gate run itself (objective) |
221
221
 
222
222
  **Deriving the provisional verdict** (build plan Step A; extended for `spec`/`tasks` in Phase 4b — the
@@ -124,7 +124,7 @@ finalize a `tasks` trust entry, anchored to what the human/dev actually did with
124
124
  Append the entry to `epics/<epic>/.sdlc/trust-log.json` (schema:
125
125
  `../yad-epic/references/state-schema.md`). `tasks` stays `human_approve` until its slice clears
126
126
  the threshold — this only *gathers* evidence. (The `implement` step's own verdict is finalized later,
127
- at the engineer review in `yad-ship`: merged as authored → `approved-unchanged`; edited first →
127
+ at the engineer review in `yad-engineer-review`: merged as authored → `approved-unchanged`; edited first →
128
128
  `approved-with-edits`; scope/contract/checks halt → `rejected`.)
129
129
 
130
130
  ## Hard rules (build plan §B, Cross-cutting)
@@ -59,7 +59,7 @@ Co-Authored-By: CodeRabbit <noreply@coderabbit.ai>
59
59
 
60
60
  - Choose the entry whose `id` matches the tool that actually helped author the diff; add more than one
61
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 `yad-ship`).
62
+ reviewed (that is `ai_review` in `yad-engineer-review`).
63
63
  - For a fully human-authored commit, pick `id: none` — i.e. **omit** the trailer. `ai_coauthor.required`
64
64
  is `false`, so a missing trailer is valid and no gate fails on it.
65
65
  - `yad-implement` installs the `.gitmessage` template (`templates/.gitmessage`) and sets
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: yad-open-pr
3
+ description: 'Build-half helper of the gated SDLC. Open a code-repo task PR/MR from the committed platform template — detect GitHub/GitLab, push the current task branch, and create the PR/MR with the template body prefilled (Summary / Story-task / Impact & Risk) and the title defaulting to the commit subject. Auto-assigns from the hub roster: assignee = the committer, reviewers = the repo''s reviewers + domain-owners. High risk / contract surface routes to domain owners (risk-route.sh). Drives the `yad open-pr` CLI; never merges. Use when the user says "open the PR", "open the MR", or "raise the merge request".'
4
+ ---
5
+
6
+ # SDLC — Open Task PR/MR (build-half helper)
7
+
8
+ **Goal:** Open the PR/MR for the current task branch from the repo's committed PR/MR template
9
+ (installed by `yad-pr-template`, Step D), with the body prefilled and the right reviewers requested.
10
+ This is the standalone open-PR step; it **never merges** — the engineer review (`yad-engineer-review`,
11
+ Step E) owns the merge. Distinct from `yad gate open`, which opens a front-half artifact-review PR on
12
+ the product hub.
13
+
14
+ ## Conventions
15
+
16
+ - Run **inside the code repo** under `{project-root}/demo-repos/<repo>/` (or pass `--repo <name>` to
17
+ resolve it from `.sdlc/repos.json`). The branch must be the task branch, not the default branch.
18
+ - **Platform** is detected from the `origin` remote (or the registry / `--platform`).
19
+ - **Title** — defaults to the last commit subject (one atomic task = one branch = one PR/MR), so it
20
+ follows the same Conventional-Commits style and passes the `pr-title` gate. Override with `--title`.
21
+ - **Body** — the committed template (`.github/pull_request_template.md` /
22
+ `.gitlab/merge_request_templates/Default.md`) with `Task:`, `Risk level:`, `Contract surface
23
+ touched:`, and `Domains` prefilled; the rest is left for the author. This satisfies the `pr-template`
24
+ gate.
25
+ - **Auto-assign** — from the hub roster scoped to this repo: assignee = the committer (resolved from
26
+ the local git identity), reviewers = the repo's `reviewer`/`domain-owner` logins minus the committer.
27
+ Degrades cleanly when there is no roster.
28
+ - **Routing** — `low`/`medium` → base rule (owner + 1 reviewer); `high` (or a touched
29
+ contract/auth/payments surface) → plus one domain-owner per touched domain. `bash
30
+ checks/risk-route.sh <body>` prints the required reviewers.
31
+
32
+ ## Inputs
33
+
34
+ - `repo` — target a registered repo by name (optional; else the current dir).
35
+ - `risk` — `low|medium|high` (default `low`); prefilled into the body.
36
+ - `contractChange` — flag; marks the contract surface touched and triggers escalation.
37
+ - `base` / `platform` / `title` — optional overrides.
38
+
39
+ ## On Activation
40
+
41
+ ### Step 1 — Confirm the branch and template
42
+ Confirm you are on the task branch (not the default branch) and that the PR/MR template is committed
43
+ (if not, run `yad-pr-template` first). The branch's commits should already carry the `Task:` trailer.
44
+
45
+ ### Step 2 — Open the PR/MR
46
+ Run from the repo root:
47
+ ```
48
+ yad open-pr [--repo <name>] [--risk <level>] [--contract-change] [--title "<subject>"]
49
+ ```
50
+ The CLI pushes the branch (sets upstream, the user's own auth), fills the template, and creates the
51
+ PR/MR with the auto-assigned assignee + reviewers.
52
+
53
+ ### Step 3 — Route the review (if escalated)
54
+ On `high` risk or a contract touch, run `bash checks/risk-route.sh <pr-body>` to print the required
55
+ domain-owner reviewers — the same escalation `yad-engineer-review` enforces.
56
+
57
+ ### Step 4 — Stop (no merge)
58
+ Report the PR/MR URL and the requested reviewers. The PR now runs the check gates (Step C); the human
59
+ engineer review and merge happen in `yad-engineer-review` (Step E).
60
+
61
+ ## Hard rules
62
+
63
+ - **One task = one branch = one PR/MR.** Never open a PR from the default branch.
64
+ - **Title follows the commit subject** — Conventional-Commits style, so the `pr-title` gate passes.
65
+ - **High risk routes to domain owners** — the same escalation as the gate; never a separate rule.
66
+ - **Opening a PR never merges.** The human owns the merge in Step E.
67
+
68
+ ## Reference
69
+ - The PR/MR template + the Impact & Risk block + routing: `../yad-pr-template/references/risk-routing.md`.
70
+ - The gates the PR must pass: `../yad-checks/references/check-gates.md` (incl. `pr-title`, `pr-template`).
71
+ - Commit first: `../yad-commit/SKILL.md`; commit + open in one step: `../yad-ship/SKILL.md`.
72
+ - The engineer review + merge that follow: `../yad-engineer-review/SKILL.md`.