cclaw-cli 0.5.16 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
1
  const STAGE_EXAMPLES = {
2
- brainstorm: `### Context
2
+ brainstorm: `## Context
3
3
 
4
4
  - **Project state:** Monorepo with CI pipeline using custom release scripts. Release checks are scattered across shell scripts with no shared validation logic.
5
5
  - **Relevant existing code/patterns:** \`scripts/pre-publish.sh\` does metadata checks. \`src/release/\` has partial validation helpers.
6
6
 
7
- ### Problem
7
+ ## Problem
8
8
 
9
9
  - **What we're solving:** release checks are fragile and inconsistent between CI and local runs. Invalid metadata sometimes reaches npm publish.
10
10
  - **Success criteria:** invalid release preconditions are caught before publish with explicit operator feedback, in both CI and local workflows.
11
11
  - **Constraints:** no new runtime dependencies; must work within existing CI pipeline structure.
12
12
 
13
- ### Clarifying Questions
13
+ ## Clarifying Questions
14
14
 
15
15
  | # | Question | Answer | Decision impact |
16
16
  | --- | --- | --- | --- |
@@ -18,7 +18,7 @@ const STAGE_EXAMPLES = {
18
18
  | 2 | Should the validation logic live in a reusable module or stay as shell scripts? | Reusable module. | Architecture: shared TypeScript module imported by CI and local tooling, not duplicated shell scripts. |
19
19
  | 3 | For v1, prioritize rapid delivery or maximum configurability? | Rapid delivery. | Minimal deterministic validation surface; defer plugin/config system to v2. |
20
20
 
21
- ### Approaches
21
+ ## Approaches
22
22
 
23
23
  | Approach | Architecture | Trade-offs | Recommendation |
24
24
  | --- | --- | --- | --- |
@@ -26,33 +26,33 @@ const STAGE_EXAMPLES = {
26
26
  | B: Hardened shell scripts | Keep existing script approach, add stricter checks and error messages. | Lowest effort. Weak reuse, CI/local divergence risk grows over time. | Viable fallback if TS module is blocked. |
27
27
  | C: Full release framework | New release orchestrator with plugin system, config files, rollback commands. | Maximum flexibility. High risk, delivery delay, over-engineered for current needs. | Not recommended for v1. |
28
28
 
29
- ### Selected Direction
29
+ ## Selected Direction
30
30
 
31
31
  - **Approach:** A — Reusable validation module
32
32
  - **Rationale:** shared TS module gives consistent behavior in CI and local, avoids script duplication, and stays within the no-new-dependency constraint.
33
33
  - **Approval:** approved
34
34
 
35
- ### Design
35
+ ## Design
36
36
 
37
37
  - **Architecture:** single \`release-validator\` module in \`src/release/\` exporting typed check functions. CI script and local CLI both import and run the same checks.
38
38
  - **Key components:** \`validateMetadata()\`, \`validateChangelog()\`, \`validateVersion()\` — each returns a typed result with error details. A \`runAll()\` orchestrator runs checks and exits non-zero on any failure.
39
39
  - **Data flow:** package.json + CHANGELOG.md → validator module → structured result → CI/CLI renders human-readable report.
40
40
 
41
- ### Assumptions and Open Questions
41
+ ## Assumptions and Open Questions
42
42
 
43
43
  - **Assumptions:** CI remains the primary execution path; existing release metadata files remain the source of truth; v1 prioritizes determinism over customization.
44
44
  - **Open questions:** What exact rollback sequence for failed publish? Should status output include machine-readable JSON alongside markdown?
45
45
 
46
- ### Notes for the next stage
46
+ ## Notes for the next stage
47
47
 
48
48
  Carry the no-new-dependency constraint and hard-block behavior directly into scope in/out boundaries.`,
49
- scope: `### Scope contract
49
+ scope: `## Scope contract
50
50
 
51
51
  **Mode selected:** SELECTIVE EXPANSION
52
52
  **Default heuristic used:** feature enhancement -> selective
53
53
  **Mode-specific analysis result:** hold-scope baseline accepted first; one expansion accepted (degraded-state UX), one deferred (real-time channel upgrade).
54
54
 
55
- ### Prime Directives (applied)
55
+ ## Prime Directives (applied)
56
56
 
57
57
  - Zero silent failures: every delivery failure maps to a visible degraded state.
58
58
  - Named error surfaces: stream disconnect, auth drift, and publisher timeout are explicit.
@@ -60,11 +60,11 @@ Carry the no-new-dependency constraint and hard-block behavior directly into sco
60
60
  - Interaction edge cases in scope: double-open panel, reconnect after sleep, stale tab state.
61
61
  - Observability in scope: stream error counter, publish-to-visible lag metric, and alert threshold.
62
62
 
63
- ### Premise challenge result
63
+ ## Premise challenge result
64
64
 
65
65
  The original premise (“add notifications”) was reframed to **“ensure users know when an action requires follow-up”**, which expands the solution space beyond toast spam to include durable inbox items, empty states, and recovery paths when delivery fails.
66
66
 
67
- ### Dream State Mapping
67
+ ## Dream State Mapping
68
68
 
69
69
  | Stage | Statement |
70
70
  | --- | --- |
@@ -73,7 +73,7 @@ The original premise (“add notifications”) was reframed to **“ensure users
73
73
  | **12-MONTH IDEAL** | Unified notification center with reliable multi-channel fan-out and user-level routing preferences. |
74
74
  | **Alignment verdict** | Aligned: this scope builds the durability foundation without prematurely committing to channel expansion. |
75
75
 
76
- ### Mode-Specific Analysis
76
+ ## Mode-Specific Analysis
77
77
 
78
78
  **Selected mode:** SELECTIVE EXPANSION
79
79
 
@@ -81,7 +81,7 @@ The original premise (“add notifications”) was reframed to **“ensure users
81
81
  - **Expansion evaluated — degraded-state UX (accepted):** Adding an explicit "live updates paused" banner and polling fallback turns a reliability gap into a visible, recoverable state. Low incremental effort (S), high user trust payoff.
82
82
  - **Expansion evaluated — real-time channel upgrade (deferred):** WebSocket channel provides lower latency but requires new infra (connection pool, auth handshake). Not justified for current load; deferred to post-v1 validation.
83
83
 
84
- ### Implementation Alternatives
84
+ ## Implementation Alternatives
85
85
 
86
86
  | Option | Summary | Effort (S/M/L/XL) | Risk | Pros | Cons | Reuses |
87
87
  | --- | --- | --- | --- | --- | --- | --- |
@@ -89,7 +89,7 @@ The original premise (“add notifications”) was reframed to **“ensure users
89
89
  | **B (recommended)** | SSE live updates + REST fallback snapshot | M | Med | Better timeliness, graceful degradation | Requires reconnect handling | Existing event publisher + REST path |
90
90
  | **C (ideal architecture)** | Event bus + WebSocket channel + feed projection | XL | High | Strong long-term scalability | Overbuilt for current demand | Partial reuse of publisher only |
91
91
 
92
- ### Temporal Interrogation
92
+ ## Temporal Interrogation
93
93
 
94
94
  | Time slice | Likely decision pressure | Lock now or defer? | Reason |
95
95
  | --- | --- | --- | --- |
@@ -98,7 +98,7 @@ The original premise (“add notifications”) was reframed to **“ensure users
98
98
  | **HOUR 4-5 (integration)** | Handling gaps between snapshot and stream cursor | **Lock now** | Prevent silent data loss during reconnect windows |
99
99
  | **HOUR 6+ (polish/tests)** | Banner copy tone and polling cadence tuning | **Defer** | Safe to iterate after baseline reliability is proven |
100
100
 
101
- ### In scope / out of scope / deferred
101
+ ## In scope / out of scope / deferred
102
102
 
103
103
  | Category | Items |
104
104
  | --- | --- |
@@ -106,29 +106,29 @@ The original premise (“add notifications”) was reframed to **“ensure users
106
106
  | **Out of scope** | Email/SMS/push providers; marketing campaigns; per-user notification preferences beyond on/off |
107
107
  | **Deferred** | WebSocket channel; rich media attachments in notifications; full-text search across historical events |
108
108
 
109
- ### Discretion Areas
109
+ ## Discretion Areas
110
110
 
111
111
  - Client-side badge rendering strategy (optimistic vs server-confirmed) is implementation discretion.
112
112
  - Polling fallback backoff curve is implementation discretion if degraded-state UX remains explicit.
113
113
 
114
- ### Error & Rescue Registry (sample entry)
114
+ ## Error & Rescue Registry (sample entry)
115
115
 
116
116
  | Capability | Failure mode | Detection | Fallback |
117
117
  | --- | --- | --- | --- |
118
118
  | Event delivery | SSE connection drops mid-session | Client \`EventSource\` error event + heartbeat timeout | Fall back to REST polling every 30s until SSE reconnect succeeds; show subtle “live updates paused” banner |
119
119
 
120
- ### Completion Dashboard
120
+ ## Completion Dashboard
121
121
 
122
122
  - Checklist findings: 9/9 complete (complex path)
123
123
  - Resolved decisions count: 7
124
124
  - Unresolved decisions: None
125
125
 
126
- ### Scope Summary
126
+ ## Scope Summary
127
127
 
128
128
  - Accepted scope: durable feed + SSE + explicit degraded UX.
129
129
  - Deferred: WebSocket channel and rich-media/search enhancements.
130
130
  - Explicitly excluded: outbound channels and marketing workflows for v1.`,
131
- design: `### Codebase Investigation (blast-radius files)
131
+ design: `## Codebase Investigation (blast-radius files)
132
132
 
133
133
  | File | Current responsibility | Patterns discovered |
134
134
  | --- | --- | --- |
@@ -139,7 +139,7 @@ The original premise (“add notifications”) was reframed to **“ensure users
139
139
 
140
140
  Discovery: existing EventEmitter-based bus has no durability — notifications must add persistence layer on top, not replace the bus.
141
141
 
142
- ### Search Before Building (sample result)
142
+ ## Search Before Building (sample result)
143
143
 
144
144
  | Layer | Label | What to reuse first |
145
145
  | --- | --- | --- |
@@ -147,7 +147,7 @@ Discovery: existing EventEmitter-based bus has no durability — notifications m
147
147
  | Layer 2 | existing codebase | Existing auth middleware, existing API client wrapper, existing feature flags helper |
148
148
  | Layer 3 | npm | A small, well-maintained SSE helper (only if Layer 1–2 cannot cover framing/reconnect ergonomics) |
149
149
 
150
- ### Architecture Diagram (mandatory)
150
+ ## Architecture Diagram (mandatory)
151
151
 
152
152
  \`\`\`
153
153
  ┌─────────────┐ ┌──────────────┐ ┌────────────────┐
@@ -163,7 +163,7 @@ Discovery: existing EventEmitter-based bus has no durability — notifications m
163
163
 
164
164
  Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Queue (persist) → Read Model (project).
165
165
 
166
- ### What Already Exists
166
+ ## What Already Exists
167
167
 
168
168
  | Sub-problem | Existing code/library | Layer | Reuse decision |
169
169
  | --- | --- | --- | --- |
@@ -172,7 +172,7 @@ Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Q
172
172
  | SSE framing | None | Layer 3 | Evaluate \`better-sse\` npm package |
173
173
  | Notification schema | None | — | New: define in \`src/schemas/notification.ts\` |
174
174
 
175
- ### Failure Mode Table
175
+ ## Failure Mode Table
176
176
 
177
177
  | Failure | Trigger | Detection | Mitigation | User impact |
178
178
  | --- | --- | --- | --- | --- |
@@ -180,13 +180,13 @@ Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Q
180
180
  | Duplicate publish | Retry after timeout | Dedupe key check in outbox | Upsert with idempotency key | None (transparent) |
181
181
  | Queue backpressure | Spike >1000 events/s | Queue depth metric alarm | Back-pressure signal to publisher, shed non-critical events | Delayed delivery of low-priority notifications |
182
182
 
183
- ### Test Strategy
183
+ ## Test Strategy
184
184
 
185
185
  - **Unit:** validator functions, dedupe-key logic, event schema factories — target 90%+ line coverage.
186
186
  - **Integration:** publisher → outbox → read-model pipeline via in-memory DB; SSE reconnect with simulated drops.
187
187
  - **E2E:** one happy-path browser test (publish → feed visible) and one degraded-path test (SSE down → REST fallback + banner).
188
188
 
189
- ### Performance Budget
189
+ ## Performance Budget
190
190
 
191
191
  | Critical path | Metric | Target | Measurement method |
192
192
  | --- | --- | --- | --- |
@@ -194,13 +194,13 @@ Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Q
194
194
  | Feed snapshot load | p99 response time | ≤ 200 ms | Load test with 1 000 items per user |
195
195
  | SSE reconnect | Time to first event after drop | ≤ 3 s | Simulated disconnect in integration suite |
196
196
 
197
- ### NOT in scope
197
+ ## NOT in scope
198
198
 
199
199
  - Outbound channels (email, push, SMS) — deferred to v2.
200
200
  - Admin notification management UI — separate workstream.
201
201
  - Notification preferences / mute rules — requires user settings redesign.
202
202
 
203
- ### Parallelization Strategy
203
+ ## Parallelization Strategy
204
204
 
205
205
  | Module | Depends on | Parallel lane | Conflict risk |
206
206
  | --- | --- | --- | --- |
@@ -208,18 +208,18 @@ Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Q
208
208
  | Publisher + outbox (T2) | T1 | Lane A | None |
209
209
  | Client feed + SSE (T3) | T1, T2 | Lane B (after T1) | Shared event type definitions |
210
210
 
211
- ### Unresolved Decisions
211
+ ## Unresolved Decisions
212
212
 
213
213
  | Decision | Status | Options | Missing info | Default if unanswered |
214
214
  | --- | --- | --- | --- | --- |
215
215
  | Feed storage model | OPEN | (A) append-only event log, (B) mutable rows, (C) hybrid | Load testing results on read patterns | (A) append-only — safest for audit trail |
216
216
 
217
- ### Interface sketch (non-binding)
217
+ ## Interface sketch (non-binding)
218
218
 
219
219
  - **Client → server:** \`GET /api/me/notifications/snapshot?limit=50\` plus optional cursor parameters (if adopted).
220
220
  - **Server → client:** \`GET /api/me/notifications/stream\` as SSE with periodic heartbeats.
221
221
 
222
- ### Completion Dashboard
222
+ ## Completion Dashboard
223
223
 
224
224
  | Review Section | Status | Issues |
225
225
  | --- | --- | --- |
@@ -231,10 +231,10 @@ Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Q
231
231
 
232
232
  **Decisions made:** 4 | **Unresolved:** 1 (feed storage model)
233
233
 
234
- ### Quality bar for this stage
234
+ ## Quality bar for this stage
235
235
 
236
236
  Design output should be **reviewable by someone who did not attend brainstorming**: they can trace from constraints → components → open decisions without reading code.`,
237
- spec: `### Acceptance Criteria
237
+ spec: `## Acceptance Criteria
238
238
 
239
239
  | ID | Criterion (observable/measurable/falsifiable) | Design Decision Ref |
240
240
  | --- | --- | --- |
@@ -242,7 +242,7 @@ Design output should be **reviewable by someone who did not attend brainstorming
242
242
  | AC-2 | Given the same logical notification is published twice with the same dedupe key, when the client processes the stream, the feed contains exactly one visible item for that key. | Architecture: dedupe-key in event schema |
243
243
  | AC-3 | Given the live connection is unavailable, when the user opens the notifications panel, the UI shows a non-blocking "live updates paused" banner and loads the latest snapshot via REST within 2 seconds. | Architecture: REST fallback + degraded UX |
244
244
 
245
- ### Edge Cases
245
+ ## Edge Cases
246
246
 
247
247
  | Criterion ID | Boundary case | Error case |
248
248
  | --- | --- | --- |
@@ -250,12 +250,12 @@ Design output should be **reviewable by someone who did not attend brainstorming
250
250
  | AC-2 | Two events with identical dedupe key arrive within same SSE frame (boundary: only one row rendered). | Dedupe-key field missing — reject event at publisher and log error. |
251
251
  | AC-3 | SSE disconnects after exactly 30 s heartbeat timeout (boundary: banner appears within 1 s of timeout). | REST snapshot endpoint returns 500 — panel shows "unable to load" with retry button. |
252
252
 
253
- ### Constraints and Assumptions
253
+ ## Constraints and Assumptions
254
254
 
255
255
  - **Constraints:** Max feed size 1 000 items per user. SSE heartbeat interval 30 s (server-side). REST snapshot p99 \u2264 200 ms. No new runtime dependencies.
256
256
  - **Assumptions:** Users have a single active session at a time for v1. Existing auth middleware provides user context. Event publisher is single-writer per user.
257
257
 
258
- ### Testability Map
258
+ ## Testability Map
259
259
 
260
260
  | Criterion ID | Verification approach | Command/manual steps |
261
261
  | --- | --- | --- |
@@ -263,11 +263,11 @@ Design output should be **reviewable by someone who did not attend brainstorming
263
263
  | AC-2 | Unit test: publish same dedupe key twice \u2192 assert single row in feed store. | \`pnpm vitest run tests/unit/dedupe-feed.test.ts\` |
264
264
  | AC-3 | E2E test: kill SSE transport \u2192 assert banner visible + REST snapshot loads. | \`pnpm playwright test tests/e2e/degraded-mode.spec.ts\` |
265
265
 
266
- ### Approval
266
+ ## Approval
267
267
 
268
268
  - Approved by: user
269
269
  - Date: 2026-04-14`,
270
- plan: `### Dependency Graph
270
+ plan: `## Dependency Graph
271
271
 
272
272
  \`\`\`
273
273
  T-1 ──▶ T-2 ──▶ T-3
@@ -277,7 +277,7 @@ T-1 ──▶ T-2 ──▶ T-3
277
277
 
278
278
  Parallel opportunity: T-1 is a prerequisite for both T-2 and T-3 (T-3 also needs T-2).
279
279
 
280
- ### Dependency Waves
280
+ ## Dependency Waves
281
281
 
282
282
  #### Wave 1 (foundation)
283
283
  - Task IDs: T-1
@@ -295,7 +295,7 @@ Parallel opportunity: T-1 is a prerequisite for both T-2 and T-3 (T-3 also needs
295
295
 
296
296
  Execution rule: complete and verify each wave before starting the next wave.
297
297
 
298
- ### Task List
298
+ ## Task List
299
299
 
300
300
  | Task ID | Description | Acceptance criterion | Verification command | Effort |
301
301
  | --- | --- | --- | --- | --- |
@@ -303,7 +303,7 @@ Execution rule: complete and verify each wave before starting the next wave.
303
303
  | T-2 | Implement publisher + outbox write path | AC-1: integration test (happy path publish) | \`\`\`pnpm vitest run tests/integration/publisher.test.ts\`\`\` |
304
304
  | T-3 | Implement client feed + SSE subscribe + REST fallback | AC-1, AC-2, AC-3: e2e tests including degraded mode | \`\`\`pnpm playwright test tests/e2e/notification-feed.spec.ts\`\`\` |
305
305
 
306
- ### Acceptance Mapping
306
+ ## Acceptance Mapping
307
307
 
308
308
  | Criterion ID | Task IDs |
309
309
  | --- | --- |
@@ -311,17 +311,17 @@ Execution rule: complete and verify each wave before starting the next wave.
311
311
  | AC-2 (idempotency) | T-1, T-2 |
312
312
  | AC-3 (failure visibility) | T-3 |
313
313
 
314
- ### Risk Assessment
314
+ ## Risk Assessment
315
315
 
316
316
  | Task/Wave | Risk | Likelihood | Impact | Mitigation |
317
317
  | --- | --- | --- | --- | --- |
318
318
  | T-3 (Wave 3) | SSE reconnect logic complex | Medium | High | Spike reconnect in isolation before integrating with feed UI |
319
319
  | Wave 2 → 3 | Publisher API contract may shift | Low | Medium | Pin contract in T-1 schema; T-2 integration test validates |
320
320
 
321
- ### WAIT_FOR_CONFIRM
321
+ ## WAIT_FOR_CONFIRM
322
322
  - Status: pending
323
323
  - Confirmed by:`,
324
- tdd: `### RED Evidence
324
+ tdd: `## RED Evidence
325
325
 
326
326
  | Slice | Test name | Command | Failure output summary |
327
327
  | --- | --- | --- | --- |
@@ -329,7 +329,7 @@ Execution rule: complete and verify each wave before starting the next wave.
329
329
  | S-2 (publisher outbox) | publishes event to outbox with dedupe key | \`\`\`pnpm vitest run tests/integration/publisher.test.ts\`\`\` | publishToOutbox is not a function |
330
330
  | S-3 (client feed + fallback) | shows notification within 5s via SSE | \`\`\`pnpm playwright test tests/e2e/notification-feed.spec.ts\`\`\` | Element [data-testid="feed-item"] not found |
331
331
 
332
- ### Acceptance Mapping
332
+ ## Acceptance Mapping
333
333
 
334
334
  | Slice | Plan task ID | Spec criterion ID |
335
335
  | --- | --- | --- |
@@ -337,7 +337,7 @@ Execution rule: complete and verify each wave before starting the next wave.
337
337
  | S-2 | T-2 | AC-1 |
338
338
  | S-3 | T-3 | AC-1, AC-2, AC-3 |
339
339
 
340
- ### Failure Analysis
340
+ ## Failure Analysis
341
341
 
342
342
  | Slice | Expected missing behavior | Actual failure reason |
343
343
  | --- | --- | --- |
@@ -345,22 +345,22 @@ Execution rule: complete and verify each wave before starting the next wave.
345
345
  | S-2 | publishToOutbox function not implemented | Function not found — correct: write path missing |
346
346
  | S-3 | Feed UI not rendered, SSE not connected | DOM element missing — correct: client component not built |
347
347
 
348
- ### GREEN Evidence
348
+ ## GREEN Evidence
349
349
 
350
350
  - Full suite command: \`\`\`pnpm vitest run && pnpm playwright test\`\`\`
351
351
  - Full suite result: 47 tests passed (3 new + 44 existing), 0 failed, 0 skipped
352
352
 
353
- ### REFACTOR Notes
353
+ ## REFACTOR Notes
354
354
 
355
355
  - What changed: Extracted \`\`\`mergeLatestByDedupeKey\`\`\` helper from inline loop in \`\`\`summarizeDedupedFeed\`\`\`; moved SSE reconnect logic into \`\`\`useSSEConnection\`\`\` hook.
356
356
  - Why: Dedupe merge logic is reused by both publisher and client; reconnect logic was duplicated across components.
357
357
  - Behavior preserved: Full suite re-run confirms 47/47 pass after refactor.
358
358
 
359
- ### Traceability
359
+ ## Traceability
360
360
 
361
361
  - Plan task IDs: T-1, T-2, T-3
362
362
  - Spec criterion IDs: AC-1, AC-2, AC-3`,
363
- review: `### Layer 1 Verdict
363
+ review: `## Layer 1 Verdict
364
364
 
365
365
  | Criterion | Verdict | Evidence |
366
366
  | --- | --- | --- |
@@ -368,7 +368,7 @@ Execution rule: complete and verify each wave before starting the next wave.
368
368
  | AC-2: Dedupe — one visible item per key | PARTIAL | Unit tests cover publisher dedupe; UI merge path lacks test for race reordering (\`feedStore.test.ts\` missing case) |
369
369
  | AC-3: Degraded mode + REST snapshot | PASS | \`NotificationsPanel.tsx:112-140\` renders banner + calls snapshot endpoint |
370
370
 
371
- ### Layer 2 Findings
371
+ ## Layer 2 Findings
372
372
 
373
373
  | ID | Severity | Category | Description | Status |
374
374
  | --- | --- | --- | --- | --- |
@@ -376,12 +376,12 @@ Execution rule: complete and verify each wave before starting the next wave.
376
376
  | R-2 | Important | performance | \`feedStore.merge()\` does full-array scan on every SSE event; O(n) per event where n is feed length. | open |
377
377
  | R-3 | Suggestion | architecture | SSE reconnect logic duplicated across \`useNotifications\` and \`usePresence\`; extract shared hook. | open |
378
378
 
379
- ### Review Army Contract
379
+ ## Review Army Contract
380
380
 
381
381
  - See \`07-review-army.json\`
382
382
  - Reconciliation summary: 1 duplicate collapsed (R-1 reported by spec-reviewer and code-reviewer), 0 conflicts
383
383
 
384
- ### Review Readiness Dashboard
384
+ ## Review Readiness Dashboard
385
385
 
386
386
  - Layer 1 complete: yes (3/3 criteria)
387
387
  - Layer 2 complete: yes (5 sections reviewed)
@@ -389,16 +389,16 @@ Execution rule: complete and verify each wave before starting the next wave.
389
389
  - Open critical blockers: 1 (R-1)
390
390
  - Ship recommendation: BLOCKED until R-1 resolved
391
391
 
392
- ### Severity Summary
392
+ ## Severity Summary
393
393
 
394
394
  - Critical: 1
395
395
  - Important: 1
396
396
  - Suggestion: 1
397
397
 
398
- ### Final Verdict
398
+ ## Final Verdict
399
399
 
400
400
  - BLOCKED`,
401
- ship: `### Preflight Results
401
+ ship: `## Preflight Results
402
402
 
403
403
  - Review verdict: APPROVED_WITH_CONCERNS (R-1 resolved, R-2 accepted as known debt)
404
404
  - Build: pass (\`pnpm build\` succeeds)
@@ -407,25 +407,25 @@ Execution rule: complete and verify each wave before starting the next wave.
407
407
  - Type-check: pass (\`pnpm typecheck\` clean)
408
408
  - Working tree clean: yes (\`git status\` shows no uncommitted changes)
409
409
 
410
- ### Release Notes
410
+ ## Release Notes
411
411
 
412
412
  - **Added:** In-app notification feed with SSE updates and REST fallback snapshotting (AC-1, AC-3).
413
413
  - **Changed:** Notification payloads now include a stable dedupe key for idempotent rendering (AC-2).
414
414
  - **Fixed:** Panel no longer drops the newest item when reconnecting after sleep/resume.
415
415
  - **Breaking changes:** None.
416
416
 
417
- ### Rollback Plan
417
+ ## Rollback Plan
418
418
 
419
419
  - Trigger conditions: error rate on \`/notifications/stream\` exceeds 5% for >5 minutes, or p95 publish-to-visible lag exceeds 10s.
420
420
  - Rollback steps: \`git revert <merge-sha> && git push origin main\` then redeploy; if DB migrations shipped, run \`2026_04_12_notifications_cursor_down.sql\` before traffic.
421
421
  - Verification steps: confirm error rate returns to pre-release baseline within 10 minutes; smoke-test feed panel manually.
422
422
 
423
- ### Monitoring
423
+ ## Monitoring
424
424
 
425
425
  - Metrics/logs to watch: error rate on \`/notifications/stream\` and snapshot endpoint for 24h; p95 publish-to-visible lag via metrics dashboard.
426
426
  - Risk note (if no monitoring): N/A — monitoring is in place.
427
427
 
428
- ### Finalization
428
+ ## Finalization
429
429
 
430
430
  - Selected enum: FINALIZE_OPEN_PR
431
431
  - Selected label: B
@@ -436,5 +436,14 @@ export function stageExamples(stage) {
436
436
  const examples = STAGE_EXAMPLES[stage];
437
437
  if (!examples)
438
438
  return "";
439
- return `## Examples\n\nConcrete samples of what good output looks like for this stage.\n\n${examples}\n`;
439
+ return [
440
+ "## Examples",
441
+ "",
442
+ "Concrete artifact samples. These mirror the exact heading levels agents must use when authoring the stage artifact (all H2 `##` sections), so they are presented inside a markdown fence to avoid collapsing into the SKILL outline.",
443
+ "",
444
+ "```markdown",
445
+ examples,
446
+ "```",
447
+ ""
448
+ ].join("\n");
440
449
  }
@@ -11,6 +11,7 @@ export interface HookRuntimeOptions {
11
11
  export declare const RUNTIME_SHELL_DETECT_ROOT = "HARNESS=\"codex\"\nif [ -n \"${CLAUDE_PROJECT_DIR:-}\" ]; then\n HARNESS=\"claude\"\nelif [ -n \"${CURSOR_PROJECT_DIR:-}\" ] || [ -n \"${CURSOR_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"cursor\"\nelif [ -n \"${OPENCODE_PROJECT_DIR:-}\" ] || [ -n \"${OPENCODE_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"opencode\"\nfi\n\nROOT=\"\"\nfor candidate in \"${CCLAW_PROJECT_ROOT:-}\" \"${CLAUDE_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_ROOT:-}\" \"${OPENCODE_PROJECT_DIR:-}\" \"${OPENCODE_PROJECT_ROOT:-}\" \"${PWD:-}\"; do\n if [ -n \"$candidate\" ] && [ -d \"$candidate/.cclaw\" ]; then\n ROOT=\"$candidate\"\n break\n fi\ndone\nif [ -z \"$ROOT\" ]; then\n ROOT=\"${CCLAW_PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-${CURSOR_PROJECT_ROOT:-${OPENCODE_PROJECT_DIR:-${OPENCODE_PROJECT_ROOT:-${PWD}}}}}}}\"\nfi";
12
12
  export declare function sessionStartScript(_options?: HookRuntimeOptions): string;
13
13
  export declare function stopCheckpointScript(): string;
14
+ export declare function preCompactScript(): string;
14
15
  export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
15
16
  export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
16
17
  export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
@@ -616,6 +616,151 @@ case "$HARNESS" in
616
616
  esac
617
617
  `;
618
618
  }
619
+ export function preCompactScript() {
620
+ return `#!/usr/bin/env bash
621
+ # cclaw pre-compact hook — generated by cclaw sync
622
+ # Persists a session digest before the harness compacts/clears context, so the
623
+ # next session-start hook can restore the most important state without the agent
624
+ # having to re-derive it from scratch.
625
+ set -uo pipefail
626
+
627
+ ${DETECT_ROOT}
628
+
629
+ INPUT=$(cat 2>/dev/null || echo '{}')
630
+
631
+ STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
632
+ STATE_FILE="$STATE_DIR/flow-state.json"
633
+ DELEGATION_FILE="$STATE_DIR/delegation-log.json"
634
+ KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.md"
635
+ DIGEST_FILE="$STATE_DIR/session-digest.md"
636
+ DIGEST_TMP="$STATE_DIR/session-digest.md.tmp.$$"
637
+
638
+ mkdir -p "$STATE_DIR" 2>/dev/null || true
639
+
640
+ cleanup_digest_tmp() {
641
+ rm -f "$DIGEST_TMP" 2>/dev/null || true
642
+ }
643
+ trap cleanup_digest_tmp EXIT INT TERM
644
+
645
+ STAGE="none"
646
+ TRACK="standard"
647
+ COMPLETED="0"
648
+ SKIPPED=""
649
+ ACTIVE_RUN="none"
650
+ PASSED_GATES=""
651
+ BLOCKED_GATES=""
652
+
653
+ if [ -f "$STATE_FILE" ]; then
654
+ if command -v jq >/dev/null 2>&1; then
655
+ STAGE=$(jq -r '.currentStage // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
656
+ TRACK=$(jq -r '.track // "standard"' "$STATE_FILE" 2>/dev/null || echo "standard")
657
+ COMPLETED=$(jq -r '(.completedStages // []) | length' "$STATE_FILE" 2>/dev/null || echo "0")
658
+ SKIPPED=$(jq -r '(.skippedStages // []) | join(",")' "$STATE_FILE" 2>/dev/null || echo "")
659
+ ACTIVE_RUN=$(jq -r '.activeRunId // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
660
+ PASSED_GATES=$(jq -r --arg stage "$STAGE" '(.stageGates[$stage].passed // []) | join(",")' "$STATE_FILE" 2>/dev/null || echo "")
661
+ BLOCKED_GATES=$(jq -r --arg stage "$STAGE" '(.stageGates[$stage].blocked // []) | join(",")' "$STATE_FILE" 2>/dev/null || echo "")
662
+ elif command -v python3 >/dev/null 2>&1; then
663
+ OUTPUT=$(python3 - "$STATE_FILE" <<'PY'
664
+ import json, sys
665
+ try:
666
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
667
+ data = json.load(fh)
668
+ except Exception:
669
+ data = {}
670
+ stage = data.get("currentStage") or "none"
671
+ track = data.get("track") or "standard"
672
+ completed = data.get("completedStages") or []
673
+ skipped = data.get("skippedStages") or []
674
+ run = data.get("activeRunId") or "none"
675
+ gates = (data.get("stageGates") or {}).get(stage) or {}
676
+ passed = gates.get("passed") or []
677
+ blocked = gates.get("blocked") or []
678
+ print(stage)
679
+ print(track)
680
+ print(len(completed) if isinstance(completed, list) else 0)
681
+ print(",".join(skipped) if isinstance(skipped, list) else "")
682
+ print(run)
683
+ print(",".join(passed) if isinstance(passed, list) else "")
684
+ print(",".join(blocked) if isinstance(blocked, list) else "")
685
+ PY
686
+ )
687
+ {
688
+ IFS= read -r STAGE
689
+ IFS= read -r TRACK
690
+ IFS= read -r COMPLETED
691
+ IFS= read -r SKIPPED
692
+ IFS= read -r ACTIVE_RUN
693
+ IFS= read -r PASSED_GATES
694
+ IFS= read -r BLOCKED_GATES
695
+ } <<EOF
696
+ $OUTPUT
697
+ EOF
698
+ fi
699
+ fi
700
+
701
+ DELEGATION_PENDING=""
702
+ if [ -f "$DELEGATION_FILE" ] && command -v jq >/dev/null 2>&1; then
703
+ DELEGATION_PENDING=$(jq -r --arg stage "$STAGE" '
704
+ (.entries // [])
705
+ | map(select((.stage // "") == $stage and (.status // "") != "completed" and (.status // "") != "waived"))
706
+ | map(.agent // "unknown")
707
+ | unique
708
+ | join(",")
709
+ ' "$DELEGATION_FILE" 2>/dev/null || echo "")
710
+ fi
711
+
712
+ KNOWLEDGE_TAIL=""
713
+ if [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
714
+ KNOWLEDGE_TAIL=$(tail -n 12 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
715
+ fi
716
+
717
+ GIT_HEAD=""
718
+ GIT_BRANCH=""
719
+ GIT_DIRTY="unknown"
720
+ if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
721
+ GIT_HEAD=$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo "")
722
+ GIT_BRANCH=$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
723
+ if [ -n "$(git -C "$ROOT" status --porcelain 2>/dev/null)" ]; then
724
+ GIT_DIRTY="dirty"
725
+ else
726
+ GIT_DIRTY="clean"
727
+ fi
728
+ fi
729
+
730
+ TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
731
+
732
+ {
733
+ printf '# Session Digest\n'
734
+ printf '_Generated by pre-compact hook at %s_\n\n' "$TS"
735
+ printf '## Flow snapshot\n'
736
+ printf '- track: %s\n' "$TRACK"
737
+ printf '- current stage: %s\n' "$STAGE"
738
+ printf '- completed: %s stages\n' "$COMPLETED"
739
+ printf '- skipped: %s\n' "\${SKIPPED:-(none)}"
740
+ printf '- run: %s\n\n' "$ACTIVE_RUN"
741
+ printf '## Gates (current stage)\n'
742
+ printf '- passed: %s\n' "\${PASSED_GATES:-(none)}"
743
+ printf '- blocked: %s\n\n' "\${BLOCKED_GATES:-(none)}"
744
+ printf '## Outstanding delegations\n'
745
+ printf '- pending: %s\n\n' "\${DELEGATION_PENDING:-(none)}"
746
+ printf '## Git\n'
747
+ printf '- branch: %s\n' "\${GIT_BRANCH:-(unknown)}"
748
+ printf '- head: %s\n' "\${GIT_HEAD:-(unknown)}"
749
+ printf '- worktree: %s\n\n' "$GIT_DIRTY"
750
+ if [ -n "$KNOWLEDGE_TAIL" ]; then
751
+ printf '## Knowledge tail\n'
752
+ printf '%s\n' "$KNOWLEDGE_TAIL"
753
+ fi
754
+ } > "$DIGEST_TMP" 2>/dev/null || true
755
+
756
+ if [ -s "$DIGEST_TMP" ]; then
757
+ mv "$DIGEST_TMP" "$DIGEST_FILE" 2>/dev/null || rm -f "$DIGEST_TMP" 2>/dev/null || true
758
+ fi
759
+
760
+ trap - EXIT INT TERM
761
+ exit 0
762
+ `;
763
+ }
619
764
  // ---------------------------------------------------------------------------
620
765
  // hooks.json generators are defined in observe.ts (shared across harnesses).
621
766
  // ---------------------------------------------------------------------------