forge-orkes 0.46.0 → 0.50.1
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/agents/verifier.md +8 -4
- package/template/.claude/hooks/forge-model-outcome.sh +177 -0
- package/template/.claude/hooks/tests/forge-model-outcome.test.sh +108 -0
- package/template/.claude/skills/chief-of-staff/SKILL.md +5 -1
- package/template/.claude/skills/executing/SKILL.md +31 -1
- package/template/.claude/skills/forge/SKILL.md +34 -4
- package/template/.claude/skills/planning/SKILL.md +27 -1
- package/template/.claude/skills/prototyping/SKILL.md +88 -0
- package/template/.claude/skills/verifying/SKILL.md +13 -8
- package/template/.forge/FORGE.md +23 -2
- package/template/.forge/migrations/0.50.1-template-releases-scrub.md +91 -0
- package/template/.forge/releases.yml +13 -106
- package/template/.forge/templates/plan.md +35 -1
- package/template/.forge/templates/project.yml +4 -0
- package/template/.forge/templates/prototypes/prototype.md +31 -0
package/package.json
CHANGED
|
@@ -24,9 +24,9 @@ Plan+must_haves (`.forge/phases/milestone-{id}/{phase}-{name}/plan.md`), executi
|
|
|
24
24
|
**Status**: PASS | FAIL | PARTIAL
|
|
25
25
|
|
|
26
26
|
## Observable Truths
|
|
27
|
-
| Truth |
|
|
28
|
-
|
|
29
|
-
| {from must_haves} |
|
|
27
|
+
| Truth | Evidence (command → exit code; output excerpt) | Status |
|
|
28
|
+
|-------|-----------------------------------------------|--------|
|
|
29
|
+
| {from must_haves} | {executed command} → {exit}; {excerpt} | PASS/FAIL/UNCERTAIN/HUMAN |
|
|
30
30
|
|
|
31
31
|
## Artifacts
|
|
32
32
|
| Artifact | Exists | Substantive | Wired | Evidence |
|
|
@@ -64,7 +64,10 @@ Read: .forge/deferred-issues.md → known pre-existing failures (if exists; trea
|
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
### 2. Truths
|
|
67
|
-
Per truth
|
|
67
|
+
Per truth, three branches (executed-evidence rule — canonical statement: verifying SKILL Level 1):
|
|
68
|
+
1. Declared `check:` in must_haves → EXECUTE it. Exit 0 = PASS, non-zero = FAIL. Record command + exit code + output excerpt.
|
|
69
|
+
2. No declared check → run your OWN command and record the same evidence triple — executed evidence qualifies regardless of plan vintage.
|
|
70
|
+
3. No executed evidence → UNCERTAIN, or HUMAN for `manual:`-routed truths (never execute the route text as shell). **Never PASS on judgment alone** — the exit code, not your assessment, is the verdict.
|
|
68
71
|
|
|
69
72
|
### 3. Artifacts
|
|
70
73
|
Per artifact:
|
|
@@ -138,6 +141,7 @@ gaps:
|
|
|
138
141
|
|
|
139
142
|
## Anti-Patterns
|
|
140
143
|
- **Fix-while-verifying**: Report, don't fix
|
|
144
|
+
- **Evidence-free PASS**: No executed command recorded → verdict must be UNCERTAIN, never PASS
|
|
141
145
|
- **Surface-level**: Exists != substantive
|
|
142
146
|
- **Skip wired**: Code exists but unused
|
|
143
147
|
- **Trust count**: Passing != correct coverage
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# forge-model-outcome — advisory per-task model-routing outcome log + promotion
|
|
3
|
+
# ladder (M24 phases 37-38).
|
|
4
|
+
#
|
|
5
|
+
# forge-model-outcome.sh record [--repo R] [--milestone M] [--skill S]
|
|
6
|
+
# [--task-type T] [--complexity C] [--model MO] [--first-try-pass true|false]
|
|
7
|
+
# [--check-exit N]
|
|
8
|
+
#
|
|
9
|
+
# Appends ONE printf-built JSON line to ${FORGE_HOME:-$HOME}/.forge/model-outcomes.jsonl
|
|
10
|
+
# (honors $HOME so tests can redirect it). Lazily creates the dir + file.
|
|
11
|
+
#
|
|
12
|
+
# ADVISORY, always: any failure (unwritable home, etc.) is swallowed and this
|
|
13
|
+
# helper still exits 0 — logging must never block or fail a task. The write
|
|
14
|
+
# path is dependency-free (printf only, no jq/python — NFR-026).
|
|
15
|
+
#
|
|
16
|
+
# forge-model-outcome.sh recommend
|
|
17
|
+
#
|
|
18
|
+
# Reads the log and prints an untested -> probation -> proven promotion ladder
|
|
19
|
+
# per (model, task_type), plus concrete routing recommendations for proven
|
|
20
|
+
# buckets. The READ path may use jq (unlike record's write path). Degrades to
|
|
21
|
+
# an "insufficient data" message + exit 0 when jq is absent or the log is
|
|
22
|
+
# absent/empty — never errors (NFR-026).
|
|
23
|
+
#
|
|
24
|
+
# Pinned field set (contract between record + recommend; keep names stable):
|
|
25
|
+
# ts repo milestone skill task_type complexity model first_try_pass check_exit
|
|
26
|
+
set -u
|
|
27
|
+
|
|
28
|
+
usage() {
|
|
29
|
+
cat <<'EOF' >&2
|
|
30
|
+
usage: forge-model-outcome.sh record [--repo R] [--milestone M] [--skill S]
|
|
31
|
+
[--task-type T] [--complexity C] [--model MO] [--first-try-pass true|false]
|
|
32
|
+
[--check-exit N]
|
|
33
|
+
forge-model-outcome.sh recommend
|
|
34
|
+
EOF
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
log_path() {
|
|
38
|
+
home="${FORGE_HOME:-$HOME}"
|
|
39
|
+
printf '%s/.forge/model-outcomes.jsonl' "$home"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
do_record() {
|
|
43
|
+
# --- parse flags (ALL optional — missing -> empty string / null) ---------
|
|
44
|
+
repo=""; milestone=""; skill=""; task_type=""; complexity=""; model=""
|
|
45
|
+
first_try_pass=""; check_exit=""
|
|
46
|
+
while [ $# -gt 0 ]; do
|
|
47
|
+
case "$1" in
|
|
48
|
+
--repo) shift; repo="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
49
|
+
--repo=*) repo="${1#*=}"; shift ;;
|
|
50
|
+
--milestone) shift; milestone="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
51
|
+
--milestone=*) milestone="${1#*=}"; shift ;;
|
|
52
|
+
--skill) shift; skill="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
53
|
+
--skill=*) skill="${1#*=}"; shift ;;
|
|
54
|
+
--task-type) shift; task_type="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
55
|
+
--task-type=*) task_type="${1#*=}"; shift ;;
|
|
56
|
+
--complexity) shift; complexity="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
57
|
+
--complexity=*) complexity="${1#*=}"; shift ;;
|
|
58
|
+
--model) shift; model="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
59
|
+
--model=*) model="${1#*=}"; shift ;;
|
|
60
|
+
--first-try-pass) shift; first_try_pass="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
61
|
+
--first-try-pass=*) first_try_pass="${1#*=}"; shift ;;
|
|
62
|
+
--check-exit) shift; check_exit="${1:-}"; [ $# -gt 0 ] && shift || true ;;
|
|
63
|
+
--check-exit=*) check_exit="${1#*=}"; shift ;;
|
|
64
|
+
-*) printf 'forge-model-outcome: unknown option: %s (ignored)\n' "$1" >&2; shift ;;
|
|
65
|
+
*) shift ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
dir="$(dirname "$(log_path)")"
|
|
70
|
+
log="$(log_path)"
|
|
71
|
+
|
|
72
|
+
# Escape backslash then double-quote for a JSON string value — these are
|
|
73
|
+
# controlled enums/basenames, so this minimal escape is enough (no jq).
|
|
74
|
+
esc() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; }
|
|
75
|
+
|
|
76
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || true)"
|
|
77
|
+
|
|
78
|
+
fp="false"
|
|
79
|
+
case "$first_try_pass" in
|
|
80
|
+
true|1|yes) fp="true" ;;
|
|
81
|
+
*) fp="false" ;;
|
|
82
|
+
esac
|
|
83
|
+
|
|
84
|
+
ce="null"
|
|
85
|
+
case "$check_exit" in
|
|
86
|
+
''|*[!0-9-]*) ce="null" ;;
|
|
87
|
+
*) ce="$check_exit" ;;
|
|
88
|
+
esac
|
|
89
|
+
|
|
90
|
+
line=$(printf '{"ts":"%s","repo":"%s","milestone":"%s","skill":"%s","task_type":"%s","complexity":"%s","model":"%s","first_try_pass":%s,"check_exit":%s}' \
|
|
91
|
+
"$(esc "$ts")" "$(esc "$repo")" "$(esc "$milestone")" "$(esc "$skill")" \
|
|
92
|
+
"$(esc "$task_type")" "$(esc "$complexity")" "$(esc "$model")" "$fp" "$ce")
|
|
93
|
+
|
|
94
|
+
# The whole create+append is best-effort: swallow any failure (unwritable
|
|
95
|
+
# home, read-only fs, a ".forge" that's a regular file, ...) and exit 0
|
|
96
|
+
# regardless — a task must never be blocked or failed by this log.
|
|
97
|
+
( mkdir -p "$dir" && printf '%s\n' "$line" >> "$log" ) 2>/dev/null
|
|
98
|
+
exit 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
do_recommend() {
|
|
102
|
+
# Promotion-ladder thresholds (Ringer parity) — documented constants.
|
|
103
|
+
VOLUME_MIN=5 # count < VOLUME_MIN -> untested
|
|
104
|
+
PROVEN_CUTOFF=0.90 # count >= VOLUME_MIN and pass_rate >= PROVEN_CUTOFF -> proven; else probation
|
|
105
|
+
|
|
106
|
+
log="$(log_path)"
|
|
107
|
+
|
|
108
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
109
|
+
printf 'recommendations unavailable: jq not found (insufficient data)\n'
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
if [ ! -s "$log" ]; then
|
|
113
|
+
printf 'recommendations unavailable: no outcome data yet (insufficient data)\n'
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
buckets="$(jq -sc --argjson min "$VOLUME_MIN" --argjson cutoff "$PROVEN_CUTOFF" '
|
|
118
|
+
group_by([.model, .task_type])
|
|
119
|
+
| map(
|
|
120
|
+
{
|
|
121
|
+
model: .[0].model,
|
|
122
|
+
task_type: .[0].task_type,
|
|
123
|
+
count: length,
|
|
124
|
+
pass_rate: (([ .[] | select(.first_try_pass == true) ] | length) / length)
|
|
125
|
+
}
|
|
126
|
+
| . + {
|
|
127
|
+
status: (
|
|
128
|
+
if .count < $min then "untested"
|
|
129
|
+
elif .pass_rate >= $cutoff then "proven"
|
|
130
|
+
else "probation"
|
|
131
|
+
end
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
| .[]
|
|
136
|
+
' "$log" 2>/dev/null)"
|
|
137
|
+
|
|
138
|
+
if [ -z "$buckets" ]; then
|
|
139
|
+
printf 'recommendations unavailable: no outcome data yet (insufficient data)\n'
|
|
140
|
+
exit 0
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
cutoff_pct="$(awk -v c="$PROVEN_CUTOFF" 'BEGIN{printf "%.0f", c*100}')"
|
|
144
|
+
printf 'Model-outcome promotion ladder (volume >= %s runs, proven cutoff >= %s%% first-try):\n\n' "$VOLUME_MIN" "$cutoff_pct"
|
|
145
|
+
|
|
146
|
+
printf '%s\n' "$buckets" | while IFS= read -r b; do
|
|
147
|
+
model="$(printf '%s' "$b" | jq -r '.model')"
|
|
148
|
+
task_type="$(printf '%s' "$b" | jq -r '.task_type')"
|
|
149
|
+
count="$(printf '%s' "$b" | jq -r '.count')"
|
|
150
|
+
status="$(printf '%s' "$b" | jq -r '.status')"
|
|
151
|
+
pct="$(printf '%s' "$b" | jq -r '.pass_rate' | awk '{printf "%.0f", $1*100}')"
|
|
152
|
+
printf ' model=%-10s task_type=%-14s status=%-10s count=%-4s first-try=%s%%\n' "$model" "$task_type" "$status" "$count" "$pct"
|
|
153
|
+
done
|
|
154
|
+
|
|
155
|
+
printf '\nRouting recommendations:\n'
|
|
156
|
+
printf '%s\n' "$buckets" | while IFS= read -r b; do
|
|
157
|
+
status="$(printf '%s' "$b" | jq -r '.status')"
|
|
158
|
+
[ "$status" = "proven" ] || continue
|
|
159
|
+
model="$(printf '%s' "$b" | jq -r '.model')"
|
|
160
|
+
task_type="$(printf '%s' "$b" | jq -r '.task_type')"
|
|
161
|
+
count="$(printf '%s' "$b" | jq -r '.count')"
|
|
162
|
+
pct="$(printf '%s' "$b" | jq -r '.pass_rate' | awk '{printf "%.0f", $1*100}')"
|
|
163
|
+
printf ' %s is proven on %s tasks (%s runs, %s%% first-try) -> route %s tasks to %s\n' "$model" "$task_type" "$count" "$pct" "$task_type" "$model"
|
|
164
|
+
done
|
|
165
|
+
|
|
166
|
+
exit 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
cmd="${1:-}"
|
|
170
|
+
[ $# -gt 0 ] && shift || true
|
|
171
|
+
|
|
172
|
+
case "$cmd" in
|
|
173
|
+
record) do_record "$@" ;;
|
|
174
|
+
recommend) do_recommend ;;
|
|
175
|
+
""|-h|--help) usage; exit 0 ;;
|
|
176
|
+
*) printf 'forge-model-outcome: unknown subcommand: %s\n' "$cmd" >&2; usage; exit 2 ;;
|
|
177
|
+
esac
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Self-contained unit test for .claude/hooks/forge-model-outcome.sh (M24 phase 37).
|
|
3
|
+
#
|
|
4
|
+
# Needs no network and no fixtures beyond a temp dir. Each case runs the helper
|
|
5
|
+
# with an isolated $HOME and asserts its exit code + the jsonl file's contents.
|
|
6
|
+
# jq is used HERE to parse the helper's output for assertions — the helper's
|
|
7
|
+
# own write path never uses jq (printf only; see forge-model-outcome.sh, NFR-026).
|
|
8
|
+
# Run:
|
|
9
|
+
# ./.claude/hooks/tests/forge-model-outcome.test.sh
|
|
10
|
+
# Exits 0 iff every case passes; non-zero on any failure. Self-cleans on exit.
|
|
11
|
+
set -u
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
HELPER="$SCRIPT_DIR/../forge-model-outcome.sh"
|
|
15
|
+
|
|
16
|
+
[ -x "$HELPER" ] || { printf 'FATAL: helper not executable: %s\n' "$HELPER" >&2; exit 1; }
|
|
17
|
+
command -v jq >/dev/null 2>&1 || { printf 'FATAL: jq required for test assertions\n' >&2; exit 1; }
|
|
18
|
+
|
|
19
|
+
ROOT="$(mktemp -d)"
|
|
20
|
+
trap 'rm -rf "$ROOT"' EXIT INT TERM
|
|
21
|
+
|
|
22
|
+
PASSED=0
|
|
23
|
+
FAILED=0
|
|
24
|
+
pass() { PASSED=$((PASSED + 1)); printf 'PASS: %s\n' "$1"; }
|
|
25
|
+
fail() { FAILED=$((FAILED + 1)); printf 'FAIL: %s\n' "$1" >&2; }
|
|
26
|
+
|
|
27
|
+
# --- Case 1: record appends one valid jsonl line with the pinned fields -----
|
|
28
|
+
d="$ROOT/case1"; mkdir -p "$d"
|
|
29
|
+
HOME="$d" "$HELPER" record --repo forge --milestone m24 --skill executing \
|
|
30
|
+
--task-type docs --complexity trivial --model haiku --first-try-pass true --check-exit 0 >/dev/null
|
|
31
|
+
if [ -f "$d/.forge/model-outcomes.jsonl" ]; then
|
|
32
|
+
pass "case1 log file created"
|
|
33
|
+
else
|
|
34
|
+
fail "case1 log file not created"
|
|
35
|
+
fi
|
|
36
|
+
line="$(tail -1 "$d/.forge/model-outcomes.jsonl" 2>/dev/null)"
|
|
37
|
+
if printf '%s' "$line" | jq -e \
|
|
38
|
+
'.task_type=="docs" and .complexity=="trivial" and .model=="haiku" and .first_try_pass==true and .check_exit==0 and .repo=="forge" and .milestone=="m24" and .skill=="executing"' \
|
|
39
|
+
>/dev/null 2>&1; then
|
|
40
|
+
pass "case1 line carries pinned fields"
|
|
41
|
+
else
|
|
42
|
+
fail "case1 line missing/incorrect fields: $line"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# --- Case 2: unwritable home degrades to exit 0, never blocks ---------------
|
|
46
|
+
d="$ROOT/case2"; mkdir -p "$d"
|
|
47
|
+
: > "$d/.forge" # regular file where a dir is expected -> mkdir -p fails
|
|
48
|
+
HOME="$d" "$HELPER" record --repo forge --milestone m24 --skill executing --model haiku >/dev/null 2>&1
|
|
49
|
+
rc=$?
|
|
50
|
+
check_rc() { [ "$1" -eq "$2" ] && pass "$3" || fail "$3 (got exit $1, want $2)"; }
|
|
51
|
+
check_rc "$rc" 0 "case2 unwritable home still exits 0"
|
|
52
|
+
|
|
53
|
+
# --- Case 3: two record calls append, they do not overwrite -----------------
|
|
54
|
+
d="$ROOT/case3"; mkdir -p "$d"
|
|
55
|
+
HOME="$d" "$HELPER" record --repo forge --milestone m24 --skill executing --model haiku --task-type docs >/dev/null
|
|
56
|
+
HOME="$d" "$HELPER" record --repo forge --milestone m24 --skill executing --model opus --task-type code-feature >/dev/null
|
|
57
|
+
lines="$(wc -l < "$d/.forge/model-outcomes.jsonl" | tr -d ' ')"
|
|
58
|
+
if [ "$lines" = "2" ]; then
|
|
59
|
+
pass "case3 two calls -> two lines (append, not overwrite)"
|
|
60
|
+
else
|
|
61
|
+
fail "case3 expected 2 lines, got $lines"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# --- Case 4: recommend — proven bucket (volume met, high pass rate) --------
|
|
65
|
+
d="$ROOT/case4"; mkdir -p "$d"
|
|
66
|
+
i=1
|
|
67
|
+
while [ "$i" -le 6 ]; do
|
|
68
|
+
HOME="$d" "$HELPER" record --repo x --milestone m24 --skill executing \
|
|
69
|
+
--task-type docs --complexity trivial --model haiku --first-try-pass true --check-exit 0 >/dev/null
|
|
70
|
+
i=$((i + 1))
|
|
71
|
+
done
|
|
72
|
+
out="$(HOME="$d" "$HELPER" recommend)"
|
|
73
|
+
if printf '%s' "$out" | grep -qi 'proven' && printf '%s' "$out" | grep -qi 'docs' && printf '%s' "$out" | grep -qi 'haiku'; then
|
|
74
|
+
pass "case4 recommend marks (haiku,docs) proven + recommends routing"
|
|
75
|
+
else
|
|
76
|
+
fail "case4 recommend missing proven/docs/haiku: $out"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# --- Case 5: recommend — probation bucket (volume met, sub-cutoff pass rate) -
|
|
80
|
+
d="$ROOT/case5"; mkdir -p "$d"
|
|
81
|
+
i=1
|
|
82
|
+
while [ "$i" -le 5 ]; do
|
|
83
|
+
fp="true"; [ "$i" -le 2 ] && fp="false" # 3/5 = 60% < 90% cutoff
|
|
84
|
+
HOME="$d" "$HELPER" record --repo x --milestone m24 --skill executing \
|
|
85
|
+
--task-type code-fix --complexity standard --model sonnet --first-try-pass "$fp" --check-exit 0 >/dev/null
|
|
86
|
+
i=$((i + 1))
|
|
87
|
+
done
|
|
88
|
+
out="$(HOME="$d" "$HELPER" recommend)"
|
|
89
|
+
if printf '%s' "$out" | grep -qi 'probation'; then
|
|
90
|
+
pass "case5 recommend marks sub-cutoff bucket probation"
|
|
91
|
+
else
|
|
92
|
+
fail "case5 recommend did not mark bucket probation: $out"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# --- Case 6: recommend degrades on an empty/absent log — exit 0 ------------
|
|
96
|
+
d="$ROOT/case6"; mkdir -p "$d"
|
|
97
|
+
out="$(HOME="$d" "$HELPER" recommend)"
|
|
98
|
+
rc=$?
|
|
99
|
+
check_rc "$rc" 0 "case6 recommend on absent log exits 0"
|
|
100
|
+
if printf '%s' "$out" | grep -qiE 'insufficient|unavailable|no data|not enough'; then
|
|
101
|
+
pass "case6 recommend on absent log prints insufficient-data message"
|
|
102
|
+
else
|
|
103
|
+
fail "case6 recommend missing insufficient-data message: $out"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# --- Report -----------------------------------------------------------------
|
|
107
|
+
printf '\n%d passed, %d failed\n' "$PASSED" "$FAILED"
|
|
108
|
+
[ "$FAILED" -eq 0 ]
|
|
@@ -200,7 +200,11 @@ Resume:
|
|
|
200
200
|
1. Run the Stream Rollup, then load the stream file and brief.
|
|
201
201
|
2. Surface status, blockers, owned surfaces, shared surfaces, and next action.
|
|
202
202
|
3. Set the **stream file's** `status` to `active`, then regenerate `active.yml`.
|
|
203
|
-
4.
|
|
203
|
+
4. **Relocate into the stream worktree (Native Worktree Entry).** When the resumed stream's file records a `runtime.worktree`, apply the canonical Native Worktree Entry contract (identical to the forge ownership-gate option 1):
|
|
204
|
+
- **If the `EnterWorktree` tool is available**, call `EnterWorktree({path: {runtime.worktree}})` to relocate this session into the stream worktree **in place**, then log `Relocated into worktree {runtime.worktree} ({runtime.branch})` (independent of any prompt, so the cwd move is visible) and continue the resume from inside the worktree.
|
|
205
|
+
- **If `EnterWorktree` is unavailable** (older Claude Code CLI, or a non-Claude-Code adapter), fall back to printing the `cd {path}` instruction (here `cd {runtime.worktree}`) — today's implicit behavior made explicit.
|
|
206
|
+
- Streams with no `runtime.worktree` resume unchanged — no relocation attempted. Exit is always `ExitWorktree({action: "keep"})`; the Chief never lets the native tool remove a worktree (teardown/merge stays the Chief's existing flow).
|
|
207
|
+
5. Run conflict detection before continuing.
|
|
204
208
|
|
|
205
209
|
## Delegate Flow
|
|
206
210
|
|
|
@@ -243,6 +243,33 @@ ID = `DI-{N}` where N = next integer after existing max (or 001 if new file).
|
|
|
243
243
|
### Quick Tier
|
|
244
244
|
Run all non-advisory verification commands once after commit. 1 retry max.
|
|
245
245
|
|
|
246
|
+
### Declared Checks (plan completion)
|
|
247
|
+
|
|
248
|
+
After the plan's FINAL task commit, extract the `check:` commands from the plan's `must_haves.truths` and run each once. Truths with a `manual:` route have no check to run — skip them (never execute the route text as shell). No declared checks → silent no-op (legacy plans unchanged).
|
|
249
|
+
|
|
250
|
+
- **Failure** → enters the auto-fix loop above with the check's failure output as fix context; retries count toward the existing 3-strike/deferred-issues rules.
|
|
251
|
+
- **This run is an EARLY SIGNAL only** — it never records a verification verdict. `verifying` re-executes every check fresh and applies the executed-evidence rule.
|
|
252
|
+
- WHY here: the check's failure output is cheapest to act on before context handoff — same reasoning as a retry-with-failure-context loop; waiting for verifying costs a full phase round-trip.
|
|
253
|
+
|
|
254
|
+
### Model-Outcome Log (mandatory, per task — M24)
|
|
255
|
+
|
|
256
|
+
Once a task's verification bookkeeping settles (3-Strike result known, declared-check exit code known), call `.claude/hooks/forge-model-outcome.sh record` **once for that task** — this is a MANDATORY step in the bookkeeping, not an optional "consider logging" note (an optional note is exactly the zero-writer failure the M11 desire-path signal caught). Source every field from state the gate already computed — no new instrumentation:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
.claude/hooks/forge-model-outcome.sh record \
|
|
260
|
+
--repo "$(basename "$(git rev-parse --show-toplevel)")" \
|
|
261
|
+
--milestone "m{id}" --skill executing \
|
|
262
|
+
--task-type "{task.task_type}" --complexity "{task.complexity}" \
|
|
263
|
+
--model "{model the task's executor ran on}" \
|
|
264
|
+
--first-try-pass {true|false — passed attempt 1 vs needed a retry} \
|
|
265
|
+
--check-exit {declared check's exit code, or omit if none}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- `model` is the **per-task resolved model** (phase 36 — `by_complexity` → `skills.executing` → `default` → parent), not a skill-wide constant.
|
|
269
|
+
- `task_type` / `complexity` come from the task's own fields (planning Step 6.2); omit either if the task left them unmarked.
|
|
270
|
+
- **One append per task, every task** — this is what closes the loop phase 38's `recommend` reads from.
|
|
271
|
+
- **Advisory** — the helper is itself dependency-free and always exits 0 (unwritable `~/.forge`, etc. are swallowed); a logging failure NEVER blocks or fails the task.
|
|
272
|
+
|
|
246
273
|
## Context Engineering
|
|
247
274
|
|
|
248
275
|
| Trigger | Action |
|
|
@@ -253,8 +280,11 @@ Run all non-advisory verification commands once after commit. 1 retry max.
|
|
|
253
280
|
| Context 40-60% | Consider fresh agent |
|
|
254
281
|
| Context under 40% | Continue normally |
|
|
255
282
|
| Switching unrelated subsystems | Spawn fresh agent |
|
|
283
|
+
| Task carries ANY `complexity` tag (COST, M24) | Spawn a fresh executor on its resolved model — even under 40% context. Only an UNTAGGED glue task runs inline on the parent model. |
|
|
284
|
+
|
|
285
|
+
**Spawn via Agent tool** with: relevant plan details, specific files, locked decisions from context.md, clear success criteria. **Resolve the model PER TASK**, not once per skill run: `models.by_complexity.{task.complexity}` → `models.skills.executing` → `models.default` → parent (complexity overrides skill — FORGE.md → Model Routing). Display per spawn: *"Spawning executor with model: {model} (from {source})"*
|
|
256
286
|
|
|
257
|
-
**
|
|
287
|
+
**Why the tag gates the spawn (enforced subagent spawning).** An inline task inherits the parent session's model and cannot be re-routed — routing only has teeth if every tagged task actually spawns. Spawning carries fixed briefing overhead, which is why the rule keys on the *tag* (a deliberate plan-time signal the work is worth routing) rather than "spawn everything": the cheapest untagged glue work stays inline. A full parent-as-orchestrator model (spawn everything, parent never implements) is a deferred follow-up, to be justified by M24 outcome-log evidence — not implemented here.
|
|
258
288
|
|
|
259
289
|
## Execution Summary
|
|
260
290
|
|
|
@@ -122,6 +122,8 @@ Uses only signals that stay current (`current.phase`, `current.status`, the road
|
|
|
122
122
|
|
|
123
123
|
Beads enabled (`forge.beads_integration: true`) → `bd prime`. Optional.
|
|
124
124
|
|
|
125
|
+
Notion adapter (opt-in): `notion.enabled: true` in `project.yml` **and** a Notion MCP server present → log *"Notion adapter active"* and (Phase 2) run `pull_inbox()` (`notion-integration` skill); config without MCP → log *"Notion adapter inactive (MCP absent)"*; neither → silent. When active, `project()` also fires at each phase **handoff** (the State Commit Protocol checkpoint) to push agent-owned state to the board. **Advisory — never blocks boot, handoff, or commit.**
|
|
126
|
+
|
|
125
127
|
Read `.forge/context.md`. **Needs Resolution** unchecked → warn: *"{N} unresolved discrepancies."* Quick proceeds; Standard/Full blocked at planning.
|
|
126
128
|
|
|
127
129
|
### 1.1a Live-Worktree Ownership Gate (enforced)
|
|
@@ -150,7 +152,10 @@ The gate (below) and this finalize path are mutually exclusive on `current.statu
|
|
|
150
152
|
|
|
151
153
|
Present the four resolutions and wait for an explicit choice:
|
|
152
154
|
|
|
153
|
-
1. **Enter the worktree** *(recommended)* —
|
|
155
|
+
1. **Enter the worktree** *(recommended)* — relocate this session into the worktree **in place** (the operator no longer has to `cd` + re-run). This is the canonical **Native Worktree Entry** contract, reused verbatim at the `chief-of-staff` resume and `orchestrating` hand-back touch points:
|
|
156
|
+
- **If the `EnterWorktree` tool is available**, call `EnterWorktree({path: {worktree_path}})` — always `{path}` into the recorded `lifecycle.worktree_path`, never `{name}` (Forge owns worktree creation; `{name}` would spawn a new one on the wrong branch convention). Then log one line — `Relocated into worktree {worktree_path} ({branch})` — **independent of any confirmation prompt**, so the cwd move is always visible in the transcript. Continue routing to the requested skill from inside the worktree: writes now land in the owner, and the ownership gate is satisfied because this session **is** the owner.
|
|
157
|
+
- **If `EnterWorktree` is unavailable** (older Claude Code CLI, or a non-Claude-Code adapter), fall back to today's behavior: print the exact command (`cd {worktree_path}` then `/forge`); re-running there anchors to this milestone and writes land in the owner. This session does nothing further to the milestone.
|
|
158
|
+
- Exit is always `ExitWorktree({action: "keep"})` — Forge never lets the native tool **remove** a worktree; teardown stays option 3 / `git worktree remove`.
|
|
154
159
|
2. **Sync, then continue in the worktree** — bring main's commits in (`git -C {worktree_path} rebase main`, or merge), then work from the worktree. Use when main has changes the worktree needs first.
|
|
155
160
|
3. **Retire the worktree** — only if it is dead/superseded. Close the stream, `git worktree remove {worktree_path}`, and clear `lifecycle.worktree_*` on the milestone — which ends its ownership so main may write. **Confirm before removing; never auto-remove a worktree.**
|
|
156
161
|
4. **Recorded override** — deliberately write from main anyway. Write `lifecycle.ownership_override: {at: {ISO}, from: main, reason: "{operator's reason}"}` into `milestone-{id}.yml`, echo it in the boot line, and carry it into the next `reviewing` report. This is the **only** way past the block and it is **never silent**. (An override is scoped to this milestone; clear it once the worktree is retired or synced.)
|
|
@@ -198,6 +203,15 @@ Downstream skills (researching, discussing, planning, executing, verifying, revi
|
|
|
198
203
|
|
|
199
204
|
**Surface at 3+.** For each remaining group with 3+ files (declined groups already skipped), **load `desire-paths-review.md` in this skill dir and follow it** — it carries the concrete fix-suggestion map (per `type`), the Apply/decline mechanics (archive to `resolved/` on apply; write a `declined/` file on decline — no re-nag), and the upstream transport for `scope: framework` signals (portable block + optional `gh issue` against `forge.upstream_repo`). Groups under 3 (the common boot) → nothing to surface; skip the load. (Occurrence count is derived from file count — no counter to reset.)
|
|
200
205
|
|
|
206
|
+
### 1.2a Prototype hygiene (parked-rot guard)
|
|
207
|
+
|
|
208
|
+
The Prototyping tier writes an always-present state file (`.forge/prototypes/{slug}.md`), so a `parked` prototype can accumulate silently — the rot risk of a durable artifact. Cheap boot check, advisory, never blocks:
|
|
209
|
+
|
|
210
|
+
- No `.forge/prototypes/` dir → skip (nothing to surface). This is the common boot.
|
|
211
|
+
- Glob `.forge/prototypes/*.md` (NOT `archive/` — graduated/discarded are already terminal and archived). For each, read `status`.
|
|
212
|
+
- Any `status: parked` with `parked` (or `created`) older than **30 days** → one line each: *"Prototype `{slug}` ({title}) parked {N}d — resume (`/forge prototype {slug}`), graduate, or discard? Parked mockups rot."* Surface only; the operator decides. `exploring` prototypes are live work in progress — do not nag them.
|
|
213
|
+
- Nothing stale → silent.
|
|
214
|
+
|
|
201
215
|
### 1.3 Interface Check
|
|
202
216
|
|
|
203
217
|
Check `interface` in `project.yml`:
|
|
@@ -220,6 +234,7 @@ Before tier detection, check if user request matches stream or lifecycle pattern
|
|
|
220
234
|
- "resume milestone {id}" / "undefer {id}" / "reactivate milestone {id}" → **Resume**
|
|
221
235
|
- "delete milestone {id}" / "archive milestone {id}" / "remove milestone {id}" → **Delete** (see below)
|
|
222
236
|
- "deferred" / "show deferred" / "deferred items" / "what's deferred" / "deferred work" → **Show Deferred**
|
|
237
|
+
- "prototype {slug}" / "resume prototype {slug}" / "mockup {slug}" / "show prototypes" / "list prototypes" → **Prototype Ops**
|
|
223
238
|
|
|
224
239
|
No match → fall through to Step 2B (tier detection).
|
|
225
240
|
|
|
@@ -244,6 +259,12 @@ Examples:
|
|
|
244
259
|
1. Invoke `Skill(deferred)` directly.
|
|
245
260
|
2. Do not enter tier detection. Do not prompt for milestone. The deferred skill is read-only and self-contained.
|
|
246
261
|
|
|
262
|
+
### Prototype Ops
|
|
263
|
+
|
|
264
|
+
Prototypes are the disposable in-app mockup tier (`.forge/prototypes/{slug}.md`; see `Skill(prototyping)`).
|
|
265
|
+
- **"prototype {slug}" / "resume prototype {slug}" / "mockup {slug}"** → invoke `Skill(prototyping)` with the slug. It reads the existing state file and continues the iteration loop (or starts a new prototype if the slug is unknown). Do not enter tier detection.
|
|
266
|
+
- **"show prototypes" / "list prototypes"** → read-only: glob `.forge/prototypes/*.md`, list each `{slug} — {title} — {status}` (skip `.forge/prototypes/archive/`). No state writes, no tier detection.
|
|
267
|
+
|
|
247
268
|
### Defer Operation
|
|
248
269
|
|
|
249
270
|
1. Validate: milestone in `index.yml`, status not `deferred`
|
|
@@ -299,6 +320,12 @@ No `project.yml` → `Skill(initializing)`. Brownfield/greenfield, absorption, s
|
|
|
299
320
|
|
|
300
321
|
## Step 2B: Detect Tier
|
|
301
322
|
|
|
323
|
+
### Prototyping
|
|
324
|
+
ANY: "mock up"/"mockup"/"sketch"/"prototype"/"draw up"/"iterate on the UI"/"explore how X could look"/"design iteration", **disposable** in-app visual exploration behind an alpha/feature flag, no requirements locked yet
|
|
325
|
+
→ `prototyping`
|
|
326
|
+
|
|
327
|
+
Distinguishing signal: the deliverable is *a thing to look at and react to*, not shippable code. If the operator wants to **see how something might look before deciding to build it**, it's Prototyping — even if it touches many files. Real UI work with known requirements is Standard + `designing` (which gates on quality/verification); Prototyping deliberately skips that gate. When genuinely ambiguous, ask: *"Is this a throwaway mockup to react to, or the real build?"*
|
|
328
|
+
|
|
302
329
|
### Quick
|
|
303
330
|
ANY: single file, typo/formatting, config/env, dep bump, <50 lines, "quick"/"small"/"minor"
|
|
304
331
|
→ `quick-tasking`
|
|
@@ -380,6 +407,7 @@ Where `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppre
|
|
|
380
407
|
| testing | sonnet | Code gen (author) + audit judgment (analyst) — matches executing/reviewing. M9: author-mode refuses e2e without `e2e:true` + `validated:true`. |
|
|
381
408
|
| deferred | haiku | Read + format only |
|
|
382
409
|
| chief-of-staff | sonnet | Cross-stream coordination and conflict judgment |
|
|
410
|
+
| prototyping | sonnet | UI code gen for disposable mockups |
|
|
383
411
|
|
|
384
412
|
| `current.status` | Route To |
|
|
385
413
|
|-------------------|----------|
|
|
@@ -396,6 +424,7 @@ Where `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppre
|
|
|
396
424
|
| `quick-tasking` | `Skill(quick-tasking)` |
|
|
397
425
|
| (keyword: `deferred` / "show deferred") | `Skill(deferred)` — read-only aggregator |
|
|
398
426
|
| (keyword: `show streams` / `start stream` / `merge safe` / `Chief of Staff`) | `Skill(chief-of-staff)` — project stream coordination |
|
|
427
|
+
| (keyword: `prototype {slug}` / `mock up` / `sketch` — disposable UI exploration) | `Skill(prototyping)` — no verify/review gate |
|
|
399
428
|
|
|
400
429
|
### Status Advancement Check
|
|
401
430
|
|
|
@@ -454,9 +483,10 @@ After clear, skills load from disk via "Read Context"/"Pre-Execution Checklist".
|
|
|
454
483
|
## State Transitions
|
|
455
484
|
|
|
456
485
|
```
|
|
457
|
-
Standard:
|
|
458
|
-
Full:
|
|
459
|
-
Advisory:
|
|
486
|
+
Standard: not_started → [init?] → researching → discussing → planning → executing → verifying → reviewing → complete
|
|
487
|
+
Full: not_started → [init?] → researching → discussing → architecting → planning → executing → verifying → reviewing → complete
|
|
488
|
+
Advisory: [init?] → discussing → [tier gate] → planning → executing → verifying → reviewing → complete
|
|
489
|
+
Prototyping: [flag] → iterate ⟲ (render → react → adjust) → resolve: graduate→milestone | park | discard (no verify/review gate)
|
|
460
490
|
|
|
461
491
|
Branches (any tier, on-demand): debugging (stuck) · designing (UI) · securing (auth/data/API) · testing (e2e/integration/flake)
|
|
462
492
|
Phase boundaries: `[clear]` recommended between phases to reset context.
|
|
@@ -193,6 +193,8 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
|
|
|
193
193
|
2. Frontmatter: phase, plan#, wave, deps, `slice_exception:` (optional, see Core Principle), `integration_checkpoint:` (optional, see below)
|
|
194
194
|
3. must_haves:
|
|
195
195
|
- **Truths:** User-observable outcomes (3-7). MUST be phrased as something the user can see, click, or receive -- not "model X exists" or "table Y created".
|
|
196
|
+
- Each truth SHOULD carry a `check:` command authored NOW, goal-backward, before any code exists -- a check written before the code cannot be retro-fitted to what got built (semantics in `.forge/templates/plan.md`).
|
|
197
|
+
- A truth that cannot be a command (visual, real-device, external service) carries a `manual:` route instead (human gate / M9 e2e) -- never a fake check.
|
|
196
198
|
- **Artifacts:** Must exist, substantive not stubs. Slice plans typically span 2-4 layers (e.g., component + handler + repo).
|
|
197
199
|
- **Key Links:** Connections between artifacts -- these prove the slice is wired, not stubbed.
|
|
198
200
|
4. XML tasks (2-3/plan, 15-60 min):
|
|
@@ -207,6 +209,18 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
|
|
|
207
209
|
</task>
|
|
208
210
|
```
|
|
209
211
|
|
|
212
|
+
#### Complexity + Task Type Assignment (cost-aware routing, M24)
|
|
213
|
+
|
|
214
|
+
At the same goal-backward moment `check:` commands are authored — plan time, before any code exists — assign each task an **optional** `complexity` (`trivial`|`standard`|`complex`) and `task_type` (`code-feature`|`code-fix`|`docs`|`research`|`config`|`test`). Executing resolves these into the spawn model via `models.by_complexity` (FORGE.md → Model Routing); an unmarked task falls back to skill-level routing (FR-131) — zero behavior change if you skip it.
|
|
215
|
+
|
|
216
|
+
Light heuristic, do not over-prescribe:
|
|
217
|
+
- **complexity** — config/doc/mechanical single-file edit -> `trivial`; ordinary feature/fix -> `standard`; new cross-layer behavior, tricky logic, or high blast radius -> `complex`.
|
|
218
|
+
- **task_type** — pick the dominant kind of work the task does.
|
|
219
|
+
|
|
220
|
+
Both fields are **advisory, not a gate**: a genuinely ambiguous task MAY be left unmarked rather than guessed. Do not add a Step 8 lint dimension for this — unlike `check:` truths, routing hints are optional annotations, not verification obligations.
|
|
221
|
+
|
|
222
|
+
**Consulting the promotion ladder (advisory).** While assigning, the planner MAY run `.claude/hooks/forge-model-outcome.sh recommend` and fold any `proven`-bucket line into the assignment as advisory input — e.g. "ladder: haiku proven on docs → a docs task here is a safe `trivial`". This is advisory only: planning NEVER blocks on it, and on the common early case (`recommend` reports insufficient data) assignment proceeds normally on the heuristic above. Do not turn this into a required lookup step.
|
|
223
|
+
|
|
210
224
|
#### Overlay / Popover must_haves (when a plan adds a floating surface)
|
|
211
225
|
|
|
212
226
|
When a plan introduces an **overlay, popover, sheet, anchored panel, or any floating surface** (especially one anchored to a moving element like a canvas block), specifying its *content* (rows, keys, payload) is **not enough** — the *behavior* questions below only surface when a human touches the running UI, each costing a full build → deploy → UAT round. Add a `must_haves.truths` entry for every item that applies, phrased as a user-observable behavior:
|
|
@@ -384,7 +398,7 @@ Decision captured once, pre-code. Does not block planning.
|
|
|
384
398
|
|
|
385
399
|
## Step 8: Verify Plans
|
|
386
400
|
|
|
387
|
-
|
|
401
|
+
10 dimensions:
|
|
388
402
|
1. **Requirement Coverage** -- every req has task(s)
|
|
389
403
|
2. **Task Completeness** -- files + action + verify + done
|
|
390
404
|
3. **Deps** -- valid DAG, no cycles
|
|
@@ -394,6 +408,7 @@ Decision captured once, pre-code. Does not block planning.
|
|
|
394
408
|
7. **Context** -- honors locked, excludes deferred
|
|
395
409
|
8. **Spec Validity** -- valid syntax, correct paths
|
|
396
410
|
9. **Slice Integrity (HARD GATE)** -- every plan delivers a vertical slice OR declares `slice_exception:`
|
|
411
|
+
10. **Check Quality** -- truth checks are real, diagnostic, and routed
|
|
397
412
|
|
|
398
413
|
### Slice Integrity Check
|
|
399
414
|
|
|
@@ -408,6 +423,17 @@ Roadmap-level check: FAIL if phase 1 has no user-observable goal.
|
|
|
408
423
|
|
|
409
424
|
On fail: restructure plan(s) into slices, or declare `slice_exception:` with one of `infra_bootstrap | shared_library | data_migration` and a one-line rationale. Re-verify.
|
|
410
425
|
|
|
426
|
+
### Check Quality Check
|
|
427
|
+
|
|
428
|
+
A check that cannot fail manufactures fake evidence -- worse than no check, because verifying will execute it and record a hollow PASS. Lint every truth `check:` before handoff (heuristics ported from Ringer's manifest lint, applied as prose -- no script):
|
|
429
|
+
|
|
430
|
+
- **(a) Cannot-fail check** -- `true`, `:`, `exit 0`, or an echo-only pipeline → **always a failure, no justify path.** Rewrite it so it can catch the truth being false.
|
|
431
|
+
- **(b) Silent-fail check** -- every conjunct is an existence test / `grep -q` / `diff -q` and no `||` branch prints diagnostics → fix or justify in one line. The failure output is what feeds executing's auto-fix loop and the verification report; a silent exit 1 starves both.
|
|
432
|
+
- **(c) Existence-only check on a behavioral truth** -- `test -f` (or equivalent) where the truth claims behavior → fix (run/validate the content) or justify.
|
|
433
|
+
- **(d) Unrouted truth** -- a truth with neither a `check:` nor a named manual route (human gate / M9 e2e) → flag; assign a route. Checks stay optional per truth -- the route does not.
|
|
434
|
+
|
|
435
|
+
On fail: same mechanics as the other dimensions -- fix, re-verify, max 3 cycles. Rule (a) findings must be fixed, never justified.
|
|
436
|
+
|
|
411
437
|
Issues -> fix, re-verify. Max 3 cycles.
|
|
412
438
|
|
|
413
439
|
## Step 9: Present
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prototyping
|
|
3
|
+
description: "Rapid, disposable, in-app design mockups. Iterate UI behind an alpha/feature flag to conceptualize how something might look BEFORE committing to a real build. Trigger: 'mock up', 'sketch a UI', 'quick design iteration', 'prototype this screen', 'explore how X could look', 'draw up options'. Prototyping tier — no verify/review gate."
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Prototyping
|
|
8
|
+
|
|
9
|
+
Fast visual exploration inside the real app. You are drawing iterations of how something *might* look, gated behind an alpha/feature flag, so the operator can react to a concrete rendering before any real build is scoped. This is **not** production work.
|
|
10
|
+
|
|
11
|
+
**What makes this its own tier.** Standard/Full ceremony (research → discuss → plan → verify → review, goal-backward verification, a milestone cursor) is the wrong shape — a mockup has no requirements yet (discovering them is the *point*) and no observable truth to verify beyond "does it look like something worth discussing?". Quick tier's <50-line / 1-2-file caps and its commit-and-done finish don't fit a multi-file, multi-iteration loop whose output is deliberately disposable. Prototyping fills that gap.
|
|
12
|
+
|
|
13
|
+
**Three rules that define the tier:**
|
|
14
|
+
1. **Disposable by default.** Everything you write lives behind a flag and is expected to be thrown away. Do not refactor for production, do not add tests, do not handle every edge case. Make it *look* right, fast.
|
|
15
|
+
2. **No verify/review gate.** There is no `verifying`, no `reviewing`, no health gate. The gate is the operator's eyes.
|
|
16
|
+
3. **Iterate, don't finish.** The loop is: render → operator reacts → adjust → render. It ends when the operator graduates, parks, or discards — never at "100% of tasks done".
|
|
17
|
+
|
|
18
|
+
## Step 1: Establish the prototype
|
|
19
|
+
|
|
20
|
+
Give the exploration a short kebab `{slug}` (e.g. `dashboard-redesign`, `onboarding-v2`).
|
|
21
|
+
|
|
22
|
+
**Check for an existing session.** Glob `.forge/prototypes/{slug}.md`. Exists → this is a **resume**: read it, summarize the iterations so far and the current open question, and continue the loop at Step 4. Absent → new prototype, continue.
|
|
23
|
+
|
|
24
|
+
**Load the design system loosely.** Read `.forge/project.yml → design_system` and `.forge/design-system.md` if present — use real components and tokens so the mockup reads as native to the app. But this is exploration: **it is fine to hand-roll a component the design system doesn't have yet** (that gap may be the very thing you're exploring). The `designing` skill's *strict* "never build custom for what the system provides" rule is relaxed here — prefer the system, don't be blocked by it.
|
|
25
|
+
|
|
26
|
+
## Step 2: Establish the visibility flag (alpha-toggle pattern)
|
|
27
|
+
|
|
28
|
+
**A prototype must never be reachable in production.** The default mechanism is to render it behind an alpha/feature flag in the real app, so it is visible in-context to whoever is exploring but off for everyone else.
|
|
29
|
+
|
|
30
|
+
Resolve the flag in this order:
|
|
31
|
+
1. `.forge/project.yml → prototype_flag` — a project convention naming how alpha features gate (e.g. `alpha`, a feature-flag key, an env check, a `?alpha=1` query param, a settings toggle). Use it.
|
|
32
|
+
2. No convention recorded → **look** at how the codebase already gates alpha/experimental UI (grep for existing flags, `alpha`, `feature`, `experimental`, env-gated routes). Found a pattern → follow it and offer to record it: *"Gate this behind `{pattern}`? I'll note `prototype_flag: {pattern}` in project.yml so future prototypes reuse it."*
|
|
33
|
+
3. No pattern anywhere → **ask once**: *"How should alpha/mockup UI be gated so it's visible to us but never in prod? (feature flag / env check / query param / settings toggle)"* — then record the answer as `prototype_flag`.
|
|
34
|
+
|
|
35
|
+
Never ship prototype code on an always-on path. If you can't find or agree a gate, STOP and ask — do not guess.
|
|
36
|
+
|
|
37
|
+
## Step 3: Write the state file
|
|
38
|
+
|
|
39
|
+
Copy `.forge/templates/prototypes/prototype.md` → `.forge/prototypes/{slug}.md` and populate the frontmatter (`slug`, `title`, `created`, `status: exploring`, `flag`, `goal`). This is a **lightweight, resumable** record — the loop's memory across `/clear`, not a plan. Keep it under the 8 KB size gate; it holds the current question and a terse iteration log, not a design doc.
|
|
40
|
+
|
|
41
|
+
## Step 4: Iterate (the loop)
|
|
42
|
+
|
|
43
|
+
This is where the time goes. Each pass:
|
|
44
|
+
|
|
45
|
+
1. **Render an iteration.** Build the mockup UI behind the flag, using the design system where it fits. Aim for *fast and legible*, not complete or robust. Static/stub data is fine and expected.
|
|
46
|
+
2. **Make it visible.** Confirm it renders behind the flag and tell the operator exactly how to see it (route, toggle, `?alpha=1`, whichever the flag is).
|
|
47
|
+
3. **Log the iteration.** Append one terse line to the state file's `## Iterations` (date, what this pass tried, what to react to). This is what makes a resume cheap.
|
|
48
|
+
4. **Operator reacts.** They eyeball it and say what to change, what to try instead, or what they've decided. Adjust and loop.
|
|
49
|
+
|
|
50
|
+
**Deviation posture (looser than executing).** Auto-fix anything blocking the render (Rules 1-3) without ceremony — the point is speed. But a **Rule 4 stop still holds**: a new DB table, a real service layer, a schema migration, a dependency install, or anything that outlives the flag is no longer a mockup — STOP and tell the operator this has outgrown prototyping (likely a graduate). Mockups fake their data; they don't build backends.
|
|
51
|
+
|
|
52
|
+
**Commits are optional and disposable.** Commit iterations only if the operator wants checkpoints to compare or revert. Use `chore(proto): {slug} — {iteration note}`. Never present prototype commits as durable work; they ride the flag and leave with it.
|
|
53
|
+
|
|
54
|
+
## Step 5: Resolve (one of three terminal outcomes)
|
|
55
|
+
|
|
56
|
+
The loop ends when the operator picks an outcome. Each has a clean terminal path so prototypes never rot.
|
|
57
|
+
|
|
58
|
+
### Graduate → promote to a real milestone
|
|
59
|
+
The mockup proved the idea; now build it for real. This mirrors the refactor-backlog promotion procedure.
|
|
60
|
+
|
|
61
|
+
1. Synthesize milestone id `m-proto-{slug}` (or a numeric id — scan `.forge/state/milestone-*.yml` for max + 1).
|
|
62
|
+
2. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{id}.yml`. Populate: `milestone.id`, `milestone.name: "Graduated from prototype: {title}"`, `milestone.origin: prototype/{slug}`, `current.tier: standard` (or `full` if the operator says so), `current.status: researching`, `current.last_updated: {ISO}`.
|
|
63
|
+
3. Append to `.forge/roadmap.yml`: one milestone entry + a first phase (`dependencies: []`). Note the prototype as the design reference so `researching`/`designing` can look at the alpha code.
|
|
64
|
+
4. Run the **State Rollup (forge 1.0)** so `index.yml` picks up the new milestone (do **not** hand-edit `index.yml`).
|
|
65
|
+
5. In the state file: set `status: graduated`, add `graduated_to: m-{id}`, `resolved: {ISO}`. **Archive** it: move `.forge/prototypes/{slug}.md` → `.forge/prototypes/archive/{slug}.md`.
|
|
66
|
+
6. **Leave the alpha flag in place** — the graduated milestone builds the real thing and removes/keeps the flag as its own work decides. Say so.
|
|
67
|
+
7. Confirm: *"Graduated prototype {slug} → milestone m-{id}. State + roadmap created, prototype archived. The alpha mockup stays as the design reference. Routing to researching."* Then fall through to normal Standard/Full routing via `forge`.
|
|
68
|
+
|
|
69
|
+
### Discard → the idea's out, clean up
|
|
70
|
+
1. **Remove the alpha-flagged code** you added for this prototype (the render, the flag branch if nothing else uses it, stub data). Grep first; only remove what this prototype introduced — never touch code the flag gated before you arrived.
|
|
71
|
+
2. Commit the removal: `chore(proto): discard {slug} mockup`.
|
|
72
|
+
3. In the state file: set `status: discarded`, add `resolved: {ISO}`, a one-line `discard_reason`. **Archive** it: move → `.forge/prototypes/archive/{slug}.md` (keeps the audit trail — what we tried and why we dropped it).
|
|
73
|
+
4. Confirm what was removed and that the file is archived.
|
|
74
|
+
|
|
75
|
+
### Park → sit on it for later
|
|
76
|
+
Keep the mockup live behind the flag; you're not ready to decide.
|
|
77
|
+
1. In the state file: set `status: parked`, add `parked: {ISO}` and a one-line note on *what would unpark it* (a decision, a dependency, a conversation).
|
|
78
|
+
2. Leave the file in `.forge/prototypes/` (not archived — it's still live). Leave the alpha code in place behind the flag.
|
|
79
|
+
3. Confirm: *"Parked prototype {slug} behind `{flag}`. Resume anytime with `/forge prototype {slug}`."*
|
|
80
|
+
|
|
81
|
+
**Parked prototypes are the rot risk your always-a-state-file choice creates.** `forge` boot surfaces parked prototypes older than a threshold so they don't accumulate silently (see forge Step 1.5 → Prototype hygiene). A park is a deferral, not a hiding place.
|
|
82
|
+
|
|
83
|
+
## What this skill does NOT do
|
|
84
|
+
|
|
85
|
+
- **No goal-backward verification.** There's nothing to prove works. If you find yourself wanting `verifying`, the work has graduated — promote it.
|
|
86
|
+
- **No production quality bar.** No test coverage, no a11y gate, no security/architecture review. (Basic keyboard/focus sanity is fine if cheap, but it is not gated.)
|
|
87
|
+
- **No unit tests, no real data layer, no migrations.** Mockups stub their data. Any of these = a Rule 4 stop → graduate.
|
|
88
|
+
- **No `git add .`** — stage individually, as everywhere in Forge.
|
|
@@ -126,17 +126,22 @@ Milestones with zero `e2e:true` stories never see this gate. Verifying logs noth
|
|
|
126
126
|
|
|
127
127
|
### Level 1: Observable Truths
|
|
128
128
|
|
|
129
|
-
Read the plan's `must_haves.truths`. For each truth:
|
|
129
|
+
Read the plan's `must_haves.truths`. Truths may be legacy bare strings or `{truth, check}` / `{truth, manual}` objects (see `.forge/templates/plan.md`). For each truth, one of three branches — **this is the canonical statement of the executed-evidence rule** (verifier.md and FORGE.md cite it):
|
|
130
130
|
|
|
131
|
-
1.
|
|
132
|
-
2.
|
|
133
|
-
3.
|
|
131
|
+
1. **Declared `check:`** (and no `manual:` route) → EXECUTE it. Exit 0 → **VERIFIED**; non-zero → **FAILED**. Record the command, exit code, and an output excerpt either way.
|
|
132
|
+
2. **No declared check** (legacy plan, or a truth planning left judgment-shaped) → run your OWN command that proves/disproves it and record the same evidence triple — executed evidence qualifies regardless of schema vintage.
|
|
133
|
+
3. **No executed evidence** → the verdict is **UNCERTAIN** or **HUMAN VERIFICATION NEEDED**. **Never VERIFIED.**
|
|
134
|
+
|
|
135
|
+
WHY the hard line: a verdict without executed evidence is the hallucination surface this rule closes — an agent designing and grading its own test can report VERIFIED without running anything. The exit code, not judgment, is the verdict.
|
|
136
|
+
|
|
137
|
+
Manual-route truths (`manual:` field, or inherently uncommandable — visual, real-device, external service) are branch 3 by design: never execute their route text as shell, never invent a proxy command — list them under HUMAN VERIFICATION NEEDED; they flow to the existing Human Verification Gate / M9 e2e gate unchanged.
|
|
134
138
|
|
|
135
139
|
```markdown
|
|
136
|
-
| Truth |
|
|
137
|
-
|
|
138
|
-
| User can edit their bio |
|
|
139
|
-
| Profile photo uploads correctly |
|
|
140
|
+
| Truth | Evidence (command → exit) | Result |
|
|
141
|
+
|-------|---------------------------|--------|
|
|
142
|
+
| User can edit their bio | `npx playwright test bio-edit.spec` → 0 | VERIFIED |
|
|
143
|
+
| Profile photo uploads correctly | `npm test -- upload.spec` → 1; "413 Payload Too Large at 6MB" | FAILED — 413 on large files |
|
|
144
|
+
| Layout matches design on mobile | (manual: human gate — not executed) | HUMAN VERIFICATION NEEDED |
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
### Level 2: Artifacts (Exists → Substantive → Wired)
|
package/template/.forge/FORGE.md
CHANGED
|
@@ -69,6 +69,11 @@ Worktrees are a **base concern** — Chief/Streams creates and references them w
|
|
|
69
69
|
|
|
70
70
|
Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
71
71
|
|
|
72
|
+
### Prototyping (minutes–hours, disposable)
|
|
73
|
+
**Triggers:** "mock up"/"sketch"/"prototype"/"draw up"/"iterate on the UI"/"explore how X could look" — disposable in-app visual exploration behind an alpha/feature flag, no requirements locked yet
|
|
74
|
+
**Flow:** `prototyping` — establish flag → iterate ⟲ (render → react → adjust) → resolve (graduate→milestone | park | discard)
|
|
75
|
+
**No verify/review gate by design.** The deliverable is a thing to look at and react to, not shippable code. Fills the gap where Standard/Full ceremony (goal-backward verification, a milestone cursor) is the wrong shape and Quick's <50-line/1-2-file caps + commit-and-done don't fit an iteration loop. Real UI work with known requirements is Standard + `designing` (which *does* gate on quality). See [ADR-022](../docs/decisions/ADR-022-prototyping-tier.md).
|
|
76
|
+
|
|
72
77
|
### Quick (minutes)
|
|
73
78
|
**Triggers:** bug fix, typo, config, dep bump, <50 lines
|
|
74
79
|
**Flow:** `quick-tasking` → commit → done
|
|
@@ -100,6 +105,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
100
105
|
| Prove work delivers on goals (+ M9 e2e validation gate when `e2e:true` stories present; captures the Human Verification Gate sign-off) | `verifying` | Standard, Full |
|
|
101
106
|
| Audit health + catalog refactoring (+ M9 e2e soft-cap, orphan-test, flake-rate audits; hard-blocks completion on the Human Verification Gate) | `reviewing` | Standard, Full |
|
|
102
107
|
| Small scoped fix | `quick-tasking` | Quick |
|
|
108
|
+
| Rapid disposable UI mockup behind an alpha flag (no verify/review gate) | `prototyping` | Prototyping |
|
|
103
109
|
| UI with design system | `designing` | When UI |
|
|
104
110
|
| Security review | `securing` | When auth/data/API |
|
|
105
111
|
| E2E/integration tests + suite audit (+ M9 author-mode gate refuses e2e without `e2e:true` + `validated:true`) | `testing` | When UI/flows or flaky suite |
|
|
@@ -127,6 +133,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
127
133
|
| `streams/{stream}/brief.md` | 8 KB | Stream context packet must fit into active session context |
|
|
128
134
|
| `streams/{stream}/packages/{id}.yml` | 10 KB | Delegated work contract stays bounded |
|
|
129
135
|
| `reservations.yml` | 50 KB | Append-only ID-reservation registry; one short line per ADR/DEF/FR/NFR reserved |
|
|
136
|
+
| `prototypes/{slug}.md` | 8 KB | Disposable-mockup state — current question + terse iteration log, not a design doc; terminal items archive to `prototypes/archive/` |
|
|
130
137
|
|
|
131
138
|
### Accumulating Artifacts
|
|
132
139
|
|
|
@@ -137,6 +144,10 @@ Every new accumulating artifact needs: owner, size gate, archive/compaction rule
|
|
|
137
144
|
source-of-truth; load scoped slices, not whole history.
|
|
138
145
|
- `phases/`, `audits/`, `research/`, `archive/`, and `context-archive.md` are
|
|
139
146
|
historical; load only by milestone/stream/package/version.
|
|
147
|
+
- `prototypes/{slug}.md` is single-owner mutable while `status: exploring | parked`
|
|
148
|
+
(the driving session writes it); terminal statuses (`graduated`, `discarded`)
|
|
149
|
+
archive to `prototypes/archive/`. `forge` boot surfaces stale `parked` prototypes
|
|
150
|
+
(>30d). Not derived, not shared — a prototype has exactly one driver.
|
|
140
151
|
- Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs, and the
|
|
141
152
|
`.git/forge/*` machine-local files — the id-reservation ledger + integration
|
|
142
153
|
flag) are excluded from framework context and packages unless debugging them.
|
|
@@ -169,6 +180,10 @@ Configure in `project.yml`:
|
|
|
169
180
|
models:
|
|
170
181
|
default: sonnet # fallback
|
|
171
182
|
parent_session: sonnet # advisory — warns on mismatch
|
|
183
|
+
by_complexity: # per-task complexity → model (M24). Overrides skills — see Precedence.
|
|
184
|
+
trivial: haiku
|
|
185
|
+
standard: sonnet
|
|
186
|
+
complex: opus
|
|
172
187
|
skills: # per-skill overrides
|
|
173
188
|
architecting: opus
|
|
174
189
|
planning: opus
|
|
@@ -176,10 +191,11 @@ models:
|
|
|
176
191
|
quick-tasking: haiku
|
|
177
192
|
```
|
|
178
193
|
|
|
179
|
-
**Precedence:** `skills.X` → `default` → parent model.
|
|
194
|
+
**Precedence:** `by_complexity.{task.complexity}` → `skills.X` → `default` → parent model.
|
|
195
|
+
**Complexity overrides skill** (LOCKED, context.md § M24): when a task declares a `<complexity>`, its `by_complexity` resolution wins over `models.skills.{skill}`. WHY — the per-task signal is the granular one and the whole point of M24; letting the coarser skill-level setting win would make per-task routing inert on any project that already sets `models.skills.executing`. A task with **no** complexity resolves via the unchanged `skills → default → parent` chain — zero behavior change for untagged tasks.
|
|
180
196
|
**All advisory** — forge displays expected model at each transition, suggests `/model` on mismatch. Review gate logs model used. Cannot auto-switch.
|
|
181
197
|
|
|
182
|
-
**Subagent inheritance.** When a skill spawns subagents via the Agent tool (parallel research, fresh-context reviewers, executor fan-out, tester author/analyst, etc.), the skill MUST pass the resolved `model` param explicitly. Subagents do NOT inherit skill-level routing automatically — without the param they run on the parent session's model, defeating the routing intent.
|
|
198
|
+
**Subagent inheritance.** When a skill spawns subagents via the Agent tool (parallel research, fresh-context reviewers, executor fan-out, tester author/analyst, etc.), the skill MUST pass the resolved `model` param explicitly. Subagents do NOT inherit skill-level routing automatically — without the param they run on the parent session's model, defeating the routing intent. Resolution is **per-task** where the task being spawned declares a `complexity` (`by_complexity` wins per the Precedence above), falling back to the skill-level chain (`models.skills.{skill}` → `models.default` → parent) otherwise — surface it: *"Spawning {role} with model: {model} (from {source})"*. **Inline-task limitation:** a task that runs inline (no spawn) inherits the parent session's model and cannot be re-routed — per-task routing is inherently a subagent-spawn feature, not something that reaches into an already-running skill session.
|
|
183
199
|
|
|
184
200
|
## Agents
|
|
185
201
|
|
|
@@ -231,6 +247,7 @@ State lives in `.forge/`:
|
|
|
231
247
|
- `releases.yml` — Append-only version-reservation registry (cross-session coordination point; see Version Reservation Protocol)
|
|
232
248
|
- `reservations.yml` — Append-only ID-reservation audit trail + cross-machine floor for ADR/DEF/FR/NFR numbers, written by `forge-reserve` (ADR-021). The same-machine cross-worktree coordination point is the machine-local ledger `.git/forge/id-reservations`; see ID Reservation Protocol.
|
|
233
249
|
- `archive/milestone-{id}/` — Archived milestone artifacts (archive-delete)
|
|
250
|
+
- `prototypes/{slug}.md` — Lightweight resumable state for a disposable in-app design mockup (Prototyping tier). Status vocab: `exploring | parked | graduated | discarded`. Terminal (`graduated`/`discarded`) → archive to `prototypes/archive/{slug}.md`. Single-owner mutable; not derived, not shared. See `Skill(prototyping)`.
|
|
234
251
|
|
|
235
252
|
**Milestones** group phases into concurrent streams. Own state file — no conflicts across sessions. Can be deferred (frozen in place) or archive-deleted.
|
|
236
253
|
`index.yml status` gates routing: `not_started | active | deferred | complete`.
|
|
@@ -349,6 +366,10 @@ verification:
|
|
|
349
366
|
- Empty commands = no gate (opt-out)
|
|
350
367
|
- `verification.e2e_soft_cap` (default 10) — advisory cap on `e2e:true` stories per milestone surfaced by the `reviewing` skill. Soft — never blocks.
|
|
351
368
|
|
|
369
|
+
### Truth checks (executable must_haves)
|
|
370
|
+
|
|
371
|
+
Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exit 0 = pass; authored goal-backward at planning, before code exists; linted by planning Step 8 dimension 10). `executing` runs declared checks once at plan completion (early fix-loop signal, never a verdict); `verifying` re-executes them and applies the **executed-evidence rule**: VERIFIED only with a recorded command + exit code + output excerpt — no executed evidence → UNCERTAIN or human-route, never VERIFIED. Legacy bare-string truths stay valid; details live in the planning/executing/verifying skills + plan template.
|
|
372
|
+
|
|
352
373
|
### Human Verification Gate (close precondition)
|
|
353
374
|
|
|
354
375
|
**No milestone reaches `complete` and no orchestration worktree is torn down until a human has explicitly signed off** — recorded as `current.human_verified` in `milestone-{id}.yml`. Code-level verification (tests, lint, type-check, the 3-level goal-backward pass) is necessary but **never sufficient** to close a milestone. A human must confirm the work in whatever form fits the milestone — device session, visual check, e2e walk, smoke test ("of some sorts").
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Migration Guide: Template-Seeded Release Registry Scrub (Forge 0.50.1)
|
|
2
|
+
|
|
3
|
+
Applies to projects whose fresh install ran with forge-orkes 0.32.0–0.50.0. Those
|
|
4
|
+
package versions shipped `template/.forge/releases.yml` containing the forge dev
|
|
5
|
+
repo's own release history (13–19 entries, versions 0.20.0–0.37.0) instead of an
|
|
6
|
+
empty scaffold, so `npx forge-orkes` seeded every new project's Version
|
|
7
|
+
Reservation Registry with foreign entries. The registry's `max()` rule then
|
|
8
|
+
resolves against forge's history — a fresh 0.1.0 project reserving its first
|
|
9
|
+
version gets 0.38.0. From 0.50.1 the template ships an empty scaffold
|
|
10
|
+
(`releases: []`) and the release script refuses to ship a template with live
|
|
11
|
+
registry entries; this guide cleans installs that were already seeded.
|
|
12
|
+
|
|
13
|
+
`reservations.yml` (the ID-reservation sibling) was audited for the same
|
|
14
|
+
pattern: its template copy has been an empty scaffold since it was introduced —
|
|
15
|
+
no migration needed there.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
1. On the new version's framework files (run `npx forge-orkes upgrade` first).
|
|
20
|
+
2. Working tree clean or changes committed — the migration rewrites
|
|
21
|
+
`.forge/releases.yml`.
|
|
22
|
+
|
|
23
|
+
## Detection
|
|
24
|
+
|
|
25
|
+
Prints `MIGRATE` when `.forge/releases.yml` carries the template-seeded foreign
|
|
26
|
+
entries; silent + exit 0 otherwise.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Fingerprint two forge-dev entries every seeded variant (0.32.0–0.50.0)
|
|
30
|
+
# contains; either match → the registry was template-seeded at install.
|
|
31
|
+
# The forge dev repo itself legitimately holds these entries — skip it.
|
|
32
|
+
F=.forge/releases.yml
|
|
33
|
+
[ -f "$F" ] || exit 0
|
|
34
|
+
[ -d packages/create-forge/template ] && exit 0
|
|
35
|
+
if grep -qF 'CLAUDE.md @import extraction (framework prose' "$F" \
|
|
36
|
+
|| grep -qF 'Nested phase layout (phases/milestone-{id}/{phase}-{name}/)' "$F"; then
|
|
37
|
+
echo "MIGRATE — .forge/releases.yml was seeded with the forge dev repo's release history at install; scrub the foreign entries"
|
|
38
|
+
fi
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Migration steps
|
|
42
|
+
|
|
43
|
+
The migration is NEVER auto-applied. In Claude Code it runs via the
|
|
44
|
+
`quick-tasking` skill, which the `upgrading` Step-7 prompt hands this guide to
|
|
45
|
+
on "yes". Step 3 is potentially lossy — confirm with the user before renumbering
|
|
46
|
+
anything.
|
|
47
|
+
|
|
48
|
+
### 1. Separate foreign entries from the project's own
|
|
49
|
+
|
|
50
|
+
An entry belongs to THIS project iff its `milestone:` matches a milestone that
|
|
51
|
+
exists here — in `.forge/state/milestone-*.yml`, `.forge/roadmap.yml`, or
|
|
52
|
+
`.forge/archive/milestone-*/`. Everything else is foreign: the seeded block is
|
|
53
|
+
contiguous, sits at the top of the `releases:` list, spans versions
|
|
54
|
+
0.20.0–0.37.0, and its summaries describe forge framework features (streams,
|
|
55
|
+
worktree gates, CLAUDE.md @import extraction, …) rather than anything this
|
|
56
|
+
project built. Entries the project itself appended (if any) follow the seeded
|
|
57
|
+
block.
|
|
58
|
+
|
|
59
|
+
### 2. Rebuild the registry
|
|
60
|
+
|
|
61
|
+
Keep the header comment block. Delete the foreign entries. Keep the project's
|
|
62
|
+
own entries in their original order. If no own entries remain, end the file
|
|
63
|
+
with `releases: []`.
|
|
64
|
+
|
|
65
|
+
### 3. Check the project's own entries for version skew (confirm with user)
|
|
66
|
+
|
|
67
|
+
Any entry the project reserved while the foreign block was present took
|
|
68
|
+
`max()` off forge's history — e.g. a project actually at 0.3.0 reserving
|
|
69
|
+
"0.38.0". For each such entry, ask the operator:
|
|
70
|
+
|
|
71
|
+
- **Not yet published or tagged anywhere** → renumber the entry (and the
|
|
72
|
+
project's `package.json` or equivalent version file) to continue the
|
|
73
|
+
project's true version line.
|
|
74
|
+
- **Already published/tagged** → keep the inflated number for continuity and
|
|
75
|
+
note the skew in a comment beside the entry.
|
|
76
|
+
|
|
77
|
+
Never renumber without explicit confirmation.
|
|
78
|
+
|
|
79
|
+
### 4. Commit
|
|
80
|
+
|
|
81
|
+
One scoped commit, e.g.
|
|
82
|
+
`fix(forge): scrub template-seeded entries from releases.yml (0.50.1 migration)`.
|
|
83
|
+
|
|
84
|
+
## Validation
|
|
85
|
+
|
|
86
|
+
- `grep -F 'CLAUDE.md @import extraction' .forge/releases.yml` → no matches.
|
|
87
|
+
- Every remaining `milestone:` in `.forge/releases.yml` names a milestone that
|
|
88
|
+
exists in this project's state, roadmap, or archive.
|
|
89
|
+
- The highest `version:` left in the registry (if any) is consistent with the
|
|
90
|
+
project's actual version in `package.json` (or equivalent).
|
|
91
|
+
- Re-running the Detection block prints nothing and exits 0.
|
|
@@ -20,110 +20,17 @@
|
|
|
20
20
|
# editing package.json. If two sessions reserve near-simultaneously, the second
|
|
21
21
|
# push rebases onto the first (append-only → no conflict) and must re-read max()
|
|
22
22
|
# and take the next number.
|
|
23
|
+
#
|
|
24
|
+
# LAZY MIGRATION: this file starts empty. The first milestone that bumps this
|
|
25
|
+
# project's version appends the first entry; while empty, max() falls back to
|
|
26
|
+
# the version already recorded in the project itself (package.json or
|
|
27
|
+
# equivalent). Entries belong to THIS project's milestones only.
|
|
28
|
+
#
|
|
29
|
+
# releases:
|
|
30
|
+
# - milestone: 3
|
|
31
|
+
# version: "0.4.0"
|
|
32
|
+
# bump: minor
|
|
33
|
+
# reserved_at: "2026-07-01"
|
|
34
|
+
# summary: "User-facing summary of what this release ships"
|
|
23
35
|
|
|
24
|
-
releases:
|
|
25
|
-
- milestone: 12
|
|
26
|
-
version: "0.21.0"
|
|
27
|
-
bump: minor
|
|
28
|
-
reserved_at: "2026-06-12"
|
|
29
|
-
summary: "CLAUDE.md @import extraction (framework prose → .forge/FORGE.md)"
|
|
30
|
-
- milestone: 13
|
|
31
|
-
version: "0.20.0"
|
|
32
|
-
bump: minor
|
|
33
|
-
reserved_at: "2026-06-12"
|
|
34
|
-
summary: "Nested phase layout (phases/milestone-{id}/{phase}-{name}/)"
|
|
35
|
-
- milestone: 14
|
|
36
|
-
version: "0.22.0"
|
|
37
|
-
bump: minor
|
|
38
|
-
reserved_at: "2026-06-13"
|
|
39
|
-
summary: "Refactor-backlog compaction migration + lifecycle (archive terminal items, size gate, status-vocab enforcement)"
|
|
40
|
-
- milestone: 10
|
|
41
|
-
version: "0.23.0"
|
|
42
|
-
bump: minor
|
|
43
|
-
reserved_at: "2026-06-14"
|
|
44
|
-
summary: "M10 auto-routing — forge routes Standard/Full through orchestrating when installed (install = consent); orchestration.auto opt-out"
|
|
45
|
-
- milestone: 15
|
|
46
|
-
version: "0.24.0"
|
|
47
|
-
bump: minor
|
|
48
|
-
reserved_at: "2026-06-14"
|
|
49
|
-
summary: "Disconfirm-before-commit rigor: debugging Confirmation Gate (blind-spot check), architecting Platform & Goal Validation + option-space framing, discussing widen-before-narrowing, CLAUDE.md principle 8"
|
|
50
|
-
- milestone: 16
|
|
51
|
-
version: "0.25.0"
|
|
52
|
-
bump: minor
|
|
53
|
-
reserved_at: "2026-06-14"
|
|
54
|
-
summary: "Worktree branch anchor in forge milestone selection — git branch deterministically selects the milestone (exact lifecycle.worktree_branch match, or milestone-id token in branch name) before the timestamp default; deferred/complete matches surface instead of auto-resuming"
|
|
55
|
-
- milestone: 16
|
|
56
|
-
version: "0.26.0"
|
|
57
|
-
bump: minor
|
|
58
|
-
reserved_at: "2026-06-14"
|
|
59
|
-
summary: "Milestone-anchored worktree naming in orchestrating — worktree dir + branch become forge/m-{id}-{session_id} (was forge/{uuid}), adds lifecycle.worktree_anchor. Makes the 0.25.0 branch-anchor id-token fallback work for orch worktrees so a moved/stale-state worktree still resolves to its milestone off the branch name alone; uuid suffix keeps collision-resistance, forge/ prefix keeps GC sweep"
|
|
60
|
-
- milestone: 17
|
|
61
|
-
version: "0.27.0"
|
|
62
|
-
bump: minor
|
|
63
|
-
reserved_at: "2026-06-15"
|
|
64
|
-
summary: "Human Verification Gate — hard block on milestone close + orchestration teardown until an explicit human sign-off is recorded as current.human_verified in milestone state. Code-level PASS is necessary but never sufficient to close. Captured by verifying (post-verdict prompt), enforced at reviewing-complete + orchestrating Step 5.0; recorded-override (method: override) is the only bypass and is surfaced. New current.human_verified state field — lazy migration: absent = prompt on next close, already-complete milestones never re-gated."
|
|
65
|
-
- milestone: 18
|
|
66
|
-
version: "0.28.0"
|
|
67
|
-
bump: minor
|
|
68
|
-
reserved_at: "2026-06-15"
|
|
69
|
-
summary: "Configurable repo-scoped worktree root — orchestration.worktree_root defaults to ../<repo-name>-worktrees/ (was shared ../forge-worktrees/). Fixes cross-repo worktree mixing when multiple repos share a parent dir, fixes cwd-sensitive resolution (relative roots now resolve against repo root, not shell cwd), and folds a gitignore-guard for repos with fully-ignored .claude/ trees (initializing writes correct carve-out for greenfield; upgrading detects + warns for brownfield)"
|
|
70
|
-
- milestone: 19
|
|
71
|
-
version: "0.29.0"
|
|
72
|
-
bump: minor
|
|
73
|
-
reserved_at: "2026-06-15"
|
|
74
|
-
summary: "Shared-state taxonomy for multi-worktree Forge — FORGE.md State Ownership expanded to enumerate all 5 file-sharing classes (single-owner mutable, derived, append-only shared, shared mutable, stable shared) with the rule each follows. Promotes 'do not touch another worktree's state files' to explicit rule. Adds structural rule for context.md + refactor-backlog.yml (write only to your milestone block, within-block append-only). New `forge` boot worktree-liveness view (lists live Forge worktrees + their owning milestones in main). Cross-tree write warnings in discussing (context.md) + reviewing (refactor-backlog.yml). Opt-in worktree-side rebase check at boot (gated by forge.worktree_rebase_check). Triggered by the canvaz 2026-06-15 case: main-session edits to m9's context block were invisible to the m9 worktree."
|
|
75
|
-
- milestone: 20
|
|
76
|
-
version: "0.30.0"
|
|
77
|
-
bump: minor
|
|
78
|
-
reserved_at: "2026-06-16"
|
|
79
|
-
summary: "Upstream desire-path feedback channel (ADR-012) — closes the capture→act loop in Principle 7. (1) Plan-anchor re-derivation guard already landed (commit 5ddf3a1): executing re-derives plan literals against HEAD before the first task; planning makes anchors drift-resistant. (2) `scope: project|framework` field added to the desire-path schema, set at capture by verifying/executing/planning (lazy migration: absent⇒project). (3) Single review owner = forge boot — retires verifying's duplicate 'Surface Recommendations', co-locates the type→evolution suggestion table, adds a decline-ledger (declined/) so declines stop re-nagging, adds a Quick-tier review trigger in quick-tasking. (4) Tiered upstream transport for scope:framework signals: always write a portable block to state/desire-paths/upstream/; offer `gh issue create` against forge.upstream_repo (default github.com/zayneupton/forge) when gh present; upgrading harvests upstream/ into {source}/docs/desire-paths-inbox/. Optional forge.upstream_repo added to project.yml. Triggered by the canvaz plan-anchor-drift signal that could only reach the framework via manual chat copy-paste."
|
|
80
|
-
- milestone: 15
|
|
81
|
-
version: "0.31.0"
|
|
82
|
-
bump: minor
|
|
83
|
-
reserved_at: "2026-06-17"
|
|
84
|
-
summary: "Data-driven migration detection — replaces hardcoded per-version detection blocks (SKILL Step 7 + bin detectLegacyLayouts) with a single loop over synced .forge/migrations/{v}-*.md guides, ranged (installed, source]. New uniform Detection-block contract: echo `MIGRATE` on stdout when the migration applies, silent/exit-0 for no-op. All 7 guides normalized to it (0.28.0 consolidated to one `## Detection`; 0.29.0 stays an explicit in-range no-op). Removes the staleness class entirely — a new guide is auto-covered the moment it ships in the template, zero per-version code, and the install-run blind spot is fixed (detection keys off freshly-synced guides + pre-bump installed version). Triggered by ptnrkit 0.19.2→0.30.0 upgrade where bin was 4 versions behind and 2 real on-disk migrations (0.20.0, 0.22.0) reported 'needs manual review: 0'."
|
|
85
|
-
- milestone: 16
|
|
86
|
-
version: "0.32.0"
|
|
87
|
-
bump: minor
|
|
88
|
-
reserved_at: "2026-06-18"
|
|
89
|
-
summary: "Project Chief / Streams orchestration — adds Project/Stream/Work Package runtime layers, Chief of Staff routing, stream/work-package templates, active context compaction, framework-wide size audit, and an idempotent migration guide. M10 is repositioned as an optional backend for worktree isolation, merge queue support, and experimental file claims."
|
|
90
|
-
- milestone: 16
|
|
91
|
-
version: "0.32.1"
|
|
92
|
-
bump: patch
|
|
93
|
-
reserved_at: "2026-06-20"
|
|
94
|
-
summary: "Chief front-door follow-up — detects projects that adopted worktree-backed streams but left legacy orchestration.auto enabled or unset, then prompts to set orchestration.auto: false so Chief/Streams is the default entry point while M10 remains explicit."
|
|
95
|
-
- milestone: 16
|
|
96
|
-
version: "0.33.0"
|
|
97
|
-
bump: minor
|
|
98
|
-
reserved_at: "2026-06-21"
|
|
99
|
-
summary: "Worktree convention promoted to base + experimental-skill drift fix + Chief registry drift check (issues #4/#5/#6). (1) FORGE.md gains a base 'Worktree Convention' (path/anchor/branch + orchestration.worktree_root resolution + lifecycle.worktree_path authority) so Chief/Streams has a fallback when a worktree was created outside orchestrating, and the convention stays upgrade-synced regardless of the experimental skill. (2) upgrading no longer blanket-skips installed experimental skills — it diffs them against experimental/*/source and offers a preview/confirm re-sync (warn at minimum), closing the silent-freeze where base migrations (e.g. 0.28.0 worktree-root) became no-ops against a frozen orchestrating skill. (3) migration-guide template gains an authoring guard: guides whose behavior lives in an experimental skill must detect the skill's content, not just the version stamp. (4) chief-of-staff gains an advisory Registry Drift Check (Show + Sync) reconciling state/index.yml active milestones against streams/active.yml."
|
|
100
|
-
- milestone: 16
|
|
101
|
-
version: "0.34.0"
|
|
102
|
-
bump: minor
|
|
103
|
-
reserved_at: "2026-06-21"
|
|
104
|
-
summary: "Live-Worktree Ownership Gate (issue #7). forge boot's worktree surfacing was advisory, so a session on main could write single-owner-mutable milestone state (milestone-{id}.yml, phases/, research/) while a live worktree owned the milestone — forking the cursor (main 'reviewing' vs worktree 'planning'). forge SKILL gains an enforced gate (1.1a): when invoked from main and the selected milestone has a live worktree, milestone-owned writes are blocked and routing stops until the operator enters the worktree, syncs it, retires it, or records an explicit lifecycle.ownership_override (the only, never-silent bypass). Shared project docs stay writable from main with a not-visible-until-sync warning. FORGE.md State Ownership promoted from advice to enforcement. Also folds in dev/template skill drift cleanup: forge/discussing/planning dev .claude/skills copies were behind the template (chief-streams + context-archive content) — brought to parity."
|
|
105
|
-
- milestone: 16
|
|
106
|
-
version: "0.35.0"
|
|
107
|
-
bump: minor
|
|
108
|
-
reserved_at: "2026-06-21"
|
|
109
|
-
summary: "Merge often and early (issue #8). Stream integration was pull-based and deferred — ready work sat on its branch until milestone end, drifting from main and staling shared surfaces. Adds the stated principle to FORGE.md Project Streams (continuous integration posture; surfacing only, never auto-merge/auto-rebase). chief-of-staff gains an advisory Merge Cadence Check (Show + Sync): ready-to-merge surfacing from active.yml status:ready + merge_queue, idle-ready nudge, sibling-rebase nudge when main advanced on a shared surface, and merge-order recommendation. forge boot gains a light ready-to-merge nudge pointing to chief-of-staff. All advisory — never blocks, never merges, never rebases."
|
|
110
|
-
- milestone: 16
|
|
111
|
-
version: "0.36.0"
|
|
112
|
-
bump: minor
|
|
113
|
-
reserved_at: "2026-06-21"
|
|
114
|
-
summary: "Stream Integration Checkpoints — event-driven continuous integration, the producer/flag/consumer follow-up to #8's polling-based cadence check. (1) planning marks integration_checkpoint phases in plan frontmatter (advisory; default = last phase + depended-on phases; the mark is the opt-in so unmarked/existing work is unaffected). (2) Producer: verifying, on a PASSED + human-verified checkpoint phase in a worktree, fast-forward-only publishes to main (git push origin HEAD:main — git rejects non-ff, which is the safety; diverged main is surfaced, never forced) and sets an integration flag in .git/forge/integration-pending (git common dir, shared across a machine's worktrees). (3) Consumer: forge boot in a worktree reads the flag cheaply — set + no divergence → auto git merge --ff-only main (conflict-free); diverged → surface a rebase prompt; not set → skip (no fetch, no scan). Supersedes the 0.35.0 forge-boot polling pointer with event-driven detection. Cross-machine falls back to opt-in worktree_rebase_check. Never force-pushes, auto-resolves a conflict, or auto-rebases."
|
|
115
|
-
- milestone: 16
|
|
116
|
-
version: "0.37.0"
|
|
117
|
-
bump: minor
|
|
118
|
-
reserved_at: "2026-06-22"
|
|
119
|
-
summary: "Derived stream registry / Stream Rollup (ADR-015) — the durable structural fix for issue #4's registry drift, superseding the 0.33.0 advisory Registry Drift Check. streams/active.yml becomes a DERIVED projection (like state/index.yml), never hand-edited, regenerated by a deterministic join-rollup over two single-sourced inputs: per-stream files streams/{stream}.yml (coordination lifecycle + ownership/runtime/merge/blockers) and active milestone files (workflow phase). Resolves the not-1:1 milestone↔stream relationship by keeping workflow phase and coordination status as orthogonal dimensions side by side in each derived row (phase from milestone, coordination from stream file) rather than collapsing one into the other; coverage is computed (active milestone with no stream file → derived 'implicit' row) so #4's exact symptom is structurally impossible. Adds stream.milestone link field (lazy; absent ⇒ standalone). forge boot + chief-of-staff Show/Sync run the Stream Rollup; chief Start/Pause/Resume/Close/Adopt write per-stream files then regenerate. Registry Drift Check removed. active.yml put in the derived state-ownership class (ADR-011); worktrees never write it. Migration: materialize per-stream files from a hand-maintained active.yml, then regenerate."
|
|
120
|
-
|
|
121
|
-
# Note on the 15 / 0.24.0 ordering: reserved as max(0.23.0)+minor. origin/main was
|
|
122
|
-
# already at 0.23.0 when this shipped (M10/M12/M13/M14 all merged), so 0.24.0 is a
|
|
123
|
-
# clean increment — no publish-order coordination needed. (Earlier draft of this
|
|
124
|
-
# release was built on a stale 0.19.2 base before a fetch corrected it.)
|
|
125
|
-
|
|
126
|
-
# Note on the 12/13 ordering: both reserved 2026-06-12. M13 holds the lower number
|
|
127
|
-
# (0.20.0) and M12 the higher (0.21.0) by explicit user decision — they ship in the
|
|
128
|
-
# same package release, M12 topping the version. max() here is 0.21.0, so the next
|
|
129
|
-
# milestone reserves 0.22.0 (minor) regardless of which milestone id is higher.
|
|
36
|
+
releases: []
|
|
@@ -29,8 +29,29 @@ must_haves:
|
|
|
29
29
|
truths: # USER-observable when plan is done. Must contain a user verb
|
|
30
30
|
# (see, click, submit, receive, view, download, error, redirect).
|
|
31
31
|
# Internal-only truths like "Schema applied" do NOT satisfy.
|
|
32
|
+
# Entry form: bare string (legacy — remains valid, no migration)
|
|
33
|
+
# OR object with an OPTIONAL executable check:
|
|
32
34
|
- "" # e.g., "User submits signup form and lands on dashboard"
|
|
33
|
-
- ""
|
|
35
|
+
- truth: "" # e.g., "Invalid email shows inline 'must be a valid email' error"
|
|
36
|
+
check: "" # OPTIONAL shell command proving the truth. Semantics:
|
|
37
|
+
# - exit 0 = pass; anything else = fail. The exit code, not
|
|
38
|
+
# agent judgment, is the verdict (executed-evidence rule).
|
|
39
|
+
# - must PRINT WHY it fails — its output feeds executing's
|
|
40
|
+
# auto-fix loop and the verification report (`diff` beats
|
|
41
|
+
# `diff -q`; an assert-with-message beats a bare `test`).
|
|
42
|
+
# - verify content/behavior, not existence (`test -f` proves
|
|
43
|
+
# existence only).
|
|
44
|
+
# - safe to run repeatedly — read-only, or idempotent against
|
|
45
|
+
# durable state (checks run in executing AND verifying,
|
|
46
|
+
# plus auto-fix retries).
|
|
47
|
+
# - authored at planning time, goal-backward, BEFORE code
|
|
48
|
+
# exists — a pre-code check can't be retro-fitted to what
|
|
49
|
+
# got built.
|
|
50
|
+
- truth: "" # A truth that can't be a command (visual, real-device,
|
|
51
|
+
manual: "" # external service) carries a `manual:` route INSTEAD of a
|
|
52
|
+
# check — e.g. "human gate" or "M9 e2e" — never a fake check.
|
|
53
|
+
# executing skips manual truths (no check to run); verifying
|
|
54
|
+
# routes them to HUMAN VERIFICATION NEEDED.
|
|
34
55
|
artifacts: # Files that must exist and be substantive (not stubs).
|
|
35
56
|
# Slice plans typically span 2-4 layers — paths should cross
|
|
36
57
|
# boundaries (component + handler + repo), not cluster in one dir.
|
|
@@ -50,6 +71,19 @@ must_haves:
|
|
|
50
71
|
|
|
51
72
|
<task type="auto">
|
|
52
73
|
<name>Action-oriented task name</name>
|
|
74
|
+
<complexity>standard</complexity> <!-- OPTIONAL (M24). complexity is trivial | standard | complex.
|
|
75
|
+
Routing signal → resolves a model via project.yml
|
|
76
|
+
models.by_complexity (see FORGE.md Model Routing).
|
|
77
|
+
trivial = config/docs/mechanical; standard = ordinary
|
|
78
|
+
feature/fix; complex = cross-layer behavior, tricky logic,
|
|
79
|
+
high blast radius. OMIT the field → the task falls back to
|
|
80
|
+
skill-level model routing, exactly as pre-M24 plans did.
|
|
81
|
+
No migration: a task with neither field parses unchanged. -->
|
|
82
|
+
<task_type>code-feature</task_type> <!-- OPTIONAL (M24). code-feature | code-fix | docs | research |
|
|
83
|
+
config | test. The promotion-ladder BUCKET key — phases
|
|
84
|
+
37-38 learn "model X is proven on task_type Y". Free choice
|
|
85
|
+
from this controlled vocabulary; pick the dominant kind.
|
|
86
|
+
OMIT → task is not bucketed; no effect on legacy plans. -->
|
|
53
87
|
<files>
|
|
54
88
|
- src/path/to/file.tsx (create)
|
|
55
89
|
- src/path/to/other.ts (modify)
|
|
@@ -90,6 +90,10 @@ models:
|
|
|
90
90
|
securing: sonnet # Security analysis needs depth
|
|
91
91
|
debugging: sonnet # Investigation needs solid reasoning
|
|
92
92
|
discussing: sonnet # Conversation needs natural fluency
|
|
93
|
+
# by_complexity: # Per-task complexity -> model (M24). Overrides skills.X — see FORGE.md Model Routing Precedence.
|
|
94
|
+
# trivial: haiku
|
|
95
|
+
# standard: sonnet
|
|
96
|
+
# complex: opus
|
|
93
97
|
|
|
94
98
|
orchestration: # M10 multi-agent orchestration (experimental). Only consulted when M10 is installed.
|
|
95
99
|
# auto: true # false = never auto-route Standard/Full through orchestrating (install = consent; set false to opt out).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
slug: "{kebab-slug}"
|
|
3
|
+
title: "{short human title}"
|
|
4
|
+
created: "{ISO date}"
|
|
5
|
+
status: exploring # exploring | parked | graduated | discarded
|
|
6
|
+
flag: "{how it's gated — e.g. alpha, ?alpha=1, feature:foo, settings.alphaMockups}"
|
|
7
|
+
goal: "{one line — what are we trying to see before building it for real?}"
|
|
8
|
+
# --- set on resolve ---
|
|
9
|
+
# graduated_to: m-{id} # when status: graduated
|
|
10
|
+
# resolved: "{ISO date}" # when graduated or discarded
|
|
11
|
+
# discard_reason: "..." # when status: discarded
|
|
12
|
+
# parked: "{ISO date}" # when status: parked
|
|
13
|
+
# unpark_when: "..." # when status: parked — what would bring this back
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Prototype: {title}
|
|
17
|
+
|
|
18
|
+
Disposable in-app mockup, gated behind `{flag}`. Not production work — see `.claude/skills/prototyping/SKILL.md`.
|
|
19
|
+
|
|
20
|
+
## Open question
|
|
21
|
+
|
|
22
|
+
{The thing this iteration round is trying to answer / react to right now. Rewrite as it evolves.}
|
|
23
|
+
|
|
24
|
+
## Iterations
|
|
25
|
+
|
|
26
|
+
<!-- One terse line per pass. This is the loop's memory across /clear — keep it scannable, not a design doc. -->
|
|
27
|
+
- {ISO date} — {what this iteration tried, and what to look at}
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
{Optional: decisions leaning one way, dead ends, things to remember if this graduates.}
|