dsh-doublecheck 0.8.0 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to dsh-doublecheck are recorded here, newest first.
4
4
 
5
+ ## [0.9.0] - 2026-08-26
6
+
7
+ ### Added
8
+
9
+ - **Headless CI gate report (JSON/SARIF).** `/gate run` now also writes a `gate-report.json` next to `gate-report.md` — the same settled `GateState` as lossless JSON. The new `doublecheck-gate` CLI serializes that state to JSON or SARIF 2.1.0 for GitHub Actions PR comments/status checks (`doublecheck-gate --format json|sarif --input gate-report.json`), exiting `0` on `deliverable` and `1` on `rework`. The CLI never re-runs the four-phase gate or the evidence folds — it only serializes the already-settled state.
10
+
5
11
  ## [0.8.0] - 2026-08-23
6
12
 
7
13
  ### Added
package/README.es.md CHANGED
@@ -192,6 +192,19 @@ Las luces rojas son comprobaciones fallidas (un spec ausente, una última ejecuc
192
192
  - counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
193
193
  ````
194
194
 
195
+ ## Salida de CI
196
+
197
+ `/gate run` también escribe un `gate-report.json` (el mismo estado asentado que el JSON sin pérdida, junto a `gate-report.md`). El CLI `doublecheck-gate` convierte ese archivo en salida legible por máquina para GitHub Actions:
198
+
199
+ ```sh
200
+ # JSON (comentario de PR / payload de estado)
201
+ doublecheck-gate --format json --input gate-report.json
202
+ # SARIF 2.1.0 (subida de code-scanning / verificación de estado)
203
+ doublecheck-gate --format sarif < gate-report.json
204
+ ```
205
+
206
+ El CLI solo serializa el `GateState` ya asentado — nunca vuelve a ejecutar la puerta de cuatro fases ni los pliegues de evidencia. Su código de salida mapea el veredicto: `0` = entregable, `1` = retrabajo, `2` = error de uso/análisis.
207
+
195
208
  ## Permisos y datos
196
209
 
197
210
  - **Lee**: el registro de sesión (`tool/call` / `tool/result` / `tool/code-dispatch`, fuentes `user/message` inyectadas y los registros de veredicto ajenos `autoReview/*`) solo en proceso; el estado opcional del servicio de modo plan.
package/README.hi.md CHANGED
@@ -192,6 +192,19 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
192
192
  - counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
193
193
  ````
194
194
 
195
+ ## CI आउटपुट
196
+
197
+ `/gate run` एक `gate-report.json` भी लिखता है (दोषरहित JSON जैसी ही स्थापित स्थिति, `gate-report.md` के पास)। `doublecheck-gate` CLI उस फ़ाइल को GitHub Actions के लिए मशीन-पठनीय आउटपुट में बदलता है:
198
+
199
+ ```sh
200
+ # JSON (PR टिप्पणी / स्थिति पेलोड)
201
+ doublecheck-gate --format json --input gate-report.json
202
+ # SARIF 2.1.0 (code-scanning अपलोड / स्थिति जाँच)
203
+ doublecheck-gate --format sarif < gate-report.json
204
+ ```
205
+
206
+ CLI केवल पहले से स्थापित `GateState` को क्रमबद्ध करता है — यह कभी चार-चरणीय द्वार या साक्ष्य तहों को दोबारा नहीं चलाता। इसका निकास कोड निर्णय को मैप करता है: `0` = डिलीवर करने योग्य, `1` = पुनः कार्य, `2` = उपयोग/पार्स त्रुटि।
207
+
195
208
  ## अनुमतियाँ और डेटा
196
209
 
197
210
  - **पढ़ता है**: सत्र लॉग (`tool/call` / `tool/result` / `tool/code-dispatch`, इंजेक्ट किए गए `user/message` स्रोत, और बाहरी `autoReview/*` निर्णय रिकॉर्ड) केवल प्रक्रिया के भीतर; वैकल्पिक प्लान-मोड सेवा स्थिति।
package/README.md CHANGED
@@ -193,10 +193,23 @@ Red lights are failed checks (a missing spec, a failing latest run, coverage bel
193
193
  - counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
194
194
  ````
195
195
 
196
+ ## CI output
197
+
198
+ `/gate run` also writes a `gate-report.json` (the same settled state as lossless JSON, next to `gate-report.md`). The `doublecheck-gate` CLI turns that file into machine-readable output for GitHub Actions:
199
+
200
+ ```sh
201
+ # JSON (PR comment / status payload)
202
+ doublecheck-gate --format json --input gate-report.json
203
+ # SARIF 2.1.0 (code-scanning upload / status check)
204
+ doublecheck-gate --format sarif < gate-report.json
205
+ ```
206
+
207
+ The CLI only serializes the already-settled `GateState` — it never re-runs the four-phase gate or the evidence folds. Its exit code maps the verdict: `0` = deliverable, `1` = rework, `2` = usage/parse error.
208
+
196
209
  ## Permissions & data
197
210
 
198
211
  - **Reads**: the session log (`tool/call` / `tool/result` / `tool/code-dispatch`, injected `user/message` sources, and the foreign `autoReview/*` verdict records) in-process only; the optional plan-mode service state.
199
- - **Writes**: `doublecheck-spec.md`, `doublecheck-report.md`, and `gate-report.md` in the session workspace (paths configurable) through the `ctx.fs` seam; the durable `doublecheck/state` and `doublecheck/gate` session events.
212
+ - **Writes**: `doublecheck-spec.md`, `doublecheck-report.md`, `gate-report.md`, and `gate-report.json` in the session workspace (paths configurable) through the `ctx.fs` seam; the durable `doublecheck/state` and `doublecheck/gate` session events.
200
213
  - **Model calls**: the gate's consistency and local-review phases (one subagent each per `/gate run`), the optional adversary review, and the `doublecheck_report` verification workflow start subagent runs; nothing else calls a model or the network.
201
214
  - **Never touched**: credentials, environment variables, or any file outside the session workspace. The workshop manifest declares `filesystem:read` and `filesystem:write` only. Gate reports carry counts, ids, and verdicts only; recognized secrets in reviewer texts are redacted before storage or display.
202
215
 
package/README.pt.md CHANGED
@@ -192,6 +192,19 @@ As luzes vermelhas são verificações que falharam (um spec ausente, uma últim
192
192
  - counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
193
193
  ````
194
194
 
195
+ ## Saída do CI
196
+
197
+ `/gate run` também grava um `gate-report.json` (o mesmo estado assentado que o JSON sem perda, ao lado de `gate-report.md`). O CLI `doublecheck-gate` converte esse arquivo em saída legível por máquina para o GitHub Actions:
198
+
199
+ ```sh
200
+ # JSON (comentário de PR / payload de status)
201
+ doublecheck-gate --format json --input gate-report.json
202
+ # SARIF 2.1.0 (upload de code-scanning / verificação de status)
203
+ doublecheck-gate --format sarif < gate-report.json
204
+ ```
205
+
206
+ O CLI apenas serializa o `GateState` já assentado — nunca reexecuta a porta de quatro fases nem as dobras de evidência. Seu código de saída mapeia o veredicto: `0` = entregável, `1` = retrabalho, `2` = erro de uso/análise.
207
+
195
208
  ## Permissões e dados
196
209
 
197
210
  - **Lê**: o registro de sessão (`tool/call` / `tool/result` / `tool/code-dispatch`, fontes `user/message` injetadas e os registros de veredicto alheios `autoReview/*`) somente em processo; o estado opcional do serviço de modo plano.
package/README.zh.md CHANGED
@@ -192,6 +192,19 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
192
192
  - counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
193
193
  ````
194
194
 
195
+ ## CI 输出
196
+
197
+ `/gate run` 还会写一个 `gate-report.json`(与无损 JSON 相同的已定状态,位于 `gate-report.md` 旁边)。`doublecheck-gate` CLI 把该文件转成可供 GitHub Actions 使用的机器可读输出:
198
+
199
+ ```sh
200
+ # JSON(PR 评论 / 状态载荷)
201
+ doublecheck-gate --format json --input gate-report.json
202
+ # SARIF 2.1.0(code-scanning 上传 / 状态检查)
203
+ doublecheck-gate --format sarif < gate-report.json
204
+ ```
205
+
206
+ CLI 只序列化已定的 `GateState` —— 它从不重新运行四阶段门禁或证据折叠。其退出码映射裁决:`0` = 可交付,`1` = 返工,`2` = 用法/解析错误。
207
+
195
208
  ## 权限与数据
196
209
 
197
210
  - **读取**:仅进程内读取会话日志(`tool/call` / `tool/result` / `tool/code-dispatch`、注入的 `user/message` 来源,以及外部的 `autoReview/*` 裁决记录);可选的计划模式服务状态。
package/lib/ci.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Headless gate-report CLI for CI (`doublecheck-gate`).
4
+ *
5
+ * Consumes a serialized {@link GateState} (the JSON the gate settles and
6
+ * writes next to `gate-report.md`) and emits a machine-readable report for a
7
+ * GH Actions PR comment or status check. It does NOT re-run the four-phase
8
+ * gate — it only serializes the already-settled state — so the gate runner
9
+ * and the evidence folds stay exactly as they are.
10
+ *
11
+ * Usage:
12
+ * doublecheck-gate --format json --input gate-report.json
13
+ * doublecheck-gate --format sarif < gate-report.json
14
+ *
15
+ * Exit code: 0 = deliverable, 1 = rework, 2 = usage/parse error.
16
+ *
17
+ * @module dsh-doublecheck/ci
18
+ */
19
+ export {};
package/lib/ci.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Headless gate-report CLI for CI (`doublecheck-gate`).
4
+ *
5
+ * Consumes a serialized {@link GateState} (the JSON the gate settles and
6
+ * writes next to `gate-report.md`) and emits a machine-readable report for a
7
+ * GH Actions PR comment or status check. It does NOT re-run the four-phase
8
+ * gate — it only serializes the already-settled state — so the gate runner
9
+ * and the evidence folds stay exactly as they are.
10
+ *
11
+ * Usage:
12
+ * doublecheck-gate --format json --input gate-report.json
13
+ * doublecheck-gate --format sarif < gate-report.json
14
+ *
15
+ * Exit code: 0 = deliverable, 1 = rework, 2 = usage/parse error.
16
+ *
17
+ * @module dsh-doublecheck/ci
18
+ */
19
+ import { readFileSync } from 'node:fs';
20
+ import { renderGateReportJson, renderGateReportSarif } from "./domain/gate.js";
21
+ /** Parse CLI arguments into `{ format, input }`. */
22
+ function parseArgs(argv) {
23
+ let format = 'json';
24
+ let input = null;
25
+ for (let index = 0; index < argv.length; index += 1) {
26
+ const arg = argv[index];
27
+ if (arg === '--format' || arg === '-f') {
28
+ const next = argv[index + 1];
29
+ if (next !== 'json' && next !== 'sarif')
30
+ throw new Error(`--format must be json or sarif, got ${JSON.stringify(next)}`);
31
+ format = next;
32
+ index += 1;
33
+ }
34
+ else if (arg === '--input' || arg === '-i') {
35
+ input = argv[index + 1] ?? null;
36
+ if (input === null)
37
+ throw new Error('--input requires a file path');
38
+ index += 1;
39
+ }
40
+ else if (arg === '--help' || arg === '-h') {
41
+ throw new Error('usage: doublecheck-gate [--format json|sarif] [--input <file>]');
42
+ }
43
+ else if (!arg.startsWith('-')) {
44
+ input = arg;
45
+ }
46
+ else {
47
+ throw new Error(`unknown argument ${JSON.stringify(arg)}`);
48
+ }
49
+ }
50
+ return { format, input };
51
+ }
52
+ /** Read the gate-state JSON from the input file or stdin (fd 0). */
53
+ function readState(input) {
54
+ const raw = input !== null ? readFileSync(input, 'utf8') : readFileSync(0, 'utf8');
55
+ return JSON.parse(raw);
56
+ }
57
+ /** Render and print the report, returning the process exit code. */
58
+ function main(argv) {
59
+ let format;
60
+ let input;
61
+ try {
62
+ ;
63
+ ({ format, input } = parseArgs(argv));
64
+ const state = readState(input);
65
+ process.stdout.write((format === 'sarif' ? renderGateReportSarif : renderGateReportJson)(state) + '\n');
66
+ return state.verdict === 'rework' ? 1 : 0;
67
+ }
68
+ catch (error) {
69
+ process.stderr.write(`doublecheck-gate: ${error instanceof Error ? error.message : String(error)}\n`);
70
+ return 2;
71
+ }
72
+ }
73
+ process.exitCode = main(process.argv.slice(2));
@@ -322,3 +322,50 @@ export declare function renderGateReportMarkdown(state: GateState, planSuggestio
322
322
  export declare function renderPhaseMarkdown(result: GatePhaseResult): string[];
323
323
  /** Human-readable phase headings (stable English audit ids). */
324
324
  export declare function phaseLabel(phase: GatePhase): string;
325
+ /**
326
+ * The machine-readable CI report derived from a settled gate state. Lossless
327
+ * JSON over the same counts/ids/verdicts the markdown report carries — no
328
+ * session text or file contents — so a GH Actions step can consume it for a
329
+ * PR comment or status check. The four-phase runner and the evidence folds
330
+ * are unchanged; this is a pure serialization of their settled output.
331
+ */
332
+ export interface CiReport {
333
+ readonly schemaVersion: 1;
334
+ readonly tool: 'dsh-doublecheck';
335
+ readonly verdict: GateVerdict;
336
+ /** Convenience boolean: true when the delivery may proceed. */
337
+ readonly deliverable: boolean;
338
+ readonly reviewEngine: GateReviewEngine;
339
+ readonly at: string;
340
+ /** Number of failing (red) checks across enabled phases. */
341
+ readonly redChecks: number;
342
+ readonly phases: Array<{
343
+ readonly phase: GatePhase;
344
+ readonly enabled: boolean;
345
+ readonly status: GateCheckStatus;
346
+ readonly checks: Array<{
347
+ readonly id: string;
348
+ readonly label: string;
349
+ readonly status: GateCheckStatus;
350
+ readonly summary: string;
351
+ readonly suggestion: string;
352
+ }>;
353
+ }>;
354
+ }
355
+ /** Build the JSON-safe CI report from a settled gate state. */
356
+ export declare function buildCiReport(state: GateState): CiReport;
357
+ /**
358
+ * Render the settled gate state as headless JSON for CI (`--ci` output).
359
+ * @param state - the settled gate state.
360
+ * @returns the pretty-printed JSON report.
361
+ */
362
+ export declare function renderGateReportJson(state: GateState): string;
363
+ /**
364
+ * Render the settled gate state as a SARIF 2.1.0 document for CI
365
+ * (GitHub code-scanning / status checks). Each failing check is an `error`
366
+ * result and each warning is a `warning` result; passing/skipped/pending
367
+ * checks are declared as rules but produce no result.
368
+ * @param state - the settled gate state.
369
+ * @returns the pretty-printed SARIF document.
370
+ */
371
+ export declare function renderGateReportSarif(state: GateState): string;
@@ -750,3 +750,87 @@ export function phaseLabel(phase) {
750
750
  case 'review': return 'Review conclusion';
751
751
  }
752
752
  }
753
+ /** Build the JSON-safe CI report from a settled gate state. */
754
+ export function buildCiReport(state) {
755
+ return {
756
+ schemaVersion: 1,
757
+ tool: 'dsh-doublecheck',
758
+ verdict: state.verdict,
759
+ deliverable: state.verdict === 'deliverable',
760
+ reviewEngine: state.reviewEngine,
761
+ at: state.at,
762
+ redChecks: countRedChecks(Object.values(state.phases)),
763
+ phases: GATE_PHASES.map(phase => {
764
+ const result = state.phases[phase];
765
+ return {
766
+ phase,
767
+ enabled: result?.enabled ?? false,
768
+ status: result?.status ?? 'pending',
769
+ checks: (result?.checks ?? []).map(check => ({
770
+ id: check.id,
771
+ label: check.label,
772
+ status: check.status,
773
+ summary: check.summary,
774
+ suggestion: check.suggestion,
775
+ })),
776
+ };
777
+ }),
778
+ };
779
+ }
780
+ /**
781
+ * Render the settled gate state as headless JSON for CI (`--ci` output).
782
+ * @param state - the settled gate state.
783
+ * @returns the pretty-printed JSON report.
784
+ */
785
+ export function renderGateReportJson(state) {
786
+ return JSON.stringify(buildCiReport(state), null, 2);
787
+ }
788
+ /**
789
+ * Render the settled gate state as a SARIF 2.1.0 document for CI
790
+ * (GitHub code-scanning / status checks). Each failing check is an `error`
791
+ * result and each warning is a `warning` result; passing/skipped/pending
792
+ * checks are declared as rules but produce no result.
793
+ * @param state - the settled gate state.
794
+ * @returns the pretty-printed SARIF document.
795
+ */
796
+ export function renderGateReportSarif(state) {
797
+ const rules = [];
798
+ const results = [];
799
+ for (const phase of GATE_PHASES) {
800
+ const result = state.phases[phase];
801
+ if (result === undefined || !result.enabled)
802
+ continue;
803
+ for (const check of result.checks) {
804
+ const ruleId = `doublecheck/${phase}/${check.id}`;
805
+ rules.push({ id: ruleId, shortDescription: { text: `${phaseLabel(phase)} — ${check.label}` } });
806
+ if (check.status === 'pass' || check.status === 'skip' || check.status === 'pending')
807
+ continue;
808
+ results.push({
809
+ ruleId,
810
+ level: check.status === 'fail' ? 'error' : 'warning',
811
+ message: { text: check.suggestion !== '' ? `${check.summary} — rework: ${check.suggestion}` : check.summary },
812
+ });
813
+ }
814
+ }
815
+ const sarif = {
816
+ version: '2.1.0',
817
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
818
+ runs: [{
819
+ tool: {
820
+ driver: {
821
+ name: 'dsh-doublecheck',
822
+ informationUri: 'https://github.com/PerryLink/dsh-doublecheck',
823
+ rules,
824
+ },
825
+ },
826
+ properties: {
827
+ verdict: state.verdict,
828
+ deliverable: state.verdict === 'deliverable',
829
+ reviewEngine: state.reviewEngine,
830
+ at: state.at,
831
+ },
832
+ results,
833
+ }],
834
+ };
835
+ return JSON.stringify(sarif, null, 2);
836
+ }
package/lib/guard/gate.js CHANGED
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
27
27
  import z from '@deepseek-ai/schemastery';
28
- import { attachEvalChecks, countRedChecks, DEFAULT_COVERAGE_PATTERN, DEFAULT_EVAL_REPORTS_CONFIG, DEFAULT_GATE_QUESTIONS, deriveGateVerdict, evaluateConsistency, evaluateRequirements, evaluateReview, evaluateTests, foldAutoReviewEvidence, foldRequirementsEvidence, foldTestEvidence, GATE_FINDINGS_SCHEMA, parseEvalReport, renderGateReportMarkdown, renderPhaseMarkdown, skippedPhase, } from "../domain/gate.js";
28
+ import { attachEvalChecks, countRedChecks, DEFAULT_COVERAGE_PATTERN, DEFAULT_EVAL_REPORTS_CONFIG, DEFAULT_GATE_QUESTIONS, deriveGateVerdict, evaluateConsistency, evaluateRequirements, evaluateReview, evaluateTests, foldAutoReviewEvidence, foldRequirementsEvidence, foldTestEvidence, GATE_FINDINGS_SCHEMA, parseEvalReport, renderGateReportJson, renderGateReportMarkdown, renderPhaseMarkdown, skippedPhase, } from "../domain/gate.js";
29
29
  import { sortFindings } from "./review.js";
30
30
  import { PROSE } from "./prose.js";
31
31
  /** The default gate configuration: every phase on, the six key questions required. */
@@ -396,19 +396,30 @@ async function writeGateFile(ctx, filePath, state, agent, signal) {
396
396
  const fs = ctx.get('fs');
397
397
  if (fs === undefined)
398
398
  return;
399
- let target;
400
399
  try {
401
- target = await fs.resolve(filePath, {
400
+ const markdownTarget = await fs.resolve(filePath, {
402
401
  ...agent.session.header.cwd !== undefined ? { cwd: agent.session.header.cwd } : {},
403
402
  signal,
404
403
  });
405
- await fs.writeText(target, renderGateReportMarkdown(state, true), undefined, signal);
404
+ await fs.writeText(markdownTarget, renderGateReportMarkdown(state, true), undefined, signal);
405
+ // The headless CI copy: the same settled state as JSON, for the
406
+ // `doublecheck-gate` CLI (PR comment / status checks). Additive output —
407
+ // the markdown report and the four-phase runner are unchanged.
408
+ const ciTarget = await fs.resolve(ciReportPath(filePath), {
409
+ ...agent.session.header.cwd !== undefined ? { cwd: agent.session.header.cwd } : {},
410
+ signal,
411
+ });
412
+ await fs.writeText(ciTarget, renderGateReportJson(state), undefined, signal);
406
413
  }
407
414
  catch (error) {
408
415
  signal.throwIfAborted();
409
416
  ctx.logger.debug(`dsh-doublecheck: gate report file write skipped: ${String(error)}`);
410
417
  }
411
418
  }
419
+ /** Derive the JSON CI-report path from the markdown report path. */
420
+ function ciReportPath(filePath) {
421
+ return filePath.endsWith('.md') ? `${filePath.slice(0, -3)}.json` : `${filePath}.json`;
422
+ }
412
423
  /**
413
424
  * Read and fold the dsh-eval report through the filesystem seam. A missing
414
425
  * seam, a missing file, malformed JSON, or a wrong document shape all settle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-doublecheck",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Delivery quality gate for DeepSeek Harness: grill the requirements, test the implementation, prove the delivery, and gate the handoff with a deliverable/rework decision.",
5
5
  "packageManager": "pnpm@11.7.0",
6
6
  "repository": {
@@ -14,6 +14,9 @@
14
14
  "type": "module",
15
15
  "main": "lib/index.js",
16
16
  "types": "lib/index.d.ts",
17
+ "bin": {
18
+ "doublecheck-gate": "lib/ci.js"
19
+ },
17
20
  "exports": {
18
21
  ".": {
19
22
  "types": "./lib/index.d.ts",