Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions _meta/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Lane = the WORKFLOW.md risk tier (`tiny` / `normal` / `full`).

| ID | Title | Source | Target artifact | Lane | Status |
|----|-------|--------|-----------------|------|--------|
| ID-904 | spec-next mints against open PR heads, not only the local listing #spec #concurrency | Intent: three parallel workers on 2026-09-16 each got SPEC-289 from spec-next because it only scanned docs/specs/, local branches, and commit subjects, none of which show a number an open unmerged PR already holds, and none of them called reserve. Added a gh-backed scan of every open PR head's docs/specs/ listing (contents API, no clone), skippable via SPEC_NEXT_NO_PR_SCAN=1, degrading to the old local-only scan with a stderr note when gh is missing or unauthenticated. lane=normal. Source: wrap step 7b, 2026-09-16 kit sweep session. | shipped [PR #661] |
| ID-903 | session observe tools: an --errors <tool> flag that groups a tool's error results by message prefix #observe #session #u-mid | Intent: session observe tools shows a tool's error COUNT (EnterWorktree 15 percent, ExitWorktree 17 percent this week) and nothing about WHY; a 30-line python over ~/.claude/projects grouping is_error tool_result content by its first 160 chars answered it in one run (17 of 19 EnterWorktree errors were subagents with a cwd override). Precedent: lib/session/observe/bin/session-observe owns the tools view; this is a flag on it, not a sibling script. lane=full. Goal draft: .claude/goals/observe-tool-errors.md. Source: wrap step 7b, 2026-09-16 kit sweep session. | queued |
| ID-885 | dispatch Step 6 releases a settled task's attempt record, and the lane parsers read a markdown-bold `Lane:` header #kit #dispatch #gate | Source: this session's follow-ups from SPEC-290 and SPEC-289. SPEC-290 wired most attempt-state verbs into commands/dispatch.md but never `release`, so kit-attempts/ grew forever. A worker writing `**Lane**: full` had its push BLOCKED with "Spec has no 'Lane:' header"; three files parsed `^Lane:` with the same expression, so all three had to change together. | commands/dispatch.md, hooks/ship-gate.sh, commands/ship.md, commands/mega.md, commands/spec.md, tests/test-ship-gate-fail-closed.sh, tests/test-meta.sh | full | shipped feat/attempt-release-lane-header [SPEC-293, proof docs/verification/attempt-release-lane-header.md] |
| ID-883 | Blocking quality gates opt-out per project: `[gate]` block in kit.toml, one reader in lib/gate/gate-policy.sh, hooks call it #gate #config #adopt | Han 2026-09-16: members adopting the kit did not want the proof-of-done gate; adopt always wrote the marker, so for adopters the gate was mandatory. Keys: proof_of_done, lane_gates, understanding_gate, commit_format; a project .kit.toml or the operator overlay sets one to false and the hook passes from the next fire, logging OFF-BY-CONFIG. Safety gates have no key. The classifier treats .kit.toml as inert so the flip itself owes no proof. Proof: docs/verification/gate-opt-out.md. Follow-up the same day (Han: make them default opt-in with the preset in the config file): the four keys default false, adopt seeds the block with resolved values and per-key comments, install prints the how-to; the operator overlay on Han's machines turns them on (dotfiles feat/kit-gates-on). Proof: docs/verification/gate-opt-in.md | lib/gate/gate-policy.sh, hooks/ship-gate.sh, hooks/anti-rationalization.sh, hooks/commit-format.sh, kit.toml [gate], lib/adopt.sh seed, tests/test-gate-opt-out.sh | normal | shipped feat/gate-opt-out |
| ID-881 | bin/wrap merge retries transient GitHub failures #wrap #resilience | bin/wrap merge has no retry for a transient GitHub failure (502/503/GraphQL executing-query errors); a real refusal (not mergeable, not green, conflict, draft) must still fail immediately. Operator hand-rolled 3 shell retry loops for ~25 PR merges during a ~90min GitHub outage. Same failure class as memory note hand-rolled-merge-loop-instead-of-wrap-merge.md, a second occurrence, missing retry is the root cause. cmd_merge in lib/wrap/wrap.sh (~line 812, gh pr merge call ~line 917). lane=full. Source: board sweep session 2026-09-13 and 14. Goal draft: .claude/goals/wrap-merge-retry.md in the kit clone. | queued |
Expand Down
6 changes: 3 additions & 3 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ GENERATED , do not hand-edit. Regenerate: `bash lib/registry/feature-registry.sh
| `/kit:design` | `[H/I]` | Opt-in interactive solution-design beat between /think and /spec. Explores 2-3 approaches one question at a time, holds for your approval p… | SPEC-003, SPEC-004, SPEC-005 +105 | test-command-emit-sweep.sh, test-command-triggers.sh, test-design-record.sh +21 |
| `/kit:devs-team` | `[H/I]` | Parallel multi-lens critique of a solution design (the active spec if present, else the decision brief). Dispatches 5 engineering lenses, m… | SPEC-016, SPEC-018, SPEC-019 +11 | test-gate-vocab-recording.sh, test-meta.sh, test-outcome-emit-sweep.sh |
| `/kit:dispatch` | `[H/I]` | Fire several disjoint VALIDATED specs concurrently, each in its own worktree, then converge. Cross-goal fan-out behind a disjointness gate … | SPEC-002, SPEC-016, SPEC-017 +80 | test-advisor-ledger-emit.sh, test-agent-effectiveness.sh, test-attempt-state.sh +31 |
| `/kit:docs` | `[H/I]` | Update all project documentation to match the current codebase. Cross-references the diff against every doc file and fixes drift. | SPEC-001, SPEC-002, SPEC-003 +194 | proof-loop-09-scenario-b.sh, run-all.sh, run-workflow.sh +69 |
| `/kit:docs` | `[H/I]` | Update all project documentation to match the current codebase. Cross-references the diff against every doc file and fixes drift. | SPEC-001, SPEC-002, SPEC-003 +194 | proof-loop-09-scenario-b.sh, run-all.sh, run-workflow.sh +70 |
| `/kit:draft-agent` | `[H/I]` | Meta-agent agent-builder. From a one-line description, generates a new subagent definition OR a mega-goal sub-goal file and (by default) in… | SPEC-089, SPEC-108, SPEC-139 +1 | test-agent-effectiveness.sh, test-command-emit-sweep.sh, test-meta-agent.sh +1 |
| `/kit:execute` | `[H/I]` | Autonomous spec execution with verification. Dispatches worker subagents per task, verifies each with task-verifier, retries fixable failur… | SPEC-001, SPEC-003, SPEC-004 +60 | test-break-it.sh, test-gate-vocab-recording.sh, test-hooks.sh +9 |
| `/kit:explain` | `[H/I]` | Turn a merged change into a literate-diff explainer a human READS to understand: background -> goal + intuition -> a prose-ordered diff -> … | SPEC-050, SPEC-060, SPEC-094 +18 | proof-loop-09-scenario-b.sh, test-boundary-lint.sh, test-command-emit-sweep.sh +11 |
Expand All @@ -30,7 +30,7 @@ GENERATED , do not hand-edit. Regenerate: `bash lib/registry/feature-registry.sh
| `/kit:grill` | `[H/I]` | Universal intake interview: one type-shaped question at a time, each with a recommended answer, until the task is actually understood. Answ… | SPEC-058, SPEC-059, SPEC-063 +21 | test-config-stamp.sh, test-e2e.sh, test-gate-ledger-plan-record.sh +8 |
| `/kit:kit-health` | `[H/I]` | Run a self-assessment of the kit against its own philosophy. Checks file count, hook performance, source citations, and structural health. | SPEC-001, SPEC-002, SPEC-004 +16 | test-command-emit-sweep.sh, test-meta.sh |
| `/kit:mega` | `[H/I]` | Turn a multi-objective destination into a sequenced roadmap of dependent sub-goals: decompose, front-load every clarification once, set the… | SPEC-034, SPEC-036, SPEC-088 +35 | proof-loop-09-scenario-b.sh, test-advisor-ledger-emit.sh, test-bin-forwarders.sh +23 |
| `/kit:next` | `[H/I]` | Pick up the next undone task from the spec. Loads context, shows acceptance criteria, lets you drive the implementation. | SPEC-001, SPEC-002, SPEC-003 +87 | run-all.sh, test-advisor-ledger-emit.sh, test-advisor.sh +34 |
| `/kit:next` | `[H/I]` | Pick up the next undone task from the spec. Loads context, shows acceptance criteria, lets you drive the implementation. | SPEC-001, SPEC-002, SPEC-003 +87 | run-all.sh, test-advisor-ledger-emit.sh, test-advisor.sh +35 |
| `/kit:onboard` | `[H/I]` | Guided first-run: detect the install mode, offer /kit:adopt for this repo, pick modules, capture the consumer knobs that make them work, di… | SPEC-199, SPEC-213, SPEC-232 +1 | proof-loop-09-scenario-b.sh, test-command-emit-sweep.sh, test-onboard-detect.sh |
| `/kit:pitch` | `[H/I]` | Assemble an outward buy-in doc from what a gated run already produced: the spec, the proof-of-done, the implementation-notes, and the gate … | SPEC-140, SPEC-141, SPEC-193 +1 | test-outcome-emit-sweep.sh, test-pitch.sh |
| `/kit:prototype` | `[H/I]` | Opt-in throwaway-spike beat beside /kit:design. Builds throwaway code that answers ONE design question: a logic/state model driven by hand … | SPEC-075, SPEC-206, SPEC-207 +2 | test-picture-section.sh |
Expand All @@ -40,7 +40,7 @@ GENERATED , do not hand-edit. Regenerate: `bash lib/registry/feature-registry.sh
| `/kit:review` | `[H/I]` | Paranoid code review. Security, architecture, regressions, missing tests, edge cases. Produces actionable TODOS. | SPEC-001, SPEC-002, SPEC-003 +102 | test-adopt.sh, test-advisor-ledger-emit.sh, test-agent-effectiveness.sh +42 |
| `/kit:ship` | `[H/I]` | Ship: review gate, tests, version bump, changelog, conventional commit, docs update, PR. Complete pipeline from done to merged. | SPEC-001, SPEC-002, SPEC-003 +78 | test-adopt.sh, test-board-mirror.sh, test-cheap-guards.sh +28 |
| `/kit:spec-validate` | `[H/I]` | Adversarial review of a spec before implementation. 6 specialist lenses attack the spec from different angles (5 advisory, 1 blocking on th… | SPEC-002, SPEC-003, SPEC-004 +58 | test-command-emit-sweep.sh, test-design-record.sh, test-every-step-review.sh +8 |
| `/kit:spec` | `[H/I]` | Generate a development spec from a feature idea or decision brief. Creates docs/specs/ with structured requirements. | SPEC-001, SPEC-002, SPEC-003 +165 | test-bin-forwarders.sh, test-break-it.sh, test-codex-hooks.sh +47 |
| `/kit:spec` | `[H/I]` | Generate a development spec from a feature idea or decision brief. Creates docs/specs/ with structured requirements. | SPEC-001, SPEC-002, SPEC-003 +165 | test-bin-forwarders.sh, test-break-it.sh, test-codex-hooks.sh +48 |
| `/kit:start` | `[H/I]` | Detect project state and suggest the right next command. The entry point for any session. | SPEC-002, SPEC-003, SPEC-004 +42 | test-command-emit-sweep.sh, test-config-stamp.sh, test-e2e.sh +30 |
| `/kit:test-plan-review-team` | `[H/I]` | Parallel multi-lens adversarial critique of a spec's test plan (the ## Test plan section), with a bounded revise loop that tightens it. Dis… | SPEC-031, SPEC-052, SPEC-062 +10 | test-meta.sh, test-outcome-emit-sweep.sh |
| `/kit:test-plan` | `[H/I]` | Derive a test-case coverage matrix from a spec's acceptance criteria before /kit:execute. Writes a `## Test plan` section into the active s… | SPEC-004, SPEC-016, SPEC-018 +27 | test-e2e.sh, test-gate-ledger-plan-record.sh, test-gate-vocab-recording.sh +5 |
Expand Down
27 changes: 27 additions & 0 deletions docs/implementation-notes/spec-next-open-pr-heads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Implementation notes: spec-next open-PR-head scan

No spec doc for this change (ID-904, override recorded via gate-ledger: scoped in
the dispatch prompt, a single additive scan function on an existing script).

## 2026-09-16 09:00 Bootstrap-failure scope

Context: the task says "when any call fails, behave exactly as today." A per-PR
`gh api .../contents/docs/specs?ref=<head>` call 404s whenever that PR does not
touch `docs/specs/`, which is the common case, not a failure.

Decision: only `gh auth status`, `gh repo view`, and `gh pr list` are treated as
bootstrap calls whose failure triggers the local-only fallback + stderr note. A
per-PR contents-API miss is silently skipped (that PR contributes no numbers).

Why: treating every PR's 404 as "the scan failed" would print a misleading note
on the majority of runs and would still be correct behavior (that PR just holds
no spec numbers), so scoping the failure note to bootstrap calls only avoids
false alarms without losing the fallback contract.

Alternatives: fail the whole scan on any single PR's API error. Rejected: one
PR without a `docs/specs/` dir would silently blind the scan to every other
open PR's numbers, defeating the point.

Impact: `lib/spec/spec-next.sh::_scan_pr_numbers`.

Open questions: none.
75 changes: 75 additions & 0 deletions docs/verification/spec-next-open-pr-heads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Proof of done: spec-next mints against open PR heads, not only the local listing

2026-09-16. Acceptance: `lib/spec/spec-next.sh next` folds an open PR's `docs/specs/`
listing into its max-number scan when `gh` is on PATH and authenticated, via the GitHub
contents API on the PR's head ref (no clone, no fetch). Falls back to the original
local-only scan (docs/specs/, local+remote branches, recent commit subjects) with one
stderr note when `gh` is missing, unauthenticated, or a bootstrap call fails.
Skippable via `SPEC_NEXT_NO_PR_SCAN=1`. No spec doc; override recorded via gate-ledger
(scoped in the dispatch prompt). Board: ID-904. Files: `lib/spec/spec-next.sh`,
`tests/test-spec-next-pr-scan.sh`.

## The failure this closes

`spec-next` only scanned `docs/specs/` filenames, local/remote branch names, and recent
commit subjects. None of those surfaces show a number an OPEN, unmerged PR already holds.
On 2026-09-16 three parallel workers each called `next` before any of them merged; two got
SPEC-289, the third had to move to 290, a fourth PR later collided at 291 with a PR holding
288. None of the workers called the existing `reserve` atomic-claim path either, so the
reservation ledger did not help.

## Green run

Command: `bash tests/test-spec-next-pr-scan.sh`
Exit: 0
Output:
```
=== T1: gh present + authed, one open PR holds SPEC-0450 -> next folds it in ===
PASS T1 next is 451 (local max 449, PR head holds 450)
=== T2: gh present but NOT authenticated -> local-only fallback + stderr note ===
PASS T2 next falls back to local max+1 (450)
PASS T2 stderr carries the not-scanned note
=== T3: SPEC_NEXT_NO_PR_SCAN=1 skips the scan even with a working gh stub ===
PASS T3 opt-out ignores the PR head (local max+1 = 450)
PASS T3 stderr names the opt-out reason
=== T4: no gh on PATH at all -> local-only fallback + stderr note ===
PASS T4 next falls back to local max+1 (450) with no gh
PASS T4 stderr says gh not on PATH
Passed: 7 / 7
spec-next-pr-scan green.
```
Verdict: PASS. Covers the fold-in case (T1), an authenticated-but-degraded case (T2), the
test-only opt-out (T3), and the no-`gh` case (T4), each checking both the returned number
and the stderr note's presence/absence.

Command: `bash tests/test-spec-reserve.sh`
Exit: 0
Output: `Passed: 41 / 41`, `spec-reserve green.`
Verdict: PASS. The existing reservation-race suite is unaffected: it isolates its own
temp repos and sets `SPEC_RESERVE_FILE`, and none of its fixture repos have `gh` reachable
in a way that changes a `next`/`reserve` result (no open PRs against those throwaway repos).

Command: `bash tests/run-all.sh`
Exit: 0
Output: `run-all: all 152 suites passed, 0 skipped for missing tooling`
Verdict: PASS. First run failed on `docs/FEATURES.md is fresh (regenerate == committed,
SPEC-219)` because the new test file shifts per-command touched-file counts in that
generated projection; `bash lib/registry/feature-registry.sh generate` produced a
counts-only diff and the suite went green on the next run.

## Negative control

Command: `bash lib/gate/negctl.sh "$PWD" "bash tests/test-spec-next-pr-scan.sh" "sed -i '' 's/_scan_pr_numbers | grep -oE .[0-9]+.; /true; /' lib/spec/spec-next.sh"`
Exit (pre-mutation): 0 (green)
Mutation: nulls the `_scan_pr_numbers` fold in `_numbers()` so an open PR's SPEC number
is never counted.
Exit (post-mutation): 1 (RED, as expected: T1 expects 451 and gets 450 with the fold
disabled)
Restore: `git checkout HEAD -- lib/spec/spec-next.sh`, exit 0 (green again)
Verdict: PASS (negctl printed `Verdict: PASS`).

## Reproducible

Any operator can re-run `bash tests/test-spec-next-pr-scan.sh` or `bash tests/run-all.sh`
from a checkout of this branch; the new test stubs `gh` on `PATH` so it needs no live
GitHub credentials or network access.
41 changes: 40 additions & 1 deletion lib/spec/spec-next.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
# portable mkdir-mutex and records it in a reservations ledger that `_numbers()` folds in, so
# a reserved number reads as TAKEN by the very next caller. `next`/`check` are unchanged in
# contract: with an empty ledger they behave byte-identically to before.
#
# A second concurrency gap: three workers each opened a PR before any of them merged, so none
# of their local scans (docs/specs/, local branches, commit subjects) saw the others' numbers,
# and none of them called `reserve`. `_numbers()` also folds in every OPEN PR's `docs/specs/`
# listing (fetched via `gh api .../contents/docs/specs?ref=<head>`, no clone, no fetch) when
# `gh` is on PATH and authenticated. Set SPEC_NEXT_NO_PR_SCAN=1 to skip this source (tests use
# it to keep runs hermetic); without `gh`, or if any bootstrap call fails, the scan falls back
# to the original local-only behavior and prints one stderr note.
set -euo pipefail

ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
Expand Down Expand Up @@ -63,6 +71,37 @@ _scan_numbers() {
} | grep -oE '[0-9]+' | sort -n | uniq
}

# Open-PR SPEC numbers: for each open PR's head ref, list docs/specs/ via the GitHub contents
# API (no clone, no fetch) and pull out SPEC-NNN filenames. Fails soft everywhere: any missing
# tool, failed auth, or failed bootstrap call prints one stderr note and returns empty so
# `_numbers()` degrades to the original local-only scan.
_scan_pr_numbers() {
if [ "${SPEC_NEXT_NO_PR_SCAN:-0}" = "1" ]; then
echo "spec-next: open PR heads not scanned (SPEC_NEXT_NO_PR_SCAN=1)" >&2
return 0
fi
command -v gh >/dev/null 2>&1 || { echo "spec-next: open PR heads not scanned (gh not on PATH)" >&2; return 0; }
gh auth status >/dev/null 2>&1 || { echo "spec-next: open PR heads not scanned (gh not authenticated)" >&2; return 0; }
local owner_repo heads
owner_repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)" || owner_repo=""
[ -n "$owner_repo" ] || { echo "spec-next: open PR heads not scanned (gh repo view failed)" >&2; return 0; }
heads="$(gh pr list --state open --json headRefName --limit 100 --jq '.[].headRefName' 2>/dev/null)"
local status=$?
if [ "$status" -ne 0 ]; then
echo "spec-next: open PR heads not scanned (gh pr list failed)" >&2
return 0
fi
[ -n "$heads" ] || return 0
local ref
while IFS= read -r ref; do
[ -n "$ref" ] || continue
# A PR with no docs/specs/ dir 404s here; that is normal (not every PR touches specs), so
# it is skipped silently rather than treated as a bootstrap failure.
gh api "repos/$owner_repo/contents/docs/specs?ref=$ref" --jq '.[].name' 2>/dev/null \
| grep -oE 'SPEC-[0-9]+' || true
done <<< "$heads"
}

# LIVE reservation numbers for THIS repo: repo-scoped AND within TTL (an expired line stops
# counting even before it is physically pruned). Empty when no ledger exists (the common case,
# which keeps `next`/`check` byte-identical to the original scan-only behavior).
Expand All @@ -89,7 +128,7 @@ _reservations() {
# The full union readers see: the real scan PLUS live reservations. `_scan_numbers` is the
# original scan-only body verbatim; folding reservations in is purely additive.
_numbers() {
{ _scan_numbers; _reservations; } | sort -n | uniq
{ _scan_numbers; _scan_pr_numbers | grep -oE '[0-9]+'; _reservations; } | sort -n | uniq
}

next() {
Expand Down
Loading
Loading