ccqa 0.13.1 → 1.0.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.
@@ -9,13 +9,15 @@ import { z } from "zod";
9
9
  * gets the same types.
10
10
  */
11
11
  /**
12
- * A run's outcome. The hub never executes a run is created when a client
13
- * pushes the report of an already-finished `ccqa run`, so the only two
14
- * terminal states that reach the hub are "passed" and "failed".
12
+ * A run's outcome. "running" is non-terminal/mutablean opened run
13
+ * (`POST /runs/open`) sits in this state while it's patched incrementally.
14
+ * "passed"/"failed" are terminal/immutable: a pushed run (`POST /runs`)
15
+ * starts there directly, and an opened run ends there once done.
15
16
  */
16
17
  declare const RunStatusSchema: z.ZodEnum<{
17
18
  passed: "passed";
18
19
  failed: "failed";
20
+ running: "running";
19
21
  }>;
20
22
  type RunStatus = z.infer<typeof RunStatusSchema>;
21
23
  /**
@@ -30,6 +32,7 @@ declare const RunSchema: z.ZodObject<{
30
32
  status: z.ZodEnum<{
31
33
  passed: "passed";
32
34
  failed: "failed";
35
+ running: "running";
33
36
  }>;
34
37
  kind: z.ZodDefault<z.ZodEnum<{
35
38
  run: "run";
@@ -133,7 +136,9 @@ type PutActualCauseRequest = z.infer<typeof PutActualCauseRequestSchema>;
133
136
  *
134
137
  * Two kinds share one namespace:
135
138
  * - "guidance": the record/live prompt bundle — `.user.md` (human-maintained)
136
- * and `.agent.md` (auto-rewritten by `ccqa run --update-agent-prompt`).
139
+ * and `.agent.md` (auto-rewritten by `ccqa run --update-agent-prompt`)
140
+ * plus `triage.user`, the human-maintained guidance injected into the
141
+ * failure-analysis (triage) prompt.
137
142
  * - "custom-prompt": `analysis-custom-prompt` — Claude-written calibration guidance
138
143
  * learned from graded triage cases, injected into the failure-analysis
139
144
  * prompt at run time.
@@ -142,10 +147,304 @@ type PutActualCauseRequest = z.infer<typeof PutActualCauseRequestSchema>;
142
147
  * extensions; `PROMPT_LOCAL_PATHS` is the single mapping every caller (push,
143
148
  * pull, learn, UI) goes through so the two never drift.
144
149
  */
145
- declare const PROMPT_NAMES: readonly ["record.user", "record.agent", "live.user", "live.agent", "analysis-custom-prompt"];
150
+ declare const PROMPT_NAMES: readonly ["record.user", "record.agent", "live.user", "live.agent", "playwright.user", "playwright.agent", "runn.user", "runn.agent", "triage.user", "analysis-custom-prompt"];
146
151
  type PromptName = (typeof PROMPT_NAMES)[number];
147
152
  //#endregion
148
153
  //#region src/report/schema.d.ts
154
+ declare const ReportSpecResultSchema: z.ZodObject<{
155
+ feature: z.ZodString;
156
+ spec: z.ZodString;
157
+ title: z.ZodNullable<z.ZodString>;
158
+ target: z.ZodOptional<z.ZodString>;
159
+ status: z.ZodEnum<{
160
+ passed: "passed";
161
+ failed: "failed";
162
+ skipped: "skipped";
163
+ }>;
164
+ skipReason: z.ZodOptional<z.ZodString>;
165
+ testCounts: z.ZodNullable<z.ZodObject<{
166
+ total: z.ZodNumber;
167
+ passed: z.ZodNumber;
168
+ failed: z.ZodNumber;
169
+ }, z.core.$strip>>;
170
+ durationMs: z.ZodNullable<z.ZodNumber>;
171
+ assertions: z.ZodNullable<z.ZodArray<z.ZodObject<{
172
+ name: z.ZodString;
173
+ status: z.ZodEnum<{
174
+ passed: "passed";
175
+ failed: "failed";
176
+ skipped: "skipped";
177
+ }>;
178
+ durationMs: z.ZodNullable<z.ZodNumber>;
179
+ }, z.core.$strip>>>;
180
+ analysis: z.ZodNullable<z.ZodObject<{
181
+ label: z.ZodEnum<{
182
+ TEST_DRIFT: "TEST_DRIFT";
183
+ SPEC_CHANGE: "SPEC_CHANGE";
184
+ PRODUCT_BUG: "PRODUCT_BUG";
185
+ UNKNOWN: "UNKNOWN";
186
+ }>;
187
+ confidence: z.ZodNumber;
188
+ subDiagnosis: z.ZodOptional<z.ZodEnum<{
189
+ TIMING_ISSUE: "TIMING_ISSUE";
190
+ OVER_ASSERTION: "OVER_ASSERTION";
191
+ SELECTOR_DRIFT: "SELECTOR_DRIFT";
192
+ DATA_MISSING: "DATA_MISSING";
193
+ NONE: "NONE";
194
+ }>>;
195
+ headline: z.ZodDefault<z.ZodString>;
196
+ recommendation: z.ZodDefault<z.ZodString>;
197
+ evidence: z.ZodArray<z.ZodObject<{
198
+ file: z.ZodOptional<z.ZodString>;
199
+ detail: z.ZodString;
200
+ }, z.core.$strip>>;
201
+ reasoning: z.ZodString;
202
+ }, z.core.$strip>>;
203
+ analysisSkipped: z.ZodNullable<z.ZodString>;
204
+ driftIssues: z.ZodNullable<z.ZodArray<z.ZodObject<{
205
+ severity: z.ZodEnum<{
206
+ OK: "OK";
207
+ WARN: "WARN";
208
+ ERROR: "ERROR";
209
+ }>;
210
+ category: z.ZodEnum<{
211
+ assertable: "assertable";
212
+ blocks: "blocks";
213
+ granularity: "granularity";
214
+ unimplemented: "unimplemented";
215
+ }>;
216
+ stepId: z.ZodNullable<z.ZodString>;
217
+ message: z.ZodString;
218
+ detail: z.ZodOptional<z.ZodNullable<z.ZodString>>;
219
+ }, z.core.$strip>>>;
220
+ failureLogExcerpt: z.ZodNullable<z.ZodString>;
221
+ diffExcerpt: z.ZodNullable<z.ZodString>;
222
+ specYaml: z.ZodNullable<z.ZodString>;
223
+ evidence: z.ZodNullable<z.ZodArray<z.ZodObject<{
224
+ stepId: z.ZodString;
225
+ source: z.ZodString;
226
+ pngPath: z.ZodString;
227
+ url: z.ZodNullable<z.ZodString>;
228
+ title: z.ZodNullable<z.ZodString>;
229
+ capturedAt: z.ZodNullable<z.ZodString>;
230
+ description: z.ZodNullable<z.ZodString>;
231
+ status: z.ZodDefault<z.ZodEnum<{
232
+ passed: "passed";
233
+ failed: "failed";
234
+ }>>;
235
+ failureSummary: z.ZodDefault<z.ZodNullable<z.ZodString>>;
236
+ }, z.core.$strip>>>;
237
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
238
+ name: z.ZodString;
239
+ path: z.ZodString;
240
+ kind: z.ZodEnum<{
241
+ image: "image";
242
+ text: "text";
243
+ json: "json";
244
+ binary: "binary";
245
+ }>;
246
+ sizeBytes: z.ZodNumber;
247
+ }, z.core.$strip>>>;
248
+ liveRun: z.ZodNullable<z.ZodObject<{
249
+ runId: z.ZodString;
250
+ sessionName: z.ZodString;
251
+ startedAt: z.ZodString;
252
+ durationMs: z.ZodNumber;
253
+ steps: z.ZodArray<z.ZodObject<{
254
+ stepId: z.ZodString;
255
+ source: z.ZodString;
256
+ instruction: z.ZodString;
257
+ expected: z.ZodString;
258
+ status: z.ZodEnum<{
259
+ passed: "passed";
260
+ failed: "failed";
261
+ skipped: "skipped";
262
+ }>;
263
+ reasoning: z.ZodString;
264
+ beforePng: z.ZodNullable<z.ZodString>;
265
+ afterPng: z.ZodNullable<z.ZodString>;
266
+ durationMs: z.ZodNumber;
267
+ cost: z.ZodObject<{
268
+ totalCostUsd: z.ZodNullable<z.ZodNumber>;
269
+ durationApiMs: z.ZodNullable<z.ZodNumber>;
270
+ numTurns: z.ZodNullable<z.ZodNumber>;
271
+ inputTokens: z.ZodNullable<z.ZodNumber>;
272
+ cacheCreationInputTokens: z.ZodNullable<z.ZodNumber>;
273
+ cacheReadInputTokens: z.ZodNullable<z.ZodNumber>;
274
+ outputTokens: z.ZodNullable<z.ZodNumber>;
275
+ models: z.ZodArray<z.ZodString>;
276
+ }, z.core.$strip>;
277
+ commands: z.ZodOptional<z.ZodArray<z.ZodString>>;
278
+ }, z.core.$strip>>;
279
+ cost: z.ZodObject<{
280
+ totalCostUsd: z.ZodNullable<z.ZodNumber>;
281
+ durationApiMs: z.ZodNullable<z.ZodNumber>;
282
+ numTurns: z.ZodNullable<z.ZodNumber>;
283
+ inputTokens: z.ZodNullable<z.ZodNumber>;
284
+ cacheCreationInputTokens: z.ZodNullable<z.ZodNumber>;
285
+ cacheReadInputTokens: z.ZodNullable<z.ZodNumber>;
286
+ outputTokens: z.ZodNullable<z.ZodNumber>;
287
+ models: z.ZodArray<z.ZodString>;
288
+ }, z.core.$strip>;
289
+ }, z.core.$strip>>;
290
+ }, z.core.$strip>;
291
+ type ReportSpecResult = z.infer<typeof ReportSpecResultSchema>;
292
+ declare const RunReportDataSchema: z.ZodObject<{
293
+ schemaVersion: z.ZodLiteral<1>;
294
+ kind: z.ZodDefault<z.ZodEnum<{
295
+ run: "run";
296
+ drift: "drift";
297
+ }>>;
298
+ createdAt: z.ZodString;
299
+ runId: z.ZodNullable<z.ZodString>;
300
+ git: z.ZodObject<{
301
+ head: z.ZodNullable<z.ZodString>;
302
+ base: z.ZodNullable<z.ZodString>;
303
+ }, z.core.$strip>;
304
+ model: z.ZodNullable<z.ZodString>;
305
+ language: z.ZodDefault<z.ZodNullable<z.ZodString>>;
306
+ promptVersion: z.ZodString;
307
+ customPromptVersion: z.ZodDefault<z.ZodNullable<z.ZodString>>;
308
+ triageUserPromptHash: z.ZodOptional<z.ZodString>;
309
+ results: z.ZodArray<z.ZodObject<{
310
+ feature: z.ZodString;
311
+ spec: z.ZodString;
312
+ title: z.ZodNullable<z.ZodString>;
313
+ target: z.ZodOptional<z.ZodString>;
314
+ status: z.ZodEnum<{
315
+ passed: "passed";
316
+ failed: "failed";
317
+ skipped: "skipped";
318
+ }>;
319
+ skipReason: z.ZodOptional<z.ZodString>;
320
+ testCounts: z.ZodNullable<z.ZodObject<{
321
+ total: z.ZodNumber;
322
+ passed: z.ZodNumber;
323
+ failed: z.ZodNumber;
324
+ }, z.core.$strip>>;
325
+ durationMs: z.ZodNullable<z.ZodNumber>;
326
+ assertions: z.ZodNullable<z.ZodArray<z.ZodObject<{
327
+ name: z.ZodString;
328
+ status: z.ZodEnum<{
329
+ passed: "passed";
330
+ failed: "failed";
331
+ skipped: "skipped";
332
+ }>;
333
+ durationMs: z.ZodNullable<z.ZodNumber>;
334
+ }, z.core.$strip>>>;
335
+ analysis: z.ZodNullable<z.ZodObject<{
336
+ label: z.ZodEnum<{
337
+ TEST_DRIFT: "TEST_DRIFT";
338
+ SPEC_CHANGE: "SPEC_CHANGE";
339
+ PRODUCT_BUG: "PRODUCT_BUG";
340
+ UNKNOWN: "UNKNOWN";
341
+ }>;
342
+ confidence: z.ZodNumber;
343
+ subDiagnosis: z.ZodOptional<z.ZodEnum<{
344
+ TIMING_ISSUE: "TIMING_ISSUE";
345
+ OVER_ASSERTION: "OVER_ASSERTION";
346
+ SELECTOR_DRIFT: "SELECTOR_DRIFT";
347
+ DATA_MISSING: "DATA_MISSING";
348
+ NONE: "NONE";
349
+ }>>;
350
+ headline: z.ZodDefault<z.ZodString>;
351
+ recommendation: z.ZodDefault<z.ZodString>;
352
+ evidence: z.ZodArray<z.ZodObject<{
353
+ file: z.ZodOptional<z.ZodString>;
354
+ detail: z.ZodString;
355
+ }, z.core.$strip>>;
356
+ reasoning: z.ZodString;
357
+ }, z.core.$strip>>;
358
+ analysisSkipped: z.ZodNullable<z.ZodString>;
359
+ driftIssues: z.ZodNullable<z.ZodArray<z.ZodObject<{
360
+ severity: z.ZodEnum<{
361
+ OK: "OK";
362
+ WARN: "WARN";
363
+ ERROR: "ERROR";
364
+ }>;
365
+ category: z.ZodEnum<{
366
+ assertable: "assertable";
367
+ blocks: "blocks";
368
+ granularity: "granularity";
369
+ unimplemented: "unimplemented";
370
+ }>;
371
+ stepId: z.ZodNullable<z.ZodString>;
372
+ message: z.ZodString;
373
+ detail: z.ZodOptional<z.ZodNullable<z.ZodString>>;
374
+ }, z.core.$strip>>>;
375
+ failureLogExcerpt: z.ZodNullable<z.ZodString>;
376
+ diffExcerpt: z.ZodNullable<z.ZodString>;
377
+ specYaml: z.ZodNullable<z.ZodString>;
378
+ evidence: z.ZodNullable<z.ZodArray<z.ZodObject<{
379
+ stepId: z.ZodString;
380
+ source: z.ZodString;
381
+ pngPath: z.ZodString;
382
+ url: z.ZodNullable<z.ZodString>;
383
+ title: z.ZodNullable<z.ZodString>;
384
+ capturedAt: z.ZodNullable<z.ZodString>;
385
+ description: z.ZodNullable<z.ZodString>;
386
+ status: z.ZodDefault<z.ZodEnum<{
387
+ passed: "passed";
388
+ failed: "failed";
389
+ }>>;
390
+ failureSummary: z.ZodDefault<z.ZodNullable<z.ZodString>>;
391
+ }, z.core.$strip>>>;
392
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
393
+ name: z.ZodString;
394
+ path: z.ZodString;
395
+ kind: z.ZodEnum<{
396
+ image: "image";
397
+ text: "text";
398
+ json: "json";
399
+ binary: "binary";
400
+ }>;
401
+ sizeBytes: z.ZodNumber;
402
+ }, z.core.$strip>>>;
403
+ liveRun: z.ZodNullable<z.ZodObject<{
404
+ runId: z.ZodString;
405
+ sessionName: z.ZodString;
406
+ startedAt: z.ZodString;
407
+ durationMs: z.ZodNumber;
408
+ steps: z.ZodArray<z.ZodObject<{
409
+ stepId: z.ZodString;
410
+ source: z.ZodString;
411
+ instruction: z.ZodString;
412
+ expected: z.ZodString;
413
+ status: z.ZodEnum<{
414
+ passed: "passed";
415
+ failed: "failed";
416
+ skipped: "skipped";
417
+ }>;
418
+ reasoning: z.ZodString;
419
+ beforePng: z.ZodNullable<z.ZodString>;
420
+ afterPng: z.ZodNullable<z.ZodString>;
421
+ durationMs: z.ZodNumber;
422
+ cost: z.ZodObject<{
423
+ totalCostUsd: z.ZodNullable<z.ZodNumber>;
424
+ durationApiMs: z.ZodNullable<z.ZodNumber>;
425
+ numTurns: z.ZodNullable<z.ZodNumber>;
426
+ inputTokens: z.ZodNullable<z.ZodNumber>;
427
+ cacheCreationInputTokens: z.ZodNullable<z.ZodNumber>;
428
+ cacheReadInputTokens: z.ZodNullable<z.ZodNumber>;
429
+ outputTokens: z.ZodNullable<z.ZodNumber>;
430
+ models: z.ZodArray<z.ZodString>;
431
+ }, z.core.$strip>;
432
+ commands: z.ZodOptional<z.ZodArray<z.ZodString>>;
433
+ }, z.core.$strip>>;
434
+ cost: z.ZodObject<{
435
+ totalCostUsd: z.ZodNullable<z.ZodNumber>;
436
+ durationApiMs: z.ZodNullable<z.ZodNumber>;
437
+ numTurns: z.ZodNullable<z.ZodNumber>;
438
+ inputTokens: z.ZodNullable<z.ZodNumber>;
439
+ cacheCreationInputTokens: z.ZodNullable<z.ZodNumber>;
440
+ cacheReadInputTokens: z.ZodNullable<z.ZodNumber>;
441
+ outputTokens: z.ZodNullable<z.ZodNumber>;
442
+ models: z.ZodArray<z.ZodString>;
443
+ }, z.core.$strip>;
444
+ }, z.core.$strip>>;
445
+ }, z.core.$strip>>;
446
+ }, z.core.$strip>;
447
+ type RunReportData = z.infer<typeof RunReportDataSchema>;
149
448
  declare const LabelsExportSchema: z.ZodObject<{
150
449
  schemaVersion: z.ZodLiteral<1>;
151
450
  runId: z.ZodNullable<z.ZodString>;
@@ -170,7 +469,30 @@ declare const LabelsExportSchema: z.ZodObject<{
170
469
  }, z.core.$strip>;
171
470
  type LabelsExport = z.infer<typeof LabelsExportSchema>;
172
471
  //#endregion
472
+ //#region src/run/incremental-report.d.ts
473
+ /**
474
+ * The report envelope fields that don't change spec-to-spec — everything in
475
+ * `RunReportData` except `results`. Captured once when the writer is created
476
+ * so each incremental flush produces the same envelope the final batch write
477
+ * used to.
478
+ */
479
+ type ReportEnvelope = Omit<RunReportData, "results">;
480
+ //#endregion
173
481
  //#region src/hub-client/index.d.ts
482
+ /**
483
+ * Body of a `PATCH /runs/:id` incremental push: the newly-finished spec rows,
484
+ * their evidence PNGs (posix relPath → base64), the report envelope metadata
485
+ * (filled once the real git diff is known), and — on the last patch — `done`
486
+ * with the terminal status.
487
+ */
488
+ interface PatchRunRequest {
489
+ rows: ReportSpecResult[];
490
+ /** reportDir-relative posix path → base64 PNG bytes. */
491
+ evidence?: Record<string, string>;
492
+ done?: boolean;
493
+ finalStatus?: "passed" | "failed";
494
+ reportMeta?: Partial<ReportEnvelope>;
495
+ }
174
496
  /**
175
497
  * TypeScript client for the ccqa hub's public REST API (docs/hub-api.md).
176
498
  * Uses the global `fetch` only — no `node:*` imports — so this same module
@@ -215,6 +537,19 @@ interface HubClient {
215
537
  profile?: string;
216
538
  kind?: "run" | "drift";
217
539
  }): Promise<Run>;
540
+ /**
541
+ * Open a `running` run to push results into incrementally. Returns the new
542
+ * run's id. Non-retryable (a dropped response after the server committed
543
+ * would leave a second open run); callers degrade to local-only on failure.
544
+ */
545
+ openRun(meta: {
546
+ project: string;
547
+ branch?: string;
548
+ profile?: string;
549
+ kind?: "run" | "drift";
550
+ }): Promise<Run>;
551
+ /** Add finished spec rows (+ evidence) to a running run; `done` closes it. */
552
+ patchRun(id: string, body: PatchRunRequest): Promise<Run>;
218
553
  listRuns(q?: {
219
554
  project?: string;
220
555
  branch?: string;
@@ -260,4 +595,4 @@ interface HubClient {
260
595
  }
261
596
  declare function createHubClient(opts: HubClientOptions): HubClient;
262
597
  //#endregion
263
- export { HubApiError, HubClient, HubClientOptions, HubPromptMeta, HubVariable, createHubClient };
598
+ export { HubApiError, HubClient, HubClientOptions, HubPromptMeta, HubVariable, PatchRunRequest, createHubClient };
@@ -104,6 +104,20 @@ function createHubClient(opts) {
104
104
  body: toBodyInit(archive)
105
105
  });
106
106
  },
107
+ openRun(meta) {
108
+ const params = new URLSearchParams({ project: meta.project });
109
+ if (meta.branch) params.set("branch", meta.branch);
110
+ if (meta.profile) params.set("profile", meta.profile);
111
+ if (meta.kind) params.set("kind", meta.kind);
112
+ return json(`/api/v1/runs/open?${params}`, { method: "POST" });
113
+ },
114
+ patchRun(id, body) {
115
+ return json(`/api/v1/runs/${encodeURIComponent(id)}`, {
116
+ method: "PATCH",
117
+ headers: { "Content-Type": "application/json" },
118
+ body: JSON.stringify(body)
119
+ });
120
+ },
107
121
  async listRuns(q = {}) {
108
122
  const { runs } = await json(`/api/v1/runs?${queryString(q)}`);
109
123
  return runs;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "0.13.1",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {
@@ -1,4 +1,4 @@
1
- import { i as FAILURE_STEP_ID, n as spawnAB, r as FAILURE_SOURCE, t as sleepSync } from "../spawn-ab-Ja8NRRab.mjs";
1
+ import { i as FAILURE_STEP_ID, n as spawnAB, r as FAILURE_SOURCE, t as sleepSync } from "../spawn-ab-Cr967J2N.mjs";
2
2
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  //#region src/runtime/test-helpers.ts
@@ -14,7 +14,8 @@ const FAILURE_STEP_ID = "failure";
14
14
  const FAILURE_SOURCE = "failed";
15
15
  //#endregion
16
16
  //#region src/runtime/spawn-ab.ts
17
- const AB = createRequire(import.meta.url).resolve("agent-browser/bin/agent-browser.js");
17
+ const require = createRequire(import.meta.url);
18
+ const AB = process.env["CCQA_AB_BIN"] ?? require.resolve("agent-browser/bin/agent-browser.js");
18
19
  const EAGAIN_PATTERN = /Resource temporarily unavailable|os error 35/i;
19
20
  const EAGAIN_TOTAL_BUDGET_MS = 3e4;
20
21
  const EAGAIN_BACKOFF_MS = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "0.13.1",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {