yadflow 2.4.2 → 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.
- package/CHANGELOG.md +11 -2
- package/README.md +44 -22
- package/bin/yad.mjs +5 -0
- package/cli/doctor.mjs +14 -2
- package/cli/gate.mjs +8 -3
- package/cli/manifest.mjs +18 -2
- package/cli/openpr.mjs +12 -2
- package/cli/platform.mjs +105 -11
- package/cli/setup.mjs +61 -7
- package/cli/ship.mjs +37 -0
- package/docs/index.html +47 -16
- package/package.json +2 -2
- package/skills/sdlc/config.yaml +7 -2
- package/skills/sdlc/install.sh +1 -1
- package/skills/sdlc/module-help.csv +7 -4
- package/skills/yad-checks/references/check-gates.md +58 -2
- package/skills/yad-checks/templates/checks/commit-message.sh +82 -0
- package/skills/yad-checks/templates/github/yad-checks.yml +27 -0
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +36 -0
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +20 -0
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +39 -0
- package/skills/yad-commit/SKILL.md +66 -0
- package/skills/yad-connect-repos/SKILL.md +14 -7
- package/skills/yad-connect-repos/references/hub-config.md +20 -11
- package/skills/yad-connect-repos/references/repos-registry.md +2 -1
- package/skills/yad-engineer-review/SKILL.md +86 -0
- package/skills/{yad-ship → yad-engineer-review}/references/ship-and-record.md +2 -2
- package/skills/{yad-ship → yad-engineer-review}/templates/.coderabbit.yaml +1 -1
- package/skills/yad-epic/references/state-schema.md +1 -1
- package/skills/yad-hub-bridge/references/login-roster.md +32 -5
- package/skills/yad-implement/SKILL.md +1 -1
- package/skills/yad-implement/references/implement-conventions.md +1 -1
- package/skills/yad-open-pr/SKILL.md +72 -0
- package/skills/yad-pr-template/SKILL.md +5 -0
- package/skills/yad-pr-template/templates/checks/pr-template.sh +62 -0
- package/skills/yad-pr-template/templates/checks/pr-title.sh +51 -0
- package/skills/yad-review-gate/references/gating.md +8 -2
- package/skills/yad-run/SKILL.md +2 -2
- package/skills/yad-run/references/run-loop.md +4 -4
- package/skills/yad-ship/SKILL.md +44 -66
- package/skills/yad-spec/SKILL.md +1 -1
|
@@ -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`.
|
|
@@ -30,10 +30,13 @@ epic's approvals. It only writes the project-wide registry and the per-repo cont
|
|
|
30
30
|
|
|
31
31
|
- `action` — `connect` | `refresh` | `list` | `disconnect` | `detect-hub` | `roster` (default `connect`).
|
|
32
32
|
- `repo` — the repo's short name (the key used in stories' `repos:` tag, e.g. `backend`).
|
|
33
|
-
- `login`, `name`, `
|
|
33
|
+
- `login`, `name`, `email`, `roles` — for `roster` (map login → name + commit email + the per-scope
|
|
34
|
+
`roles` map, e.g. `roles: hub=owner,reviewer backend=domain-owner`). Validate the login against the
|
|
35
|
+
hub (`gh api users/<login>` / `glab api users?username=`); a miss is flagged `unverified` (warn-only).
|
|
34
36
|
- `path` — local path to the code repo (relative to `{project-root}` or absolute). For local repos.
|
|
35
37
|
- `git_url` — optional remote (SSH or HTTPS; GitHub or GitLab). Used when the repo is not yet on disk.
|
|
36
|
-
- `
|
|
38
|
+
- `domain_owners` — the engineer(s) who own this repo's domain (a repo may have several; drives per-repo
|
|
39
|
+
review routing). Each name is also written into that person's `roles[<repo>]` map in `hub.json`.
|
|
37
40
|
|
|
38
41
|
## On Activation
|
|
39
42
|
|
|
@@ -83,7 +86,8 @@ HEAD sha as `syncedHead` (this drives staleness):
|
|
|
83
86
|
"path": "<path rel. to project-root>",
|
|
84
87
|
"git_url": "<url or null>",
|
|
85
88
|
"platform": "github|gitlab|null",
|
|
86
|
-
"
|
|
89
|
+
"domain_owners": ["<owner>", "…"],
|
|
90
|
+
"domain_owner": "<domain_owners[0] — legacy mirror>",
|
|
87
91
|
"default_branch": "<branch>",
|
|
88
92
|
"connectedAt": "<YYYY-MM-DD>",
|
|
89
93
|
"lastSyncedAt": "<YYYY-MM-DD>",
|
|
@@ -122,10 +126,13 @@ write only `{project-root}/.sdlc/hub.json` (`config.yaml` `hub.config`) — neve
|
|
|
122
126
|
repos: `github.com` → `github`, GitLab host → `gitlab`, no remote → `platform: null`. Record
|
|
123
127
|
`git_url`, `default_branch`, `detectedAt`, and `bridge_enabled: true` (preserve an existing roster).
|
|
124
128
|
Auth is the local user's own `gh`/`glab`/git; **store no tokens**. Idempotent — safe to re-run.
|
|
125
|
-
- **`roster`** — set one roster entry mapping a platform `login` → SDLC `name` + `
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
- **`roster`** — set one roster entry mapping a platform `login` → SDLC `name` + `email` + a per-scope
|
|
130
|
+
`roles` map (`roles: { hub: ["owner","reviewer"], <repo>: ["domain-owner", …] }`). Upsert by `login`;
|
|
131
|
+
a person may hold several roles across several scopes, and a repo several people per role. Validate the
|
|
132
|
+
`login` against the hub (warn-only; flag `unverified` on a miss). `domain-owner` is written into
|
|
133
|
+
`roles[<repo>]` (and still **derived** as a fallback when a roster `name` equals a repo's
|
|
134
|
+
`domain_owner`/`domain_owners` in `repos.json` — see `references/hub-config.md`). An unmapped login
|
|
135
|
+
degrades to a plain `reviewer`, never auto-promoted to owner/domain-owner.
|
|
129
136
|
|
|
130
137
|
If the hub has no remote (`platform: null`) or the bridge is disabled, the front-half gate runs
|
|
131
138
|
file-only with no error — the bridge is purely additive.
|
|
@@ -21,27 +21,36 @@ login to an SDLC name + role. It is a single object for the hub itself — the s
|
|
|
21
21
|
"bridge_enabled": true, // open review PRs/MRs on the hub for front-half reviews
|
|
22
22
|
"detectedAt": "2026-06-08", // last detect-hub run (YYYY-MM-DD)
|
|
23
23
|
"roster": [
|
|
24
|
-
{ "login": "abdelrahmannasr", "name": "alice", "
|
|
25
|
-
|
|
24
|
+
{ "login": "abdelrahmannasr", "name": "alice", "email": "alice@example.com",
|
|
25
|
+
"roles": { "hub": ["owner", "reviewer"] } },
|
|
26
|
+
{ "login": "carol-gh", "name": "carol", "email": "carol@example.com",
|
|
27
|
+
"roles": { "hub": ["reviewer"], "backend": ["domain-owner", "owner"], "payments": ["reviewer"] } }
|
|
26
28
|
]
|
|
27
29
|
}
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
## The roster — login → name →
|
|
32
|
+
## The roster — login → name → per-scope roles
|
|
31
33
|
|
|
32
|
-
The roster is how a platform identity (a GitHub/GitLab **login**) becomes an SDLC **name + role** in
|
|
33
|
-
file ledger (`approvals.json` / `comments.json`). Roles are the same three the gate
|
|
34
|
+
The roster is how a platform identity (a GitHub/GitLab **login**) becomes an SDLC **name + role(s)** in
|
|
35
|
+
the file ledger (`approvals.json` / `comments.json`). Roles are the same three the gate uses:
|
|
34
36
|
`owner | reviewer | domain-owner`.
|
|
35
37
|
|
|
36
38
|
- **`login`** — the platform username whose PR review / approval is being mapped.
|
|
37
39
|
- **`name`** — the SDLC name written into the ledger (the same names used across `approvals.json`,
|
|
38
40
|
`comments.json`, and `epic.md` `owner`). Keep it stable.
|
|
39
|
-
- **`
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
- **`email`** — the commit email; drives the **committer → login** reverse lookup that auto-assigns PRs.
|
|
42
|
+
- **`roles`** — a **per-scope map**: scope (`hub`, or a connected repo name) → the roles held there. A
|
|
43
|
+
person can be **owner + reviewer + domain-owner at once** and across scopes; a repo gets **several**
|
|
44
|
+
people per role by appearing in several entries' maps. Validated against the hub during `yad setup` /
|
|
45
|
+
`yad doctor`; a login that does not resolve is flagged `unverified` (warn-only, never blocks).
|
|
46
|
+
|
|
47
|
+
**Back-compat:** readers also accept a flat array `"roles": ["owner","reviewer"]` (treated as `hub`
|
|
48
|
+
roles) and the legacy single `"role": "owner"` (a `hub` role).
|
|
49
|
+
|
|
50
|
+
**`domain-owner` may also be DERIVED from `repos.json`.** A roster entry whose `name` equals a repo's
|
|
51
|
+
`domain_owner`/`domain_owners` in `repos.json` is treated as that repo's domain-owner **when that repo is
|
|
52
|
+
a touched domain for the step under review** — kept as a fallback so pre per-scope projects still resolve.
|
|
53
|
+
New setups write the grant directly into the person's `roles[<repo>]` map.
|
|
45
54
|
|
|
46
55
|
- **Unmapped login fallback.** A login absent from the roster maps to `name: <login>`, `role: reviewer`,
|
|
47
56
|
and is flagged `<!-- unverified login: <login> -->` in the review record (mirrors the code-map
|
|
@@ -21,7 +21,8 @@ not under any `epics/EP-<slug>/.sdlc/`.
|
|
|
21
21
|
"path": "demo-repos/backend", // path to the code repo, rel. to {project-root} (or absolute)
|
|
22
22
|
"git_url": "git@github.com:org/backend.git", // optional remote; SSH or HTTPS; GitHub or GitLab; null if local-only
|
|
23
23
|
"platform": "github", // github | gitlab (from the URL host); null when local-only
|
|
24
|
-
"
|
|
24
|
+
"domain_owners": ["carol", "dave"], // engineers who own this repo's domain (review routing); a repo may have several
|
|
25
|
+
"domain_owner": "carol", // legacy single-owner mirror = domain_owners[0] (kept for back-compat readers)
|
|
25
26
|
"default_branch": "main",
|
|
26
27
|
"connectedAt": "2026-06-08", // first connect (YYYY-MM-DD)
|
|
27
28
|
"lastSyncedAt": "2026-06-08", // last connect/refresh
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
@@ -7,19 +7,33 @@ in `../../yad-connect-repos/references/hub-config.md`; this file covers how the
|
|
|
7
7
|
## Entry
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
|
-
{
|
|
10
|
+
{
|
|
11
|
+
"login": "abdelrahmannasr",
|
|
12
|
+
"name": "alice",
|
|
13
|
+
"email": "alice@example.com",
|
|
14
|
+
"roles": { "hub": ["owner", "reviewer"], "backend": ["domain-owner"] }
|
|
15
|
+
}
|
|
11
16
|
```
|
|
12
17
|
|
|
13
18
|
- `login` — the GitHub/GitLab username whose review/approval is being mapped.
|
|
14
19
|
- `name` — the SDLC name written to `approvals.json` / `comments.json` (the same names as `epic.md`
|
|
15
20
|
`owner` and `repos.json` `domain_owner`). Keep it stable.
|
|
16
|
-
- `
|
|
21
|
+
- `email` — the commit email; drives the **committer → login** reverse lookup used to auto-assign PRs.
|
|
22
|
+
- `roles` — a **per-scope map** from a scope (`hub`, or a connected repo name) to the roles held there
|
|
23
|
+
(`owner` / `reviewer` / `domain-owner`). A person can hold several roles in one scope (owner **and**
|
|
24
|
+
reviewer **and** domain-owner at once), and several scopes; a repo gets several owners/reviewers/
|
|
25
|
+
domain-owners by being listed in several people's maps.
|
|
26
|
+
|
|
27
|
+
**Back-compat (read on all three shapes):** the per-scope object above; a flat array
|
|
28
|
+
`"roles": ["owner","reviewer"]` (treated as `hub` roles); and the legacy single `"role": "owner"`
|
|
29
|
+
(a `hub` role). The legacy `repos.json` `domain_owner` field is still honored (see Resolution step 2).
|
|
17
30
|
|
|
18
31
|
## Resolution
|
|
19
32
|
|
|
20
|
-
1. **login → name +
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
1. **login → name + roles** from the roster. The `hub` roles map straight to records; each touched
|
|
34
|
+
domain `R` contributes the roles in `roles[R]` (a `domain-owner` role carries `domain: R`).
|
|
35
|
+
2. **Legacy domain-owner fallback:** if the resolved `name` equals a repo's `domain_owner` in
|
|
36
|
+
`repos.json`, and that repo is a **touched domain** for the step under review, the bridge also emits a
|
|
23
37
|
`domain-owner` approval scoped to that repo (`domain: <repo>`). One person owning several repos yields
|
|
24
38
|
several `domain-owner` records with different `domain` values — exactly what the gate predicate allows.
|
|
25
39
|
3. **Unmapped login → reviewer (flagged).** A login not in the roster maps to `name: <login>`,
|
|
@@ -27,6 +41,19 @@ in `../../yad-connect-repos/references/hub-config.md`; this file covers how the
|
|
|
27
41
|
reviewer but is **never** auto-promoted to owner/domain-owner, so a stranger can never satisfy the
|
|
28
42
|
owner/domain-owner requirement. The marker prompts a human to add the login to the roster.
|
|
29
43
|
|
|
44
|
+
## Auto-assignee / auto-reviewer on PR/MR open
|
|
45
|
+
|
|
46
|
+
When a review PR/MR is opened (hub `yad gate open`, or a code-repo `yad open-pr`):
|
|
47
|
+
|
|
48
|
+
- **Assignee = the committer/opener** — resolved from local git identity (`user.email`, then
|
|
49
|
+
`user.name`) through the roster (`email`/`name`/`login`). On GitHub an unresolved committer still
|
|
50
|
+
self-assigns via `@me`.
|
|
51
|
+
- **Reviewers = `reviewer` + `domain-owner`** for the touched scope(s) (`hub` plus every touched
|
|
52
|
+
domain for a hub review; the repo itself for a code PR), **minus the committer** — you do not review
|
|
53
|
+
your own PR. The artifact **owner/author is recorded, not requested.**
|
|
54
|
+
- Logins are validated against the hub during `yad setup` / `yad doctor` (`gh api users/<login>`,
|
|
55
|
+
`glab api users?username=<login>`); a miss is flagged `unverified` but never blocks (fail-open).
|
|
56
|
+
|
|
30
57
|
## Per-repo routing (stories review, and any escalated step)
|
|
31
58
|
|
|
32
59
|
The stories review needs a `domain-owner` per repo in the **union of every story's `repos`**. On 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-
|
|
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-
|
|
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`.
|
|
@@ -70,6 +70,11 @@ required reviewers:
|
|
|
70
70
|
approval per touched domain — identical to `yad-review-gate`'s escalation. The actual approvals are
|
|
71
71
|
recorded by the engineer review (Step E), via `yad-review-gate`.
|
|
72
72
|
|
|
73
|
+
When the PR/MR is actually opened with `yad open-pr`, these reviewers are **requested automatically**
|
|
74
|
+
from the repo-scoped roster (everyone with `reviewer`/`domain-owner` for the repo, minus the committer),
|
|
75
|
+
and the **committer is set as the assignee**. `risk-route.sh` remains the advisory printout of who the
|
|
76
|
+
gate will require.
|
|
77
|
+
|
|
73
78
|
### Step 4 — Stop (no auto-advance)
|
|
74
79
|
Report what was committed (or the routing result). The template and routing are advisory inputs to the
|
|
75
80
|
human review (Step E); they do not approve or merge. Do not touch the epic's `.sdlc/` state.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pr-template gate.
|
|
3
|
+
# The PR/MR BODY must actually USE the committed template — i.e. carry its required sections so the
|
|
4
|
+
# review (and risk-route.sh) has the inputs it needs. This catches an empty / free-form description
|
|
5
|
+
# that bypassed the template.
|
|
6
|
+
# --profile code (default) — the code-repo task template (yad-pr-template templates/<platform>/):
|
|
7
|
+
# requires `## Summary`, `## Impact & Risk`, `## Checklist`, and a filled `Risk level:` (low|medium|high).
|
|
8
|
+
# --profile hub — the front-half artifact-review template (templates/hub/<platform>/):
|
|
9
|
+
# requires `## Artifact under review`, `## Impact & Risk (front-half)`, `## Checklist`, and a `Risk tags:` line.
|
|
10
|
+
# The body is passed as a FILE path (single positional arg); CI writes the event body to a temp file
|
|
11
|
+
# (GitHub: github.event.pull_request.body; GitLab: $CI_MERGE_REQUEST_DESCRIPTION).
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
PROFILE=code
|
|
15
|
+
ARGS=()
|
|
16
|
+
while [ $# -gt 0 ]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--profile) PROFILE="${2:-code}"; shift 2 ;;
|
|
19
|
+
--profile=*) PROFILE="${1#*=}"; shift ;;
|
|
20
|
+
*) ARGS+=("$1"); shift ;;
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
case "$PROFILE" in code|hub) ;; *) echo "FAIL [pr-template]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
|
|
24
|
+
|
|
25
|
+
BODY="${ARGS[0]:-}"
|
|
26
|
+
if [ -z "$BODY" ] || [ ! -f "$BODY" ]; then
|
|
27
|
+
echo "FAIL [pr-template]: body file not found — pass the PR/MR description as a file path."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
rc=0
|
|
32
|
+
require_heading() {
|
|
33
|
+
if ! grep -qiE "^[[:space:]]*$1[[:space:]]*$" "$BODY"; then
|
|
34
|
+
echo "FAIL [pr-template]: missing section '${2}' — the PR/MR body does not use the template."
|
|
35
|
+
rc=1
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if [ "$PROFILE" = hub ]; then
|
|
40
|
+
require_heading '## Artifact under review' '## Artifact under review'
|
|
41
|
+
require_heading '## Impact & Risk \(front-half\)' '## Impact & Risk (front-half)'
|
|
42
|
+
require_heading '## Checklist' '## Checklist'
|
|
43
|
+
if ! grep -qiE '(\*\*)?Risk tags:' "$BODY"; then
|
|
44
|
+
echo "FAIL [pr-template]: missing 'Risk tags:' line (front-half Impact & Risk)."
|
|
45
|
+
rc=1
|
|
46
|
+
fi
|
|
47
|
+
else
|
|
48
|
+
require_heading '## Summary' '## Summary'
|
|
49
|
+
require_heading '## Impact & Risk' '## Impact & Risk'
|
|
50
|
+
require_heading '## Checklist' '## Checklist'
|
|
51
|
+
# Risk level must be present AND filled with a real value (not the <placeholder>).
|
|
52
|
+
rl="$(grep -iE '(\*\*)?Risk level:' "$BODY" | head -1 \
|
|
53
|
+
| sed -E 's/<!--.*$//; s/^[^:]*://; s/[*`]//g; s/^[[:space:]]*//; s/[[:space:]]*$//' || true)"
|
|
54
|
+
rl="$(printf '%s' "$rl" | tr 'A-Z' 'a-z' | grep -oE 'low|medium|high' | head -1 || true)"
|
|
55
|
+
if [ -z "$rl" ]; then
|
|
56
|
+
echo "FAIL [pr-template]: 'Risk level:' missing or not set to low|medium|high."
|
|
57
|
+
rc=1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
[ "$rc" = 0 ] && echo "PASS [pr-template]: body uses the ${PROFILE} template (required sections present)."
|
|
62
|
+
exit "$rc"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pr-title gate.
|
|
3
|
+
# The PR/MR TITLE must follow the convention for its repo kind:
|
|
4
|
+
# --profile code (default) — a Conventional-Commits subject "<type>: <description>", no trailing
|
|
5
|
+
# period (config.yaml build.pr_title_style: same_as_commit_subject; one task = one PR, the title is
|
|
6
|
+
# the squash-merge subject). Keep <type> in sync with cli/manifest.mjs COMMIT_TYPES.
|
|
7
|
+
# --profile hub — a front-half artifact-review title "review: <artifact> (EP-<slug>)", the shape
|
|
8
|
+
# `yad gate open` creates (cli/gate.mjs).
|
|
9
|
+
# The title is passed as the (single) positional arg; CI injects it from the event payload
|
|
10
|
+
# (GitHub: github.event.pull_request.title; GitLab: $CI_MERGE_REQUEST_TITLE).
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
PROFILE=code
|
|
14
|
+
ARGS=()
|
|
15
|
+
while [ $# -gt 0 ]; do
|
|
16
|
+
case "$1" in
|
|
17
|
+
--profile) PROFILE="${2:-code}"; shift 2 ;;
|
|
18
|
+
--profile=*) PROFILE="${1#*=}"; shift ;;
|
|
19
|
+
*) ARGS+=("$1"); shift ;;
|
|
20
|
+
esac
|
|
21
|
+
done
|
|
22
|
+
case "$PROFILE" in code|hub) ;; *) echo "FAIL [pr-title]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
|
|
23
|
+
|
|
24
|
+
TITLE="${ARGS[0]:-}"
|
|
25
|
+
if [ -z "$TITLE" ]; then
|
|
26
|
+
echo "FAIL [pr-title]: empty title — pass the PR/MR title as the argument."
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
TYPES='feat|fix|docs|refactor|test|perf|build|ci|chore|revert'
|
|
31
|
+
|
|
32
|
+
if [ "$PROFILE" = hub ]; then
|
|
33
|
+
# review: <artifact> (EP-<slug>)
|
|
34
|
+
if printf '%s' "$TITLE" | grep -qE '^review: .+ \(EP-[a-z0-9-]+\)$'; then
|
|
35
|
+
echo "PASS [pr-title]: '${TITLE}' (profile: hub)"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
echo "FAIL [pr-title]: '${TITLE}' is not a hub review title 'review: <artifact> (EP-<slug>)'."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# code profile — Conventional-Commits subject (optional scope + breaking `!`), no trailing period.
|
|
43
|
+
if ! printf '%s' "$TITLE" | grep -qE "^(${TYPES})(\([a-z0-9._-]+\))?!?: .+"; then
|
|
44
|
+
echo "FAIL [pr-title]: '${TITLE}' is not '<type>(<scope>)?!?: <description>' (type one of: ${TYPES//|/, })."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
if printf '%s' "$TITLE" | grep -qE '\.$'; then
|
|
48
|
+
echo "FAIL [pr-title]: '${TITLE}' must not end with a period."
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
echo "PASS [pr-title]: '${TITLE}' (profile: code)"
|