cclaw-cli 0.5.9 → 0.5.11

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.
@@ -242,7 +242,16 @@ export async function lintArtifact(projectRoot, stage) {
242
242
  }
243
243
  const raw = await fs.readFile(absFile, "utf8");
244
244
  const sections = extractH2Sections(raw);
245
+ const isTrivialOverride = schema.trivialOverrideSections &&
246
+ schema.trivialOverrideSections.length > 0 &&
247
+ /trivial.change|mini.design|escape.hatch/iu.test(raw);
248
+ const overrideSet = isTrivialOverride
249
+ ? new Set(schema.trivialOverrideSections.map((s) => normalizeHeadingTitle(s).toLowerCase()))
250
+ : null;
245
251
  for (const v of schema.artifactValidation) {
252
+ const effectiveRequired = overrideSet
253
+ ? overrideSet.has(normalizeHeadingTitle(v.section).toLowerCase()) ? true : false
254
+ : v.required;
246
255
  const hasHeading = headingPresent(sections, v.section);
247
256
  const body = hasHeading ? sectionBodyByName(sections, v.section) : null;
248
257
  const validation = body === null
@@ -251,7 +260,7 @@ export async function lintArtifact(projectRoot, stage) {
251
260
  const found = hasHeading && validation.ok;
252
261
  findings.push({
253
262
  section: v.section,
254
- required: v.required,
263
+ required: effectiveRequired,
255
264
  rule: v.validationRule,
256
265
  found,
257
266
  details: found
@@ -180,12 +180,34 @@ 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
184
+
185
+ - **Unit:** validator functions, dedupe-key logic, event schema factories — target 90%+ line coverage.
186
+ - **Integration:** publisher → outbox → read-model pipeline via in-memory DB; SSE reconnect with simulated drops.
187
+ - **E2E:** one happy-path browser test (publish → feed visible) and one degraded-path test (SSE down → REST fallback + banner).
188
+
189
+ ### Performance Budget
190
+
191
+ | Critical path | Metric | Target | Measurement method |
192
+ | --- | --- | --- | --- |
193
+ | Publish → visible in feed | p95 latency | ≤ 5 s | Integration test with deterministic clock + production Datadog SLO |
194
+ | Feed snapshot load | p99 response time | ≤ 200 ms | Load test with 1 000 items per user |
195
+ | SSE reconnect | Time to first event after drop | ≤ 3 s | Simulated disconnect in integration suite |
196
+
183
197
  ### NOT in scope
184
198
 
185
199
  - Outbound channels (email, push, SMS) — deferred to v2.
186
200
  - Admin notification management UI — separate workstream.
187
201
  - Notification preferences / mute rules — requires user settings redesign.
188
202
 
203
+ ### Parallelization Strategy
204
+
205
+ | Module | Depends on | Parallel lane | Conflict risk |
206
+ | --- | --- | --- | --- |
207
+ | Notification schema (T1) | — | Lane A | None |
208
+ | Publisher + outbox (T2) | T1 | Lane A | None |
209
+ | Client feed + SSE (T3) | T1, T2 | Lane B (after T1) | Shared event type definitions |
210
+
189
211
  ### Unresolved Decisions
190
212
 
191
213
  | Decision | Status | Options | Missing info | Default if unanswered |
@@ -212,42 +234,39 @@ Data flow: Gateway → Service (validate + enrich) → Publisher (fan-out) → Q
212
234
  ### Quality bar for this stage
213
235
 
214
236
  Design output should be **reviewable by someone who did not attend brainstorming**: they can trace from constraints → components → open decisions without reading code.`,
215
- spec: `### Acceptance criteria (Given / When / Then)
237
+ spec: `### Acceptance Criteria
216
238
 
217
- **Criterion 1 — delivery**
218
-
219
- - **Given** a signed-in user with an active session
220
- - **When** the server publishes a new notification event for that user
221
- - **Then** the client feed shows the new item within 5 seconds without a full page reload
222
-
223
- **Criterion 2 — idempotency**
224
-
225
- - **Given** the same logical notification is published twice with the same dedupe key
226
- - **When** the client processes the stream
227
- - **Then** the feed contains exactly one visible item for that key
239
+ | ID | Criterion (observable/measurable/falsifiable) |
240
+ | --- | --- |
241
+ | AC-1 | Given a signed-in user with an active session, when the server publishes a new notification event for that user, the client feed shows the new item within 5 seconds without a full page reload. |
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. |
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. |
228
244
 
229
- **Criterion 3 — failure visibility**
245
+ ### Edge Cases
230
246
 
231
- - **Given** the live connection is unavailable
232
- - **When** the user opens the notifications panel
233
- - **Then** the UI shows a non-blocking degraded state and still loads the latest snapshot via REST
247
+ | Criterion ID | Boundary case | Error case |
248
+ | --- | --- | --- |
249
+ | AC-1 | Notification published during client reconnect window (boundary: \u2264 5 s delivery still holds after reconnect). | Server publish fails mid-write — client never receives event; REST snapshot fills gap. |
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
+ | 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. |
234
252
 
235
- ### Non-testable fixed (comparison)
253
+ ### Constraints and Assumptions
236
254
 
237
- | Vague (non-testable) | Fixed (observable + testable) |
238
- | --- | --- |
239
- | “Notifications should be fast.” | “p95 time from publish to visible feed update ≤ 5s under steady load.” |
240
- | “The system should handle errors gracefully.” | “If SSE is down, panel renders REST snapshot within 2s and shows ‘live updates paused’.” |
241
- | “Users should not see duplicates.” | “For dedupe key K, repeated publishes produce exactly one row with key K.” |
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
+ - **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.
242
257
 
243
- ### Test doubles / fixtures (planning notes)
258
+ ### Testability Map
244
259
 
245
- - Use a deterministic clock for the “within 5 seconds” criterion in automated tests.
246
- - Use a fake transport for SSE in unit tests; reserve browser-level tests for one happy path + one degraded path.
260
+ | Criterion ID | Verification approach | Command/manual steps |
261
+ | --- | --- | --- |
262
+ | AC-1 | Integration test: publish event \u2192 assert feed contains item within 5 s (deterministic clock). | \`pnpm vitest run tests/integration/notification-delivery.test.ts\` |
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
+ | AC-3 | E2E test: kill SSE transport \u2192 assert banner visible + REST snapshot loads. | \`pnpm playwright test tests/e2e/degraded-mode.spec.ts\` |
247
265
 
248
- ### Traceability reminder
266
+ ### Approval
249
267
 
250
- Every criterion should map to **at least one automated check** (unit/integration/e2e) before the work is considered “specified enough” to start TDD in earnest.`,
268
+ - Approved by: user
269
+ - Date: 2026-04-14`,
251
270
  plan: `### Task breakdown (sample)
252
271
 
253
272
  | ID | Title | depends_on | acceptance_criteria | estimated_effort |
@@ -786,8 +786,15 @@ export default function cclawPlugin(ctx) {
786
786
  return parts.join("\\n");
787
787
  }
788
788
 
789
- function emitBootstrap() {
790
- console.log(buildBootstrap());
789
+ let bootstrapCache = "";
790
+
791
+ function refreshBootstrapCache() {
792
+ bootstrapCache = buildBootstrap();
793
+ }
794
+
795
+ function getBootstrap() {
796
+ if (!bootstrapCache) refreshBootstrapCache();
797
+ return bootstrapCache;
791
798
  }
792
799
 
793
800
  async function runHookScript(scriptFileName, payload = {}) {
@@ -843,7 +850,10 @@ export default function cclawPlugin(ctx) {
843
850
  eventType === "session.compacted" ||
844
851
  eventType === "session.cleared"
845
852
  ) {
846
- emitBootstrap();
853
+ // Avoid writing directly to stdout in lifecycle hooks because it can
854
+ // interfere with OpenCode TUI rendering. Bootstrap is injected via
855
+ // the system transform hook instead.
856
+ refreshBootstrapCache();
847
857
  }
848
858
  if (eventType === "session.idle") {
849
859
  await runHookScript("stop-checkpoint.sh", { loop_count: 0 });
@@ -868,7 +878,7 @@ export default function cclawPlugin(ctx) {
868
878
  await runHookScript("context-monitor.sh", payload);
869
879
  },
870
880
  "experimental.chat.system.transform": (payload) => {
871
- const bootstrap = buildBootstrap();
881
+ const bootstrap = getBootstrap();
872
882
  if (typeof payload === "string") {
873
883
  return payload.includes("cclaw loaded.") ? payload : \`\${payload}\\n\\n\${bootstrap}\`;
874
884
  }
@@ -71,6 +71,8 @@ export interface StageSchema {
71
71
  decisionRecordFormat?: string;
72
72
  /** When true, stage skill includes wave auto-execute guidance (tdd). */
73
73
  waveExecutionAllowed?: boolean;
74
+ /** Sections that remain required even when the trivial-change escape hatch is active (design only). */
75
+ trivialOverrideSections?: string[];
74
76
  /** Agent names that MUST be dispatched (or waived) before stage transition — derived from mandatory auto-subagent rows. */
75
77
  mandatoryDelegations: string[];
76
78
  }
@@ -412,7 +412,8 @@ const DESIGN = {
412
412
  "For design baseline approval: present the full baseline. **STOP.** Do NOT proceed until user explicitly approves the design.",
413
413
  "Take a firm position on every recommendation. Do NOT hedge with 'it depends' or 'you could do either'. State your opinion, then justify it.",
414
414
  "Use pushback patterns for weak framing: if the user says 'it's just a small change', respond with 'small changes to shared interfaces have outsized blast radius — let's map it'. If 'we'll refactor later', respond with 'later never comes — show me the refactor ticket or do it now'.",
415
- "When the user's proposed architecture is suboptimal, say so directly. Offer the alternative with concrete trade-offs, do not bury criticism in praise."
415
+ "When the user's proposed architecture is suboptimal, say so directly. Offer the alternative with concrete trade-offs, do not bury criticism in praise.",
416
+ "When encountering ambiguity, classify it before acting: (A) ask user for missing info, (B) enumerate interpretations and pick one with justification, (C) propose hypothesis with validation path. Do NOT silently resolve ambiguity."
416
417
  ],
417
418
  process: [
418
419
  "Read upstream artifacts (brainstorm, scope).",
@@ -514,7 +515,8 @@ const DESIGN = {
514
515
  { name: "Owner Preference Alignment", description: "Every recommendation must align with project conventions (DRY, test style, minimal diff, edge-case rigor). Read existing patterns before recommending new ones." },
515
516
  { name: "Failure Is Information", description: "A design that fails fast and visibly is better than one that silently degrades. Map every failure mode and make it observable. Undetected failures compound." },
516
517
  { name: "Search Breadth Before Depth", description: "Before committing to a design path, survey the full solution space: stdlib, existing code, open-source, prior art. A 30-minute search can save a 30-hour custom build." },
517
- { name: "Outside Voice", description: "When confidence is high and options seem obvious, that is exactly when to seek contradiction. Ask: what would a skeptical reviewer challenge here? What assumption am I not questioning?" }
518
+ { name: "Outside Voice", description: "When confidence is high and options seem obvious, that is exactly when to seek contradiction. Ask: what would a skeptical reviewer challenge here? What assumption am I not questioning?" },
519
+ { name: "Ambiguity Classification", description: "Before resolving any unclear requirement, classify it: (A) Insufficient information — ask the user. (B) Multiple valid interpretations — enumerate and pick with justification. (C) Genuinely unknown — propose hypothesis and validation path. Never treat all ambiguity the same way." }
518
520
  ],
519
521
  reviewSections: [
520
522
  {
@@ -578,17 +580,23 @@ const DESIGN = {
578
580
  traceabilityRule: "Every architecture decision must trace to a scope boundary. Every downstream spec requirement must trace to a design decision."
579
581
  },
580
582
  artifactValidation: [
583
+ { section: "Codebase Investigation", required: true, validationRule: "Must list blast-radius files with current responsibilities and discovered patterns." },
584
+ { section: "Search Before Building", required: true, validationRule: "For each technical choice: Layer 1 (exact match), Layer 2 (partial match), Layer 3 (inspiration), EUREKA labels with reuse-first default." },
581
585
  { section: "Architecture Boundaries", required: true, validationRule: "Must list component boundaries with ownership." },
582
586
  { section: "Architecture Diagram", required: true, validationRule: "At least one diagram (ASCII, Mermaid, or image) showing component boundaries and data flow direction." },
583
587
  { section: "Data Flow", required: true, validationRule: "Must include happy path, nil input, empty input, upstream error paths." },
584
588
  { section: "Failure Mode Table", required: true, validationRule: "Each failure mode has: trigger, detection, mitigation, user impact." },
585
589
  { section: "Test Strategy", required: true, validationRule: "Must define unit/integration/e2e expectations with coverage targets." },
590
+ { section: "Performance Budget", required: true, validationRule: "For each critical path: metric name, target threshold, and measurement method." },
586
591
  { section: "What Already Exists", required: true, validationRule: "For each sub-problem: existing code/library found (Layer 1-3/EUREKA label), reuse decision, and adaptation needed." },
587
592
  { section: "NOT in scope", required: true, validationRule: "Work considered and explicitly deferred with one-line rationale." },
588
593
  { section: "Parallelization Strategy", required: false, validationRule: "If multi-module: dependency table, parallel lanes, conflict flags." },
589
594
  { section: "Unresolved Decisions", required: false, validationRule: "If any: what info is missing, who provides it, default if unanswered." },
595
+ { section: "Interface Contracts", required: false, validationRule: "If present: for each module boundary list produces (outputs) and consumes (inputs) with data types." },
596
+ { section: "Patterns to Mirror", required: false, validationRule: "If present: list discovered codebase patterns to follow, with file references and rationale for each." },
590
597
  { section: "Completion Dashboard", required: true, validationRule: "Lists every review section with status (clear / issues-found-resolved / issues-open), decision count, and unresolved items (or 'None')." }
591
598
  ],
599
+ trivialOverrideSections: ["Architecture Boundaries", "NOT in scope", "Completion Dashboard"],
592
600
  namedAntiPattern: {
593
601
  title: "Architecture Will Emerge While Coding",
594
602
  description: "Emergent architecture is a myth for non-trivial systems. What actually emerges is accidental complexity, incompatible module boundaries, and tech debt that costs 10x to fix later. Lock architecture explicitly before writing code."
@@ -636,7 +644,8 @@ const SPEC = {
636
644
  "Resolve ambiguity before moving to plan. Challenge vague language.",
637
645
  "Capture assumptions explicitly, not implicitly.",
638
646
  "Require user confirmation on the written spec. **STOP.** Do NOT proceed to plan until user approves.",
639
- "For each criterion, ask: how would you test this? If the answer is unclear, rewrite."
647
+ "For each criterion, ask: how would you test this? If the answer is unclear, rewrite.",
648
+ "When encountering ambiguity, classify it before acting: (A) ask user for missing info, (B) enumerate interpretations and pick one with justification, (C) propose hypothesis with validation path. Do NOT silently resolve ambiguity."
640
649
  ],
641
650
  process: [
642
651
  "Define measurable acceptance criteria.",
@@ -705,7 +714,8 @@ const SPEC = {
705
714
  cognitivePatterns: [
706
715
  { name: "Observable Over Descriptive", description: "Requirements describe what can be observed, not what should feel like. Replace every adjective with a measurement." },
707
716
  { name: "Boundary Precision", description: "Every acceptance criterion has boundary conditions. What is the minimum valid input? Maximum? What happens at the edges?" },
708
- { name: "Assumption Surfacing", description: "Implicit assumptions are invisible requirements. Force every assumption into an explicit statement. If you cannot name the assumption, you have not found it yet." }
717
+ { name: "Assumption Surfacing", description: "Implicit assumptions are invisible requirements. Force every assumption into an explicit statement. If you cannot name the assumption, you have not found it yet." },
718
+ { name: "Ambiguity Classification", description: "Before resolving any unclear requirement, classify it: (A) Insufficient information — ask the user. (B) Multiple valid interpretations — enumerate and pick with justification. (C) Genuinely unknown — propose hypothesis and validation path. Never treat all ambiguity the same way." }
709
719
  ],
710
720
  reviewSections: [],
711
721
  completionStatus: ["DONE", "DONE_WITH_CONCERNS", "BLOCKED"],
@@ -715,12 +725,17 @@ const SPEC = {
715
725
  traceabilityRule: "Every acceptance criterion must trace to a design decision. Every downstream plan task must trace to a spec criterion."
716
726
  },
717
727
  artifactValidation: [
718
- { section: "Acceptance Criteria", required: true, validationRule: "Each criterion is observable, measurable, and falsifiable." },
728
+ { section: "Acceptance Criteria", required: true, validationRule: "Each criterion is observable, measurable, and falsifiable. Table should include a Design Decision Ref column tracing back to design artifact." },
719
729
  { section: "Edge Cases", required: true, validationRule: "At least one boundary and one error condition per criterion." },
720
730
  { section: "Constraints and Assumptions", required: true, validationRule: "All implicit assumptions surfaced. Constraints have sources." },
721
- { section: "Testability Map", required: true, validationRule: "Each criterion maps to a concrete test description." },
731
+ { section: "Testability Map", required: true, validationRule: "Each criterion maps to a concrete test description with verification approach (unit, integration, e2e, manual) and command or manual steps." },
732
+ { section: "Interface Contracts", required: false, validationRule: "If present: for each module boundary list produces (outputs) and consumes (inputs) with data types." },
722
733
  { section: "Approval", required: true, validationRule: "Explicit user approval marker present." }
723
- ]
734
+ ],
735
+ namedAntiPattern: {
736
+ title: "Implementation Will Clarify Requirements",
737
+ description: "Unclear specs do not become clear during coding — they become contradictory implementations, rework, and scope creep. If a requirement cannot be stated in observable, testable terms right now, it is not ready for implementation. Rewrite it until it is falsifiable."
738
+ }
724
739
  };
725
740
  // ---------------------------------------------------------------------------
726
741
  // PLAN
@@ -162,6 +162,11 @@ export const ARTIFACT_TEMPLATES = {
162
162
  - Integration:
163
163
  - E2E:
164
164
 
165
+ ## Performance Budget
166
+ | Critical path | Metric | Target | Measurement method |
167
+ |---|---|---|---|
168
+ | | | | |
169
+
165
170
  ## NOT in scope
166
171
  -
167
172
 
@@ -169,6 +174,16 @@ export const ARTIFACT_TEMPLATES = {
169
174
  - Parallel lanes:
170
175
  - Conflict risks:
171
176
 
177
+ ## Patterns to Mirror
178
+ | Pattern | Source file | Rationale |
179
+ |---|---|---|
180
+ | | | |
181
+
182
+ ## Interface Contracts
183
+ | Module | Produces | Consumes |
184
+ |---|---|---|
185
+ | | | |
186
+
172
187
  ## Unresolved Decisions
173
188
  | Decision | Missing info | Owner | Default |
174
189
  |---|---|---|---|
@@ -188,9 +203,9 @@ export const ARTIFACT_TEMPLATES = {
188
203
  "04-spec.md": `# Specification Artifact
189
204
 
190
205
  ## Acceptance Criteria
191
- | ID | Criterion (observable/measurable/falsifiable) |
192
- |---|---|
193
- | AC-1 | |
206
+ | ID | Criterion (observable/measurable/falsifiable) | Design Decision Ref |
207
+ |---|---|---|
208
+ | AC-1 | | |
194
209
 
195
210
  ## Edge Cases
196
211
  | Criterion ID | Boundary case | Error case |
@@ -206,6 +221,11 @@ export const ARTIFACT_TEMPLATES = {
206
221
  |---|---|---|
207
222
  | AC-1 | | |
208
223
 
224
+ ## Interface Contracts
225
+ | Module | Produces | Consumes |
226
+ |---|---|---|
227
+ | | | |
228
+
209
229
  ## Approval
210
230
  - Approved by:
211
231
  - Date:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {