yadflow 4.0.0-next.3 → 4.0.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 +699 -0
- package/README.md +85 -22
- package/bin/commands.mjs +3 -1
- package/bin/yad.mjs +183 -29
- package/cli/capture.mjs +4 -2
- package/cli/checkpoint.mjs +10 -10
- package/cli/claims.mjs +4 -4
- package/cli/commit.mjs +48 -12
- package/cli/docs.mjs +15 -13
- package/cli/doctor.mjs +557 -141
- package/cli/epic-state.mjs +41 -18
- package/cli/errors.mjs +3 -2
- package/cli/find-product.mjs +197 -0
- package/cli/fold.mjs +4 -4
- package/cli/gate-local.mjs +13 -13
- package/cli/gate.mjs +111 -103
- package/cli/history.mjs +13 -13
- package/cli/hook.mjs +201 -30
- package/cli/index-command.mjs +6 -6
- package/cli/lib.mjs +91 -29
- package/cli/manifest.mjs +171 -104
- package/cli/migrate.mjs +208 -17
- package/cli/mode.mjs +22 -22
- package/cli/next.mjs +6 -6
- package/cli/openpr.mjs +32 -24
- package/cli/people.mjs +22 -10
- package/cli/plan.mjs +718 -45
- package/cli/platform.mjs +13 -7
- package/cli/product-index.mjs +4 -4
- package/cli/{hubcommit.mjs → productcommit.mjs} +11 -7
- package/cli/protection.mjs +19 -9
- package/cli/reconcile.mjs +98 -23
- package/cli/repo-publish.mjs +22 -16
- package/cli/repo.mjs +128 -33
- package/cli/report.mjs +4 -2
- package/cli/review.mjs +26 -19
- package/cli/setup.mjs +105 -36
- package/cli/ship.mjs +1 -1
- package/cli/skip.mjs +4 -4
- package/cli/tidy.mjs +7 -7
- package/cli/update-commit.mjs +22 -19
- package/cli/workspace.mjs +495 -0
- package/package.json +7 -4
- package/skills/sdlc/config.yaml +14 -13
- package/skills/sdlc/module-help.csv +8 -8
- package/skills/yad-analysis/SKILL.md +6 -4
- package/skills/yad-architecture/SKILL.md +4 -3
- package/skills/yad-backfill/SKILL.md +11 -5
- package/skills/yad-backfill/references/backfill.md +33 -3
- package/skills/yad-backfill/templates/checks/backfill-check.sh +200 -17
- package/skills/yad-change/SKILL.md +1 -1
- package/skills/yad-checks/SKILL.md +48 -27
- package/skills/yad-checks/references/check-gates.md +213 -53
- package/skills/yad-checks/templates/checks/commit-message.sh +39 -13
- package/skills/yad-checks/templates/checks/contract-check.sh +472 -23
- package/skills/yad-checks/templates/checks/epic-open.sh +398 -8
- package/skills/yad-checks/templates/checks/ledger-guard.sh +75 -25
- package/skills/yad-checks/templates/checks/lineage-check.sh +391 -7
- package/skills/yad-checks/templates/checks/product-checkout.sh +141 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +399 -8
- package/skills/yad-checks/templates/checks/risk-map-check.sh +35 -3
- package/skills/yad-checks/templates/checks/spec-link.sh +36 -3
- package/skills/yad-checks/templates/checks/verified-commits.sh +26 -2
- package/skills/yad-checks/templates/github/yad-checks.yml +33 -13
- package/skills/yad-checks/templates/github/{yad-hub-checks.yml → yad-product-checks.yml} +14 -10
- package/skills/yad-checks/templates/github/yad-update-guard.yml +1 -1
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +1 -1
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +12 -0
- package/skills/yad-checks/templates/gitlab/{yad-hub-checks.gitlab-ci.yml → yad-product-checks.gitlab-ci.yml} +16 -16
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +2 -2
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.mjs +130 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.mjs +117 -0
- package/skills/yad-checks/templates/hooks/yad-capture.mjs +117 -0
- package/skills/yad-commit/SKILL.md +18 -7
- package/skills/yad-connect-design/SKILL.md +1 -1
- package/skills/yad-connect-design/references/design-registry.md +1 -1
- package/skills/yad-connect-docs/SKILL.md +16 -11
- package/skills/yad-connect-docs/references/docs-registry.md +8 -8
- package/skills/yad-connect-learning/SKILL.md +1 -1
- package/skills/yad-connect-learning/references/learning-registry.md +1 -1
- package/skills/yad-connect-repos/SKILL.md +31 -8
- package/skills/yad-connect-repos/references/code-context.md +1 -1
- package/skills/yad-connect-repos/references/{hub-config.md → product-config.md} +19 -11
- package/skills/yad-connect-repos/references/repos-registry.md +1 -1
- package/skills/yad-connect-testing/SKILL.md +1 -1
- package/skills/yad-connect-testing/references/testing-registry.md +1 -1
- package/skills/yad-discovery/SKILL.md +2 -2
- package/skills/yad-discovery/references/discovery-schema.md +1 -1
- package/skills/yad-docs/SKILL.md +1 -1
- package/skills/yad-docs/references/data-mapping.md +1 -1
- package/skills/yad-docs/templates/app/package-lock.json +8 -8
- package/skills/yad-docs/templates/app/package.json +1 -1
- package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +2 -2
- package/skills/yad-docs-overview/references/pipeline-model.md +3 -3
- package/skills/yad-engineer-review/SKILL.md +2 -2
- package/skills/yad-engineer-review/references/ship-and-record.md +3 -3
- package/skills/yad-epic/SKILL.md +6 -4
- package/skills/yad-epic/references/state-schema.md +13 -12
- package/skills/yad-open-pr/SKILL.md +1 -1
- package/skills/yad-pair-review/SKILL.md +5 -5
- package/skills/yad-pair-review/references/session-state.md +2 -2
- package/skills/yad-pr-template/SKILL.md +14 -12
- package/skills/yad-pr-template/templates/checks/pr-template.sh +18 -16
- package/skills/yad-pr-template/templates/checks/pr-title.sh +15 -13
- package/skills/yad-pr-template/templates/{hub → product}/github/pull_request_template.md +2 -2
- package/skills/yad-pr-template/templates/{hub → product}/gitlab/merge_request_templates/Default.md +2 -2
- package/skills/{yad-hub-bridge → yad-product-bridge}/SKILL.md +15 -15
- package/skills/{yad-hub-bridge → yad-product-bridge}/references/bridge.md +15 -10
- package/skills/{yad-hub-bridge → yad-product-bridge}/references/login-roster.md +2 -2
- package/skills/{yad-hub-bridge/templates/checks/hub-route.sh → yad-product-bridge/templates/checks/product-route.sh} +2 -2
- package/skills/{yad-hub-bridge → yad-product-bridge}/templates/github/yad-gate-sync.yml +29 -16
- package/skills/{yad-hub-bridge → yad-product-bridge}/templates/gitlab/gitlab-ci.include-root.yml +1 -1
- package/skills/{yad-hub-bridge → yad-product-bridge}/templates/gitlab/yad-gate-sync.gitlab-ci.yml +5 -3
- package/skills/yad-report/SKILL.md +1 -1
- package/skills/yad-review-companion/SKILL.md +3 -3
- package/skills/yad-review-gate/SKILL.md +22 -23
- package/skills/yad-review-gate/references/gating.md +13 -13
- package/skills/yad-run/SKILL.md +2 -2
- package/skills/yad-run/references/run-loop.md +2 -2
- package/skills/yad-spec/SKILL.md +2 -1
- package/skills/yad-spec/references/spec-handoff.md +19 -10
- package/skills/yad-stories/SKILL.md +2 -2
- package/skills/yad-sync-repos/SKILL.md +19 -3
- package/skills/yad-test-cases/SKILL.md +2 -2
- package/skills/yad-ui/SKILL.md +2 -2
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +0 -91
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +0 -100
- package/skills/yad-checks/templates/hooks/yad-capture.sh +0 -53
|
@@ -28,10 +28,12 @@
|
|
|
28
28
|
# seed rides the first review PR/MR; see the carve-out below. Mutation stays bot-only.
|
|
29
29
|
#
|
|
30
30
|
# A "bot commit" must be BOTH authored by the gate bot (name/email contains yad-gate-sync) AND
|
|
31
|
-
# platform-VERIFIED
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
31
|
+
# platform-VERIFIED. A spoofed-author commit that is not Verified is treated as a human edit and
|
|
32
|
+
# rejected. Known gap: the Verified badge proves only that the COMMITTER signed with their own key, not
|
|
33
|
+
# that the bot did, so a contributor who signs can still write the bot's name as the author. The
|
|
34
|
+
# GitHub workflow therefore gives this job no token: the badge lookup fails and every bot-attributed
|
|
35
|
+
# ledger commit is refused. On GitLab the project token reaches this job, so the gap is still open there
|
|
36
|
+
# (see references/check-gates.md).
|
|
35
37
|
#
|
|
36
38
|
# Scope: enforced ONLY when the ledger is verified — hub.json carries BOTH a `platform` and either
|
|
37
39
|
# `ledger: "verified"` or, on a project that has not run `yad migrate` yet, `bridge_enabled` (or the
|
|
@@ -43,6 +45,30 @@
|
|
|
43
45
|
# badge) WARNs and waives the signature half — the same stance verified-commits takes.
|
|
44
46
|
set -euo pipefail
|
|
45
47
|
|
|
48
|
+
# --- shared Product settings file (byte-identical across the gates; they are standalone by design, so
|
|
49
|
+
# --- it is duplicated, not sourced) ---
|
|
50
|
+
# The Product's settings live under two names until v5: `.sdlc/product.json`, read first from 4.0, and
|
|
51
|
+
# `.sdlc/hub.json`, which an older yadflow wrote and its gates read. The environment may name the file
|
|
52
|
+
# instead: SDLC_PRODUCT_CONFIG, or the older SDLC_HUB_CONFIG. Two names that say different things are
|
|
53
|
+
# never settled by picking one — the CLI refuses the same way (YAD-STATE-008) — so the gate FAILS and
|
|
54
|
+
# names both. Said on stderr and returned as a failure: this runs inside `$(...)`, where an `exit` would
|
|
55
|
+
# only leave the subshell. `cmp -s` is byte for byte, like the CLI's own comparison.
|
|
56
|
+
product_config() {
|
|
57
|
+
if [ -n "${SDLC_PRODUCT_CONFIG:-}" ] && [ -n "${SDLC_HUB_CONFIG:-}" ] \
|
|
58
|
+
&& [ "$SDLC_PRODUCT_CONFIG" != "$SDLC_HUB_CONFIG" ] && ! cmp -s "$SDLC_PRODUCT_CONFIG" "$SDLC_HUB_CONFIG"; then
|
|
59
|
+
echo "FAIL [product-settings]: SDLC_PRODUCT_CONFIG (${SDLC_PRODUCT_CONFIG}) and SDLC_HUB_CONFIG (${SDLC_HUB_CONFIG}) name files that say different things. Set only SDLC_PRODUCT_CONFIG." >&2
|
|
60
|
+
return 1
|
|
61
|
+
fi
|
|
62
|
+
if [ -n "${SDLC_PRODUCT_CONFIG:-}" ]; then printf '%s' "$SDLC_PRODUCT_CONFIG"; return 0; fi
|
|
63
|
+
if [ -n "${SDLC_HUB_CONFIG:-}" ]; then printf '%s' "$SDLC_HUB_CONFIG"; return 0; fi
|
|
64
|
+
if [ -f .sdlc/product.json ] && [ -f .sdlc/hub.json ] && ! cmp -s .sdlc/product.json .sdlc/hub.json; then
|
|
65
|
+
echo "FAIL [product-settings]: .sdlc/product.json and .sdlc/hub.json say different things — they are one file under two names until v5. Run \`yad migrate\` in the Product to choose the copy to keep, and commit both." >&2
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
if [ -f .sdlc/product.json ]; then printf '%s' .sdlc/product.json; else printf '%s' .sdlc/hub.json; fi
|
|
69
|
+
}
|
|
70
|
+
PRODUCT_CONFIG="$(product_config)" || exit 1
|
|
71
|
+
|
|
46
72
|
# ---- bridge gate: only CI-owned ledgers are guarded -------------------------------------------
|
|
47
73
|
# The predicate is BOTH a platform and the verified ledger flag, exactly as `isVerifiedLedger` (`cli/manifest.mjs`) and
|
|
48
74
|
# `productActions` (cli/plan.mjs) define it. Requiring the flag alone put this gate out of step with every
|
|
@@ -68,22 +94,22 @@ set -euo pipefail
|
|
|
68
94
|
# Flattened ONCE into a variable and matched with here-strings, never `tr … | grep -q`: under the
|
|
69
95
|
# `pipefail` set above, `grep -q` exits at the first match and can SIGPIPE `tr`, which would make a
|
|
70
96
|
# MATCHING pipeline report failure. Reading from a here-string has no upstream process to kill.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
SETTINGS="$PRODUCT_CONFIG"
|
|
98
|
+
SETTINGS_FLAT="$({ tr -d '\n' < "$SETTINGS"; } 2>/dev/null || true)"
|
|
99
|
+
SETTINGS_ROOT="${SETTINGS_FLAT#*\{}"
|
|
100
|
+
SETTINGS_ROOT="${SETTINGS_ROOT%\}*}"
|
|
75
101
|
while :; do
|
|
76
|
-
_stripped="$(sed -E 's/\{[^{}]*\}//g; s/\[[^][]*\]//g' <<< "$
|
|
77
|
-
[ "$_stripped" = "$
|
|
78
|
-
|
|
102
|
+
_stripped="$(sed -E 's/\{[^{}]*\}//g; s/\[[^][]*\]//g' <<< "$SETTINGS_ROOT")"
|
|
103
|
+
[ "$_stripped" = "$SETTINGS_ROOT" ] && break
|
|
104
|
+
SETTINGS_ROOT="$_stripped"
|
|
79
105
|
done
|
|
80
106
|
# One line in, so `sed` emits at most one line out — no `head` needed (which would re-introduce the
|
|
81
107
|
# SIGPIPE-under-pipefail problem this avoids).
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# Is the key present with a STRING value at all, empty included? `
|
|
108
|
+
setting_str() { sed -nE "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/p" <<< "$SETTINGS_ROOT"; }
|
|
109
|
+
setting_true() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*true" <<< "$SETTINGS_ROOT"; }
|
|
110
|
+
# Is the key present with a STRING value at all, empty included? `setting_str` cannot answer this: it
|
|
85
111
|
# returns nothing both for a missing key and for an empty one.
|
|
86
|
-
|
|
112
|
+
setting_has_str() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*\"" <<< "$SETTINGS_ROOT"; }
|
|
87
113
|
|
|
88
114
|
# READ ORDER, identical to `isVerifiedLedger` (cli/manifest.mjs) — the two must never disagree:
|
|
89
115
|
# 1. `ledger`, if hub.json carries it — shape 2 and later. "verified" and nothing else.
|
|
@@ -104,13 +130,13 @@ hub_has_str() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*\"" <<< "$HUB_ROOT"; }
|
|
|
104
130
|
# OPPOSITE answers — the guard rejecting human writes while the CLI kept the local path open, which
|
|
105
131
|
# is the no-writer deadlock of #186 reached from a third direction.
|
|
106
132
|
verified=no
|
|
107
|
-
if
|
|
108
|
-
if [ "$(
|
|
109
|
-
elif
|
|
133
|
+
if setting_has_str ledger; then
|
|
134
|
+
if [ "$(setting_str ledger)" = "verified" ]; then verified=yes; fi
|
|
135
|
+
elif setting_true bridge_enabled || setting_true bridge; then
|
|
110
136
|
verified=yes
|
|
111
137
|
fi
|
|
112
138
|
|
|
113
|
-
if [ ! -f "$
|
|
139
|
+
if [ ! -f "$SETTINGS" ] || [ -z "$(setting_str platform)" ] || [ "$verified" != yes ]; then
|
|
114
140
|
echo "PASS [ledger-guard]: the ledger is locally owned (ledger: local) — nothing to guard."
|
|
115
141
|
exit 0
|
|
116
142
|
fi
|
|
@@ -119,7 +145,7 @@ fi
|
|
|
119
145
|
# --- is duplicated, not sourced) ---
|
|
120
146
|
# With no explicit base, RESOLVE the trunk instead of assuming a hardcoded `origin/main` — on a repo
|
|
121
147
|
# whose trunk is `develop`/`master` that guess either fails closed or, where a stale `main` still
|
|
122
|
-
# exists, silently diffs the WRONG range (issue #161). Mirrors the CLI's own order (cli/
|
|
148
|
+
# exists, silently diffs the WRONG range (issue #161). Mirrors the CLI's own order (cli/productcommit.mjs,
|
|
123
149
|
# cli/repo.mjs): the CONFIGURED default_branch first, then the remote's published default
|
|
124
150
|
# (origin/HEAD), then origin/main. Each candidate must actually resolve before it is used, so a
|
|
125
151
|
# DANGLING origin/HEAD (trunk renamed, the old remote-tracking ref pruned) falls through to the next
|
|
@@ -128,7 +154,7 @@ fi
|
|
|
128
154
|
# command substitution in an assignment aborts the script.
|
|
129
155
|
resolve_base() {
|
|
130
156
|
# tr first: a key and its value may legally sit on separate lines, which a per-line match misses.
|
|
131
|
-
_cfg="$(tr -d '\n' < "$
|
|
157
|
+
_cfg="$({ tr -d '\n' < "$PRODUCT_CONFIG"; } 2>/dev/null | sed -nE 's/.*"default_branch"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p')" || _cfg=""
|
|
132
158
|
_head="$(git symbolic-ref --short --quiet refs/remotes/origin/HEAD 2>/dev/null)" || _head=""
|
|
133
159
|
for _c in "origin/${_cfg}" "${_head}" origin/main; do
|
|
134
160
|
case "$_c" in ''|origin/) continue ;; esac
|
|
@@ -197,6 +223,15 @@ trusted_bot() {
|
|
|
197
223
|
signature_verified "$1"
|
|
198
224
|
}
|
|
199
225
|
|
|
226
|
+
# Text a commit's author chose — a name, a path, a Ledger-Override reason — is printed into the CI log.
|
|
227
|
+
# The GitHub runner ends a log line at a lone carriage return too, so every control character (CR, ESC,
|
|
228
|
+
# …) becomes a space first; otherwise `\r::error::…` inside a path or a reason would start a line
|
|
229
|
+
# GitHub Actions reads as a workflow command.
|
|
230
|
+
# `printable` keeps newlines, for a list read line by line; `oneline` is for one value (a name, a path),
|
|
231
|
+
# where a newline would start a line of its own too.
|
|
232
|
+
printable() { LC_ALL=C tr '\000-\011\013-\037\177' ' '; }
|
|
233
|
+
oneline() { printf '%s' "$1" | LC_ALL=C tr '\000-\037\177' ' '; }
|
|
234
|
+
|
|
200
235
|
# ---- seeding carve-out: creation is not mutation (#162) ---------------------------------------
|
|
201
236
|
# A brand-new epic's ledger has no CI author. `gate ci` only ADVANCES an existing chain — it bails on
|
|
202
237
|
# a missing state.json ("the review branch is cut from the default branch, so it should carry it") and
|
|
@@ -266,12 +301,20 @@ is_seeding() { # $1 = epic slug; 0 when that epic has no le
|
|
|
266
301
|
in_list "$_f" "${base_slugs[@]}" && return 1
|
|
267
302
|
in_list "$_f" "${noted_slugs[@]}" && return 0
|
|
268
303
|
noted_slugs[${#noted_slugs[@]}]="$_f"
|
|
269
|
-
if [ "$_f" = "ep-foundation" ]; then _where="foundation"; else _where="epics/$1"; fi
|
|
304
|
+
if [ "$_f" = "ep-foundation" ]; then _where="foundation"; else _where="epics/$(oneline "$1")"; fi
|
|
270
305
|
echo "note [ledger-guard]: ${_where} has no ledger on ${BASE} — new epic, its seed is exempt (creation, not mutation)."
|
|
271
306
|
return 0
|
|
272
307
|
}
|
|
273
308
|
|
|
274
309
|
violations=0
|
|
310
|
+
# E49: `yad commit --manual --reason` records why a person committed past the local ledger hook, as a
|
|
311
|
+
# `Ledger-Override:` trailer. Anyone can type a trailer, so it changes NOTHING about the verdict — the
|
|
312
|
+
# failing commit still fails. The reason is quoted under the FAIL so the reviewer sees why it was done.
|
|
313
|
+
# Every quoted line goes through `printable` (above) and starts with the commit's sha: a reason is the
|
|
314
|
+
# commit author's text, and a line whose first non-blank characters were `::` would be read by GitHub
|
|
315
|
+
# Actions as a workflow command. Indenting alone would not stop that — the runner skips leading spaces —
|
|
316
|
+
# so every line that prints author text starts with text of ours (the sha, `note [ledger-guard]:`).
|
|
317
|
+
overrides=()
|
|
275
318
|
for sha in $commits; do
|
|
276
319
|
touches_ledger=0
|
|
277
320
|
# quotePath=false + -z on both git reads: a path git chose to escape ("epics/EP-caf\303\251/…") and a
|
|
@@ -282,14 +325,14 @@ for sha in $commits; do
|
|
|
282
325
|
case "$f" in
|
|
283
326
|
.sdlc/index.json) # the Product index (E19) — derived, CI-written, never a seed
|
|
284
327
|
touches_ledger=1
|
|
285
|
-
echo " ${sha} (author $(git show -s --format='%an' "$sha")) → $f"
|
|
328
|
+
echo " ${sha} (author $(oneline "$(git show -s --format='%an' "$sha")")) → $(oneline "$f")"
|
|
286
329
|
;;
|
|
287
330
|
epics/*/.sdlc/contract-lock.json) ;; # artifact-side — allowed
|
|
288
331
|
epics/*/.sdlc/state.json|epics/*/.sdlc/approvals.json|epics/*/.sdlc/comments.json|epics/*/.sdlc/product-prs.json|epics/*/.sdlc/hub-prs.json|epics/*/reviews/*.md)
|
|
289
332
|
_slug="${f#epics/}"; _slug="${_slug%%/*}"
|
|
290
333
|
is_seeding "$_slug" && continue # a new epic's seed — not a mutation of a CI-owned ledger
|
|
291
334
|
touches_ledger=1
|
|
292
|
-
echo " ${sha} (author $(git show -s --format='%an' "$sha")) → $f"
|
|
335
|
+
echo " ${sha} (author $(oneline "$(git show -s --format='%an' "$sha")")) → $(oneline "$f")"
|
|
293
336
|
;;
|
|
294
337
|
# The Foundation's ledger (E75) — the same files, in the Product level's own folder, under the fixed
|
|
295
338
|
# id EP-foundation. Matched on the path BELOW `foundation/` with a leading `/` put back, so the
|
|
@@ -300,7 +343,7 @@ for sha in $commits; do
|
|
|
300
343
|
*/.sdlc/state.json|*/.sdlc/approvals.json|*/.sdlc/comments.json|*/.sdlc/product-prs.json|*/.sdlc/hub-prs.json|*/reviews/*.md)
|
|
301
344
|
is_seeding "EP-foundation" && continue
|
|
302
345
|
touches_ledger=1
|
|
303
|
-
echo " ${sha} (author $(git show -s --format='%an' "$sha")) → $f"
|
|
346
|
+
echo " ${sha} (author $(oneline "$(git show -s --format='%an' "$sha")")) → $(oneline "$f")"
|
|
304
347
|
;;
|
|
305
348
|
esac
|
|
306
349
|
;;
|
|
@@ -308,11 +351,18 @@ for sha in $commits; do
|
|
|
308
351
|
done < <(git -c core.quotePath=false diff-tree --no-commit-id --name-only -r -z "$sha")
|
|
309
352
|
if [ "$touches_ledger" = 1 ] && ! trusted_bot "$sha"; then
|
|
310
353
|
violations=$((violations + 1))
|
|
354
|
+
while IFS= read -r _why; do
|
|
355
|
+
[ -n "$_why" ] && overrides[${#overrides[@]}]=" ${sha} Ledger-Override: ${_why}"
|
|
356
|
+
done < <(git show -s --format='%(trailers:key=Ledger-Override,valueonly,unfold)' "$sha" 2>/dev/null | printable || true)
|
|
311
357
|
fi
|
|
312
358
|
done
|
|
313
359
|
|
|
314
360
|
if [ "$violations" -gt 0 ]; then
|
|
315
361
|
echo "FAIL [ledger-guard]: ${violations} commit(s) change CI-owned gate files without a verified gate-bot signature. The ledger is CI-owned — let CI sync the gate; do not commit .sdlc/*.json or reviews/*.md yourself."
|
|
362
|
+
if [ "${#overrides[@]}" -gt 0 ]; then
|
|
363
|
+
echo "note [ledger-guard]: these commits carry a Ledger-Override trailer — the reason a person gave for committing past the local hook (\`yad commit --manual\` writes it). It is quoted for the reviewer and does not change the verdict:"
|
|
364
|
+
printf '%s\n' "${overrides[@]}"
|
|
365
|
+
fi
|
|
316
366
|
# An epic's seed is exempt only while its ledger is off the base ref. Once the first review PR merges
|
|
317
367
|
# (squashed or rebased, so the SHAs differ), those same seed commits still sitting on a sibling
|
|
318
368
|
# authoring branch read as mutations — the author did nothing wrong and the remedy is a rebase, so
|