codebyplan 1.13.64 → 1.13.65

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.13.64",
3
+ "version": "1.13.65",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,6 +40,8 @@ jobs:
40
40
  ci:
41
41
  name: Lint + typecheck + test + build
42
42
  runs-on: ubuntu-latest
43
+ # Cap the job so a hung step can't burn the 6h default and waste runner minutes.
44
+ timeout-minutes: 20
43
45
  steps:
44
46
  - name: Checkout
45
47
  uses: actions/checkout@v4
@@ -85,6 +87,9 @@ jobs:
85
87
  ci-strict:
86
88
  name: Strict whole-repo green{{STRICT_NAME_SUFFIX}}
87
89
  runs-on: ubuntu-latest{{STRICT_CONTINUE_ON_ERROR_LINE}}
90
+ # Whole-repo green is heavier than the soft tier; allow more headroom but
91
+ # still cap it so a hang can't run to the 6h default.
92
+ timeout-minutes: 30
88
93
  steps:
89
94
  - name: Checkout
90
95
  uses: actions/checkout@v4
@@ -0,0 +1,67 @@
1
+ #!/bin/bash
2
+ # @scope: org-shared
3
+ # Hook: SessionStart
4
+ # Purpose: Stamp the Claude harness session UUID into
5
+ # .codebyplan/state/session/session-id.json so the CLI create-log path
6
+ # (and other session-aware tools) can correlate log entries with the
7
+ # running Claude session.
8
+ #
9
+ # Hook-safe: all errors are swallowed, always exits 0. Non-fatal / best-effort.
10
+ # No-op when not inside a codebyplan repo or when the session UUID cannot be
11
+ # derived from the hook payload.
12
+ #
13
+ # UUID derivation order:
14
+ # 1. stdin JSON payload `.session_id` field
15
+ # 2. basename (without extension) of `.transcript_path` field
16
+ # 3. no-op — exit 0 without writing
17
+
18
+ # C0 — require jq; if absent, exit 0 (fail-open).
19
+ if ! command -v jq > /dev/null 2>&1; then
20
+ exit 0
21
+ fi
22
+
23
+ # Resolve the project dir: Claude Code sets CLAUDE_PROJECT_DIR; fall back to pwd.
24
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
25
+
26
+ # No-op when not inside a codebyplan repo (sentinel file absent).
27
+ if [ ! -f "$PROJECT_DIR/.codebyplan/repo.json" ]; then
28
+ exit 0
29
+ fi
30
+
31
+ # Read stdin once into a variable.
32
+ INPUT=$(cat)
33
+
34
+ # Derive the session UUID — try session_id field first.
35
+ SESSION_UUID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
36
+
37
+ # Fall back to transcript_path basename (strip directory + extension).
38
+ if [ -z "$SESSION_UUID" ]; then
39
+ TRANSCRIPT=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
40
+ if [ -n "$TRANSCRIPT" ]; then
41
+ BASENAME=$(basename "$TRANSCRIPT")
42
+ SESSION_UUID="${BASENAME%.*}"
43
+ fi
44
+ fi
45
+
46
+ # If still no UUID, nothing to write — exit gracefully.
47
+ if [ -z "$SESSION_UUID" ]; then
48
+ exit 0
49
+ fi
50
+
51
+ # UUID guard — accept only a canonical UUID (8-4-4-4-12 hex) to avoid
52
+ # stamping garbage values (e.g. a literal "null" or an arbitrary path stem).
53
+ UUID_PATTERN='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
54
+ if ! printf '%s' "$SESSION_UUID" | grep -qE "$UUID_PATTERN"; then
55
+ exit 0
56
+ fi
57
+
58
+ # Ensure the target directory exists.
59
+ SESSION_DIR="$PROJECT_DIR/.codebyplan/state/session"
60
+ mkdir -p "$SESSION_DIR" 2>/dev/null || true
61
+
62
+ # Stamp the session UUID with an ISO timestamp.
63
+ STAMPED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
64
+ printf '{"claude_session_id":"%s","stamped_at":"%s"}\n' "$SESSION_UUID" "$STAMPED_AT" \
65
+ > "$SESSION_DIR/session-id.json" 2>/dev/null || true
66
+
67
+ exit 0
@@ -614,6 +614,111 @@ else
614
614
 
615
615
  fi
616
616
 
617
+ # ===== HOOK SMOKE TESTS — cbp-session-id-stamp (CHK-231) =====
618
+ echo "## Hook Smoke Tests — cbp-session-id-stamp (CHK-231)"
619
+
620
+ STAMP_HOOK="$HOOKS_DIR/cbp-session-id-stamp.sh"
621
+
622
+ if [ ! -f "$STAMP_HOOK" ]; then
623
+ test_result "cbp-session-id-stamp.sh present" "passed" "missing"
624
+ else
625
+ test_result "cbp-session-id-stamp.sh present" "passed" "passed"
626
+
627
+ FIRST_LINE=$(head -1 "$STAMP_HOOK")
628
+ if echo "$FIRST_LINE" | grep -q '^#!/'; then
629
+ test_result "cbp-session-id-stamp.sh has shebang" "passed" "passed"
630
+ else
631
+ test_result "cbp-session-id-stamp.sh has shebang" "passed" "missing"
632
+ fi
633
+
634
+ if grep -q '@scope: org-shared' "$STAMP_HOOK"; then
635
+ test_result "cbp-session-id-stamp.sh has @scope: org-shared" "passed" "passed"
636
+ else
637
+ test_result "cbp-session-id-stamp.sh has @scope: org-shared" "passed" "missing"
638
+ fi
639
+
640
+ if bash -n "$STAMP_HOOK" 2>/dev/null; then
641
+ test_result "cbp-session-id-stamp.sh bash -n syntax ok" "passed" "passed"
642
+ else
643
+ test_result "cbp-session-id-stamp.sh bash -n syntax ok" "passed" "failed"
644
+ fi
645
+
646
+ # Case 1: session_id present in payload → session-id.json written with that UUID
647
+ ISO=$(mktemp -d)
648
+ mkdir -p "$ISO/.codebyplan"
649
+ printf '{}' > "$ISO/.codebyplan/repo.json"
650
+ FAKE_UUID="aaaabbbb-cccc-dddd-eeee-ffffffffffff"
651
+ PAYLOAD=$(jq -n --arg s "$FAKE_UUID" '{session_id:$s}')
652
+ EXIT_CODE=$(echo "$PAYLOAD" | CLAUDE_PROJECT_DIR="$ISO" bash "$STAMP_HOOK" >/dev/null 2>&1; echo $?)
653
+ STAMPED_ID=$(jq -r '.claude_session_id // empty' "$ISO/.codebyplan/state/session/session-id.json" 2>/dev/null)
654
+ if [ "$EXIT_CODE" = "0" ] && [ "$STAMPED_ID" = "$FAKE_UUID" ]; then
655
+ test_result "cbp-session-id-stamp.sh session_id present → session-id.json written with UUID" "passed" "passed"
656
+ else
657
+ test_result "cbp-session-id-stamp.sh session_id present → session-id.json written with UUID" "passed" "failed (exit=$EXIT_CODE stamped=$STAMPED_ID)"
658
+ fi
659
+ rm -rf "$ISO"
660
+
661
+ # Case 2: no session_id but transcript_path present (basename is a valid UUID) →
662
+ # UUID derived from transcript basename and written to session-id.json.
663
+ ISO=$(mktemp -d)
664
+ mkdir -p "$ISO/.codebyplan"
665
+ printf '{}' > "$ISO/.codebyplan/repo.json"
666
+ TRANSCRIPT_UUID="11112222-3333-4444-5555-666677778888"
667
+ PAYLOAD=$(jq -n --arg t "/tmp/sessions/${TRANSCRIPT_UUID}.jsonl" '{transcript_path:$t}')
668
+ EXIT_CODE=$(echo "$PAYLOAD" | CLAUDE_PROJECT_DIR="$ISO" bash "$STAMP_HOOK" >/dev/null 2>&1; echo $?)
669
+ STAMPED_ID=$(jq -r '.claude_session_id // empty' "$ISO/.codebyplan/state/session/session-id.json" 2>/dev/null)
670
+ if [ "$EXIT_CODE" = "0" ] && [ "$STAMPED_ID" = "$TRANSCRIPT_UUID" ]; then
671
+ test_result "cbp-session-id-stamp.sh transcript_path fallback → UUID derived from basename" "passed" "passed"
672
+ else
673
+ test_result "cbp-session-id-stamp.sh transcript_path fallback → UUID derived from basename" "passed" "failed (exit=$EXIT_CODE stamped=$STAMPED_ID)"
674
+ fi
675
+ rm -rf "$ISO"
676
+
677
+ # Case 3: neither session_id nor transcript_path → no-op (no file written), exit 0
678
+ ISO=$(mktemp -d)
679
+ mkdir -p "$ISO/.codebyplan"
680
+ printf '{}' > "$ISO/.codebyplan/repo.json"
681
+ PAYLOAD='{"tool_name":"some_tool"}'
682
+ EXIT_CODE=$(echo "$PAYLOAD" | CLAUDE_PROJECT_DIR="$ISO" bash "$STAMP_HOOK" >/dev/null 2>&1; echo $?)
683
+ STAMP_FILE="$ISO/.codebyplan/state/session/session-id.json"
684
+ if [ "$EXIT_CODE" = "0" ] && [ ! -f "$STAMP_FILE" ]; then
685
+ test_result "cbp-session-id-stamp.sh neither present → no-op, exit 0, no file written" "passed" "passed"
686
+ else
687
+ test_result "cbp-session-id-stamp.sh neither present → no-op, exit 0, no file written" "passed" "failed (exit=$EXIT_CODE file_exists=$([ -f "$STAMP_FILE" ] && echo yes || echo no))"
688
+ fi
689
+ rm -rf "$ISO"
690
+
691
+ # Case 4: outside a codebyplan repo (no .codebyplan/repo.json) → no-op, exit 0
692
+ ISO=$(mktemp -d)
693
+ FAKE_UUID="aaaabbbb-cccc-dddd-eeee-ffffffffffff"
694
+ PAYLOAD=$(jq -n --arg s "$FAKE_UUID" '{session_id:$s}')
695
+ EXIT_CODE=$(echo "$PAYLOAD" | CLAUDE_PROJECT_DIR="$ISO" bash "$STAMP_HOOK" >/dev/null 2>&1; echo $?)
696
+ STAMP_FILE="$ISO/.codebyplan/state/session/session-id.json"
697
+ if [ "$EXIT_CODE" = "0" ] && [ ! -f "$STAMP_FILE" ]; then
698
+ test_result "cbp-session-id-stamp.sh no repo.json → no-op, exit 0" "passed" "passed"
699
+ else
700
+ test_result "cbp-session-id-stamp.sh no repo.json → no-op, exit 0" "passed" "failed (exit=$EXIT_CODE file_exists=$([ -f "$STAMP_FILE" ] && echo yes || echo no))"
701
+ fi
702
+ rm -rf "$ISO"
703
+
704
+ # Case 5: session_id present but not a canonical UUID → guard rejects, no file written, exit 0
705
+ ISO=$(mktemp -d)
706
+ mkdir -p "$ISO/.codebyplan"
707
+ printf '{}' > "$ISO/.codebyplan/repo.json"
708
+ PAYLOAD=$(jq -n '{session_id:"not-a-valid-uuid"}')
709
+ EXIT_CODE=$(echo "$PAYLOAD" | CLAUDE_PROJECT_DIR="$ISO" bash "$STAMP_HOOK" >/dev/null 2>&1; echo $?)
710
+ STAMP_FILE="$ISO/.codebyplan/state/session/session-id.json"
711
+ if [ "$EXIT_CODE" = "0" ] && [ ! -f "$STAMP_FILE" ]; then
712
+ test_result "cbp-session-id-stamp.sh invalid UUID → guard rejects, no file written" "passed" "passed"
713
+ else
714
+ test_result "cbp-session-id-stamp.sh invalid UUID → guard rejects, no file written" "passed" "failed (exit=$EXIT_CODE file_exists=$([ -f "$STAMP_FILE" ] && echo yes || echo no))"
715
+ fi
716
+ rm -rf "$ISO"
717
+
718
+ fi
719
+
720
+ echo ""
721
+
617
722
  echo ""
618
723
 
619
724
  # ===== HOOK SMOKE TESTS — cbp-skill-context-guard model-aware tier (CHK-224) =====
@@ -0,0 +1,65 @@
1
+ # Handoff File Convention
2
+
3
+ Per-level handoff notes for cross-session context. Files live under
4
+ `.codebyplan/handoff/` and are committed (negation entry `!.codebyplan/handoff/`
5
+ in the managed `.gitignore` block).
6
+
7
+ ## Layout
8
+
9
+ | Level | Path template | Number format |
10
+ |------------|--------------------------------------------------|-------------------|
11
+ | repo | `.codebyplan/handoff/repo.md` | n/a (per-section) |
12
+ | checkpoint | `.codebyplan/handoff/checkpoint/<NNN>.md` | 3-digit zero-pad |
13
+ | task | `.codebyplan/handoff/task/<NNN>-<T>.md` | CHK zero-pad + T |
14
+ | standalone | `.codebyplan/handoff/standalone/<N>.md` | bare number |
15
+
16
+ ## repo.md — per-worktree sections
17
+
18
+ `repo.md` uses `## <worktree-label>` sections so multiple worktrees can write
19
+ to the same file without conflict. Label resolution order:
20
+
21
+ 1. Read `.codebyplan/state/worktrees.json`; find entry whose `branch` matches
22
+ the current git branch; use its `name`.
23
+ 2. Fallback: git branch name with `/` replaced by `-`.
24
+
25
+ Each CLI verb (`write`, `append`, `clear`) operates on the current worktree's
26
+ section only. Merge conflicts are structurally impossible: two worktrees write
27
+ to different `##` sections of the same file.
28
+
29
+ ## Empty = absent / whitespace-only
30
+
31
+ A handoff is considered **empty** when the file is absent OR its content is
32
+ whitespace-only. The CLI deletes the file when it becomes empty:
33
+
34
+ - Non-repo levels: `write --content ""` and `clear` both delete the file.
35
+ - repo level: `write --content ""` and `clear` remove the current worktree's
36
+ `##` section; the file is deleted when no non-empty sections remain.
37
+
38
+ All delete operations swallow ENOENT (idempotent).
39
+
40
+ ## CLI verbs
41
+
42
+ ```
43
+ codebyplan handoff read --level <l> [--number N] [--task T]
44
+ codebyplan handoff write --level <l> [--number N] [--task T] --content "..."
45
+ codebyplan handoff append --level <l> [--number N] [--task T] --content "..."
46
+ codebyplan handoff clear --level <l> [--number N] [--task T]
47
+ codebyplan handoff status [--json]
48
+ ```
49
+
50
+ ## status --json shape (stable contract)
51
+
52
+ ```json
53
+ {
54
+ "nonEmpty": [{ "level": "checkpoint", "identifier": "005", "path": "/abs/path" }],
55
+ "empty": []
56
+ }
57
+ ```
58
+
59
+ `status` is consumed by TASK-3 session-start/end gates to decide whether a
60
+ handoff prompt is shown. TASK-4 web UI reads the same files via the API.
61
+
62
+ ## Content format
63
+
64
+ Freeform markdown. No structured schema is enforced; the files are human-written
65
+ notes surfaced to Claude at session boundaries.
@@ -252,13 +252,29 @@
252
252
  "Bash(codebyplan cd:*)",
253
253
  "Bash(npx codebyplan cd:*)",
254
254
  "Bash(codebyplan cleanup-plan-folders:*)",
255
- "Bash(npx codebyplan cleanup-plan-folders:*)"
255
+ "Bash(npx codebyplan cleanup-plan-folders:*)",
256
+ "Bash(codebyplan handoff:*)",
257
+ "Bash(npx codebyplan handoff:*)"
256
258
  ]
257
259
  },
258
260
  "attribution": {
259
261
  "commit": "",
260
262
  "pr": ""
261
263
  },
264
+ "hooks": {
265
+ "SessionStart": [
266
+ {
267
+ "hooks": [
268
+ {
269
+ "_owner": "codebyplan-claude",
270
+ "_hook_id": "cbp-session-id-stamp.sh",
271
+ "type": "command",
272
+ "command": "bash ./.claude/hooks/cbp-session-id-stamp.sh"
273
+ }
274
+ ]
275
+ }
276
+ ]
277
+ },
262
278
  "showClearContextOnPlanAccept": false,
263
279
  "syntaxHighlightingDisabled": false
264
280
  }
@@ -72,6 +72,38 @@ Complete remaining tasks first, then run `/cbp-checkpoint-complete`.
72
72
  ```
73
73
  Stop here.
74
74
 
75
+ ### Step 2.6: Verify Checkpoint Handoff Is Resolved
76
+
77
+ Run `codebyplan handoff status --json` and parse the output.
78
+
79
+ Check if `nonEmpty[]` contains an entry where `level === 'checkpoint'` AND `identifier === '{NNN}'`
80
+ (the 3-digit zero-padded checkpoint number — e.g. `231`).
81
+
82
+ If a matching entry is found, BLOCK with:
83
+
84
+ ```
85
+ ## Cannot Complete Checkpoint — Handoff Unresolved
86
+
87
+ CHK-[NNN] has unresolved handoff content at `.codebyplan/handoff/checkpoint/[NNN].md`.
88
+
89
+ The handoff file contains scope that has not been resolved. You must clear it before
90
+ completing this checkpoint. Three options — pick exactly one:
91
+
92
+ 1. **Absorb** — address the pending scope now, then clear:
93
+ `codebyplan handoff clear --level checkpoint --number [NNN]`
94
+ 2. **Defer** — create a follow-up checkpoint or standalone task to cover the scope,
95
+ then clear: `codebyplan handoff clear --level checkpoint --number [NNN]`
96
+ 3. **Escalate** — move the content to the repo handoff:
97
+ `codebyplan handoff append --level repo --content "<content>"`
98
+ then clear: `codebyplan handoff clear --level checkpoint --number [NNN]`
99
+
100
+ Never silently delete handoff content — it represents tracked scope.
101
+ ```
102
+
103
+ (STOP)
104
+
105
+ If no matching entry: continue to Step 3.
106
+
75
107
  ### Step 3: Aggregate QA
76
108
 
77
109
  Collect QA results from all tasks and rounds. Build checkpoint-level QA summary:
@@ -48,6 +48,28 @@ Resuming from handoff:
48
48
  Next action: /<next-action>
49
49
  ```
50
50
 
51
+ ### Step 2.5 — Surface committed handoffs
52
+
53
+ Surface any committed handoff files before proceeding. Non-blocking.
54
+
55
+ 1. Run `codebyplan handoff status --json` → parse `nonEmpty[]`.
56
+ 2. If `nonEmpty` is empty → skip silently.
57
+ 3. For each entry in `nonEmpty`, read the content:
58
+ - `repo` → `codebyplan handoff read --level repo`
59
+ - `checkpoint` → `codebyplan handoff read --level checkpoint --number <identifier>`
60
+ - `task` → split identifier `"<NNN>-<T>"` → `codebyplan handoff read --level task --number <NNN> --task <T>`
61
+ - `standalone` → `codebyplan handoff read --level standalone --number <identifier>`
62
+ 4. Display:
63
+ ```
64
+ Handoff notes from previous session(s):
65
+
66
+ [<level>: <identifier>]
67
+ <content>
68
+
69
+ ---
70
+ ```
71
+ 5. Non-blocking — proceed to Step 3 regardless.
72
+
51
73
  ### Step 3 — Delete the handoff BEFORE re-invoking
52
74
 
53
75
  Delete `.codebyplan/clear/handoff.md` once its contents have been read and displayed:
@@ -80,7 +102,7 @@ the guard will deny again. Follow the same cycle: `/cbp-clear-prep` → `/clear`
80
102
  ## Integration
81
103
 
82
104
  - **Invoked by**: user after `/clear` following `/cbp-clear-prep`
83
- - **Reads**: `.codebyplan/clear/handoff.md`
105
+ - **Reads**: `.codebyplan/clear/handoff.md` (Step 1); `codebyplan handoff status --json` + `codebyplan handoff read` (Step 2.5 — committed handoff surfacing)
84
106
  - **Deletes**: `.codebyplan/clear/handoff.md` (Step 3, before resuming)
85
107
  - **Then invokes**: the skill from `next_action` via Skill tool
86
108
  - **Companion**: `.claude/skills/cbp-clear-prep/SKILL.md` writes the handoff
@@ -86,6 +86,27 @@ next_action: /<skill-name> <args if any>
86
86
  After /clear, run: /cbp-clear-continue
87
87
  ```
88
88
 
89
+ ### Step 4.5 — Flush carryover to committed handoff
90
+
91
+ If `checkpoint_number` from Step 2 is `unknown` → skip silently (no reliable key).
92
+
93
+ Otherwise:
94
+
95
+ 1. Compose a brief carryover note:
96
+ ```markdown
97
+ ## Context-clear carryover — CHK-<N>
98
+
99
+ Captured: <ISO now>
100
+ Blocked skill: <blocked_skill>
101
+ Next action: `<next_action>`
102
+
103
+ <in-flight notes from Step 3>
104
+ ```
105
+ 2. Run `codebyplan handoff write --level checkpoint --number <N> --content "<content>"`.
106
+ 3. Output: `Carryover handoff written to .codebyplan/handoff/checkpoint/<NNN>.md`.
107
+
108
+ Non-blocking — Step 5 continues regardless.
109
+
89
110
  ### Step 5 — Instruct the user
90
111
 
91
112
  Output exactly this summary (fill in the real values):
@@ -116,7 +137,7 @@ The user must run both commands manually.
116
137
  ## Integration
117
138
 
118
139
  - **Invoked when**: `cbp-skill-context-guard` PreToolUse hook emits `permissionDecision: deny`
119
- - **Writes**: `.codebyplan/clear/handoff.md`
140
+ - **Writes**: `.codebyplan/clear/handoff.md` (Step 4 — transient, gitignored); `codebyplan handoff write --level checkpoint --number <N>` (Step 4.5 — committed carryover flush; skipped when checkpoint_number unknown)
120
141
  - **Next**: user runs `/clear`, then `/cbp-clear-continue`
121
142
  - **Companion**: `.claude/skills/cbp-clear-continue/SKILL.md` reads `.codebyplan/clear/handoff.md`
122
143
  - **Guard hook**: `.claude/hooks/cbp-skill-context-guard.sh` — fires when context exceeds the model-aware threshold: `CBP_CONTEXT_WARN_TOKENS` (default 200000) for standard models; `CBP_CONTEXT_WARN_TOKENS_1M` (default 800000) for `[1m]`-context models. No model id found → standard tier (fail-conservative).
@@ -85,6 +85,42 @@ Stop here.
85
85
 
86
86
  `task.context.verify_verdict` must exist and have `verdict: 'READY'` (written by `/cbp-verify` Phase 6 when it runs at task scope — whole-repo `codebyplan check --scope task`, holistic `cbp-verify-reviewer`, and the single batched human walkthrough all passed). If absent or not `READY`, surface "Run `/cbp-verify` first" and stop.
87
87
 
88
+ ### Step 2.6: Verify Task Handoff Is Resolved
89
+
90
+ Run `codebyplan handoff status --json` and parse the output.
91
+
92
+ Check if `nonEmpty[]` contains an entry where `level === 'task'` AND `identifier === '{NNN}-{T}'`
93
+ (the 3-digit zero-padded checkpoint number, hyphen, task number — e.g. `231-3`).
94
+
95
+ > Gate scope is intentional: only **task**-level handoffs block here. Checkpoint-level handoffs
96
+ > (written by `/cbp-session-end` as resume-pointers) are deliberately scoped to
97
+ > `/cbp-checkpoint-complete`, NOT task finalize — do not "fix" this to gate on checkpoint level.
98
+
99
+ If a matching entry is found, BLOCK with:
100
+
101
+ ```
102
+ ## Cannot Complete Task — Handoff Unresolved
103
+
104
+ TASK-[N] has unresolved handoff content at `.codebyplan/handoff/task/[NNN]-[T].md`.
105
+
106
+ The handoff file contains scope that has not been resolved. You must clear it before
107
+ completing this task. Three options — pick exactly one:
108
+
109
+ 1. **Absorb** — address the pending scope in current work, then clear:
110
+ `codebyplan handoff clear --level task --number [NNN] --task [T]`
111
+ 2. **Defer** — create a follow-up task or standalone task to cover the scope,
112
+ then clear: `codebyplan handoff clear --level task --number [NNN] --task [T]`
113
+ 3. **Escalate** — move the content to the repo handoff:
114
+ `codebyplan handoff append --level repo --content "<content>"`
115
+ then clear: `codebyplan handoff clear --level task --number [NNN] --task [T]`
116
+
117
+ Never silently delete handoff content — it represents tracked scope.
118
+ ```
119
+
120
+ (STOP)
121
+
122
+ If no matching entry: continue to Step 3.
123
+
88
124
  ### Step 3: Verify QA and File Approval
89
125
 
90
126
  Load `task.qa` and `task.files_changed`:
@@ -22,46 +22,44 @@ Always write a session log for this session — **even if empty**. `/cbp-session
22
22
  2. Pull facts from local state files rather than narrating from memory:
23
23
  - Rounds added/completed, tasks advanced/completed during this session (read from `.codebyplan/state/checkpoints/` subtree)
24
24
  - Decisions, blockers, or discoveries recorded in checkpoint/task context
25
- 3. Run `codebyplan session update-log --id <log-id> --ended-at <now> --summary <text> --pending <text> --git-branch <current-branch>` (CLI write-through: updates `.codebyplan/state/session/current.json` + REST). Break-glass fallback: MCP `update_session_log` when the CLI is unavailable. Fields:
25
+ 3. Run `codebyplan session update-log --id <log-id> --ended-at <now> --git-branch <current-branch>` (CLI write-through: updates `.codebyplan/state/session/current.json` + REST). Break-glass fallback: MCP `update_session_log` when the CLI is unavailable. Fields:
26
26
  - `ended_at`: now (maps to the `closed_at` column per TASK-2 alias)
27
- - `summary`: concise — may be empty if nothing happened
28
- - `pending`: open items the next session should see first
29
27
  - `git_branch`: the current git branch (informational; `git rev-parse --abbrev-ref HEAD`)
30
- - **Do NOT pass `--handoff` / `handoff` field.** The DB handoff field is no longer written here. Omit entirely never pass `handoff:null` (it clobbers summary/pending content).
28
+ - Carryover notes now go to committed `.codebyplan/handoff/` files (Step 1.3)not the session log.
31
29
 
32
30
  ### Step 1.3: Optional Handoff File (mid-work only)
33
31
 
34
32
  When an active in-progress task or round exists, offer to write a handoff file so the next session can auto-resume. Skip entirely when not mid-work.
35
33
 
36
- 1. Read `.codebyplan/state/todos.json` (local-first) and check `rows[0]` (queue head, ordered by `sort_order`). If missing/stale, run `npx codebyplan sync` once and re-read. Break-glass fallback: MCP `get_todos({ repo_id })` when the state dir is absent and sync fails. The worker stamps `command`, `instructions`, `state`, and entity ids `checkpoint_id` / `task_id` / `round_id` on every row.
34
+ 1. Read `.codebyplan/state/todos.json` (local-first) and check `rows[0]` (queue head, ordered by `sort_order`). If missing/stale, run `npx codebyplan sync` once and re-read. Break-glass fallback: MCP `get_todos({ repo_id })` when the state dir is absent and sync fails.
37
35
 
38
36
  2. **If `rows[0]` exists and its `command` is non-empty** (active work in flight):
39
- - Determine the handoff file path. The directory is keyed by the human-facing **number** (`<NNN>` checkpoint number / `<N>` standalone task number) matching the committed `.codebyplan/checkpoint/<NNN>/` plan-artifact convention and the session-start resume probe (which globs by number). Resolve the number from local state (break-glass: MCP):
40
- - Checkpoint work: read `.codebyplan/state/checkpoints/<rows[0].checkpoint_id>.json` `number` (break-glass: MCP `get_checkpoints`). Path: `.codebyplan/checkpoint/<NNN>/handoff.md`.
41
- - Standalone work: read the standalone task's `number` from local standalone state (break-glass: MCP `get_standalone_tasks`). Path: `.codebyplan/standalone/<N>/handoff.md`.
42
- - Offer to write the file:
37
+ - Resolve the level and number (level is `checkpoint`/`standalone`, NOT `task`a session-end
38
+ handoff is a checkpoint-scope resume-pointer; `task`-level handoffs are reserved for explicit
39
+ user-deferred scope and gate only `/cbp-finalize`):
40
+ - Checkpoint work: read `.codebyplan/state/checkpoints/<rows[0].checkpoint_id>.json` → `number` (break-glass: MCP `get_checkpoints`). Level: `checkpoint`, number: `<NNN>` (3-digit zero-pad).
41
+ - Standalone work: read the standalone task `number` from local standalone state (break-glass: MCP `get_standalone_tasks`). Level: `standalone`, number: `<N>`.
42
+ - Offer to write:
43
43
  ```
44
- Write handoff file for next session?
45
- <path>
44
+ Write handoff for next session?
45
+ .codebyplan/handoff/<level>/<identifier>.md
46
46
 
47
47
  Reply: yes | no
48
48
  ```
49
- - On `yes`: write the file with this structure:
49
+ - On `yes`: run `codebyplan handoff write --level <level> --number <N> --content "<content>"` where content is:
50
50
  ```markdown
51
- ---
52
- command: <rows[0].command>
53
- state: <rows[0].state>
54
- captured_at: <ISO now>
55
- checkpoint_id: <rows[0].checkpoint_id or null>
56
- task_id: <rows[0].task_id or null>
57
- round_id: <rows[0].round_id or null>
58
- ---
59
-
60
- <rows[0].instructions — human-readable trigger reason>
51
+ ## Session handoff — CHK-<N>
52
+
53
+ Captured: <ISO now>
54
+ Next action: `<rows[0].command>`
55
+
56
+ <rows[0].instructions>
61
57
  ```
62
- - The file is committed with the session's final commit (Step 1.5) or the user can stage it manually.
58
+ (Standalone variant: `## Session handoff TASK-<N>` heading.)
59
+ - On `no`: skip — do not create an empty file.
60
+ - The committed file is picked up by the Step 1.5 infra-commit offer automatically (it lives under the managed `.gitignore` negation `!.codebyplan/handoff/`).
63
61
 
64
- 3. **If the queue is empty or `rows[0].command` is empty/idle**: skip — no handoff file is written.
62
+ 3. **If the queue is empty or `rows[0].command` is empty/idle**: skip.
65
63
 
66
64
  ### Step 1.5: Commit Non-Task Files
67
65
 
@@ -146,7 +144,7 @@ You can close this window.
146
144
 
147
145
  - **Triggered by**: user invocation (prompted by `/cbp-todo` when no work remains)
148
146
  - **Reads**: `.codebyplan/repo.json`; local-first reads (with `npx codebyplan sync` + MCP break-glass): `.codebyplan/state/session/current.json` (Step 1 resolve log), `.codebyplan/state/todos.json` (Step 1.3 mid-work detection + Step 1.5 active-task lookup), `.codebyplan/state/checkpoints/<id>/tasks/<id>/rounds/` (Step 1.5 task-file resolution); `codebyplan session freshness-gate` (Step 1.7 package-freshness gate, without --halt-on-update); `codebyplan session infra-files --json --task-files <csv>` (Step 1.5 infra-file set math)
149
- - **Writes**: `codebyplan session update-log --id <id> --git-branch <branch> ...` (Step 1 finalize — CLI write-through to `.codebyplan/state/session/current.json`; break-glass: MCP `update_session_log`; handoff field never passed), `codebyplan session create-log` (Step 1 fallback when no log exists; break-glass: MCP `create_session_log`), optional handoff file at `.codebyplan/checkpoint/<NNN>/handoff.md` or `.codebyplan/standalone/<N>/handoff.md` (Step 1.3, mid-work only), `codebyplan session update-state --action deactivate` (Step 3 — CLI write-through to `.codebyplan/state/session/state.json`; break-glass: MCP `update_session_state`)
147
+ - **Writes**: `codebyplan session update-log --id <id> --ended-at <now> --git-branch <branch>` (Step 1 finalize — CLI write-through to `.codebyplan/state/session/current.json`; break-glass: MCP `update_session_log`; summary/pending/handoff fields never passed), `codebyplan session create-log` (Step 1 fallback when no log exists; break-glass: MCP `create_session_log`), `codebyplan handoff write --level <checkpoint|standalone> --number <N>` (Step 1.3, mid-work only, committed under `.codebyplan/handoff/`), `codebyplan session update-state --action deactivate` (Step 3 — CLI write-through to `.codebyplan/state/session/state.json`; break-glass: MCP `update_session_state`)
150
148
  - **Spawns**: none
151
149
  - **Triggers**: none at the skill-contract level. Step 1.5 may invoke `/cbp-git-commit` inline on user approval; Step 1.7 may invoke `/cbp-git-commit` on the `result === 'updated'` path (committing changed `.claude/` and `.codebyplan/` paths).
152
150
  - **Paired with**: `/cbp-session-start`
@@ -78,7 +78,29 @@ The envelope shape:
78
78
  **Parse and branch**:
79
79
 
80
80
  - `status === 'update_halt'` → print `rendered_block` (the update-halt message) and **STOP**. No further writes, no `/cbp-todo` trigger.
81
- - Otherwise → print `rendered_block` (the Step 6 output block), then proceed to Step 5.7 and Step 7.
81
+ - Otherwise → print `rendered_block` (the Step 6 output block), then proceed to Step 5.5, Step 5.7, and Step 7.
82
+
83
+ ### Step 5.5: Surface Committed Handoff Notes
84
+
85
+ After printing the orchestrator output, surface any committed handoff files from previous sessions. Non-blocking.
86
+
87
+ 1. Run `codebyplan handoff status --json` → parse `nonEmpty[]`.
88
+ 2. If `nonEmpty` is empty → skip silently.
89
+ 3. For each entry in `nonEmpty`, read the content:
90
+ - `repo` → `codebyplan handoff read --level repo`
91
+ - `checkpoint` → `codebyplan handoff read --level checkpoint --number <identifier>`
92
+ - `task` → split identifier `"<NNN>-<T>"` → `codebyplan handoff read --level task --number <NNN> --task <T>`
93
+ - `standalone` → `codebyplan handoff read --level standalone --number <identifier>`
94
+ 4. Display:
95
+ ```
96
+ Handoff notes from previous session(s):
97
+
98
+ [<level>: <identifier>]
99
+ <content>
100
+
101
+ ---
102
+ ```
103
+ 5. Non-blocking — proceed to Step 5.7 regardless.
82
104
 
83
105
  ### Step 5.7: Commit Non-Task Files (Claude-side)
84
106
 
@@ -109,7 +131,7 @@ Route from `envelope.next_action`:
109
131
  ## Integration
110
132
 
111
133
  - **Triggered by**: user invocation, `/clear` recovery
112
- - **Reads**: MCP `health_check` (Step 0 hard gate — stays MCP unconditionally); `codebyplan session start --json` (Steps 1–5.8 — the orchestrator runs `codebyplan whoami --json` for caller identity and reads `.codebyplan/repo.json`, `.codebyplan/git.json`, `.codebyplan/state/session/current.json`, `.codebyplan/state/todos.json`, `.codebyplan/state/checkpoints/` entity files, `scripts/infra-drift.mjs`, `.codebyplan/architecture.json`, `.codebyplan/lsp.json`)
134
+ - **Reads**: MCP `health_check` (Step 0 hard gate — stays MCP unconditionally); `codebyplan session start --json` (Steps 1–5.8 — the orchestrator runs `codebyplan whoami --json` for caller identity and reads `.codebyplan/repo.json`, `.codebyplan/git.json`, `.codebyplan/state/session/current.json`, `.codebyplan/state/todos.json`, `.codebyplan/state/checkpoints/` entity files, `scripts/infra-drift.mjs`, `.codebyplan/architecture.json`, `.codebyplan/lsp.json`); `codebyplan handoff status --json` + `codebyplan handoff read --level <l> ...` (Step 5.5 — committed handoff surfacing)
113
135
  - **Writes**: orchestrator calls `codebyplan session update-state --action activate` (Step 7) and `codebyplan session create-log` (Step 8 — user-level; no worktree_id in the body) — both SKIPPED on Step 0 hard-fail and on `status: update_halt`
114
136
  - **Spawns**: none
115
137
  - **Triggers**: `/cbp-git-commit` (conditional, on user approval at Step 5.7), `envelope.handoff.command` (on `next_action: resume_handoff`), `/cbp-todo` (on `next_action: trigger_todo` or `commit_then_todo`); STOPS with no trigger on `next_action: stop` or `mcp_update_halt`
@@ -80,6 +80,38 @@ Stop here.
80
80
 
81
81
  `task.context.task_testing_output` must exist with `all_passed: true`. If not, surface "Run `/cbp-standalone-task-testing` first" and stop.
82
82
 
83
+ ### Step 2.7: Verify Standalone Task Handoff Is Resolved
84
+
85
+ Run `codebyplan handoff status --json` and parse the output.
86
+
87
+ Check if `nonEmpty[]` contains an entry where `level === 'standalone'` AND `identifier === '{N}'`
88
+ (the bare standalone task number — e.g. `45`).
89
+
90
+ If a matching entry is found, BLOCK with:
91
+
92
+ ```
93
+ ## Cannot Complete Standalone Task — Handoff Unresolved
94
+
95
+ Standalone TASK-[N] has unresolved handoff content at `.codebyplan/handoff/standalone/[N].md`.
96
+
97
+ The handoff file contains scope that has not been resolved. You must clear it before
98
+ completing this task. Three options — pick exactly one:
99
+
100
+ 1. **Absorb** — address the pending scope in current work, then clear:
101
+ `codebyplan handoff clear --level standalone --number [N]`
102
+ 2. **Defer** — create a new standalone task to cover the scope,
103
+ then clear: `codebyplan handoff clear --level standalone --number [N]`
104
+ 3. **Escalate** — move the content to the repo handoff:
105
+ `codebyplan handoff append --level repo --content "<content>"`
106
+ then clear: `codebyplan handoff clear --level standalone --number [N]`
107
+
108
+ Never silently delete handoff content — it represents tracked scope.
109
+ ```
110
+
111
+ (STOP)
112
+
113
+ If no matching entry: continue to Step 3.
114
+
83
115
  ### Step 3: Verify QA and File Approval
84
116
 
85
117
  Load `task.qa` and `task.files_changed`: