yadflow 3.16.2 → 3.17.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 +31 -0
- package/README.md +4 -0
- package/bin/yad.mjs +33 -5
- package/cli/doctor.mjs +55 -1
- package/cli/gate.mjs +5 -6
- package/cli/hook.mjs +224 -0
- package/cli/manifest.mjs +40 -0
- package/cli/openpr.mjs +36 -7
- package/cli/plan.mjs +160 -5
- package/cli/platform.mjs +88 -0
- package/cli/reconcile.mjs +2 -2
- package/cli/review.mjs +10 -5
- package/cli/setup.mjs +10 -1
- package/package.json +1 -1
- package/skills/sdlc/module-help.csv +2 -2
- package/skills/yad-checks/SKILL.md +42 -0
- package/skills/yad-checks/references/check-gates.md +106 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +69 -0
- package/skills/yad-hub-bridge/SKILL.md +3 -1
- package/skills/yad-open-pr/SKILL.md +24 -2
- package/skills/yad-review-gate/SKILL.md +3 -1
- package/skills/yad-ship/SKILL.md +6 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ledger-guard HARNESS HOOK — the local half of the CI gate of the same name (#171).
|
|
3
|
+
#
|
|
4
|
+
# The gate ledger is CI-owned in bridge mode: `checks/ledger-guard.sh` rejects any non-bot commit
|
|
5
|
+
# that changes `epics/*/.sdlc/{state,approvals,comments,hub-prs}.json` or `epics/*/reviews/*.md`.
|
|
6
|
+
# This hook says so at the moment an agent tries the edit, instead of twenty minutes later in a
|
|
7
|
+
# failed pipeline, and names the command that owns the transition (`yad gate open`).
|
|
8
|
+
#
|
|
9
|
+
# This file is only the ADAPTER. It locates `yad` and hands the tool-call payload to
|
|
10
|
+
# `yad hook ledger-guard`, which holds the decision — so the wiring never hard-codes an install path
|
|
11
|
+
# and the logic stays testable. The contract it passes through:
|
|
12
|
+
#
|
|
13
|
+
# stdin the harness's tool-call payload as JSON (optional)
|
|
14
|
+
# exit 0 allow
|
|
15
|
+
# exit 2 deny, reason on stderr
|
|
16
|
+
#
|
|
17
|
+
# Wired for Claude Code as a `PreToolUse` hook in `.claude/settings.json` (`yad check --fix` writes
|
|
18
|
+
# that entry). Any harness that can run a command and read those two exit codes can use it.
|
|
19
|
+
#
|
|
20
|
+
# FAIL-OPEN: if no `yad` can be found, this ALLOWS and says why on stderr. A guardrail that blocked
|
|
21
|
+
# every edit the moment an install went sideways would be worse than the problem. The CI gate fails
|
|
22
|
+
# CLOSED and is what actually protects the ledger.
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
# The hub root is this script's grandparent — hooks/ledger-guard.sh — so the resolution below does
|
|
26
|
+
# not depend on the harness's working directory.
|
|
27
|
+
HOOK_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
HUB_ROOT="$(dirname -- "$HOOK_DIR")"
|
|
29
|
+
|
|
30
|
+
# Resolution order, cheapest and most specific first: an explicit override, then the copy installed
|
|
31
|
+
# in this hub, then whatever is on PATH, then a network-free npx. `--no-install` matters — a hook
|
|
32
|
+
# runs on every tool call and must never pause an agent to download a package.
|
|
33
|
+
CMD=()
|
|
34
|
+
_yad_bin="${YAD_BIN:-}"
|
|
35
|
+
if [ -n "${_yad_bin//[[:space:]]/}" ]; then
|
|
36
|
+
# Split deliberately: YAD_BIN is commonly an interpreter + script ("node /path/to/yad.mjs").
|
|
37
|
+
# The whitespace-stripped test above matters: a YAD_BIN of only spaces would leave CMD empty, and
|
|
38
|
+
# macOS's bash 3.2 treats "${CMD[@]}" on an empty array as an unbound variable under `set -u` —
|
|
39
|
+
# aborting the script with a confusing 127 instead of taking one of the branches below.
|
|
40
|
+
read -r -a CMD <<< "$_yad_bin"
|
|
41
|
+
elif [ -f "$HUB_ROOT/node_modules/yadflow/bin/yad.mjs" ] && command -v node >/dev/null 2>&1; then
|
|
42
|
+
CMD=(node "$HUB_ROOT/node_modules/yadflow/bin/yad.mjs")
|
|
43
|
+
elif command -v yad >/dev/null 2>&1; then
|
|
44
|
+
CMD=(yad)
|
|
45
|
+
elif command -v npx >/dev/null 2>&1; then
|
|
46
|
+
CMD=(npx --no-install yadflow)
|
|
47
|
+
else
|
|
48
|
+
echo " • yad hook: no \`yad\` on PATH and none installed in $HUB_ROOT — allowing (install yadflow to re-arm the ledger guard)" >&2
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Belt and braces for bash 3.2's empty-array-is-unbound rule: every branch above sets CMD, but an
|
|
53
|
+
# unexpanded array under `set -u` would abort the script rather than allow, so check before using it.
|
|
54
|
+
if [ "${#CMD[@]}" -eq 0 ]; then
|
|
55
|
+
echo " • yad hook: could not resolve a \`yad\` to run — allowing" >&2
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Run it rather than `exec`, so the exit code can be mapped. ONLY an explicit deny (2) blocks: a
|
|
60
|
+
# `yad` that is present but cannot run — an `npx --no-install` with no yadflow to find, a crash, a
|
|
61
|
+
# broken install — must not read as a refusal. Fail-open is the whole stance of this hook; the CI
|
|
62
|
+
# gate is what fails closed.
|
|
63
|
+
"${CMD[@]}" hook ledger-guard "$@"
|
|
64
|
+
rc=$?
|
|
65
|
+
[ "$rc" -eq 2 ] && exit 2
|
|
66
|
+
if [ "$rc" -ne 0 ]; then
|
|
67
|
+
echo " • yad hook: \`${CMD[*]} hook ledger-guard\` exited $rc — allowing (run \`yad doctor\` to check the install)" >&2
|
|
68
|
+
fi
|
|
69
|
+
exit 0
|
|
@@ -87,7 +87,9 @@ Install the hub CI that turns the human **merge** into a `yad gate ci` run, with
|
|
|
87
87
|
writer** of the ledger. There is no pre-merge CI write — during review the platform PR/MR is the
|
|
88
88
|
source of truth (native approvals + threads). On merge, CI re-reads approvals from the platform,
|
|
89
89
|
advances the step, and flips the artifact `status:` on the **default branch** (the only place CI ever
|
|
90
|
-
commits). Also install the `ledger-guard` check (yad-checks) so humans cannot commit gate-state files
|
|
90
|
+
commits). Also install the `ledger-guard` check (yad-checks) so humans cannot commit gate-state files,
|
|
91
|
+
and its local counterpart `hooks/ledger-guard.sh` — the harness hook that refuses an **agent** the
|
|
92
|
+
same write at the moment it tries it, instead of letting it surface as a CI failure later (#171).
|
|
91
93
|
Revoke-on-change is enforced at merge: on **GitHub** in code (an approval whose commit ≠ the merged
|
|
92
94
|
head is dropped — no setting needed); on **GitLab** it has no per-approval commit SHA, so enabling the
|
|
93
95
|
platform's **"remove all approvals when commits are added to the source branch"** is **required** for
|
|
@@ -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.
|
|
@@ -188,7 +188,9 @@ PR only — against the `review/<epic>/<artifact>` branch, which must already ex
|
|
|
188
188
|
records this skill describes. The skill's
|
|
189
189
|
job is the human half: presenting the artifact, helping the owner address comments, and narrating the
|
|
190
190
|
gate. Local `yad gate sync` is advisory in bridge mode (reads the platform, prints status, writes
|
|
191
|
-
nothing); a human must never commit gate-state files (the `ledger-guard` check rejects it
|
|
191
|
+
nothing); a human must never commit gate-state files (the `ledger-guard` check rejects it, and the
|
|
192
|
+
`hooks/ledger-guard.sh` harness hook refuses an agent the edit up front, naming `yad gate open`
|
|
193
|
+
instead — see `yad-checks`). The single
|
|
192
194
|
exception is an epic's **seed** — no CI path can create a ledger, so a brand-new epic's `.sdlc/` rides
|
|
193
195
|
its **first** review PR/MR, cut from the authoring branch (creation, not mutation, #162).
|
|
194
196
|
|
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.
|