Skip to content

Commit 1ce600c

Browse files
committed
feat: bridge Codex v0.4.5–v0.4.7 (bug lifecycle, handoff review gate, resume selection boundary)
- v0.4.5: bug lifecycle consolidation — /bug-report verify with VERIFIED FIXED can complete verification, closure, stale triage cleanup, and session-state routing under one approved changeset instead of forced verify→close→triage handoffs. /bug-triage gains zero-open-bugs closure-refresh path. - v0.4.6: /handoff mandatory two-round review gate with STANDARD/ADVERSARIAL tier selection, pure-document exemptions, finding triage, conditional second review, pass caps, audit trail, and no-egress contract (stays inside active OpenCode session). AGENTS.md handoff exception expanded for intent-preserving fixes and round-two non-trivial-finding stop rule. - v0.4.7: hard /resume-from-handoff lane-selection boundary — focus arguments only bias ranking, single lanes wait for numeric 1, follow-up forks are new decisions, FIRST verification cannot be waived. Separated session-cache write (Step 6) from briefing (Step 7). AGENTS.md resume exception expanded. - 3 new audit validators: bug-lifecycle, handoff-review, resume-contract.
1 parent c5f579d commit 1ce600c

9 files changed

Lines changed: 611 additions & 101 deletions

File tree

.opencode/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.4.1
1+
0.4.2

.opencode/audit.sh

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
# closeout Check closeout-routing contract on completion skills
1212
# checkpoint Check active.md silent-checkpoint contract on skills/agents
1313
# playtest Check playtest-focus contract on root/continuity/skill surfaces
14+
# bug-lifecycle Check bug lifecycle consolidation contract on bug-report/triage
15+
# handoff-review Check handoff two-round review-gate contract on handoff/AGENTS.md
16+
# resume-contract Check resume lane-selection boundary on resume-from-handoff
1417
# runtime Check for stale references
1518
# config Validate opencode.json
1619
# hooks Test hook scripts
@@ -27,7 +30,7 @@ command="${1:-all}"
2730
while [ "$#" -gt 0 ]; do
2831
case "$1" in
2932
--root) root="$(cd "$2" && pwd -P)"; shift 2 ;;
30-
all|agents|skills|runtime|config|hooks|smoke|release|closeout|checkpoint|playtest) command="$1"; shift ;;
33+
all|agents|skills|runtime|config|hooks|smoke|release|closeout|checkpoint|playtest|bug-lifecycle|handoff-review|resume-contract) command="$1"; shift ;;
3134
*) shift ;;
3235
esac
3336
done
@@ -257,6 +260,208 @@ run_playtest_focus() {
257260
printf ' %d playtest-focus surfaces checked\n' "$checked"
258261
}
259262

263+
run_bug_lifecycle() {
264+
printf '\n── Bug Lifecycle Contract ────────────────────────────────\n'
265+
local checked=0
266+
267+
# --- bug-report required phrases ---
268+
local report="$root/.opencode/skills/bug-report/SKILL.md"
269+
checked=$((checked + 1))
270+
if [ ! -f "$report" ]; then fail "bug-report/SKILL.md (missing)"; else
271+
local report_norm; report_norm=$(tr -s '[:space:]' ' ' < "$report")
272+
local -a req=(
273+
"treat verification, closure, stale triage"
274+
"one deterministic bug lifecycle operation"
275+
"Do not stop after VERIFIED FIXED to offer"
276+
"refresh stale triage metadata under the same approval"
277+
"zero-open-bugs refresh"
278+
"derived checkpoint"
279+
'Do not ask a separate "May I write?" for'
280+
"Do not bundle and stop for user decision if triage would require assigning"
281+
)
282+
local missing=() p
283+
for p in "${req[@]}"; do
284+
grep -qiF "$p" <<< "$report_norm" 2>/dev/null || missing+=("$p")
285+
done
286+
# Forbidden old-language fragments
287+
local -a forbidden=(
288+
"is referenced in the triage report"
289+
"write the closure record and update status"
290+
"remove it from the active list"
291+
)
292+
local bad=()
293+
for p in "${forbidden[@]}"; do
294+
grep -qiF "$p" <<< "$report_norm" 2>/dev/null && bad+=("$p")
295+
done
296+
if [ "${#missing[@]}" -eq 0 ] && [ "${#bad[@]}" -eq 0 ]; then
297+
pass "bug-report/SKILL.md (lifecycle contract)"
298+
else
299+
local d=""
300+
[ "${#missing[@]}" -gt 0 ] && d+=" missing: ${missing[*]}"
301+
[ "${#bad[@]}" -gt 0 ] && d+=" forbidden-present: ${bad[*]}"
302+
fail "bug-report/SKILL.md (lifecycle contract)$d"
303+
fi
304+
fi
305+
306+
# --- bug-triage required phrases ---
307+
local triage="$root/.opencode/skills/bug-triage/SKILL.md"
308+
checked=$((checked + 1))
309+
if [ ! -f "$triage" ]; then fail "bug-triage/SKILL.md (missing)"; else
310+
local triage_norm; triage_norm=$(tr -s '[:space:]' ' ' < "$triage")
311+
local -a req2=(
312+
"zero-open-bugs closure refresh"
313+
"Treat it as metadata cleanup"
314+
"non-blocking follow-up"
315+
"Exception for bundled bug lifecycle cleanup"
316+
"deterministic metadata cleanup"
317+
"It must be explicitly marked non-blocking if it cannot be completed safely"
318+
"Do not bundle if the triage work would require assigning priorities"
319+
)
320+
local missing2=() p2
321+
for p2 in "${req2[@]}"; do
322+
grep -qiF "$p2" <<< "$triage_norm" 2>/dev/null || missing2+=("$p2")
323+
done
324+
if [ "${#missing2[@]}" -eq 0 ]; then
325+
pass "bug-triage/SKILL.md (lifecycle contract)"
326+
else
327+
fail "bug-triage/SKILL.md missing: ${missing2[*]}"
328+
fi
329+
fi
330+
printf ' %d bug-lifecycle surfaces checked\n' "$checked"
331+
}
332+
333+
run_handoff_review() {
334+
printf '\n── Handoff Review Gate Contract ──────────────────────────\n'
335+
local checked=0
336+
337+
# --- handoff SKILL.md required phrases ---
338+
local skill="$root/.opencode/skills/handoff/SKILL.md"
339+
checked=$((checked + 1))
340+
if [ ! -f "$skill" ]; then fail "handoff/SKILL.md (missing)"; else
341+
local skill_norm; skill_norm=$(tr -s '[:space:]' ' ' < "$skill")
342+
local -a req=(
343+
"## Round 1"
344+
"## Round 2"
345+
"\`STANDARD\`"
346+
"\`ADVERSARIAL\`"
347+
"Foundation ADR cluster closure"
348+
"pure design/process-document"
349+
"self-review is sufficient and the native cross-check is skipped"
350+
"Mixed code-and-document changes are not exempt"
351+
"distinct native review pass"
352+
"current OpenCode session"
353+
"\`HIGH\`, \`MEDIUM\`, or \`LOW\`"
354+
"\`CLEAN\`"
355+
"\`path:line\`"
356+
"If uncertain whether the work meets a major trigger, use \`STANDARD\`"
357+
"quoted verbatim"
358+
"stop before Phase 1"
359+
"second native cross-check"
360+
"\`HIGH\` finding"
361+
"cross-cutting executable behavior"
362+
"Trivial and confidently intent-preserving only"
363+
"Any non-trivial fix"
364+
"Do not run a third pass"
365+
"three native review passes"
366+
"fourth native review pass"
367+
"active reported context percentage"
368+
"review audit trail"
369+
"every finding"
370+
"Only then proceed to Phase 1"
371+
)
372+
local missing=() p
373+
for p in "${req[@]}"; do
374+
grep -qF "$p" <<< "$skill_norm" 2>/dev/null || missing+=("$p")
375+
done
376+
if [ "${#missing[@]}" -eq 0 ]; then
377+
pass "handoff/SKILL.md (review gate contract)"
378+
else
379+
fail "handoff/SKILL.md missing: ${missing[*]}"
380+
fi
381+
fi
382+
383+
# --- AGENTS.md required phrases ---
384+
local agents="$root/AGENTS.md"
385+
checked=$((checked + 1))
386+
if [ ! -f "$agents" ]; then fail "AGENTS.md (missing)"; else
387+
local agents_norm; agents_norm=$(tr -s '[:space:]' ' ' < "$agents")
388+
local -a req2=(
389+
"files already created or materially modified during the session"
390+
"intent-preserving review fixes"
391+
"active OpenCode session"
392+
"Round-two non-trivial findings"
393+
"external data-egress approval"
394+
"new intent, architecture, game-feel, balance, or scope decisions"
395+
)
396+
local missing2=() p2
397+
for p2 in "${req2[@]}"; do
398+
grep -qF "$p2" <<< "$agents_norm" 2>/dev/null || missing2+=("$p2")
399+
done
400+
if [ "${#missing2[@]}" -eq 0 ]; then
401+
pass "AGENTS.md (handoff review exception)"
402+
else
403+
fail "AGENTS.md missing: ${missing2[*]}"
404+
fi
405+
fi
406+
printf ' %d handoff-review surfaces checked\n' "$checked"
407+
}
408+
409+
run_resume_contract() {
410+
printf '\n── Resume Lane-Selection Contract ────────────────────────\n'
411+
if python3 - "$root" <<'PY'
412+
import re, sys, os
413+
root = sys.argv[1]
414+
rel = ".opencode/skills/resume-from-handoff/SKILL.md"
415+
path = os.path.join(root, rel)
416+
if not os.path.isfile(path):
417+
print(f" ! {rel}: missing file")
418+
sys.exit(1)
419+
text = open(path, encoding="utf-8").read()
420+
norm = re.sub(r"\s+", " ", text)
421+
422+
required = (
423+
"A focus argument biases ranking; it does not select a lane.",
424+
"Never start an unselected lane.",
425+
"recommendation as the first option",
426+
"wait for the user to reply `1`",
427+
"Resume selection authorizes entering only the selected workflow",
428+
"FIRST verification cannot be waived by choosing another lane",
429+
"Follow-up fork",
430+
"`question` tool",
431+
"Playable/Slice State Source",
432+
"production/stage.txt",
433+
".opencode/docs/workflow-catalog.yaml",
434+
"production/session-state/active.md",
435+
)
436+
missing = [p for p in required if p not in norm]
437+
if missing:
438+
print(f" ! {rel}: missing phrase(s): " + ", ".join(missing))
439+
440+
fails = len(missing)
441+
for i, line in enumerate(text.splitlines(), start=1):
442+
lower = line.lower()
443+
starts_lane = re.search(r"\b(?:start|begin|enter)\b", lower)
444+
bypasses = any(w in lower for w in (
445+
"automatically", "immediately", "without waiting", "without selection"
446+
))
447+
forbidden = any(w in lower for w in (
448+
"do not", "don't", "never", "must not", "cannot"
449+
))
450+
if starts_lane and bypasses and not forbidden:
451+
print(f" ! {rel}:{i}: automatic lane startup forbidden; "
452+
"pause for selection boundary")
453+
fails += 1
454+
455+
print(f" resume-from-handoff checked, {fails} violation(s)")
456+
sys.exit(1 if fails else 0)
457+
PY
458+
then
459+
pass "resume selection contract satisfied"
460+
else
461+
fail "resume selection contract violations"
462+
fi
463+
}
464+
260465
run_runtime() {
261466
printf '\n── Runtime References ──────────────────────────────────────\n'
262467
# Check for stale Claude references
@@ -404,6 +609,9 @@ case "$command" in
404609
run_closeout
405610
run_checkpoint
406611
run_playtest_focus
612+
run_bug_lifecycle
613+
run_handoff_review
614+
run_resume_contract
407615
run_runtime
408616
run_config
409617
run_hooks
@@ -414,12 +622,15 @@ case "$command" in
414622
closeout) run_closeout ;;
415623
checkpoint) run_checkpoint ;;
416624
playtest) run_playtest_focus ;;
625+
bug-lifecycle) run_bug_lifecycle ;;
626+
handoff-review) run_handoff_review ;;
627+
resume-contract) run_resume_contract ;;
417628
runtime) run_runtime ;;
418629
config) run_config ;;
419630
hooks) run_hooks ;;
420631
smoke) run_smoke ;;
421632
release) run_release ;;
422-
*) printf 'Unknown command: %s\nAvailable: all, agents, skills, closeout, checkpoint, playtest, runtime, config, hooks, smoke, release\n' "$command" >&2; exit 2 ;;
633+
*) printf 'Unknown command: %s\nAvailable: all, agents, skills, closeout, checkpoint, playtest, bug-lifecycle, handoff-review, resume-contract, runtime, config, hooks, smoke, release\n' "$command" >&2; exit 2 ;;
423634
esac
424635

425636
printf '\n── Result: %d error(s) ──\n' "$errors"

.opencode/skills/bug-report/SKILL.md

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,47 @@ Produce a verification verdict:
105105
- **STILL PRESENT** — bug reproduces as described; fix did not resolve the issue
106106
- **CANNOT VERIFY** — automated checks inconclusive; manual playtest required
107107

108-
Ask: "May I update `production/qa/bugs/[BUG-ID].md` to set Status: Verified Fixed / Still Present / Cannot Verify?"
108+
If the verdict is **VERIFIED FIXED**, treat verification, closure, stale triage
109+
metadata cleanup, and session-state routing as one deterministic bug lifecycle
110+
operation when the facts are unambiguous.
111+
112+
Before writing, present the verification evidence and ask once for the full
113+
changeset:
114+
115+
> "May I update these files to mark [BUG-ID] Verified Fixed, add verification
116+
> evidence, close the bug, refresh stale triage metadata when safe, and update
117+
> the derived checkpoint in `production/session-state/active.md`?
118+
> Files: `production/qa/bugs/[BUG-ID].md`, [any affected
119+
> `production/qa/bug-triage-*.md` files], `production/session-state/active.md`."
120+
121+
Do not stop after VERIFIED FIXED to offer `/bug-report close [BUG-ID]` as the
122+
next action when closure facts are deterministic. Do not ask a separate "May I
123+
write?" for `production/session-state/active.md` when the update is only a
124+
derived checkpoint for completed bug lifecycle work. Do not ask a separate "May
125+
I write?" for this file.
126+
127+
Bundle only deterministic metadata cleanup:
128+
- Set top-level `**Status**: Verified Fixed`.
129+
- Add or update verification evidence with the command(s), grep checks, commit
130+
or file evidence, and verifier.
131+
- Append the Closure Record from Phase 2D and set top-level `**Status**: Closed`
132+
when the closure record can be completed from known facts.
133+
- Refresh affected `production/qa/bug-triage-*.md` reports only when the refresh
134+
removes closed bugs, updates open/closed counts, clears a stale recommended
135+
action, or records "0 open bugs" without assigning priorities or changing
136+
sprint scope.
137+
- Update `production/session-state/active.md` only with derived checkpoint
138+
routing: completed bug lifecycle work, files touched, owed verification, and
139+
the next valid Session Worklist lane.
140+
141+
Do not bundle and stop for user decision if triage would require assigning
142+
priorities, choosing sprint scope, marking bugs Won't Fix, changing severity, or
143+
resolving conflicting bug states.
144+
145+
If the verdict is **STILL PRESENT** or **CANNOT VERIFY**, ask:
146+
147+
> "May I update `production/qa/bugs/[BUG-ID].md` to set Status: Still Present /
148+
> Cannot Verify and add the verification evidence?"
109149
110150
If STILL PRESENT: reopen the bug, set Status back to Open, and suggest re-running `/hotfix [BUG-ID]`.
111151

@@ -130,9 +170,29 @@ Append a closure record to the bug file:
130170

131171
Update the top-level `**Status**: Open` field to `**Status**: Closed`.
132172

133-
Ask: "May I update `production/qa/bugs/[BUG-ID].md` to mark it Closed?"
173+
If the bug is already `Verified Fixed`, close the bug and refresh stale triage
174+
metadata under the same approval when the refresh is safe deterministic cleanup.
175+
Before writing, list the exact files and ask once:
134176

135-
After closing, check `production/qa/bug-triage-*.md` — if the bug appears in an open triage report, note: "Bug [ID] is referenced in the triage report. Run `/bug-triage` to refresh the open bug count."
177+
> "May I update these files to close [BUG-ID], refresh stale triage metadata
178+
> when safe, and update the derived checkpoint in
179+
> `production/session-state/active.md`?
180+
> Files: `production/qa/bugs/[BUG-ID].md`, [any affected
181+
> `production/qa/bug-triage-*.md` files], `production/session-state/active.md`."
182+
183+
Do not ask a separate "May I write?" for `production/session-state/active.md`
184+
when the update is only a derived checkpoint for completed bug lifecycle work.
185+
Do not ask a separate "May I write?" for this file.
186+
187+
Safe stale triage metadata refresh includes removing the closed bug from open
188+
bug tables, updating open/closed counts, clearing stale "fix this bug" actions,
189+
and recording a zero-open-bugs refresh. It must not assign priorities, choose
190+
sprint scope, mark bugs Won't Fix, change severity, or resolve conflicting bug
191+
states.
192+
193+
If stale triage metadata exists but is unsafe to refresh automatically, close
194+
the bug and mark triage cleanup as non-blocking owed follow-up instead of
195+
blocking closure.
136196

137197
---
138198

@@ -161,5 +221,8 @@ After saving, suggest based on mode:
161221
- Never mark a bug closed without verification — a fix that doesn't verify is still Open
162222

163223
**After verify returns VERIFIED FIXED:**
164-
- Run `/bug-report close [BUG-ID]` — write the closure record and update status
165-
- Run `/bug-triage` to refresh the open bug count and remove it from the active list
224+
- When closure facts are deterministic, complete verification, closure, stale
225+
triage metadata cleanup, and derived session-state routing under the same
226+
approved changeset.
227+
- If closure or triage refresh requires a manual decision, stop at the decision
228+
point and make the blocked item explicit.

.opencode/skills/bug-triage/SKILL.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ ensures no critical bug is lost between sprints.
2121
- Sprint start — assign open bugs to the new sprint or backlog
2222
- After `/team-qa` completes and new bugs have been filed
2323
- When the bug count crosses 10+ open items
24+
- As bundled metadata cleanup after `/bug-report verify [BUG-ID]` or
25+
`/bug-report close [BUG-ID]` closes a bug and stale triage report fields can
26+
be refreshed without new prioritization decisions
2427

2528
---
2629

@@ -51,6 +54,13 @@ If no bug files found:
5154
5255
Stop and report. Do not proceed if no bugs exist.
5356

57+
If this run is a zero-open-bugs closure refresh started from an approved
58+
`/bug-report verify [BUG-ID]` or `/bug-report close [BUG-ID]` lifecycle, do not
59+
stop for a separate triage decision. Treat it as metadata cleanup: update the
60+
affected stale triage report so the open bug count, priority tables, and
61+
recommended action reflect that no bugs remain open. Mark any broader trend or
62+
sprint-priority work as non-blocking follow-up.
63+
5464
### Step 2b — Load sprint context
5565

5666
Read the most recently modified file in `production/sprints/` to understand:
@@ -222,6 +232,19 @@ Present the report in conversation, then ask:
222232

223233
Write only after approval.
224234

235+
Exception for bundled bug lifecycle cleanup: when `/bug-report verify [BUG-ID]`
236+
or `/bug-report close [BUG-ID]` already obtained approval for the full
237+
changeset, do not ask a second triage write prompt for deterministic metadata
238+
cleanup. The approval must have listed the exact stale
239+
`production/qa/bug-triage-*.md` file(s) being updated.
240+
241+
Bundled triage cleanup is allowed only when it removes closed bugs from open
242+
tables, updates counts, clears stale recommended actions, or records a
243+
zero-open-bugs closure refresh. It must be explicitly marked non-blocking if it
244+
cannot be completed safely. Do not bundle if the triage work would require
245+
assigning priorities, choosing sprint scope, marking bugs Won't Fix, changing
246+
severity, or resolving conflicting bug states.
247+
225248
After writing:
226249
- If any S1 bugs are unassigned: "S1 bugs must be assigned before the sprint
227250
can be considered healthy. Run `/sprint-status` to see current capacity."

0 commit comments

Comments
 (0)