yadflow 2.16.0 → 2.16.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 +3 -3
- package/README.md +2 -2
- package/bin/yad.mjs +5 -3
- package/cli/commit.mjs +9 -2
- package/cli/gate.mjs +8 -3
- package/cli/openpr.mjs +78 -6
- package/package.json +1 -1
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +7 -0
- package/skills/yad-commit/SKILL.md +2 -1
- package/skills/yad-open-pr/SKILL.md +8 -0
- package/skills/yad-ship/SKILL.md +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
## [2.16.1](https://github.com/abdelrahmannasr/yadflow/compare/v2.16.0...v2.16.1) (2026-06-25)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
* make
|
|
6
|
+
* **open-pr:** make build helpers stage-aware on the hub (closes [#80](https://github.com/abdelrahmannasr/yadflow/issues/80)) ([#81](https://github.com/abdelrahmannasr/yadflow/issues/81)) ([8d74e3d](https://github.com/abdelrahmannasr/yadflow/commit/8d74e3d267d4c056992d3bc6f5a2a7a15a66b431))
|
|
7
7
|
|
|
8
8
|
# [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
|
|
9
9
|
|
package/README.md
CHANGED
|
@@ -98,8 +98,8 @@ with `npx` from your **product hub** repo — no clone needed.
|
|
|
98
98
|
| `yad gate status <epic>` | Show each review step and its recorded approvals. |
|
|
99
99
|
| `yad gate ci [--branch <head>] [--pr <n>]` | The CI entry the hub workflow calls on review/merge events: derive the epic/artifact from the `review/EP-*` branch, run the same sync, and commit **only the ledger** to the hub default branch (sweep every open review PR when no `--branch`). |
|
|
100
100
|
| `yad commit --type <t> -m <subject>` | Commit by the SDLC convention — Conventional subject, `Task`/`Contract-Change`/`Co-Authored-By` trailers, atomic-file guard. |
|
|
101
|
-
| `yad open-pr [--repo <name>]` | Open a
|
|
102
|
-
| `yad ship --type <t> -m <subject>` | Commit **and** open the task PR/MR in one step (`yad commit` then `yad open-pr`)
|
|
101
|
+
| `yad open-pr [--repo <name>]` | Open a **task** PR/MR from the platform template (build half). **Stage-aware on the hub:** a `review/EP-*` branch opens the front-half artifact-review PR (delegates to `yad gate open`); any other hub branch uses the code-task template (so hub tooling PRs pass the `pr-template` gate). |
|
|
102
|
+
| `yad ship --type <t> -m <subject>` | Commit **and** open the task PR/MR in one step (`yad commit` then `yad open-pr`) — stage-aware, same as `open-pr`. |
|
|
103
103
|
| `yad repo list` / `yad repo refresh [name]` | List connected repos as **fresh / stale**, and re-pack a stale one — staleness is now an explicit human decision, never an automatic skill side-effect. |
|
|
104
104
|
| `yad repo sync [name]` | Switch every connected repo to its **default branch** and fast-forward it from origin (one or all). Dirty repos are skipped, never overwritten; fast-forward only. |
|
|
105
105
|
| `npx yadflow --version` | Print the installed CLI version. |
|
package/bin/yad.mjs
CHANGED
|
@@ -56,8 +56,10 @@ ${c.bold('Review gate (front half)')}
|
|
|
56
56
|
|
|
57
57
|
${c.bold('Build helpers')}
|
|
58
58
|
yad commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
|
|
59
|
-
yad open-pr [--repo <name>] Open a
|
|
60
|
-
|
|
59
|
+
yad open-pr [--repo <name>] Open a task PR/MR — stage-aware on the hub: a review/EP-*
|
|
60
|
+
branch opens the front-half artifact-review PR (delegates to
|
|
61
|
+
gate open), any other hub branch uses the code-task template
|
|
62
|
+
yad ship --type <t> -m <subject> Commit AND open the task PR/MR in one step (stage-aware)
|
|
61
63
|
yad repo list Show connected repos (fresh / stale)
|
|
62
64
|
yad repo refresh [name] Re-pack a stale repo (a human decision)
|
|
63
65
|
|
|
@@ -181,7 +183,7 @@ async function main() {
|
|
|
181
183
|
// In bridge mode CI is the sole ledger writer: `open` only opens the PR, and local `sync` is
|
|
182
184
|
// advisory (reads the platform, prints status, writes nothing). The artifact status flip is
|
|
183
185
|
// CI's job at merge — never wired into the local gate. File-only mode keeps local writes.
|
|
184
|
-
if (action === 'open') await gateOpen(o.dir, { epic, artifact
|
|
186
|
+
if (action === 'open') await gateOpen(o.dir, { epic, artifact });
|
|
185
187
|
else if (action === 'sync') await gateSync(o.dir, { epic, artifact, today, local: true });
|
|
186
188
|
else if (action === 'comments') await gateComments(o.dir, { epic, artifact, today });
|
|
187
189
|
else if (action === 'status') await gateStatus(o.dir, { epic });
|
package/cli/commit.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import fs from 'node:fs';
|
|
|
7
7
|
import { c, log, ok, info, warn, fail, run, exists } from './lib.mjs';
|
|
8
8
|
import {
|
|
9
9
|
COMMIT_TYPES, AI_COAUTHORS, ATOMIC_FILE_LIMIT,
|
|
10
|
-
TASK_TRAILER, CONTRACT_CHANGE_TRAILER, COAUTHOR_TRAILER,
|
|
10
|
+
TASK_TRAILER, CONTRACT_CHANGE_TRAILER, COAUTHOR_TRAILER, PROJECT_FILES,
|
|
11
11
|
} from './manifest.mjs';
|
|
12
12
|
|
|
13
13
|
// PURE — unit tested directly. Build the full commit message text.
|
|
@@ -51,7 +51,14 @@ export async function runCommit(root, opts = {}) {
|
|
|
51
51
|
|
|
52
52
|
const branch = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: root }).stdout;
|
|
53
53
|
const task = opts.task || taskFromBranch(branch);
|
|
54
|
-
if (!task)
|
|
54
|
+
if (!task) {
|
|
55
|
+
// spec-link is a code-repo gate (REPO_WIRING.common), not a hub gate — so a missing Task trailer
|
|
56
|
+
// is expected on a hub PR (front-half artifact review or hub tooling) and only matters on a repo.
|
|
57
|
+
const onHub = exists(path.join(root, PROJECT_FILES.hubConfig));
|
|
58
|
+
warn(onHub
|
|
59
|
+
? 'no Task trailer (none given and branch has no -S0N-T0N) — fine for a hub PR; required on code-repo tasks (spec-link gate)'
|
|
60
|
+
: 'no Task trailer (none given and branch has no -S0N-T0N) — spec-link gate will fail on a code repo');
|
|
61
|
+
}
|
|
55
62
|
|
|
56
63
|
let message;
|
|
57
64
|
try {
|
package/cli/gate.mjs
CHANGED
|
@@ -449,7 +449,11 @@ export async function gateStatus(root, { epic } = {}) {
|
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
|
|
452
|
+
// `head` overrides the review branch the PR is opened against — `open-pr` delegates here after pushing
|
|
453
|
+
// the user's checked-out branch, which for a per-story review (review/EP-*/stories-S01) does NOT equal
|
|
454
|
+
// the branch this would otherwise recompute (artifactFromBase collapses stories-S01 → stories/). Pass
|
|
455
|
+
// the real pushed head so the PR targets a branch that exists. `creator` is injected in tests.
|
|
456
|
+
export async function gateOpen(root, { epic, artifact, head, creator = createPr } = {}) {
|
|
453
457
|
const { hub } = loadHub(root);
|
|
454
458
|
const epicDir = epicRoot(root, epic);
|
|
455
459
|
const ledger = loadLedger(epicDir);
|
|
@@ -458,7 +462,7 @@ export async function gateOpen(root, { epic, artifact } = {}) {
|
|
|
458
462
|
const step = findReviewStep(ledger.state, artifact);
|
|
459
463
|
if (!step) { fail(`no review step for ${artifact}`); process.exitCode = 1; return; }
|
|
460
464
|
const b = base(artifact);
|
|
461
|
-
const branch = `review/${epic}/${b}`;
|
|
465
|
+
const branch = head || `review/${epic}/${b}`;
|
|
462
466
|
const domains = touchedDomains(epicDir, step);
|
|
463
467
|
warnUnlockedContract(epicDir, artifact);
|
|
464
468
|
|
|
@@ -487,7 +491,7 @@ export async function gateOpen(root, { epic, artifact } = {}) {
|
|
|
487
491
|
const assignees = committer ? [committer] : [];
|
|
488
492
|
const labels = isEscalated(step) ? domains.map((d) => `domain:${d}`) : [];
|
|
489
493
|
info(`opening review ${hub.platform === 'gitlab' ? 'MR' : 'PR'} on branch ${branch} …`);
|
|
490
|
-
const r =
|
|
494
|
+
const r = creator(hub.platform, { title: `review: ${artifact} (${epic})`, body, base: hub.default_branch || 'main', head: branch, reviewers, assignees, labels, cwd: root });
|
|
491
495
|
if (!r.ok) { warn(`could not open PR (${r.reason || 'unknown'})${bridge ? ' — open it manually; CI records the gate on merge' : '; step is in_review file-only'}`); return; }
|
|
492
496
|
|
|
493
497
|
if (!bridge) {
|
|
@@ -498,6 +502,7 @@ export async function gateOpen(root, { epic, artifact } = {}) {
|
|
|
498
502
|
hand(bridge
|
|
499
503
|
? 'reviewers approve/comment there; CI advances the gate on the default branch when it is merged'
|
|
500
504
|
: `reviewers approve/comment there; then run \`yad gate sync ${epic} ${artifact}\``);
|
|
505
|
+
return { url: r.url };
|
|
501
506
|
}
|
|
502
507
|
|
|
503
508
|
// ---- helpers ------------------------------------------------------------------------------------
|
package/cli/openpr.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import { c, log, ok, info, hand, fail, run, exists, readJSON } from './lib.mjs';
|
|
|
8
8
|
import { PROJECT_FILES } from './manifest.mjs';
|
|
9
9
|
import { detectPlatform, createPr, reviewersForScopes, resolveCommitterLogin } from './platform.mjs';
|
|
10
10
|
import { taskFromBranch } from './commit.mjs';
|
|
11
|
+
import { parseReviewBranch, artifactFromBase } from './epic-state.mjs';
|
|
12
|
+
import { gateOpen } from './gate.mjs';
|
|
11
13
|
|
|
12
14
|
// Resolve the target code repo: --repo <name> from the registry, else --dir, else cwd.
|
|
13
15
|
function resolveRepo(root, { repo, dir }) {
|
|
@@ -19,11 +21,59 @@ function resolveRepo(root, { repo, dir }) {
|
|
|
19
21
|
return { repoRoot: path.resolve(root, dir || '.'), meta: null };
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// Which SDLC stage is this PR? The hub serves two vehicles; a code repo only one. Mirrors the
|
|
25
|
+
// `--head` split the hub pattern gates (pr-title.sh/pr-template.sh) already apply:
|
|
26
|
+
// code-repo — NOT the product hub (a registry repo via --repo, or root is not a hub).
|
|
27
|
+
// hub-front — the hub itself AND head is a review/EP-* branch (artifact-review PR).
|
|
28
|
+
// hub-tooling — the hub itself AND head is anything else (a tooling/CI change to the hub).
|
|
29
|
+
// `meta` (truthy when resolved from the repos registry via --repo) is a connected code repo, so it is
|
|
30
|
+
// never the hub regardless of its path. Otherwise "is the hub" = repoRoot resolves to root AND root
|
|
31
|
+
// carries .sdlc/hub.json. path.resolve normalises `--dir .` / trailing slashes.
|
|
32
|
+
export function detectStage(root, repoRoot, head, meta) {
|
|
33
|
+
if (meta) return 'code-repo';
|
|
34
|
+
const isHub = path.resolve(repoRoot) === path.resolve(root)
|
|
35
|
+
&& exists(path.join(root, PROJECT_FILES.hubConfig));
|
|
36
|
+
if (!isHub) return 'code-repo';
|
|
37
|
+
return /^review\/EP-[a-z0-9-]+\//.test(head || '') ? 'hub-front' : 'hub-tooling';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The bundled code-task template — the same file `REPO_WIRING` installs into code repos, resolved
|
|
41
|
+
// from the package (mirrors how manifest.mjs reads ../package.json). Used for a hub-tooling PR, whose
|
|
42
|
+
// `.github/pull_request_template.md` is the ARTIFACT-REVIEW template (wrong shape for the code-task
|
|
43
|
+
// hub gate). Falls back to a minimal body that still carries every section the gate requires.
|
|
44
|
+
function codeTaskTemplate(platform) {
|
|
45
|
+
const rel = platform === 'gitlab'
|
|
46
|
+
? '../skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md'
|
|
47
|
+
: '../skills/yad-pr-template/templates/github/pull_request_template.md';
|
|
48
|
+
try {
|
|
49
|
+
return fs.readFileSync(new URL(rel, import.meta.url), 'utf8');
|
|
50
|
+
} catch {
|
|
51
|
+
return [
|
|
52
|
+
'## Summary', '',
|
|
53
|
+
'## Impact & Risk',
|
|
54
|
+
'- **Domains / repos touched:** <repo>',
|
|
55
|
+
'- **Contract surface touched:** no',
|
|
56
|
+
'- **Risk level:** low',
|
|
57
|
+
'',
|
|
58
|
+
'## Checklist',
|
|
59
|
+
'- [ ] Lint, build, and tests pass (build/test/lint gate)',
|
|
60
|
+
'',
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function templateBody(repoRoot, platform, { task, risk, contract, domains, stage }) {
|
|
66
|
+
// hub-tooling: the hub's own template is artifact-review — use the bundled code-task template so the
|
|
67
|
+
// body matches the shape the hub `pr-template` gate demands for a non-review head.
|
|
68
|
+
let base;
|
|
69
|
+
if (stage === 'hub-tooling') {
|
|
70
|
+
base = codeTaskTemplate(platform);
|
|
71
|
+
} else {
|
|
72
|
+
const tplPath = platform === 'gitlab'
|
|
73
|
+
? path.join(repoRoot, '.gitlab/merge_request_templates/Default.md')
|
|
74
|
+
: path.join(repoRoot, '.github/pull_request_template.md');
|
|
75
|
+
base = exists(tplPath) ? fs.readFileSync(tplPath, 'utf8') : codeTaskTemplate(platform);
|
|
76
|
+
}
|
|
27
77
|
// Fill the obvious fields; leave the rest of the committed template intact for the author.
|
|
28
78
|
return base
|
|
29
79
|
.replace(/EP-<slug>-S0N-T0N/g, task || 'EP-<slug>-S0N-T0N')
|
|
@@ -45,6 +95,28 @@ export async function runOpenPr(root, opts = {}) {
|
|
|
45
95
|
const baseBranch = opts.base || meta?.default_branch || 'main';
|
|
46
96
|
if (branch === baseBranch) { fail(`on ${baseBranch} — switch to your task branch first`); process.exitCode = 1; return; }
|
|
47
97
|
|
|
98
|
+
const stage = detectStage(root, repoRoot, branch, meta);
|
|
99
|
+
|
|
100
|
+
// hub-front: this is a front-half artifact-review PR (review/EP-*/<artifact> head on the hub). The
|
|
101
|
+
// artifact-review title, body, and ledger bookkeeping all live in `yad gate open` — delegate to it
|
|
102
|
+
// rather than emit the code-task shape (which the hub gate would reject). Push first (gateOpen does
|
|
103
|
+
// not push), then hand off; any --title/--message is dropped (gateOpen sets `review: …`).
|
|
104
|
+
if (stage === 'hub-front') {
|
|
105
|
+
const parsed = parseReviewBranch(branch);
|
|
106
|
+
if (!parsed) { fail(`could not parse review branch '${branch}' (expected review/EP-<slug>/<artifact>)`); process.exitCode = 1; return; }
|
|
107
|
+
info(`pushing ${branch} …`);
|
|
108
|
+
const fpush = run('git', ['push', '-u', 'origin', branch], { cwd: repoRoot });
|
|
109
|
+
if (!fpush.ok) { fail(`git push failed — ${fpush.stderr.split('\n')[0] || 'unknown'}`); process.exitCode = 1; return; }
|
|
110
|
+
// Pass the branch we just pushed as the head so gateOpen opens the PR against it (its own
|
|
111
|
+
// recompute would collapse a per-story base). gateOpen signals failure by returning no url —
|
|
112
|
+
// mirror open-pr's own error contract so `ship` sees the non-zero exit and never reports success.
|
|
113
|
+
// (On a platform-less hub gateOpen marks the step in_review file-only and returns no url; open-pr's
|
|
114
|
+
// job is to open a PR, so "no PR opened" is a non-zero outcome here, unlike `yad gate open`.)
|
|
115
|
+
const res = await gateOpen(root, { epic: parsed.epic, artifact: artifactFromBase(parsed.base), head: branch });
|
|
116
|
+
if (!res?.url) process.exitCode = 1;
|
|
117
|
+
return res;
|
|
118
|
+
}
|
|
119
|
+
|
|
48
120
|
// Push the branch (sets upstream) using the user's own auth. Abort on failure — creating a PR for a
|
|
49
121
|
// branch that is not on the remote just fails with a more confusing error.
|
|
50
122
|
info(`pushing ${branch} …`);
|
|
@@ -54,7 +126,7 @@ export async function runOpenPr(root, opts = {}) {
|
|
|
54
126
|
const task = opts.task || taskFromBranch(branch);
|
|
55
127
|
const title = opts.title || run('git', ['log', '-1', '--format=%s'], { cwd: repoRoot }).stdout || `task ${task || branch}`;
|
|
56
128
|
const body = templateBody(repoRoot, platform, {
|
|
57
|
-
task, risk: opts.risk || 'low', contract: !!opts.contractChange, domains: meta?.name,
|
|
129
|
+
task, risk: opts.risk || 'low', contract: !!opts.contractChange, domains: meta?.name, stage,
|
|
58
130
|
});
|
|
59
131
|
|
|
60
132
|
// Auto-assign from the hub roster, scoped to this repo: assignee = the committer (resolved from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.1",
|
|
4
4
|
"description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, ship, repo). A BMAD module + 30 yad-* skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AbdelRahman Nasr",
|
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
name: yad-hub-checks
|
|
10
10
|
on:
|
|
11
11
|
pull_request:
|
|
12
|
+
# `edited` (beyond the opened/synchronize/reopened defaults) so a PR title/body correction
|
|
13
|
+
# re-runs the pattern gates without a close/reopen — e.g. fixing a body the pr-template gate held.
|
|
14
|
+
types: [opened, synchronize, reopened, edited]
|
|
12
15
|
branches: ["**"]
|
|
13
16
|
|
|
14
17
|
jobs:
|
|
18
|
+
# commit-message + ledger-guard read the commit range, not the PR title/body — skip them on a bare
|
|
19
|
+
# `edited` event (only pr-title/pr-template need to re-check a title/body fix).
|
|
15
20
|
commit-message:
|
|
16
21
|
runs-on: ubuntu-latest
|
|
22
|
+
if: github.event.action != 'edited'
|
|
17
23
|
steps:
|
|
18
24
|
- uses: actions/checkout@v4
|
|
19
25
|
with: { fetch-depth: 0 }
|
|
@@ -52,6 +58,7 @@ jobs:
|
|
|
52
58
|
# The gate ledger is CI-owned: reject non-bot commits to .sdlc/*.json or reviews/*.md.
|
|
53
59
|
ledger-guard:
|
|
54
60
|
runs-on: ubuntu-latest
|
|
61
|
+
if: github.event.action != 'edited'
|
|
55
62
|
steps:
|
|
56
63
|
- uses: actions/checkout@v4
|
|
57
64
|
with: { fetch-depth: 0 }
|
|
@@ -21,7 +21,8 @@ and `yad-ship` use. It **never auto-advances**; it just commits.
|
|
|
21
21
|
`feat|fix|docs|refactor|test|perf|build|ci|chore|revert`; proper nouns/acronyms keep their case.
|
|
22
22
|
- **Task trailer** — required on a code repo (anchors the `spec-link` + `commit-message` gates). Given
|
|
23
23
|
with `--task`, else derived from the branch (`feat/<story>-<task>-…`). Hub commits are not
|
|
24
|
-
task-scoped, so the trailer is optional there
|
|
24
|
+
task-scoped, so the trailer is optional there — a missing-Task warning is informational on the hub
|
|
25
|
+
(`spec-link` is a code-repo gate) and only flags a real gate failure in a code repo.
|
|
25
26
|
- **Contract-Change trailer** — `--contract-change` only when the diff touches the locked contract
|
|
26
27
|
surface; it routes the change back to the architecture gate.
|
|
27
28
|
- **AI co-author footer — OFF by default.** No `Co-Authored-By` trailer is written unless `--ai <id>`
|
|
@@ -22,6 +22,14 @@ the product hub.
|
|
|
22
22
|
`.gitlab/merge_request_templates/Default.md`) with `Task:`, `Risk level:`, `Contract surface
|
|
23
23
|
touched:`, and `Domains` prefilled; the rest is left for the author. This satisfies the `pr-template`
|
|
24
24
|
gate.
|
|
25
|
+
- **Stage-aware on the product hub** — `open-pr` mirrors the `--head` split the hub gates apply:
|
|
26
|
+
- a **`review/EP-*/<artifact>`** branch is a front-half artifact-review PR → it **delegates to
|
|
27
|
+
`yad gate open`** (artifact-review title `review: <artifact> (EP-<slug>)`, the hub artifact-review
|
|
28
|
+
body, and the gate ledger bookkeeping all in one place). Any `--title`/`-m` is ignored here.
|
|
29
|
+
- any **other hub branch** is a tooling/CI change → it uses the bundled **code-task** template
|
|
30
|
+
(`## Summary` / `Risk level:` / `## Checklist`) instead of the hub's artifact-review
|
|
31
|
+
`pull_request_template.md`, so the hub `pr-template` gate passes.
|
|
32
|
+
In a code repo nothing changes — it reads the repo's own committed code-task template.
|
|
25
33
|
- **Auto-assign** — from the hub roster scoped to this repo: assignee = the committer (resolved from
|
|
26
34
|
the local git identity), reviewers = the repo's `reviewer`/`domain-owner` logins minus the committer.
|
|
27
35
|
Degrades cleanly when there is no roster.
|
package/skills/yad-ship/SKILL.md
CHANGED
|
@@ -22,6 +22,10 @@ its own and **never merges**. The engineer review + merge are Step E (`yad-engin
|
|
|
22
22
|
roster auto-assign, risk routing (`../yad-open-pr/SKILL.md`).
|
|
23
23
|
- **Order matters:** the PR/MR is opened **only if the commit lands**. A failed commit, a tripped
|
|
24
24
|
atomic guard, or `--dry-run` stops the step before anything is pushed.
|
|
25
|
+
- **Stage-aware on the product hub** (via `yad-open-pr`): on a `review/EP-*` branch `ship` opens the
|
|
26
|
+
front-half **artifact-review** PR (delegating to `yad gate open` — `--title` is ignored); on any
|
|
27
|
+
other hub branch it opens the **code-task** PR from the bundled code-task template. In a code repo
|
|
28
|
+
it is unchanged.
|
|
25
29
|
|
|
26
30
|
## Inputs
|
|
27
31
|
|