pincer-workflow 0.4.1 → 0.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/README.md +9 -7
- package/bin/pincer.js +58 -5
- package/package.json +3 -3
- package/template/.agents/skills/pincer-code/SKILL.md +117 -12
- package/template/.agents/skills/pincer-evaluate/SKILL.md +57 -10
- package/template/.agents/skills/pincer-narrow/SKILL.md +48 -8
- package/template/.agents/skills/pincer-plan/SKILL.md +12 -4
- package/template/.agents/skills/pincer-release/SKILL.md +32 -3
- package/template/.agents/skills/pincer-status/SKILL.md +25 -2
- package/template/.claude/commands/pincer-code.md +117 -12
- package/template/.claude/commands/pincer-evaluate.md +57 -10
- package/template/.claude/commands/pincer-narrow.md +48 -8
- package/template/.claude/commands/pincer-plan.md +12 -4
- package/template/.claude/commands/pincer-release.md +32 -3
- package/template/.claude/commands/pincer-status.md +25 -2
- package/template/.claude/hooks/hook-policy.cjs +24 -3
- package/template/.claude/references/prd-template.md +11 -4
- package/template/.claude/references/ticket-template.md +4 -0
- package/template/.codex/README.md +3 -2
- package/template/.github/prompts/pincer-code.prompt.md +117 -12
- package/template/.github/prompts/pincer-evaluate.prompt.md +57 -10
- package/template/.github/prompts/pincer-narrow.prompt.md +48 -8
- package/template/.github/prompts/pincer-plan.prompt.md +12 -4
- package/template/.github/prompts/pincer-release.prompt.md +32 -3
- package/template/.github/prompts/pincer-status.prompt.md +25 -2
- package/template/AGENTS.md +22 -0
- package/template/docs/dry-run-checklist.md +70 -6
- package/template/docs/release-checklist.md +5 -2
- package/template/docs/runtime-contracts.md +1683 -0
- package/template/scripts/pincer-evidence.cjs +13 -229
- package/template/scripts/pincer-runtime/adopt.cjs +132 -0
- package/template/scripts/pincer-runtime/agreement.cjs +240 -0
- package/template/scripts/pincer-runtime/authorization.cjs +167 -0
- package/template/scripts/pincer-runtime/changes.cjs +517 -0
- package/template/scripts/pincer-runtime/checks.cjs +48 -0
- package/template/scripts/pincer-runtime/coverage.cjs +361 -0
- package/template/scripts/pincer-runtime/dispositions.cjs +95 -0
- package/template/scripts/pincer-runtime/evidence.cjs +676 -0
- package/template/scripts/pincer-runtime/fsutil.cjs +37 -0
- package/template/scripts/pincer-runtime/gates.cjs +73 -0
- package/template/scripts/pincer-runtime/identity.cjs +163 -0
- package/template/scripts/pincer-runtime/impact.cjs +177 -0
- package/template/scripts/pincer-runtime/io.cjs +41 -0
- package/template/scripts/pincer-runtime/lifecycle.cjs +311 -0
- package/template/scripts/pincer-runtime/locator.cjs +158 -0
- package/template/scripts/pincer-runtime/migrate.cjs +204 -0
- package/template/scripts/pincer-runtime/parse.cjs +316 -0
- package/template/scripts/pincer-runtime/phases.cjs +245 -0
- package/template/scripts/pincer-runtime/readiness.cjs +97 -0
- package/template/scripts/pincer-runtime/requirements.cjs +255 -0
- package/template/scripts/pincer-runtime/resume.cjs +205 -0
- package/template/scripts/pincer-runtime/routing.cjs +54 -0
- package/template/scripts/pincer-runtime/runner.cjs +242 -0
- package/template/scripts/pincer-runtime/sanitize.cjs +63 -0
- package/template/scripts/pincer-runtime/source.cjs +129 -0
- package/template/scripts/pincer-runtime/state.cjs +314 -0
- package/template/scripts/pincer-runtime/status.cjs +514 -0
- package/template/scripts/pincer-runtime/transaction.cjs +200 -0
- package/template/scripts/pincer-runtime/transitions.cjs +134 -0
- package/template/scripts/pincer-runtime.cjs +661 -0
- package/template/scripts/pincer-status.sh +11 -162
- package/template/scripts/pincer-ticket.sh +19 -139
- package/template/scripts/pincer-ticket-lib.sh +0 -321
|
@@ -2,167 +2,16 @@
|
|
|
2
2
|
# PINCER status — where the workflow stands, read from the artifacts on disk.
|
|
3
3
|
# Read-only. Every playbook runs this first; /pincer-status wraps it.
|
|
4
4
|
#
|
|
5
|
-
# scripts/pincer-status.sh
|
|
5
|
+
# scripts/pincer-status.sh [--json]
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# and
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
cd "$ROOT"
|
|
17
|
-
BUDGET=${PINCER_BUILD_BUDGET_MIN:-}
|
|
18
|
-
NOW=$(date -u +%s)
|
|
19
|
-
|
|
20
|
-
to_epoch() { # ISO-8601 UTC -> seconds (GNU date, then BSD date)
|
|
21
|
-
date -u -d "$1" +%s 2>/dev/null || date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$1" +%s 2>/dev/null || echo 0
|
|
7
|
+
# Compatibility wrapper: the report is computed by scripts/pincer-runtime.cjs
|
|
8
|
+
# (`status`), which prints the PRD state and profile, every ticket with its
|
|
9
|
+
# state and clock-based elapsed time, the wall-clock elapsed build line while a
|
|
10
|
+
# ticket is in progress or PINCER_BUILD_BUDGET_MIN is set, the evidence verdict,
|
|
11
|
+
# readiness warnings, and the next command. Exit codes follow
|
|
12
|
+
# docs/runtime-contracts.md (0 inspected, 4 invalid input).
|
|
13
|
+
command -v node >/dev/null 2>&1 || {
|
|
14
|
+
echo 'pincer-status: Node.js 22+ is required — the runtime is scripts/pincer-runtime.cjs' >&2
|
|
15
|
+
exit 4
|
|
22
16
|
}
|
|
23
|
-
|
|
24
|
-
hhmm() { [ -n "$1" ] && printf '%s' "$1" | cut -c12-16 || printf '—'; }
|
|
25
|
-
|
|
26
|
-
echo "PINCER status · $(date -u +%Y-%m-%dT%H:%MZ) · $ROOT"
|
|
27
|
-
|
|
28
|
-
# ── PRD ──
|
|
29
|
-
if ! prd=$(latest_prd 2>&1); then
|
|
30
|
-
printf 'WARN invalid PRD: %s\n' "$prd"
|
|
31
|
-
echo 'Next repair PRD input before continuing'
|
|
32
|
-
exit 1
|
|
33
|
-
fi
|
|
34
|
-
prd_status=""
|
|
35
|
-
if [ -z "$prd" ]; then
|
|
36
|
-
echo "PRD none"
|
|
37
|
-
else
|
|
38
|
-
prd_status=$(fm_get "$prd" status)
|
|
39
|
-
echo "PRD $prd · status: ${prd_status:-?} · profile: $(prd_profile "$prd") · date: $(fm_get "$prd" date)"
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Reject malformed/ambiguous tickets instead of treating unknown states as open.
|
|
43
|
-
if ! errors=$(validate_ticket_set 2>&1); then
|
|
44
|
-
printf 'WARN invalid tickets: %s\n' "$errors"
|
|
45
|
-
echo "Next repair ticket input before continuing"
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# ── Tickets ──
|
|
50
|
-
n_open=0; n_prog=0; n_done=0; first_start=""; in_prog=""; next_open=""; warn=""; reverify=""
|
|
51
|
-
files=""; historical=0; unresolved=0
|
|
52
|
-
for f in tickets/T-*.md; do
|
|
53
|
-
[ -e "$f" ] || continue
|
|
54
|
-
if ! associated=$(ticket_prd "$f" 2>&1); then
|
|
55
|
-
printf 'WARN unresolved ticket PRD: %s\n' "$associated"
|
|
56
|
-
unresolved=$((unresolved + 1)); continue
|
|
57
|
-
fi
|
|
58
|
-
if [ "$associated" = "$prd" ]; then
|
|
59
|
-
files="$files$f
|
|
60
|
-
"
|
|
61
|
-
else
|
|
62
|
-
historical=$((historical + 1))
|
|
63
|
-
fi
|
|
64
|
-
done
|
|
65
|
-
[ "$historical" -eq 0 ] || echo "History $historical ticket(s) associated with other PRDs"
|
|
66
|
-
if [ -z "$files" ]; then
|
|
67
|
-
echo "Tickets none"
|
|
68
|
-
else
|
|
69
|
-
rows=""
|
|
70
|
-
while IFS= read -r f; do
|
|
71
|
-
[ -n "$f" ] || continue
|
|
72
|
-
id=$(fm_get "$f" ticket); [ -n "$id" ] || id=$(basename "$f" | cut -c1-4)
|
|
73
|
-
st=$(fm_get "$f" status); size=$(fm_get "$f" size)
|
|
74
|
-
started=$(fm_get "$f" started); verified=$(fm_get "$f" verified); finished=$(fm_get "$f" finished)
|
|
75
|
-
attempt=$(fm_get "$f" last_check)
|
|
76
|
-
# Done tickets report through ticket_readiness below, so each problem is printed once.
|
|
77
|
-
if [ "$st" != done ] && [ -n "$attempt" ] && ! printf '%s' "$attempt" | grep -q ' passed '; then
|
|
78
|
-
warn="$warn WARN $id latest verification: $attempt — re-run verify\n"
|
|
79
|
-
fi
|
|
80
|
-
deps=$(fm_get "$f" depends_on | grep -oE 'T-[0-9]+' | tr '\n' ' ' || true)
|
|
81
|
-
if [ -n "$started" ]; then
|
|
82
|
-
se=$(to_epoch "$started")
|
|
83
|
-
[ -z "$first_start" ] || [ "$se" -lt "$first_start" ] && first_start=$se
|
|
84
|
-
fi
|
|
85
|
-
case "$st" in
|
|
86
|
-
done)
|
|
87
|
-
n_done=$((n_done + 1))
|
|
88
|
-
detail="started $(hhmm "$started") · finished $(hhmm "$finished")"
|
|
89
|
-
[ -n "$started" ] && [ -n "$finished" ] && detail="$detail ($(mins "$(to_epoch "$started")" "$(to_epoch "$finished")"))"
|
|
90
|
-
if ! readiness=$(ticket_readiness "$f"); then
|
|
91
|
-
warn="$warn WARN $id $readiness\n"
|
|
92
|
-
reverify="$reverify $id"
|
|
93
|
-
fi
|
|
94
|
-
;;
|
|
95
|
-
in_progress)
|
|
96
|
-
n_prog=$((n_prog + 1)); in_prog="$in_prog $id"
|
|
97
|
-
detail="started $(hhmm "$started")"
|
|
98
|
-
[ -n "$started" ] && detail="$detail · elapsed $(mins "$(to_epoch "$started")" "$NOW")"
|
|
99
|
-
if [ -n "$verified" ]; then detail="$detail · receipt ✓"; else detail="$detail · no receipt yet"; fi
|
|
100
|
-
;;
|
|
101
|
-
*)
|
|
102
|
-
n_open=$((n_open + 1)); st=${st:-open}
|
|
103
|
-
blocked=""
|
|
104
|
-
for d in $deps; do
|
|
105
|
-
df=$(ticket_file "$d" 2>/dev/null || true)
|
|
106
|
-
dep_prd=""
|
|
107
|
-
[ -z "$df" ] || dep_prd=$(ticket_prd "$df" 2>/dev/null || true)
|
|
108
|
-
if [ -z "$df" ] || [ "$(fm_get "$df" status)" != done ] ||
|
|
109
|
-
[ "$dep_prd" != "$prd" ] || ! usable_ticket_prd "$df" >/dev/null 2>&1 ||
|
|
110
|
-
! ticket_readiness "$df" >/dev/null; then
|
|
111
|
-
blocked="$blocked $d"
|
|
112
|
-
fi
|
|
113
|
-
done
|
|
114
|
-
if [ -n "$blocked" ]; then detail="blocked by${blocked}"; else detail="ready"; [ -n "$next_open" ] || next_open=$id; fi
|
|
115
|
-
;;
|
|
116
|
-
esac
|
|
117
|
-
rows="$rows$(printf ' %-5s %-12s %-2s %s' "$id" "$st" "${size:-?}" "$detail")\n"
|
|
118
|
-
done <<< "$files"
|
|
119
|
-
echo "Tickets $((n_open + n_prog + n_done)) total · $n_done done · $n_prog in progress · $n_open open"
|
|
120
|
-
printf '%b' "$rows"
|
|
121
|
-
printf '%b' "$warn"
|
|
122
|
-
# Wall-clock elapsed is shown only while work is active or against an explicit
|
|
123
|
-
# budget; on a finished build it is noise and it never measures execution time.
|
|
124
|
-
if [ -n "$first_start" ] && { [ "$n_prog" -gt 0 ] || [ -n "$BUDGET" ]; }; then
|
|
125
|
-
build="Build wall-clock elapsed $(mins "$first_start" "$NOW") since the first ticket started (not active execution time)"
|
|
126
|
-
[ -z "$BUDGET" ] || build="$build · budget ${BUDGET}m"
|
|
127
|
-
echo "$build"
|
|
128
|
-
fi
|
|
129
|
-
fi
|
|
130
|
-
|
|
131
|
-
notes_valid=no
|
|
132
|
-
if notes=$(notes_current "$prd"); then notes_valid=yes; fi
|
|
133
|
-
echo "Notes NOTES.md: $notes"
|
|
134
|
-
# The evidence line reports the shared validator's verdict for the manifest the
|
|
135
|
-
# notes name, independent of whether the candidate is still current.
|
|
136
|
-
manifest=""
|
|
137
|
-
[ -f NOTES.md ] && validate_metadata NOTES.md >/dev/null 2>&1 && manifest=$(fm_get NOTES.md evidence)
|
|
138
|
-
if [ -n "$manifest" ]; then
|
|
139
|
-
if ev=$(evidence_validate "$manifest" "$(fm_get NOTES.md candidate)" "$(fm_get NOTES.md base)" "$prd"); then
|
|
140
|
-
echo "Evidence $manifest · ok"
|
|
141
|
-
else
|
|
142
|
-
echo "Evidence $manifest · $(evidence_reason "$ev")"
|
|
143
|
-
fi
|
|
144
|
-
fi
|
|
145
|
-
|
|
146
|
-
# ── Next action ──
|
|
147
|
-
if [ -z "$prd" ]; then
|
|
148
|
-
next="/pincer-plan <brief> — no PRD yet"
|
|
149
|
-
elif [ "$unresolved" -gt 0 ]; then
|
|
150
|
-
next="resolve PRD association with pincer-ticket.sh bind T-NN .prd/prd-vN.md before continuing"
|
|
151
|
-
elif [ "$prd_status" = draft ]; then
|
|
152
|
-
next="/pincer-narrow — current PRD is draft; earlier tickets and notes do not complete it"
|
|
153
|
-
elif [ -z "$files" ]; then
|
|
154
|
-
next="/pincer-narrow — PRD exists, no tickets yet"
|
|
155
|
-
elif [ -n "$in_prog" ]; then
|
|
156
|
-
next="resume${in_prog}: /pincer-code${in_prog} (check git status for uncommitted work; then verify → done)"
|
|
157
|
-
elif [ "$n_open" -gt 0 ]; then
|
|
158
|
-
next="/pincer-code — next ready ticket: ${next_open:-none (all remaining are blocked — check depends_on)}"
|
|
159
|
-
elif [ -n "$reverify" ]; then
|
|
160
|
-
next="/pincer-code — re-run verify for${reverify}; resolve readiness warnings before evaluation or release"
|
|
161
|
-
elif [ "$notes_valid" = no ] || [ "$prd_status" != built ]; then
|
|
162
|
-
next="/pincer-evaluate — all tickets done"
|
|
163
|
-
[ "$prd_status" = built ] || next="$next (PRD status is '${prd_status:-?}', expected 'built')"
|
|
164
|
-
else
|
|
165
|
-
next="/pincer-release — evaluation matches the current PRD and candidate; audit the artifacts"
|
|
166
|
-
fi
|
|
167
|
-
echo "Next $next"
|
|
168
|
-
[ "$unresolved" -eq 0 ] || exit 1
|
|
17
|
+
exec node "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/pincer-runtime.cjs" status "$@"
|
|
@@ -1,145 +1,25 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# PINCER ticket state machine — the ONLY writer of a ticket's
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# those fields so "done"
|
|
2
|
+
# PINCER ticket state machine — the ONLY writer of a ticket's lifecycle fields.
|
|
3
|
+
# Compatibility wrapper: every call is executed by scripts/pincer-runtime.cjs
|
|
4
|
+
# (docs/runtime-contracts.md). On Claude Code a PreToolUse hook
|
|
5
|
+
# (.claude/hooks/ticket-guard.sh) blocks hand edits of those fields so "done"
|
|
6
|
+
# can only be reached through a passing check.
|
|
6
7
|
#
|
|
7
|
-
# scripts/pincer-ticket.sh start T-03 open -> in_progress; refuses while a depends_on ticket isn't
|
|
8
|
-
# scripts/pincer-ticket.sh verify T-03 runs the ticket's Verification block
|
|
9
|
-
# scripts/pincer-ticket.sh done T-03
|
|
10
|
-
#
|
|
8
|
+
# scripts/pincer-ticket.sh start T-03 open -> in_progress; refuses while a depends_on ticket isn't ready
|
|
9
|
+
# scripts/pincer-ticket.sh verify T-03 runs the ticket's Verification block and records the outcome
|
|
10
|
+
# scripts/pincer-ticket.sh done T-03 closes the ticket on a current passing check with all criteria ticked
|
|
11
|
+
# scripts/pincer-ticket.sh bind T-03 .prd/prd-vN.md associates a legacy ticket with its PRD
|
|
11
12
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ROOT=${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}
|
|
20
|
-
cd "$ROOT"
|
|
21
|
-
|
|
22
|
-
die() { printf 'pincer-ticket: %b\n' "$*" >&2; exit 1; }
|
|
23
|
-
now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
fm_set() { # file key value — replace inside the frontmatter (keeping an inline comment) or add before the closing ---
|
|
27
|
-
local tmp; tmp=$(mktemp)
|
|
28
|
-
awk -v k="$2" -v v="$3" '
|
|
29
|
-
NR == 1 { print; next }
|
|
30
|
-
!closed && !done && index($0, k ":") == 1 {
|
|
31
|
-
c = $0; if (sub(/^[^#]*#/, "#", c)) print k ": " v " " c; else print k ": " v
|
|
32
|
-
done = 1; next
|
|
33
|
-
}
|
|
34
|
-
!closed && $0 == "---" { if (!done) print k ": " v; closed = 1 }
|
|
35
|
-
{ print }' "$1" > "$tmp" && mv "$tmp" "$1"
|
|
13
|
+
# Unmigrated (legacy) projects keep the v0.4.1 receipts: `verify` writes
|
|
14
|
+
# `last_check` and `verified` into the ticket and `done` re-runs the check.
|
|
15
|
+
# Migrated projects record attempts under .pincer/runtime/ and `done` consumes
|
|
16
|
+
# the current passing attempt. Exit codes follow docs/runtime-contracts.md.
|
|
17
|
+
command -v node >/dev/null 2>&1 || {
|
|
18
|
+
echo 'pincer-ticket: Node.js 22+ is required — the runtime is scripts/pincer-runtime.cjs' >&2
|
|
19
|
+
exit 4
|
|
36
20
|
}
|
|
37
|
-
|
|
38
|
-
fm_unset() {
|
|
39
|
-
local tmp; tmp=$(mktemp)
|
|
40
|
-
awk -v k="$2" 'NR > 1 && $0 == "---" { closed = 1 }
|
|
41
|
-
!closed && index($0, k ":") == 1 { next } { print }' "$1" > "$tmp" && mv "$tmp" "$1"
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
cmd_bind() {
|
|
45
|
-
local f ref existing
|
|
46
|
-
f=$(ticket_file "$1"); ref=$2
|
|
47
|
-
validate_prd "$ref" || exit 1
|
|
48
|
-
existing=$(fm_get "$f" prd)
|
|
49
|
-
[ -z "$existing" ] || [ "$existing" = "$ref" ] || die "$1 already references $existing; refusing to rebind it to $ref"
|
|
50
|
-
[ "$existing" = "$ref" ] || fm_set "$f" prd "$ref"
|
|
51
|
-
echo "$1 bound to PRD $ref"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
cmd_start() {
|
|
55
|
-
local f id st dep df ref dep_ref readiness
|
|
56
|
-
f=$(ticket_file "$1"); id=$(normalize "$1"); st=$(fm_get "$f" status)
|
|
57
|
-
ref=$(usable_ticket_prd "$f") || exit 1
|
|
58
|
-
case "$st" in
|
|
59
|
-
in_progress)
|
|
60
|
-
[ -n "$(fm_get "$f" prd)" ] || fm_set "$f" prd "$ref"
|
|
61
|
-
echo "$id already in progress (started $(fm_get "$f" started))"; return 0 ;;
|
|
62
|
-
done) die "$id is already done" ;;
|
|
63
|
-
esac
|
|
64
|
-
for dep in $(fm_get "$f" depends_on | grep -oE 'T-[0-9]+' || true); do
|
|
65
|
-
df=$(ticket_file "$dep")
|
|
66
|
-
[ "$(fm_get "$df" status)" = done ] || die "$id depends on $dep, which is '$(fm_get "$df" status)' — finish $dep first (or fix depends_on in $f)"
|
|
67
|
-
dep_ref=$(usable_ticket_prd "$df") || exit 1
|
|
68
|
-
[ "$dep_ref" = "$ref" ] || die "$id references $ref but dependency $dep references $dep_ref"
|
|
69
|
-
if ! readiness=$(ticket_readiness "$df"); then die "$id depends on $dep: $readiness"; fi
|
|
70
|
-
done
|
|
71
|
-
[ -n "$(fm_get "$f" prd)" ] || fm_set "$f" prd "$ref"
|
|
72
|
-
fm_set "$f" status in_progress
|
|
73
|
-
[ -n "$(fm_get "$f" started)" ] || fm_set "$f" started "$(now)"
|
|
74
|
-
echo "▶ $id started $(fm_get "$f" started) — $f"
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
cmd_verify() {
|
|
78
|
-
local f id st cmds rc hash child
|
|
79
|
-
f=$(ticket_file "$1"); id=$(normalize "$1"); st=$(fm_get "$f" status)
|
|
80
|
-
usable_ticket_prd "$f" >/dev/null || exit 1
|
|
81
|
-
case "$st" in
|
|
82
|
-
open) cmd_start "$id" ;;
|
|
83
|
-
done) echo "$id is done — re-running its check and updating the latest outcome" ;;
|
|
84
|
-
esac
|
|
85
|
-
cmds=$(verification_cmds "$f")
|
|
86
|
-
fm_unset "$f" verified
|
|
87
|
-
hash=$(verify_hash "$f")
|
|
88
|
-
fm_set "$f" last_check "$(now) running $hash"
|
|
89
|
-
printf '%s\n' "$cmds" | grep -vE '^[[:space:]]*(#|$)' >/dev/null || die "no runnable command in the Verification block of $f"
|
|
90
|
-
echo "── $id verification ──"
|
|
91
|
-
printf '%s\n' "$cmds" | sed 's/^/ $ /'
|
|
92
|
-
bash -eo pipefail -c "$cmds" & child=$!
|
|
93
|
-
trap 'kill "$child" 2>/dev/null || true; fm_set "$f" last_check "$(now) interrupted $hash"; exit 130' INT TERM
|
|
94
|
-
set +e; wait "$child"; rc=$?; set -e
|
|
95
|
-
trap - INT TERM
|
|
96
|
-
if [ "$rc" -ne 0 ]; then
|
|
97
|
-
fm_set "$f" last_check "$(now) failed $hash"
|
|
98
|
-
echo "✗ $id verification FAILED (exit $rc) — failure recorded in last_check of $f; any prior successful receipt was revoked. Fix, then re-run verify." >&2
|
|
99
|
-
exit "$rc"
|
|
100
|
-
fi
|
|
101
|
-
if [ "$hash" != "$(verify_hash "$f")" ]; then
|
|
102
|
-
fm_set "$f" last_check "$(now) failed $hash"
|
|
103
|
-
die "Verification block changed during execution — re-run verify"
|
|
104
|
-
fi
|
|
105
|
-
if ! validate_ticket "$f" || ! usable_ticket_prd "$f" >/dev/null; then
|
|
106
|
-
fm_set "$f" last_check "$(now) failed $hash"
|
|
107
|
-
die "ticket became invalid during verification — fix it and re-run verify"
|
|
108
|
-
fi
|
|
109
|
-
fm_set "$f" last_check "$(now) passed $hash"
|
|
110
|
-
fm_set "$f" verified "$(now) $hash"
|
|
111
|
-
echo "✓ $id verified — receipt: $(fm_get "$f" verified)"
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
cmd_done() {
|
|
115
|
-
local f id st rec cur u slug
|
|
116
|
-
f=$(ticket_file "$1"); id=$(normalize "$1"); st=$(fm_get "$f" status)
|
|
117
|
-
usable_ticket_prd "$f" >/dev/null || exit 1
|
|
118
|
-
[ "$st" = in_progress ] || [ "$st" = done ] || die "$id is '$st' — run '$0 verify $id' first"
|
|
119
|
-
rec=$(fm_get "$f" verified)
|
|
120
|
-
[ -n "$rec" ] || die "no verification receipt on $id — run '$0 verify $id' and get a green check first"
|
|
121
|
-
cur=$(verify_hash "$f")
|
|
122
|
-
[ "${rec##* }" = "$cur" ] || die "receipt hash ${rec##* } does not match the current Verification block ($cur): the check changed after it passed — run '$0 verify $id' again"
|
|
123
|
-
u=$(unticked "$f")
|
|
124
|
-
[ -z "$u" ] || die "unticked acceptance criteria on $id:\n$u\nTick each verified criterion; a criterion that was cut is a scope change to record in the PRD, not a box to skip."
|
|
125
|
-
cmd_verify "$id"
|
|
126
|
-
u=$(unticked "$f")
|
|
127
|
-
[ -z "$u" ] || die "unticked acceptance criteria on $id after verification:\n$u"
|
|
128
|
-
[ "$st" = done ] && { echo "$id already done — current check passed"; return 0; }
|
|
129
|
-
fm_set "$f" status done
|
|
130
|
-
fm_set "$f" finished "$(now)"
|
|
131
|
-
slug=$(basename "$f" .md); slug=${slug#T-[0-9][0-9]-}
|
|
132
|
-
echo "✓ $id done. Inspect staged work, stage only this ticket's paths, review git diff --cached, then commit: $id: ${slug//-/ }"
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
case "${1:-}" in
|
|
136
|
-
start|verify|done|bind) validate_ticket_set || exit 1 ;;
|
|
137
|
-
esac
|
|
138
|
-
|
|
139
21
|
case "${1:-}" in
|
|
140
|
-
start)
|
|
141
|
-
|
|
142
|
-
done) [ $# -eq 2 ] || die "usage: $0 done T-NN"; cmd_done "$2" ;;
|
|
143
|
-
bind) [ $# -eq 3 ] || die "usage: $0 bind T-NN .prd/prd-vN.md"; cmd_bind "$2" "$3" ;;
|
|
144
|
-
*) sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;;
|
|
22
|
+
start|verify|done|bind) ;;
|
|
23
|
+
*) sed -n '8,11p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;;
|
|
145
24
|
esac
|
|
25
|
+
exec node "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/pincer-runtime.cjs" "$@"
|