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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/hooks/forge-reserve.sh +51 -15
  3. package/template/.claude/hooks/forge-slice-notify.sh +66 -0
  4. package/template/.claude/hooks/forge-slice-runner.sh +593 -0
  5. package/template/.claude/hooks/tests/verify-signoff.test.sh +122 -0
  6. package/template/.claude/hooks/tests/wu-done.test.sh +68 -0
  7. package/template/.claude/hooks/verify-signoff.sh +139 -0
  8. package/template/.claude/hooks/wu-done.sh +48 -0
  9. package/template/.claude/skills/chief-of-staff/SKILL.md +11 -3
  10. package/template/.claude/skills/deferred/SKILL.md +8 -4
  11. package/template/.claude/skills/discussing/SKILL.md +4 -2
  12. package/template/.claude/skills/executing/SKILL.md +21 -17
  13. package/template/.claude/skills/forge/SKILL.md +13 -10
  14. package/template/.claude/skills/planning/SKILL.md +3 -1
  15. package/template/.claude/skills/prototyping/SKILL.md +10 -7
  16. package/template/.claude/skills/quick-tasking/SKILL.md +3 -1
  17. package/template/.claude/skills/researching/SKILL.md +2 -0
  18. package/template/.claude/skills/reviewing/SKILL.md +7 -7
  19. package/template/.claude/skills/verifying/SKILL.md +8 -7
  20. package/template/.forge/FORGE.md +14 -7
  21. package/template/.forge/gitignore +8 -0
  22. package/template/.forge/migrations/0.52.0-id-reservation-milestone-refactor.md +45 -0
  23. package/template/.forge/templates/deferred-issue.md +22 -0
  24. package/template/.forge/templates/slice-runner/config.yml +62 -0
  25. package/template/.forge/templates/slice-runner/phase-report.schema.json +51 -0
  26. package/template/.forge/templates/slice-runner/phase-report.yml +58 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-orkes",
3
- "version": "0.50.1",
3
+ "version": "0.55.0",
4
4
  "description": "Set up the Forge meta-prompting framework for Claude Code in your project",
5
5
  "bin": {
6
6
  "create-forge": "./bin/create-forge.js"
@@ -51,8 +51,10 @@ case "$kind" in
51
51
  def) prefix="DEF" ;;
52
52
  fr) prefix="FR" ;;
53
53
  nfr) prefix="NFR" ;;
54
- "") printf 'forge-reserve: missing kind (adr|def|fr|nfr)\n' >&2; exit 2 ;;
55
- *) printf 'forge-reserve: unknown kind: %s (expected adr|def|fr|nfr)\n' "$kind" >&2; exit 2 ;;
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 { split($2,a,"-"); n=a[2]+0; if(n>m)m=n } END{print m+0}' "$ledger")"
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
- if [ "$kind" = "adr" ]; then
112
- intree_max="$(ls "$top/docs/decisions" 2>/dev/null \
113
- | awk -F- '/^ADR-[0-9]/ { n=$2+0; if(n>m)m=n } END{print m+0}')"
114
- else
115
- intree_max="$(grep -rhoE '[A-Z]+-[0-9]+' "$top/.forge/requirements/" 2>/dev/null \
116
- | awk -F- -v p="$prefix" '$1==p { n=$2+0; if(n>m)m=n } END{print m+0}')"
117
- fi
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]+-[0-9]+' \
123
- | awk -F- -v p="$prefix" '$1==p { n=$2+0; if(n>m)m=n } END{print m+0}')"
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="$(printf '%s-%03d' "$prefix" "$next")"
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