yadflow 3.16.2 → 3.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/bin/yad.mjs +9 -3
- package/cli/openpr.mjs +36 -7
- package/cli/platform.mjs +88 -0
- package/cli/review.mjs +10 -5
- package/package.json +1 -1
- package/skills/sdlc/module-help.csv +2 -2
- package/skills/yad-open-pr/SKILL.md +24 -2
- package/skills/yad-ship/SKILL.md +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [3.16.3](https://github.com/abdelrahmannasr/yadflow/compare/v3.16.2...v3.16.3) (2026-08-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **open-pr:** base the task PR on the repo default branch, not main ([63da011](https://github.com/abdelrahmannasr/yadflow/commit/63da011fd2c4b6e81940c646879cc692b634a877)), closes [#168](https://github.com/abdelrahmannasr/yadflow/issues/168)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Performance Improvements
|
|
10
|
+
|
|
11
|
+
* **review:** stop probing the platform for an already-configured base ([0fc3de3](https://github.com/abdelrahmannasr/yadflow/commit/0fc3de34c7a522ec3463953778787f247ab28071)), closes [#191](https://github.com/abdelrahmannasr/yadflow/issues/191)
|
|
12
|
+
|
|
1
13
|
## [3.16.2](https://github.com/abdelrahmannasr/yadflow/compare/v3.16.1...v3.16.2) (2026-08-12)
|
|
2
14
|
|
|
3
15
|
|
package/bin/yad.mjs
CHANGED
|
@@ -103,9 +103,11 @@ ${c.bold('Review gate (front half)')}
|
|
|
103
103
|
|
|
104
104
|
${c.bold('Build helpers')}
|
|
105
105
|
yad commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
|
|
106
|
-
yad open-pr [--repo <name>] Open a task PR/MR
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
yad open-pr [--repo <name>] Open a task PR/MR against the repo's DEFAULT branch (never a
|
|
107
|
+
hardcoded main; --base overrides) — stage-aware on the hub: a
|
|
108
|
+
review/EP-* branch opens the front-half artifact-review PR
|
|
109
|
+
(delegates to gate open), any other hub branch uses the
|
|
110
|
+
code-task template
|
|
109
111
|
yad ship --type <t> -m <subject> Commit AND open the task PR/MR in one step (stage-aware)
|
|
110
112
|
yad checkpoint [--push] Commit the machine-written back-half hub state
|
|
111
113
|
(trust-log/build-log/build-state) — plus any story
|
|
@@ -153,6 +155,10 @@ ${c.bold('Options')}
|
|
|
153
155
|
--contract-change commit/open-pr: mark the contract surface touched
|
|
154
156
|
--risk <level> open-pr: low|medium|high (default low)
|
|
155
157
|
--repo <name> open-pr: target a registered repo by name
|
|
158
|
+
--base <branch> open-pr: override the PR/MR base — default is the repo's own default
|
|
159
|
+
branch (repos.json default_branch, else hub.json default_branch for a PR
|
|
160
|
+
on the hub itself, else the platform, else origin/HEAD, else main); a
|
|
161
|
+
non-default base loses the AI first pass (warns, never blocks)
|
|
156
162
|
--epic <id> docs: target one epic's site (EP-<slug>)
|
|
157
163
|
--overview docs: target the project SDLC-overview site
|
|
158
164
|
--check/--refresh/--wire docs sync: report stale / rebuild / install Pages CI
|
package/cli/openpr.mjs
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
// on the product hub.
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
|
-
import { c, log, ok, info, hand, fail, run, exists, readJSON } from './lib.mjs';
|
|
7
|
+
import { c, log, ok, info, warn, hand, fail, run, exists, readJSON } from './lib.mjs';
|
|
8
8
|
import { PROJECT_FILES } from './manifest.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
detectPlatform, createPr, reviewersForScopes, resolveCommitterLogin, resolveBaseBranch,
|
|
11
|
+
} from './platform.mjs';
|
|
10
12
|
import { taskFromBranch } from './commit.mjs';
|
|
11
13
|
import { parseReviewBranch, artifactFromBase } from './epic-state.mjs';
|
|
12
14
|
import { gateOpen } from './gate.mjs';
|
|
@@ -104,9 +106,6 @@ export async function runOpenPr(root, opts = {}) {
|
|
|
104
106
|
if (!platform) { fail('could not detect platform (github/gitlab) — pass --platform'); process.exitCode = 1; return; }
|
|
105
107
|
|
|
106
108
|
const branch = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoRoot }).stdout;
|
|
107
|
-
const baseBranch = opts.base || meta?.default_branch || 'main';
|
|
108
|
-
if (branch === baseBranch) { fail(`on ${baseBranch} — switch to your task branch first`); process.exitCode = 1; return; }
|
|
109
|
-
|
|
110
109
|
const stage = detectStage(root, repoRoot, branch, meta);
|
|
111
110
|
|
|
112
111
|
// hub-front: this is a front-half artifact-review PR (review/EP-*/<artifact> head on the hub). The
|
|
@@ -129,6 +128,34 @@ export async function runOpenPr(root, opts = {}) {
|
|
|
129
128
|
return res;
|
|
130
129
|
}
|
|
131
130
|
|
|
131
|
+
// The hub roster + its default_branch. The latter only applies when the PR targets the hub ITSELF
|
|
132
|
+
// (a hub-tooling branch) — for a connected code repo the hub's trunk belongs to a different repo and
|
|
133
|
+
// must never leak in. Resolved AFTER the hub-front hand-off above, which delegates its own base to
|
|
134
|
+
// `yad gate open`: resolving before it would spend a platform round-trip and print a base that the
|
|
135
|
+
// delegated path then ignores.
|
|
136
|
+
const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig), { roster: [] });
|
|
137
|
+
|
|
138
|
+
// Resolve the base rather than assume it (#168). Hardcoding 'main' mis-based every PR on a repo
|
|
139
|
+
// whose trunk is something else — and CodeRabbit decides auto-review eligibility from the base at
|
|
140
|
+
// PR-OPEN time, so those PRs silently got no AI first pass at all.
|
|
141
|
+
const { base: baseBranch, source: baseSource, platformDefault } = resolveBaseBranch(platform, {
|
|
142
|
+
cwd: repoRoot, explicit: opts.base, meta, hub: stage === 'code-repo' ? null : hub, runner: opts.runner,
|
|
143
|
+
});
|
|
144
|
+
if (branch === baseBranch) { fail(`on ${baseBranch} — switch to your task branch first`); process.exitCode = 1; return; }
|
|
145
|
+
info(`base ${baseBranch} ${c.dim(`(from ${baseSource})`)}`);
|
|
146
|
+
// A base that is not the remote's default is legitimate (stacked PRs, release branches) but it costs
|
|
147
|
+
// the AI first pass, invisibly and irreversibly — so it is never silent. The REMEDY depends on where
|
|
148
|
+
// the base came from: telling someone to override a `default_branch` they deliberately configured
|
|
149
|
+
// would mean contradicting their own committed config on every PR, forever.
|
|
150
|
+
if (platformDefault && platformDefault !== baseBranch) {
|
|
151
|
+
warn(`base '${baseBranch}' is not the repo default '${platformDefault}' — CodeRabbit skips auto-review on a non-default base unless .coderabbit.yaml lists it under reviews.base_branches, and retargeting later does NOT undo the skip`);
|
|
152
|
+
if (baseSource === 'registry' || baseSource === 'hub') {
|
|
153
|
+
hand(`the configured default_branch (${baseSource === 'hub' ? '.sdlc/hub.json' : '.sdlc/repos.json'}) disagrees with the platform — reconcile them, or allow '${baseBranch}' in .coderabbit.yaml`);
|
|
154
|
+
} else {
|
|
155
|
+
hand(`open against '${platformDefault}' (or pass --base ${platformDefault}) unless you meant to stack this PR`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
132
159
|
// Push the branch (sets upstream) using the user's own auth. Abort on failure — creating a PR for a
|
|
133
160
|
// branch that is not on the remote just fails with a more confusing error.
|
|
134
161
|
info(`pushing ${branch} …`);
|
|
@@ -155,7 +182,6 @@ export async function runOpenPr(root, opts = {}) {
|
|
|
155
182
|
// Auto-assign from the hub roster, scoped to this repo: assignee = the committer (resolved from
|
|
156
183
|
// local git identity), reviewers = the repo's reviewers + domain-owners, minus the committer.
|
|
157
184
|
// Degrades cleanly when there is no roster / the committer is unmapped (gh self-assigns via @me).
|
|
158
|
-
const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig), { roster: [] });
|
|
159
185
|
const roster = hub.roster || [];
|
|
160
186
|
const committer = resolveCommitterLogin(repoRoot, roster);
|
|
161
187
|
const scope = meta?.name ? [meta.name] : [];
|
|
@@ -164,7 +190,10 @@ export async function runOpenPr(root, opts = {}) {
|
|
|
164
190
|
const reviewers = reviewersForScopes(roster, scope, { excludeLogin: committer, repos: meta ? [meta] : [] });
|
|
165
191
|
const assignees = committer ? [committer] : [];
|
|
166
192
|
|
|
167
|
-
|
|
193
|
+
// `creator` is injectable (mirrors gateOpen's) so a test can assert the base that reaches the
|
|
194
|
+
// platform CLI without shelling out to gh/glab.
|
|
195
|
+
const creator = opts.creator || createPr;
|
|
196
|
+
const r = creator(platform, { title, body, base: baseBranch, head: branch, reviewers, assignees, cwd: repoRoot });
|
|
168
197
|
if (!r.ok) { fail(`could not open PR/MR — ${r.reason || 'unknown'}`); process.exitCode = 1; return; }
|
|
169
198
|
ok(`opened ${r.url}`);
|
|
170
199
|
if (r.mentioned?.length) info(`@-mentioned (GitLab single-reviewer field): ${r.mentioned.join(', ')}`);
|
package/cli/platform.mjs
CHANGED
|
@@ -378,6 +378,94 @@ export function branchExists(cwd, branch) {
|
|
|
378
378
|
return remote.code === 2 ? false : null;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
// ---- default branch -----------------------------------------------------------------------------
|
|
382
|
+
// The REMOTE's own default branch, asked of the platform. This is the branch the platform (and the
|
|
383
|
+
// tooling that keys off it — CodeRabbit's auto-review eligibility, branch protection, "compare"
|
|
384
|
+
// defaults) considers the trunk, so it is the only authoritative answer to "what should a PR target?".
|
|
385
|
+
// `runner` is injectable so the read is unit-testable without shelling out (mirrors searchIssues).
|
|
386
|
+
// Returns { ok, branch, reason }; never throws — an absent/unauthenticated CLI is just `ok:false`.
|
|
387
|
+
// It is a READ of the remote's own config, so it costs one API round-trip; callers that only need a
|
|
388
|
+
// base branch get it folded into resolveBaseBranch below rather than calling this twice.
|
|
389
|
+
export function platformDefaultBranch(platform, { cwd, runner = run } = {}) {
|
|
390
|
+
// No `platformReady` probe: an absent CLI already surfaces as a failed spawn, and skipping the probe
|
|
391
|
+
// keeps the read a pure function of `runner` (so a test never depends on gh/glab being installed).
|
|
392
|
+
if (!cliFor(platform)) return { ok: false, reason: 'no platform (github/gitlab) to ask' };
|
|
393
|
+
// This is a live network round-trip on a SYNCHRONOUS command path, so it carries the same ceiling
|
|
394
|
+
// branchExists documents for its own remote probe: "cannot ask" has to be fast, or a black-holed
|
|
395
|
+
// host / wedged credential helper turns `yad open-pr` into a hang. A timeout surfaces as ok:false,
|
|
396
|
+
// which the caller already treats as "the platform could not tell me".
|
|
397
|
+
const opts = { cwd, timeout: 10_000 };
|
|
398
|
+
if (platform === 'gitlab') {
|
|
399
|
+
// `:id` is glab's own placeholder for the project the cwd resolves to (same form as readPrGitLab).
|
|
400
|
+
const r = runner('glab', ['api', 'projects/:id'], opts);
|
|
401
|
+
if (!r.ok) return { ok: false, reason: r.stderr || 'glab api projects/:id failed' };
|
|
402
|
+
try {
|
|
403
|
+
const branch = JSON.parse(r.stdout)?.default_branch;
|
|
404
|
+
return branch ? { ok: true, branch } : { ok: false, reason: 'project has no default_branch' };
|
|
405
|
+
} catch { return { ok: false, reason: 'unreadable glab api response' }; }
|
|
406
|
+
}
|
|
407
|
+
const r = runner('gh', ['repo', 'view', '--json', 'defaultBranchRef', '-q', '.defaultBranchRef.name'], opts);
|
|
408
|
+
if (!r.ok) return { ok: false, reason: r.stderr || 'gh repo view failed' };
|
|
409
|
+
return r.stdout ? { ok: true, branch: r.stdout } : { ok: false, reason: 'gh returned no defaultBranchRef' };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// The branch a PR/MR should target, resolved rather than assumed (issue #168: open-pr hardcoded
|
|
413
|
+
// 'main', so every task PR on a `staging`-trunk repo was mis-based — and CodeRabbit, which decides
|
|
414
|
+
// auto-review eligibility at PR-OPEN time from the base, silently skipped every one of them).
|
|
415
|
+
//
|
|
416
|
+
// Order — most explicit first, and configuration outranks the remote: the same
|
|
417
|
+
// configuration-outranks-the-remote order `yad repo sync` (repo.mjs) and the contract-check gate use,
|
|
418
|
+
// though only this chain has a platform rung — they stop at the local `origin/HEAD`:
|
|
419
|
+
// 1 flag — an explicit --base; the human said so
|
|
420
|
+
// 2 registry — the repo's `default_branch` in .sdlc/repos.json
|
|
421
|
+
// 3 hub — hub.json's `default_branch`, for a PR against the product hub itself
|
|
422
|
+
// 4 platform — what the remote says (see platformDefaultBranch)
|
|
423
|
+
// 5 origin-head — local `refs/remotes/origin/HEAD`, the same read repo.mjs/hubcommit.mjs use.
|
|
424
|
+
// Deliberately NOT `ls-remote`: see branchExists above for why a network probe on
|
|
425
|
+
// this path is a hang hazard.
|
|
426
|
+
// 6 fallback — 'main'
|
|
427
|
+
//
|
|
428
|
+
// `probe` decides WHEN the platform is asked, and exists because the two callers want different things:
|
|
429
|
+
// true (default) — ask up front, so `platformDefault` rides along even when an earlier rung won.
|
|
430
|
+
// `yad open-pr` needs that to warn about a base that is not the remote's trunk,
|
|
431
|
+
// and it is about to shell out to gh/glab anyway.
|
|
432
|
+
// false — ask only if the config rungs all miss. A caller that just wants a base (the
|
|
433
|
+
// review companion) would otherwise pay a live round-trip — up to the full 10s
|
|
434
|
+
// timeout on a slow/unreachable host — for a `platformDefault` it discards.
|
|
435
|
+
// Either way the probe runs AT MOST once. Returns { base, source, platformDefault }; with `probe:false`
|
|
436
|
+
// and an early rung winning, `platformDefault` is null because it was never asked, not because the
|
|
437
|
+
// platform had no answer.
|
|
438
|
+
export function resolveBaseBranch(platform, {
|
|
439
|
+
cwd, explicit = null, meta = null, hub = null, runner = run, probe = true,
|
|
440
|
+
} = {}) {
|
|
441
|
+
let platformDefault = null;
|
|
442
|
+
let asked = false;
|
|
443
|
+
const askPlatform = () => {
|
|
444
|
+
if (asked) return platformDefault;
|
|
445
|
+
asked = true;
|
|
446
|
+
const remote = platformDefaultBranch(platform, { cwd, runner });
|
|
447
|
+
platformDefault = remote.ok ? remote.branch : null;
|
|
448
|
+
return platformDefault;
|
|
449
|
+
};
|
|
450
|
+
if (probe) askPlatform();
|
|
451
|
+
const originHead = () => {
|
|
452
|
+
const r = runner('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { cwd });
|
|
453
|
+
return r.ok && r.stdout ? r.stdout.replace(/^origin\//, '') : null;
|
|
454
|
+
};
|
|
455
|
+
const chain = [
|
|
456
|
+
['flag', explicit],
|
|
457
|
+
['registry', meta?.default_branch],
|
|
458
|
+
['hub', hub?.default_branch],
|
|
459
|
+
['platform', askPlatform],
|
|
460
|
+
['origin-head', originHead],
|
|
461
|
+
];
|
|
462
|
+
for (const [source, value] of chain) {
|
|
463
|
+
const branch = typeof value === 'function' ? value() : value;
|
|
464
|
+
if (branch) return { base: branch, source, platformDefault };
|
|
465
|
+
}
|
|
466
|
+
return { base: 'main', source: 'fallback', platformDefault };
|
|
467
|
+
}
|
|
468
|
+
|
|
381
469
|
// ---- create a PR/MR -----------------------------------------------------------------------------
|
|
382
470
|
// `assignees` = the committer/PR-opener (always set, so the PR is owned by whoever pushed it);
|
|
383
471
|
// `reviewers` = the scope's reviewers + domain-owners (computed by reviewersForScopes). On GitHub an
|
package/cli/review.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { updateShip } from './ledger.mjs';
|
|
|
12
12
|
import { epicRoot } from './epic-state.mjs';
|
|
13
13
|
import {
|
|
14
14
|
detectPlatform, readPr, mapApprovers, getPrBody, editPrBody, postComment, prNumberFromUrl,
|
|
15
|
+
resolveBaseBranch,
|
|
15
16
|
} from './platform.mjs';
|
|
16
17
|
import { upsertTrailerBlock, nudgeMessage, parseEngagement } from './companion.mjs';
|
|
17
18
|
import { sequenceDiff } from './walkthrough.mjs';
|
|
@@ -40,12 +41,16 @@ function platformOf(root, repoRoot, meta) {
|
|
|
40
41
|
// Build (but don't print) the back-half grounding bundle. Shared by `context` and `walkthrough` so the
|
|
41
42
|
// pair walkthrough adds an ordered stop-list on top of the exact same grounding the companion uses.
|
|
42
43
|
// Returns { error } on a bad --repo, else { bundle, repoRoot, base }.
|
|
43
|
-
function contextBundle(root, { repo, dir, pr } = {}) {
|
|
44
|
+
function contextBundle(root, { repo, dir, pr, runner = run } = {}) {
|
|
44
45
|
const rr = resolveRepo(root, { repo, dir });
|
|
45
46
|
if (rr.error) return { error: rr.error };
|
|
46
47
|
const { repoRoot, meta } = rr;
|
|
47
48
|
const platform = platformOf(root, repoRoot, meta);
|
|
48
|
-
|
|
49
|
+
// Same resolution as `yad open-pr` (#168): without it a repo whose trunk is not `main` grounded the
|
|
50
|
+
// companion on the wrong diff range — or on a branch that does not exist at all. `probe: false`
|
|
51
|
+
// because this caller only wants the base: a configured `default_branch` must answer locally and
|
|
52
|
+
// instantly, never behind a live gh/glab round-trip that could stall for the full timeout.
|
|
53
|
+
const { base } = resolveBaseBranch(platform, { cwd: repoRoot, meta, runner, probe: false });
|
|
49
54
|
const bundle = {
|
|
50
55
|
repo: meta?.name || null,
|
|
51
56
|
repoRoot,
|
|
@@ -66,8 +71,8 @@ function contextBundle(root, { repo, dir, pr } = {}) {
|
|
|
66
71
|
|
|
67
72
|
// `yad review context --repo <r> --pr <n>` — print the grounding bundle the companion uses to generate
|
|
68
73
|
// the trailer / cards and run the chat over the CODE diff (grounded in the repo code-map + the PR).
|
|
69
|
-
export async function reviewContext(root, { repo, dir, pr } = {}) {
|
|
70
|
-
const r = contextBundle(root, { repo, dir, pr });
|
|
74
|
+
export async function reviewContext(root, { repo, dir, pr, runner = run } = {}) {
|
|
75
|
+
const r = contextBundle(root, { repo, dir, pr, runner });
|
|
71
76
|
if (r.error) { fail(r.error); process.exitCode = 1; return; }
|
|
72
77
|
log(JSON.stringify(r.bundle, null, 2));
|
|
73
78
|
return r.bundle;
|
|
@@ -78,7 +83,7 @@ export async function reviewContext(root, { repo, dir, pr } = {}) {
|
|
|
78
83
|
// first). The CLI sequences deterministically; the skill (yad-pair-review) walks the stops, generates
|
|
79
84
|
// the per-stop briefing + Socratic question, and runs the two-way session. No LLM here, no ledger write.
|
|
80
85
|
export async function reviewWalkthrough(root, { repo, dir, pr, runner = run } = {}) {
|
|
81
|
-
const r = contextBundle(root, { repo, dir, pr });
|
|
86
|
+
const r = contextBundle(root, { repo, dir, pr, runner });
|
|
82
87
|
if (r.error) { fail(r.error); process.exitCode = 1; return; }
|
|
83
88
|
const { bundle, repoRoot, base } = r;
|
|
84
89
|
const diff = runner('git', ['-C', repoRoot, 'diff', `${base}...HEAD`]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "3.16.
|
|
3
|
+
"version": "3.16.3",
|
|
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, thread, reconcile). A BMAD module + 38 yad-* skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AbdelRahman Nasr",
|
|
@@ -20,8 +20,8 @@ SDLC Workflow,yad-implement,Implement Task,IM,"Build-half Step B: with the dev l
|
|
|
20
20
|
SDLC Workflow,yad-checks,Check Gates,CK,"Build-half Step C: wire and run the production-safety CI gates on a code repo (and product hub) — spec-link (every change links a real story/spec via its Task trailer), contract-check (a contract-surface change without Contract-Change + an updated re-locked contract FAILS and routes back to the architecture gate), build/test/lint, verified-commits (signed + roster-known authors), and the pattern gates commit-message / pr-title / pr-template (profile-aware code|hub). CI-agnostic bash invoked by GitHub Actions and GitLab CI. Blocking in CI; the human still owns the merge. Never auto-advances.",,{repo: <one of an epic's repos | hub>} {action: wire|run} {base: target branch},3-build,yad-implement,,false,demo-repos/<repo>/,checks/*.sh .github/workflows/yad-checks.yml .gitlab-ci.yml
|
|
21
21
|
SDLC Workflow,yad-pr-template,PR/MR Template,PT,"Build-half Step D: detect a code repo's platform and commit the matching PR/MR template (.github/pull_request_template.md or .gitlab/merge_request_templates/Default.md) with an Impact & Risk block. A high risk level (or a touched contract/auth/payments surface) routes the review to domain owners — the same escalation yad-review-gate applies. Ships the routing helper risk-route.sh plus the pattern-gate scripts pr-title.sh and pr-template.sh (used by yad-checks). Never auto-advances.",,{repo: <one of an epic's repos | hub>} {action: wire|route} {body: PR description file},3-build,yad-checks,,false,demo-repos/<repo>/,.github/pull_request_template.md .gitlab/merge_request_templates/Default.md checks/risk-route.sh checks/pr-title.sh checks/pr-template.sh
|
|
22
22
|
SDLC Workflow,yad-commit,Commit by Convention,CM,"Build-half helper: commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer block (Task -> Contract-Change -> Co-Authored-By), and the <=3-file atomic guard. 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). Drives the yad commit CLI. Never auto-advances.",,{type: feat|fix|...} {message: subject} {ai: <tool|none>} {task: <id>} {contract-change: true|false},3-build,yad-implement,,false,<repo>/,one commit
|
|
23
|
-
SDLC Workflow,yad-open-pr,Open PR/MR,OP,"Build-half helper: open a code-repo task PR/MR from the committed platform template — detect GitHub/GitLab, push the task branch, create the PR/MR with the body prefilled (Summary / Story-task / Impact & Risk) and the title defaulting to the commit subject. Auto-assigns from the hub roster (assignee = committer, reviewers = repo reviewers + domain-owners); high risk / contract surface routes to domain owners (risk-route.sh). Drives the yad open-pr CLI. Never merges; never auto-advances.",,{repo: <name>} {risk: low|medium|high} {contract-change: true|false},3-build,yad-commit,,false,<repo>/,one PR/MR
|
|
24
|
-
SDLC Workflow,yad-ship,Commit + Open PR/MR,SP2,"Build-half helper: commit AND open the task PR/MR in one step — a thin orchestration over yad-commit then yad-open-pr. Commits the staged atomic change by the conventions, then pushes the branch and opens the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the yad ship CLI. Never merges; never auto-advances.",,{type: feat|fix|...} {message: subject} {ai: <tool|none>} {repo: <name>} {risk: low|medium|high} {contract-change: true|false},3-build,yad-pr-template,,false,<repo>/,one commit + one PR/MR
|
|
23
|
+
SDLC Workflow,yad-open-pr,Open PR/MR,OP,"Build-half helper: open a code-repo task PR/MR from the committed platform template — detect GitHub/GitLab, push the task branch, create the PR/MR with the body prefilled (Summary / Story-task / Impact & Risk) and the title defaulting to the commit subject. Auto-assigns from the hub roster (assignee = committer, reviewers = repo reviewers + domain-owners); high risk / contract surface routes to domain owners (risk-route.sh). Bases the PR on the repo's RESOLVED default branch (repos.json default_branch, else the platform's own default, else origin/HEAD, else main) instead of a hardcoded main, and warns when the base is not the platform default — that loses the AI first pass irreversibly. Drives the yad open-pr CLI. Never merges; never auto-advances.",,{repo: <name>} {risk: low|medium|high} {contract-change: true|false} {base: <branch>},3-build,yad-commit,,false,<repo>/,one PR/MR
|
|
24
|
+
SDLC Workflow,yad-ship,Commit + Open PR/MR,SP2,"Build-half helper: commit AND open the task PR/MR in one step — a thin orchestration over yad-commit then yad-open-pr. Commits the staged atomic change by the conventions, then pushes the branch and opens the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the yad ship CLI. Never merges; never auto-advances.",,{type: feat|fix|...} {message: subject} {ai: <tool|none>} {repo: <name>} {risk: low|medium|high} {contract-change: true|false} {base: <branch>},3-build,yad-pr-template,,false,<repo>/,one commit + one PR/MR
|
|
25
25
|
SDLC Workflow,yad-hub-bridge,Hub Review Bridge,HB,"The templated PR/MR bridge for the front-half review gate: when the product hub has a platform (.sdlc/hub.json), open a review PR/MR on the hub for an authored artifact, set required reviewers/labels from the routing rule, and provide the read-only gh/glab recipes yad-review-gate's sync uses to pull platform comments + approvals into the file ledger. Local-user auth, no stored tokens; file ledger stays the source of truth; degrades to file-only when no platform/CLI. Never auto-advances.",,{epic: EP-<slug>} {artifact: epic.md|architecture.md|ui-design.md|stories/} {action: open|route},1-front,yad-review-gate,yad-review-gate,false,epics/EP-<slug>/.sdlc/,hub-prs.json
|
|
26
26
|
SDLC Workflow,yad-engineer-review,Engineer Review & Merge,ER,"Build-half Step E: wire an advisory AI first-pass (CodeRabbit) on the PR, 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), and on merge record the ship in epics/<epic>/.sdlc/build-log.json and update the story state. AI review is advisory, never the authority; the human owns the merge. Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {task: T0N} {repo: <repo>} {action: ai-review|approve|ship},3-build,yad-ship,,false,epics/EP-<slug>/.sdlc/,build-log.json story-status
|
|
27
27
|
SDLC Workflow,yad-backfill,Backfill Specs,BF,"Build-half Step G: generate specs for already-built features in an existing repo. Confirm Repomix (npx repomix CLI), pack ONE feature (compress + git logs, secret-scan), feed to AI with a 'describe what exists, do not invent' prompt, write a DRAFT spec marked verified: false. Human approval (reuse yad-review-gate) makes it real. Boundary auto-proposed and human-confirmed. A change is blocked only until the features it touches have approved specs. The promote action flips a brownfield stub epic (yad-stub) to a real, verified feature epic once its backfill spec is approved. Never auto-advances.",,{repo: <repo>} {feature: <name + globs>} {action: pack|draft|approve|gate|promote} {epic: EP-<slug> (promote)},3-build,,,false,demo-repos/<repo>/specs/backfill/<feature>/,spec.md backfill-check.sh
|
|
@@ -30,6 +30,17 @@ the product hub.
|
|
|
30
30
|
(`## Summary` / `Risk level:` / `## Checklist`) instead of the hub's artifact-review
|
|
31
31
|
`pull_request_template.md`, so the hub `pr-template` gate passes.
|
|
32
32
|
In a code repo nothing changes — it reads the repo's own committed code-task template.
|
|
33
|
+
- **Base branch** — **resolved, never assumed.** In order: `--base` → the repo's `default_branch` in
|
|
34
|
+
`.sdlc/repos.json` → (for a PR against the hub itself) `hub.json`'s `default_branch` → what the
|
|
35
|
+
platform reports (`gh repo view --json defaultBranchRef` / `glab api projects/:id`) → local
|
|
36
|
+
`origin/HEAD` → `main`. The same **configuration-outranks-the-remote** order `yad repo sync` and the
|
|
37
|
+
contract-check gate already use (they stop at `origin/HEAD`; only this chain also asks the platform).
|
|
38
|
+
The CLI prints which rung answered.
|
|
39
|
+
**If the resolved base is not the platform's default branch it warns and still opens** — that is a
|
|
40
|
+
legitimate stacked-PR / release-branch move, but it costs the AI first pass: CodeRabbit decides
|
|
41
|
+
auto-review eligibility from the base at PR-**open** time, and retargeting afterwards does not undo
|
|
42
|
+
the skip. Hardcoding `main` here is the same bug the check gates already refuse to make (see
|
|
43
|
+
`../yad-checks/references/check-gates.md`).
|
|
33
44
|
- **Auto-assign** — from the hub roster scoped to this repo: assignee = the committer (resolved from
|
|
34
45
|
the local git identity), reviewers = the repo's `reviewer`/`domain-owner` logins minus the committer.
|
|
35
46
|
Degrades cleanly when there is no roster.
|
|
@@ -42,7 +53,9 @@ the product hub.
|
|
|
42
53
|
- `repo` — target a registered repo by name (optional; else the current dir).
|
|
43
54
|
- `risk` — `low|medium|high` (default `low`); prefilled into the body.
|
|
44
55
|
- `contractChange` — flag; marks the contract surface touched and triggers escalation.
|
|
45
|
-
- `base`
|
|
56
|
+
- `base` — override the PR/MR base (optional; defaults to the repo's own default branch —
|
|
57
|
+
see **Base branch** above). Only pass it deliberately: a non-default base loses the AI first pass.
|
|
58
|
+
- `platform` / `title` — optional overrides.
|
|
46
59
|
|
|
47
60
|
## On Activation
|
|
48
61
|
|
|
@@ -56,7 +69,14 @@ Run from the repo root:
|
|
|
56
69
|
yad open-pr [--repo <name>] [--risk <level>] [--contract-change] [--title "<subject>"]
|
|
57
70
|
```
|
|
58
71
|
The CLI pushes the branch (sets upstream, the user's own auth), fills the template, and creates the
|
|
59
|
-
PR/MR with the auto-assigned assignee + reviewers.
|
|
72
|
+
PR/MR with the auto-assigned assignee + reviewers. It prints the base it resolved and where that came
|
|
73
|
+
from.
|
|
74
|
+
|
|
75
|
+
The non-default-base warning is **advisory — it does not block, and the PR/MR is already open by the
|
|
76
|
+
time you read it.** If the base was intended (a stacked PR, a release branch), carry on. If it was
|
|
77
|
+
not, do **not** just retarget the open PR — that leaves the AI first pass skipped. Close it, fix the
|
|
78
|
+
cause (the repo's `default_branch`, or drop the wrong `--base`), and re-run `yad open-pr` so the PR
|
|
79
|
+
is *created* against the right base.
|
|
60
80
|
|
|
61
81
|
### Step 3 — Route the review (if escalated)
|
|
62
82
|
On `high` risk or a contract touch, run `bash checks/risk-route.sh <pr-body>` to print the required
|
|
@@ -79,6 +99,8 @@ engineer review and merge happen in `yad-engineer-review` (Step E).
|
|
|
79
99
|
## Hard rules
|
|
80
100
|
|
|
81
101
|
- **One task = one branch = one PR/MR.** Never open a PR from the default branch.
|
|
102
|
+
- **The base is the repo's default branch** unless you deliberately chose otherwise with `--base`.
|
|
103
|
+
Never hardcode `main`, and never ignore the non-default-base warning silently.
|
|
82
104
|
- **Title follows the commit subject** — Conventional-Commits style, so the `pr-title` gate passes.
|
|
83
105
|
- **High risk routes to domain owners** — the same escalation as the gate; never a separate rule.
|
|
84
106
|
- **Opening a PR never merges.** The human owns the merge in Step E.
|
package/skills/yad-ship/SKILL.md
CHANGED
|
@@ -34,7 +34,11 @@ its own and **never merges**. The engineer review + merge are Step E (`yad-engin
|
|
|
34
34
|
footer**; the `Co-Authored-By` trailer appears only when this flag names a tool).
|
|
35
35
|
- `task` — Task trailer (optional; derived from the branch when omitted).
|
|
36
36
|
- `contractChange` — flag; marks the contract surface touched (commit trailer + PR escalation).
|
|
37
|
-
- `repo` / `risk` / `
|
|
37
|
+
- `repo` / `risk` / `platform` / `title` — PR/MR options (see `yad-open-pr`).
|
|
38
|
+
- `base` — override the PR/MR base. The default is the repo's own default branch, resolved
|
|
39
|
+
by the full chain in **Base branch** (`yad-open-pr`) — the canonical description, including the
|
|
40
|
+
hub rung — never a hardcoded `main`. A non-default base loses the AI first pass; `ship` warns and
|
|
41
|
+
still opens.
|
|
38
42
|
|
|
39
43
|
## On Activation
|
|
40
44
|
|
|
@@ -59,6 +63,7 @@ the engineer review and merge are Step E (`yad-engineer-review`).
|
|
|
59
63
|
## Hard rules
|
|
60
64
|
|
|
61
65
|
- **One staged atomic task = one commit = one PR/MR.** Never bundle; never open from the default branch.
|
|
66
|
+
- **The PR targets the repo's default branch** unless `--base` says otherwise; never assume `main`.
|
|
62
67
|
- **No AI footer by default.** The wrapped commit writes a `Co-Authored-By` trailer ONLY when `--ai <id>`
|
|
63
68
|
is explicitly passed; never add it on the AI's own initiative.
|
|
64
69
|
- **No PR without a landed commit.** A failed/`--dry-run` commit stops the step before pushing.
|