forge-orkes 0.50.1 → 0.55.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/package.json +1 -1
- package/template/.claude/hooks/forge-reserve.sh +51 -15
- package/template/.claude/hooks/forge-slice-notify.sh +66 -0
- package/template/.claude/hooks/forge-slice-runner.sh +593 -0
- package/template/.claude/hooks/tests/verify-signoff.test.sh +122 -0
- package/template/.claude/hooks/tests/wu-done.test.sh +68 -0
- package/template/.claude/hooks/verify-signoff.sh +139 -0
- package/template/.claude/hooks/wu-done.sh +48 -0
- package/template/.claude/skills/chief-of-staff/SKILL.md +11 -3
- package/template/.claude/skills/deferred/SKILL.md +8 -4
- package/template/.claude/skills/discussing/SKILL.md +4 -2
- package/template/.claude/skills/executing/SKILL.md +21 -17
- package/template/.claude/skills/forge/SKILL.md +13 -10
- package/template/.claude/skills/planning/SKILL.md +3 -1
- package/template/.claude/skills/prototyping/SKILL.md +10 -7
- package/template/.claude/skills/quick-tasking/SKILL.md +3 -1
- package/template/.claude/skills/researching/SKILL.md +2 -0
- package/template/.claude/skills/reviewing/SKILL.md +7 -7
- package/template/.claude/skills/verifying/SKILL.md +8 -7
- package/template/.forge/FORGE.md +14 -7
- package/template/.forge/gitignore +8 -0
- package/template/.forge/migrations/0.52.0-id-reservation-milestone-refactor.md +45 -0
- package/template/.forge/templates/deferred-issue.md +22 -0
- package/template/.forge/templates/slice-runner/config.yml +62 -0
- package/template/.forge/templates/slice-runner/phase-report.schema.json +51 -0
- package/template/.forge/templates/slice-runner/phase-report.yml +58 -0
package/package.json
CHANGED
|
@@ -51,8 +51,10 @@ case "$kind" in
|
|
|
51
51
|
def) prefix="DEF" ;;
|
|
52
52
|
fr) prefix="FR" ;;
|
|
53
53
|
nfr) prefix="NFR" ;;
|
|
54
|
-
""
|
|
55
|
-
|
|
54
|
+
milestone) prefix="M" ;; # ADR-023 — unpadded (M-26); ids used bare across the framework
|
|
55
|
+
refactor) prefix="R" ;; # ADR-023 — zero-padded R0NN, matching the backlog format
|
|
56
|
+
"") printf 'forge-reserve: missing kind (adr|def|fr|nfr|milestone|refactor)\n' >&2; exit 2 ;;
|
|
57
|
+
*) printf 'forge-reserve: unknown kind: %s (expected adr|def|fr|nfr|milestone|refactor)\n' "$kind" >&2; exit 2 ;;
|
|
56
58
|
esac
|
|
57
59
|
[ -n "$milestone" ] || { printf 'forge-reserve: --milestone <id> is required\n' >&2; exit 2; }
|
|
58
60
|
|
|
@@ -99,33 +101,67 @@ trap 'rmdir "$lock" 2>/dev/null || true' EXIT INT TERM
|
|
|
99
101
|
# Everything numeric goes through awk (n+0) so leading-zero ids like 021 are read
|
|
100
102
|
# as decimal 21, never octal — no shell $(( )) octal trap.
|
|
101
103
|
|
|
102
|
-
# (a) ledger — same-machine siblings, INCLUDING uncommitted (the ADR-016-blind input)
|
|
104
|
+
# (a) ledger — same-machine siblings, INCLUDING uncommitted (the ADR-016-blind input).
|
|
105
|
+
# Extract the trailing number by stripping non-digits, NOT by split on "-": refactor
|
|
106
|
+
# ids (R100) carry no hyphen (ADR-023), so a hyphen-split would read them as 0 and
|
|
107
|
+
# reallocate a live id. gsub handles ADR-014, M-26, R100, FR-023 uniformly.
|
|
103
108
|
ledger_max=0
|
|
104
109
|
if [ -f "$ledger" ]; then
|
|
105
110
|
ledger_max="$(awk -F'\t' -v k="$kind" \
|
|
106
|
-
'$1==k {
|
|
111
|
+
'$1==k { x=$2; gsub(/[^0-9]/,"",x); n=x+0; if(n>m)m=n } END{print m+0}' "$ledger")"
|
|
107
112
|
fi
|
|
108
113
|
|
|
109
114
|
# (b) in-tree — landed ids in THIS worktree. Exact-prefix match in awk ($1==p) so
|
|
110
|
-
# FR does not swallow NFR (FR is a substring of NFR).
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
# FR does not swallow NFR (FR is a substring of NFR). milestone + refactor (ADR-023)
|
|
116
|
+
# each scan BOTH live and archive locations so a burned id is never reallocated.
|
|
117
|
+
case "$kind" in
|
|
118
|
+
adr)
|
|
119
|
+
intree_max="$(ls "$top/docs/decisions" 2>/dev/null \
|
|
120
|
+
| awk -F- '/^ADR-[0-9]/ { n=$2+0; if(n>m)m=n } END{print m+0}')"
|
|
121
|
+
;;
|
|
122
|
+
milestone)
|
|
123
|
+
# live state/milestone-<N>.yml ∪ archived .forge/archive/milestone-<N>/
|
|
124
|
+
intree_max="$( { ls "$top/.forge/state"/milestone-*.yml 2>/dev/null; \
|
|
125
|
+
ls -d "$top/.forge/archive"/milestone-*/ 2>/dev/null; } \
|
|
126
|
+
| grep -oE 'milestone-[0-9]+' \
|
|
127
|
+
| awk -F- '{ n=$2+0; if(n>m)m=n } END{print m+0}')"
|
|
128
|
+
;;
|
|
129
|
+
refactor)
|
|
130
|
+
# live refactor-backlog.yml ∪ refactor-backlog-archive.yml
|
|
131
|
+
intree_max="$(grep -hoE 'R[0-9]+' \
|
|
132
|
+
"$top/.forge/refactor-backlog.yml" \
|
|
133
|
+
"$top/.forge/refactor-backlog-archive.yml" 2>/dev/null \
|
|
134
|
+
| awk '{ n=substr($0,2)+0; if(n>m)m=n } END{print m+0}')"
|
|
135
|
+
;;
|
|
136
|
+
*)
|
|
137
|
+
intree_max="$(grep -rhoE '[A-Z]+-[0-9]+' "$top/.forge/requirements/" 2>/dev/null \
|
|
138
|
+
| awk -F- -v p="$prefix" '$1==p { n=$2+0; if(n>m)m=n } END{print m+0}')"
|
|
139
|
+
;;
|
|
140
|
+
esac
|
|
118
141
|
|
|
119
142
|
# (c) main:reservations.yml — cross-machine / cold-start floor (survives a
|
|
120
143
|
# .git/forge wipe; carries another machine's MERGED reservations). Absent → 0.
|
|
144
|
+
# Hyphen is OPTIONAL in the token ([A-Z]+-?[0-9]+): refactor ids (R100) carry none
|
|
145
|
+
# (ADR-023), so requiring a hyphen would hide them and drop the floor to 0. Split the
|
|
146
|
+
# alpha prefix from the digits in awk so R does not match e.g. FR (anchor ^prefix$).
|
|
121
147
|
main_max="$(git show main:.forge/reservations.yml 2>/dev/null \
|
|
122
|
-
| grep -oE '[A-Z]
|
|
123
|
-
| awk -
|
|
148
|
+
| grep -oE '[A-Z]+-?[0-9]+' \
|
|
149
|
+
| awk -v p="$prefix" '{ a=$0; sub(/[-0-9].*$/,"",a); d=$0; gsub(/[^0-9]/,"",d);
|
|
150
|
+
if(a==p){ n=d+0; if(n>m)m=n } } END{print m+0}')"
|
|
124
151
|
[ -n "$main_max" ] || main_max=0
|
|
125
152
|
|
|
126
153
|
next="$(awk -v a="$ledger_max" -v b="$intree_max" -v c="$main_max" \
|
|
127
154
|
'BEGIN{m=a; if(b>m)m=b; if(c>m)m=c; print m+1}')"
|
|
128
|
-
id
|
|
155
|
+
# Per-kind id format (ADR-023). The prefix-hyphen-paddednumber form (ADR-021's
|
|
156
|
+
# ADR-014, FR-023) is NOT universal:
|
|
157
|
+
# milestone → M-26 : hyphen, UNPADDED — ids are used bare (milestone-26.yml, m-26)
|
|
158
|
+
# refactor → R064 : NO hyphen, zero-padded 3-wide — matches the backlog convention
|
|
159
|
+
# adr/def/fr/nfr → ADR-014 : hyphen, zero-padded 3-wide (unchanged)
|
|
160
|
+
case "$kind" in
|
|
161
|
+
milestone) id="$(printf '%s-%d' "$prefix" "$next")" ;;
|
|
162
|
+
refactor) id="$(printf '%s%03d' "$prefix" "$next")" ;;
|
|
163
|
+
*) id="$(printf '%s-%03d' "$prefix" "$next")" ;;
|
|
164
|
+
esac
|
|
129
165
|
|
|
130
166
|
# --- 6. dual-write (still under the lock) -----------------------------------
|
|
131
167
|
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# forge-slice-notify.sh <type> <payload> — the DEFAULT notify channel for
|
|
3
|
+
# forge-slice-runner.sh. Delivers a §8 ping to the operator's phone via the
|
|
4
|
+
# PushNotification agent tool through a thin headless `claude -p` micro-call, and
|
|
5
|
+
# ALSO appends the ping to a local log so a suppressed/undelivered push is never
|
|
6
|
+
# silently lost.
|
|
7
|
+
#
|
|
8
|
+
# The runner calls this as `${FORGE_SLICE_NOTIFY:-forge-slice-notify.sh} <type>
|
|
9
|
+
# <payload>`; the whole channel is swappable via that env (tests use notify-stub.sh).
|
|
10
|
+
#
|
|
11
|
+
# type one of the §8 surface: ambiguity | irreversible | fork | budget | done
|
|
12
|
+
# (plus `halt`, the failure slice-end). The runner has already validated
|
|
13
|
+
# the type before calling; this script just delivers it.
|
|
14
|
+
# payload the ping body. For a design `fork` it is a PATH to a sketch/mockup the
|
|
15
|
+
# operator LOOKS AT (§8) — passed through so the notification names the file.
|
|
16
|
+
#
|
|
17
|
+
# BEST-EFFORT by contract: this must exit 0 even when delivery is impossible (no
|
|
18
|
+
# `claude` on PATH, no CLAUDE_CODE_OAUTH_TOKEN, the tool suppressed while the
|
|
19
|
+
# operator is active). The runner treats a non-zero exit as a best-effort miss, not
|
|
20
|
+
# a slice failure — so never hard-fail here.
|
|
21
|
+
#
|
|
22
|
+
# ── DEFERRED phone-delivery caveat (context.md M29, 2026-07-15) ───────────────
|
|
23
|
+
# A PushNotification SENT from a DETACHED one-shot `claude -p` returns success but
|
|
24
|
+
# may NOT buzz the phone — this is the Claude-app Remote-Control DEVICE-pairing
|
|
25
|
+
# side (an ephemeral unpaired session), not this code. It is a known open item,
|
|
26
|
+
# fixed later. The channel is swappable precisely so that fix can land without
|
|
27
|
+
# touching the runner. Escalate only if delivery turns out to need Remote-Control
|
|
28
|
+
# state a detached runner cannot satisfy.
|
|
29
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
set -eu
|
|
31
|
+
|
|
32
|
+
TYPE="${1:-note}"
|
|
33
|
+
PAYLOAD="${2:-}"
|
|
34
|
+
|
|
35
|
+
# 1) Durable local record — always, before any delivery attempt. Never lost even
|
|
36
|
+
# if the phone channel is down. Default location travels with the slice.
|
|
37
|
+
LOG="${FORGE_SLICE_NOTIFY_LOG:-${FORGE_SLICE_WORK_DIR:-.}/notify.log}"
|
|
38
|
+
mkdir -p "$(dirname "$LOG")" 2>/dev/null || true
|
|
39
|
+
printf '%s\t%s\t%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo now)" "$TYPE" "$PAYLOAD" >> "$LOG" 2>/dev/null || true
|
|
40
|
+
|
|
41
|
+
# 2) Best-effort phone delivery via PushNotification. Skip cleanly if headless
|
|
42
|
+
# `claude` or its credential is absent (dev boxes, CI) — that is a miss, not a
|
|
43
|
+
# failure. A design fork's payload is a path, so name it in the body.
|
|
44
|
+
LAUNCHER="${FORGE_SLICE_NOTIFY_LAUNCHER:-claude}"
|
|
45
|
+
if command -v "$LAUNCHER" >/dev/null 2>&1 \
|
|
46
|
+
&& { [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] || [ -n "${ANTHROPIC_API_KEY:-}" ]; }; then
|
|
47
|
+
_title="Forge slice: ${TYPE}"
|
|
48
|
+
case "$TYPE" in
|
|
49
|
+
fork) _body="Design fork — review the sketch: ${PAYLOAD}" ;;
|
|
50
|
+
done) _body="Slice complete. ${PAYLOAD}" ;;
|
|
51
|
+
*) _body="${PAYLOAD}" ;;
|
|
52
|
+
esac
|
|
53
|
+
# One-shot: ask the headless agent to fire PushNotification with the title/body.
|
|
54
|
+
printf 'Send a push notification with title "%s" and body "%s". Use only the PushNotification tool; do nothing else.\n' \
|
|
55
|
+
"$_title" "$_body" \
|
|
56
|
+
| "$LAUNCHER" -p \
|
|
57
|
+
--allowedTools PushNotification \
|
|
58
|
+
--permission-mode bypassPermissions \
|
|
59
|
+
--output-format json \
|
|
60
|
+
>/dev/null 2>&1 \
|
|
61
|
+
|| printf 'forge-slice-notify: push delivery best-effort miss for %s (logged locally at %s)\n' "$TYPE" "$LOG" >&2
|
|
62
|
+
else
|
|
63
|
+
printf 'forge-slice-notify: no headless claude/credential — %s ping logged locally only (%s)\n' "$TYPE" "$LOG" >&2
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
exit 0
|