dsh-doublecheck 0.7.3 → 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 +12 -0
- package/README.es.md +20 -2
- package/README.hi.md +20 -2
- package/README.md +21 -3
- package/README.pt.md +20 -2
- package/README.zh.md +20 -2
- package/lib/ci.d.ts +19 -0
- package/lib/ci.js +73 -0
- package/lib/domain/gate.d.ts +108 -0
- package/lib/domain/gate.js +178 -0
- package/lib/guard/gate.d.ts +3 -1
- package/lib/guard/gate.js +71 -7
- package/package.json +4 -1
- package/strict.patch.yml +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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
|
+
|
|
11
|
+
## [0.8.0] - 2026-08-23
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **dsh-eval evidence in the test-evidence phase**: the new `gate.tests.evalReports.*` config block (weak dependency, `enabled: false` by default) folds the dsh-auto-review eval engine's `dsh-eval` report file — its prompt-regression / stress / fairness suites — into the gate's test evidence as one audit-safe counts check. A present report whose suite passed every case is a pass; a report with failing/erroring/cancelled cases is a red light with a rework suggestion; a missing or malformed report degrades to an honest skip (or a red light when `required: true`). The folded counts ride the durable `doublecheck/gate` record, so a settled run still replays even though the source file is not part of the session log.
|
|
16
|
+
|
|
5
17
|
## v0.7.3 — 2026-08-22
|
|
6
18
|
|
|
7
19
|
### Changed
|
package/README.es.md
CHANGED
|
@@ -116,6 +116,10 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
|
|
|
116
116
|
| `gate.tests.allowFailingRuns` | `0` | Ejecuciones fallidas tras el último verde permitidas antes del rojo. |
|
|
117
117
|
| `gate.tests.requireCoverage` | `false` | Encendido exige evidencia de cobertura en la salida de prueba. |
|
|
118
118
|
| `gate.tests.minCoveragePct` | `80` | Porcentaje mínimo de cobertura (0–100). |
|
|
119
|
+
| `gate.tests.evalReports.enabled` | `false` | Encendido pliega el informe dsh-eval (el motor de evaluación de dsh-auto-review) en la evidencia de prueba. |
|
|
120
|
+
| `gate.tests.evalReports.dir` | `'.eval-reports'` | Directorio relativo al espacio de trabajo que contiene el informe del motor. |
|
|
121
|
+
| `gate.tests.evalReports.file` | `'report.json'` | Nombre del archivo de informe dentro del directorio. |
|
|
122
|
+
| `gate.tests.evalReports.required` | `false` | Un informe ausente es una luz roja exactamente cuando es true (un salto en caso contrario). |
|
|
119
123
|
| `gate.consistency.*` | `provider: 'fork'`, `model: null`, `tools: ['read','glob','grep']`, `timeoutMs: 120000`, `maxFindings: 5` | Ajustes del revisor local de consistencia (`model: null` = modelo principal). |
|
|
120
124
|
| `gate.review.engine` | `'auto'` | `auto` = registros de veredicto de dsh-auto-review cuando están presentes, si no el revisor local; `local` = siempre local. |
|
|
121
125
|
| `gate.review.provider` | `'fork'` | Proveedor del revisor local de revisión (su `model`/`tools`/`timeoutMs`/`maxFindings` coinciden con `gate.consistency.*`). |
|
|
@@ -143,11 +147,11 @@ La puerta de entrega agrega la evidencia durable de la sesión en una lista conf
|
|
|
143
147
|
| Fase | Comprobaciones | Fuente de evidencia | Costo de modelo |
|
|
144
148
|
|---|---|---|---|
|
|
145
149
|
| Interrogatorio de requisitos | Lista de preguntas clave confirmadas una a una (seis preguntas de dimensión de spec por defecto) | `doublecheck_spec` confirmado + llamadas `ask_user_question` | ninguno |
|
|
146
|
-
| Evidencia de prueba | Color de la última ejecución, ejecuciones fallidas tras el verde, umbral de cobertura opcional | Ejecuciones de prueba shell en el registro de sesión (`[exit code: N]`, porcentajes de cobertura) | ninguno |
|
|
150
|
+
| Evidencia de prueba | Color de la última ejecución, ejecuciones fallidas tras el verde, umbral de cobertura opcional, informe dsh-eval opcional | Ejecuciones de prueba shell en el registro de sesión (`[exit code: N]`, porcentajes de cobertura); el archivo de informe dsh-eval cuando `gate.tests.evalReports.enabled` | ninguno |
|
|
147
151
|
| Consistencia de implementación | Mapeo diff ↔ requisito: cada edición debe servir a una dimensión de spec | Revisor bifurcado local (hallazgos estructurados, herramientas de solo lectura) | un subagente |
|
|
148
152
|
| Conclusión de revisión | El veredicto de entrega; `engine: auto` consume los registros de veredicto durables de dsh-auto-review cuando están presentes, si no el revisor local | Eventos `autoReview/verdict` / `autoReview/rejection`, o el revisor bifurcado local | un subagente (local) |
|
|
149
153
|
|
|
150
|
-
Las luces rojas son comprobaciones fallidas (un spec ausente, una última ejecución fallida, cobertura bajo el mínimo, una edición sin mapear, hallazgos blocker/major) — cada una lleva una sugerencia de retrabajo. Las advertencias y los saltos nunca invierten la decisión. La puerta integra [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) como dependencia débil: `review.engine: auto` pliega sus registros de veredicto cuando están presentes y degrada al revisor local en caso contrario; la puerta nunca sintetiza solicitudes de aprobación.
|
|
154
|
+
Las luces rojas son comprobaciones fallidas (un spec ausente, una última ejecución fallida, cobertura bajo el mínimo, una edición sin mapear, hallazgos blocker/major) — cada una lleva una sugerencia de retrabajo. Las advertencias y los saltos nunca invierten la decisión. La puerta integra [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) como dependencia débil: `review.engine: auto` pliega sus registros de veredicto cuando están presentes y degrada al revisor local en caso contrario; `gate.tests.evalReports.enabled` pliega el informe dsh-eval de su motor de evaluación (suites de regresión de prompt / estrés / equidad) en la evidencia de prueba y salta honestamente cuando no existe informe. La puerta nunca sintetiza solicitudes de aprobación.
|
|
151
155
|
|
|
152
156
|
## Informe de ejemplo
|
|
153
157
|
|
|
@@ -188,6 +192,19 @@ Las luces rojas son comprobaciones fallidas (un spec ausente, una última ejecuc
|
|
|
188
192
|
- counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
|
|
189
193
|
````
|
|
190
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
|
+
|
|
191
208
|
## Permisos y datos
|
|
192
209
|
|
|
193
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.
|
|
@@ -208,6 +225,7 @@ Las luces rojas son comprobaciones fallidas (un spec ausente, una última ejecuc
|
|
|
208
225
|
- **Escrituras durables.** `/doublecheck on\|off` → `doublecheck/state` y `/gate run` → `doublecheck/gate` necesitan la superficie de append `ignorable` del host (post-rc.6), que todo host compatible (≥ `0.1.1-rc.2`) proporciona.
|
|
209
226
|
- **Interfaces opcionales.** El espacio de ajustes `doublecheck.gate` se registra solo cuando el servicio de ajustes está montado; la línea de modo plan de `/gate status` lee el `ctx.planMode` opcional (muestra `unknown` sin él); la revisión adversarial necesita `ctx.subagents`; la verificación necesita `workflowEngine`.
|
|
210
227
|
- **Degradación local.** `gate.review.engine: auto` degrada al revisor local cuando dsh-auto-review está ausente o no tiene registros de veredicto en esta sesión — el informe nombra la razón en lugar de inventar un veredicto.
|
|
228
|
+
- **La evidencia dsh-eval se basa en archivos.** El motor de evaluación de dsh-auto-review (`dsh-eval`) escribe sus resultados de regresión de prompt / estrés / equidad en un archivo de informe del espacio de trabajo, no en el registro de sesión. `gate.tests.evalReports.enabled` pliega ese archivo (desactivado por defecto; salta cuando está ausente) y los recuentos pliegados viajan en el registro duradero `doublecheck/gate` para que una ejecución asentada siga siendo reproducible.
|
|
211
229
|
|
|
212
230
|
## Desarrollo
|
|
213
231
|
|
package/README.hi.md
CHANGED
|
@@ -116,6 +116,10 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
116
116
|
| `gate.tests.allowFailingRuns` | `0` | नवीनतम हरे के बाद लाल से पहले अनुमत असफल रन। |
|
|
117
117
|
| `gate.tests.requireCoverage` | `false` | चालू करने पर परीक्षण आउटपुट में कवरेज साक्ष्य आवश्यक। |
|
|
118
118
|
| `gate.tests.minCoveragePct` | `80` | न्यूनतम कवरेज प्रतिशत (0–100)। |
|
|
119
|
+
| `gate.tests.evalReports.enabled` | `false` | चालू करने पर dsh-eval रिपोर्ट (dsh-auto-review का मूल्यांकन इंजन) परीक्षण साक्ष्य में मोड़ी जाती है। |
|
|
120
|
+
| `gate.tests.evalReports.dir` | `'.eval-reports'` | इंजन की रिपोर्ट रखने वाली कार्यक्षेत्र-सापेक्ष निर्देशिका। |
|
|
121
|
+
| `gate.tests.evalReports.file` | `'report.json'` | निर्देशिका के भीतर रिपोर्ट फ़ाइल का नाम। |
|
|
122
|
+
| `gate.tests.evalReports.required` | `false` | true होने पर अनुपस्थित रिपोर्ट लाल बत्ती होती है (अन्यथा छोड़ दी जाती है)। |
|
|
119
123
|
| `gate.consistency.*` | `provider: 'fork'`, `model: null`, `tools: ['read','glob','grep']`, `timeoutMs: 120000`, `maxFindings: 5` | स्थानीय संगति समीक्षक के नॉब (`model: null` = मुख्य मॉडल)। |
|
|
120
124
|
| `gate.review.engine` | `'auto'` | `auto` = उपस्थित होने पर dsh-auto-review के निर्णय रिकॉर्ड, अन्यथा स्थानीय समीक्षक; `local` = हमेशा स्थानीय। |
|
|
121
125
|
| `gate.review.provider` | `'fork'` | स्थानीय समीक्षा समीक्षक का प्रदाता (इसके `model`/`tools`/`timeoutMs`/`maxFindings` `gate.consistency.*` से मेल खाते हैं)। |
|
|
@@ -143,11 +147,11 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
143
147
|
| चरण | जाँचें | साक्ष्य स्रोत | मॉडल लागत |
|
|
144
148
|
|---|---|---|---|
|
|
145
149
|
| आवश्यकता पूछताछ | मुख्य-प्रश्न सूची एक-एक करके पुष्ट (डिफ़ॉल्ट रूप से छह spec-आयाम प्रश्न) | प्रतिबद्ध `doublecheck_spec` + `ask_user_question` कॉलें | कोई नहीं |
|
|
146
|
-
| परीक्षण साक्ष्य | नवीनतम रन रंग, हरे के बाद असफल रन, वैकल्पिक कवरेज
|
|
150
|
+
| परीक्षण साक्ष्य | नवीनतम रन रंग, हरे के बाद असफल रन, वैकल्पिक कवरेज थ्रेशोल्ड, वैकल्पिक dsh-eval रिपोर्ट | सत्र लॉग में शेल परीक्षण रन (`[exit code: N]`, कवरेज प्रतिशत); `gate.tests.evalReports.enabled` होने पर dsh-eval रिपोर्ट फ़ाइल | कोई नहीं |
|
|
147
151
|
| कार्यान्वयन संगति | diff ↔ आवश्यकता मैपिंग: हर एडिट को किसी spec आयाम की सेवा करनी चाहिए | स्थानीय फ़ोर्क समीक्षक (संरचित निष्कर्ष, केवल-पढ़ने वाले उपकरण) | एक subagent |
|
|
148
152
|
| समीक्षा निष्कर्ष | डिलीवरी निर्णय; `engine: auto` उपस्थित होने पर dsh-auto-review के टिकाऊ निर्णय रिकॉर्ड का उपभोग करता है, अन्यथा स्थानीय समीक्षक | `autoReview/verdict` / `autoReview/rejection` इवेंट, या स्थानीय फ़ोर्क समीक्षक | एक subagent (स्थानीय) |
|
|
149
153
|
|
|
150
|
-
लाल बत्तियाँ असफल जाँचें हैं (अनुपस्थित spec, असफल नवीनतम रन, न्यूनतम से कम कवरेज, अनमैप एडिट, blocker/major निष्कर्ष) — हर एक पुनः-कार्य सुझाव रखता है। चेतावनियाँ और छोड़े जाने कभी निर्णय नहीं बदलते। द्वार [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) को कमज़ोर निर्भरता के रूप में एकीकृत करता है: `review.engine: auto` उपस्थित होने पर उसके निर्णय रिकॉर्ड मोड़ता है और अन्यथा स्थानीय समीक्षक पर घट जाता है; द्वार कभी अनुमोदन अनुरोध संश्लेषित नहीं करता।
|
|
154
|
+
लाल बत्तियाँ असफल जाँचें हैं (अनुपस्थित spec, असफल नवीनतम रन, न्यूनतम से कम कवरेज, अनमैप एडिट, blocker/major निष्कर्ष) — हर एक पुनः-कार्य सुझाव रखता है। चेतावनियाँ और छोड़े जाने कभी निर्णय नहीं बदलते। द्वार [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) को कमज़ोर निर्भरता के रूप में एकीकृत करता है: `review.engine: auto` उपस्थित होने पर उसके निर्णय रिकॉर्ड मोड़ता है और अन्यथा स्थानीय समीक्षक पर घट जाता है; `gate.tests.evalReports.enabled` उसके मूल्यांकन इंजन की dsh-eval रिपोर्ट (prompt-regression / stress / fairness सूट) परीक्षण साक्ष्य में मोड़ता है और रिपोर्ट न होने पर ईमानदारी से छोड़ देता है। द्वार कभी अनुमोदन अनुरोध संश्लेषित नहीं करता।
|
|
151
155
|
|
|
152
156
|
## उदाहरण रिपोर्ट
|
|
153
157
|
|
|
@@ -188,6 +192,19 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
188
192
|
- counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
|
|
189
193
|
````
|
|
190
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
|
+
|
|
191
208
|
## अनुमतियाँ और डेटा
|
|
192
209
|
|
|
193
210
|
- **पढ़ता है**: सत्र लॉग (`tool/call` / `tool/result` / `tool/code-dispatch`, इंजेक्ट किए गए `user/message` स्रोत, और बाहरी `autoReview/*` निर्णय रिकॉर्ड) केवल प्रक्रिया के भीतर; वैकल्पिक प्लान-मोड सेवा स्थिति।
|
|
@@ -208,6 +225,7 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
208
225
|
- **टिकाऊ लेखन।** `/doublecheck on\|off` → `doublecheck/state` और `/gate run` → `doublecheck/gate` को host की `ignorable` append सतह (rc.6 के बाद) चाहिए, जो हर समर्थित host (≥ `0.1.1-rc.2`) प्रदान करता है।
|
|
209
226
|
- **वैकल्पिक इंटरफ़ेस।** `doublecheck.gate` सेटिंग्स नेमस्पेस केवल तब पंजीकृत होता है जब सेटिंग्स सेवा माउंट हो; `/gate status` की प्लान-मोड पंक्ति वैकल्पिक `ctx.planMode` पढ़ती है (इसके बिना `unknown` दिखाती है); प्रतिकूल समीक्षा को `ctx.subagents` चाहिए; सत्यापन को `workflowEngine` चाहिए।
|
|
210
227
|
- **स्थानीय अवनति।** जब dsh-auto-review अनुपस्थित हो या इस सत्र में उसके कोई निर्णय रिकॉर्ड न हों, तो `gate.review.engine: auto` स्थानीय समीक्षक पर घट जाता है — रिपोर्ट निर्णय गढ़ने के बजाय कारण बताती है।
|
|
228
|
+
- **dsh-eval साक्ष्य फ़ाइल-आधारित है।** dsh-auto-review का मूल्यांकन इंजन (`dsh-eval`) अपने prompt-regression / stress / fairness परिणाम कार्यक्षेत्र रिपोर्ट फ़ाइल में लिखता है, सत्र लॉग में नहीं। `gate.tests.evalReports.enabled` उस फ़ाइल को मोड़ता है (डिफ़ॉल्ट रूप से बंद; अनुपस्थित होने पर छोड़ देता है) और मोड़ी गई गणनाएँ टिकाऊ `doublecheck/gate` रिकॉर्ड पर चलती हैं, ताकि तयशुदा रन फिर भी रीप्ले हो सके।
|
|
211
229
|
|
|
212
230
|
## विकास
|
|
213
231
|
|
package/README.md
CHANGED
|
@@ -117,6 +117,10 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
117
117
|
| `gate.tests.allowFailingRuns` | `0` | Failing runs after the latest green allowed before red. |
|
|
118
118
|
| `gate.tests.requireCoverage` | `false` | On requires coverage evidence in the test output. |
|
|
119
119
|
| `gate.tests.minCoveragePct` | `80` | Minimum coverage percentage (0–100). |
|
|
120
|
+
| `gate.tests.evalReports.enabled` | `false` | On folds the dsh-eval report (dsh-auto-review's eval engine) into the test evidence. |
|
|
121
|
+
| `gate.tests.evalReports.dir` | `'.eval-reports'` | Workspace-relative directory holding the engine's report. |
|
|
122
|
+
| `gate.tests.evalReports.file` | `'report.json'` | Report file name inside the directory. |
|
|
123
|
+
| `gate.tests.evalReports.required` | `false` | A missing report is a red light exactly when true (a skip otherwise). |
|
|
120
124
|
| `gate.consistency.*` | `provider: 'fork'`, `model: null`, `tools: ['read','glob','grep']`, `timeoutMs: 120000`, `maxFindings: 5` | The local consistency reviewer's knobs (`model: null` = main model). |
|
|
121
125
|
| `gate.review.engine` | `'auto'` | `auto` = dsh-auto-review verdict records when present, else the local reviewer; `local` = always local. |
|
|
122
126
|
| `gate.review.provider` | `'fork'` | The local review reviewer's provider (its `model`/`tools`/`timeoutMs`/`maxFindings` match `gate.consistency.*`). |
|
|
@@ -144,11 +148,11 @@ The delivery gate aggregates the session's durable evidence into a configurable
|
|
|
144
148
|
| Phase | Checks | Evidence source | Model cost |
|
|
145
149
|
|---|---|---|---|
|
|
146
150
|
| Requirements interrogation | Key-question checklist confirmed item by item (six spec-dimension questions by default) | Committed `doublecheck_spec` + `ask_user_question` calls | none |
|
|
147
|
-
| Test evidence | Latest run color, failing runs after green, optional coverage threshold | Shell test runs in the session log (`[exit code: N]`, coverage percentages) | none |
|
|
151
|
+
| Test evidence | Latest run color, failing runs after green, optional coverage threshold, optional dsh-eval report | Shell test runs in the session log (`[exit code: N]`, coverage percentages); the dsh-eval report file when `gate.tests.evalReports.enabled` | none |
|
|
148
152
|
| Implementation consistency | Diff ↔ requirement mapping: every edit must serve a spec dimension | Local forked reviewer (structured findings, read-only tools) | one subagent |
|
|
149
153
|
| Review conclusion | The delivery verdict; `engine: auto` consumes dsh-auto-review's durable verdict records when present, else the local reviewer | `autoReview/verdict` / `autoReview/rejection` events, or the local forked reviewer | one subagent (local) |
|
|
150
154
|
|
|
151
|
-
Red lights are failed checks (a missing spec, a failing latest run, coverage below minimum, an unmapped edit, blocker/major findings) — each carries a rework suggestion. Warnings and skips never flip the decision. The gate integrates [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) as a weak dependency: `review.engine: auto` folds its verdict records when present and degrades to the local reviewer otherwise; the gate never synthesizes approval requests.
|
|
155
|
+
Red lights are failed checks (a missing spec, a failing latest run, coverage below minimum, an unmapped edit, blocker/major findings) — each carries a rework suggestion. Warnings and skips never flip the decision. The gate integrates [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) as a weak dependency: `review.engine: auto` folds its verdict records when present and degrades to the local reviewer otherwise; `gate.tests.evalReports.enabled` folds its eval engine's dsh-eval report (prompt-regression / stress / fairness suites) into the test evidence and skips honestly when no report exists. The gate never synthesizes approval requests.
|
|
152
156
|
|
|
153
157
|
## Example report
|
|
154
158
|
|
|
@@ -189,10 +193,23 @@ Red lights are failed checks (a missing spec, a failing latest run, coverage bel
|
|
|
189
193
|
- counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
|
|
190
194
|
````
|
|
191
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
|
+
|
|
192
209
|
## Permissions & data
|
|
193
210
|
|
|
194
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.
|
|
195
|
-
- **Writes**: `doublecheck-spec.md`, `doublecheck-report.md`, and `gate-report.
|
|
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.
|
|
196
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.
|
|
197
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.
|
|
198
215
|
|
|
@@ -209,6 +226,7 @@ Red lights are failed checks (a missing spec, a failing latest run, coverage bel
|
|
|
209
226
|
- **Durable writes.** `/doublecheck on\|off` → `doublecheck/state` and `/gate run` → `doublecheck/gate` ride the host's `ignorable` append surface (post-rc.6), which every supported host (≥ `0.1.1-rc.2`) provides.
|
|
210
227
|
- **Optional seams.** The `doublecheck.gate` settings namespace registers only when the settings service is mounted; the `/gate status` plan-mode line reads the optional `ctx.planMode` (shows `unknown` without it); the adversary review needs `ctx.subagents`; verification needs `workflowEngine`.
|
|
211
228
|
- **Local degrade.** `gate.review.engine: auto` degrades to the local reviewer when dsh-auto-review is absent or has no verdict records this session — the report names the reason instead of inventing a verdict.
|
|
229
|
+
- **dsh-eval evidence is file-based.** The dsh-auto-review eval engine (`dsh-eval`) writes its prompt-regression / stress / fairness results to a workspace report file, not the session log. `gate.tests.evalReports.enabled` folds that file (off by default; skips when absent) and the folded counts ride the durable `doublecheck/gate` record so a settled run still replays.
|
|
212
230
|
|
|
213
231
|
## Development
|
|
214
232
|
|
package/README.pt.md
CHANGED
|
@@ -116,6 +116,10 @@ Todos os ajustes são campos `Config` do Schemastery (alteráveis a partir do co
|
|
|
116
116
|
| `gate.tests.allowFailingRuns` | `0` | Execuções que falham após o último verde permitidas antes do vermelho. |
|
|
117
117
|
| `gate.tests.requireCoverage` | `false` | Ligado exige evidência de cobertura na saída do teste. |
|
|
118
118
|
| `gate.tests.minCoveragePct` | `80` | Percentual mínimo de cobertura (0–100). |
|
|
119
|
+
| `gate.tests.evalReports.enabled` | `false` | Ligado dobra o relatório dsh-eval (o motor de avaliação do dsh-auto-review) na evidência de teste. |
|
|
120
|
+
| `gate.tests.evalReports.dir` | `'.eval-reports'` | Diretório relativo ao espaço de trabalho que contém o relatório do motor. |
|
|
121
|
+
| `gate.tests.evalReports.file` | `'report.json'` | Nome do arquivo de relatório dentro do diretório. |
|
|
122
|
+
| `gate.tests.evalReports.required` | `false` | Um relatório ausente é uma luz vermelha exatamente quando true (um pulo caso contrário). |
|
|
119
123
|
| `gate.consistency.*` | `provider: 'fork'`, `model: null`, `tools: ['read','glob','grep']`, `timeoutMs: 120000`, `maxFindings: 5` | Ajustes do revisor local de consistência (`model: null` = modelo principal). |
|
|
120
124
|
| `gate.review.engine` | `'auto'` | `auto` = registros de veredicto do dsh-auto-review quando presentes, senão o revisor local; `local` = sempre local. |
|
|
121
125
|
| `gate.review.provider` | `'fork'` | Provedor do revisor local de revisão (seu `model`/`tools`/`timeoutMs`/`maxFindings` coincidem com `gate.consistency.*`). |
|
|
@@ -143,11 +147,11 @@ O portão de entrega agrega a evidência durável da sessão em uma lista config
|
|
|
143
147
|
| Fase | Verificações | Fonte de evidência | Custo de modelo |
|
|
144
148
|
|---|---|---|---|
|
|
145
149
|
| Interrogação de requisitos | Lista de perguntas-chave confirmadas item a item (seis perguntas de dimensão de spec por padrão) | `doublecheck_spec` confirmado + chamadas `ask_user_question` | nenhum |
|
|
146
|
-
| Evidência de teste | Cor da última execução, execuções que falham após o verde, limite de cobertura opcional | Execuções de teste shell no registro de sessão (`[exit code: N]`, percentuais de cobertura) | nenhum |
|
|
150
|
+
| Evidência de teste | Cor da última execução, execuções que falham após o verde, limite de cobertura opcional, relatório dsh-eval opcional | Execuções de teste shell no registro de sessão (`[exit code: N]`, percentuais de cobertura); o arquivo de relatório dsh-eval quando `gate.tests.evalReports.enabled` | nenhum |
|
|
147
151
|
| Consistência de implementação | Mapeamento diff ↔ requisito: cada edição deve servir a uma dimensão de spec | Revisor bifurcado local (achados estruturados, ferramentas somente leitura) | um subagente |
|
|
148
152
|
| Conclusão de revisão | O veredicto de entrega; `engine: auto` consome os registros de veredicto duráveis do dsh-auto-review quando presentes, senão o revisor local | Eventos `autoReview/verdict` / `autoReview/rejection`, ou o revisor bifurcado local | um subagente (local) |
|
|
149
153
|
|
|
150
|
-
As luzes vermelhas são verificações que falharam (um spec ausente, uma última execução que falhou, cobertura abaixo do mínimo, uma edição sem mapeamento, achados blocker/major) — cada uma carrega uma sugestão de retrabalho. Avisos e pulos nunca invertem a decisão. O portão integra o [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) como dependência fraca: `review.engine: auto` dobra seus registros de veredicto quando presentes e degrada para o revisor local caso contrário; o portão nunca sintetiza solicitações de aprovação.
|
|
154
|
+
As luzes vermelhas são verificações que falharam (um spec ausente, uma última execução que falhou, cobertura abaixo do mínimo, uma edição sem mapeamento, achados blocker/major) — cada uma carrega uma sugestão de retrabalho. Avisos e pulos nunca invertem a decisão. O portão integra o [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) como dependência fraca: `review.engine: auto` dobra seus registros de veredicto quando presentes e degrada para o revisor local caso contrário; `gate.tests.evalReports.enabled` dobra o relatório dsh-eval de seu motor de avaliação (suítes de regressão de prompt / estresse / equidade) na evidência de teste e pula honestamente quando não há relatório. O portão nunca sintetiza solicitações de aprovação.
|
|
151
155
|
|
|
152
156
|
## Relatório de exemplo
|
|
153
157
|
|
|
@@ -188,6 +192,19 @@ As luzes vermelhas são verificações que falharam (um spec ausente, uma últim
|
|
|
188
192
|
- counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
|
|
189
193
|
````
|
|
190
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
|
+
|
|
191
208
|
## Permissões e dados
|
|
192
209
|
|
|
193
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.
|
|
@@ -208,6 +225,7 @@ As luzes vermelhas são verificações que falharam (um spec ausente, uma últim
|
|
|
208
225
|
- **Escritas duráveis.** `/doublecheck on\|off` → `doublecheck/state` e `/gate run` → `doublecheck/gate` precisam da superfície de append `ignorable` do host (pós-rc.6), que todo host compatível (≥ `0.1.1-rc.2`) fornece.
|
|
209
226
|
- **Interfaces opcionais.** O namespace de configurações `doublecheck.gate` é registrado apenas quando o serviço de configurações está montado; a linha de modo plano de `/gate status` lê o `ctx.planMode` opcional (mostra `unknown` sem ele); a revisão adversarial precisa de `ctx.subagents`; a verificação precisa de `workflowEngine`.
|
|
210
227
|
- **Degradação local.** `gate.review.engine: auto` degrada para o revisor local quando o dsh-auto-review está ausente ou não tem registros de veredicto nesta sessão — o relatório nomeia a razão em vez de inventar um veredicto.
|
|
228
|
+
- **A evidência dsh-eval é baseada em arquivos.** O motor de avaliação do dsh-auto-review (`dsh-eval`) grava seus resultados de regressão de prompt / estresse / equidade em um arquivo de relatório do espaço de trabalho, não no registro de sessão. `gate.tests.evalReports.enabled` dobra esse arquivo (desativado por padrão; pula quando ausente) e as contagens dobradas viajam no registro durável `doublecheck/gate` para que uma execução assentada ainda seja reproduzível.
|
|
211
229
|
|
|
212
230
|
## Desenvolvimento
|
|
213
231
|
|
package/README.zh.md
CHANGED
|
@@ -116,6 +116,10 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
116
116
|
| `gate.tests.allowFailingRuns` | `0` | 最近一次绿色之后允许的失败运行次数,超过则红灯。 |
|
|
117
117
|
| `gate.tests.requireCoverage` | `false` | 开启则要求在测试输出中有覆盖率证据。 |
|
|
118
118
|
| `gate.tests.minCoveragePct` | `80` | 最低覆盖率百分比(0–100)。 |
|
|
119
|
+
| `gate.tests.evalReports.enabled` | `false` | 开启则把 dsh-eval 报告(dsh-auto-review 的评测引擎)并入测试证据。 |
|
|
120
|
+
| `gate.tests.evalReports.dir` | `'.eval-reports'` | 存放引擎报告的工作区相对目录。 |
|
|
121
|
+
| `gate.tests.evalReports.file` | `'report.json'` | 目录内的报告文件名。 |
|
|
122
|
+
| `gate.tests.evalReports.required` | `false` | 为 true 时缺失报告即为红灯(否则跳过)。 |
|
|
119
123
|
| `gate.consistency.*` | `provider: 'fork'`、`model: null`、`tools: ['read','glob','grep']`、`timeoutMs: 120000`、`maxFindings: 5` | 本地一致性评审者的旋钮(`model: null` = 主模型)。 |
|
|
120
124
|
| `gate.review.engine` | `'auto'` | `auto` = 存在时使用 dsh-auto-review 的裁决记录,否则使用本地评审者;`local` = 始终使用本地评审者。 |
|
|
121
125
|
| `gate.review.provider` | `'fork'` | 本地评审评审者的提供者(其 `model`/`tools`/`timeoutMs`/`maxFindings` 与 `gate.consistency.*` 相同)。 |
|
|
@@ -143,11 +147,11 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
143
147
|
| 阶段 | 检查 | 证据来源 | 模型成本 |
|
|
144
148
|
|---|---|---|---|
|
|
145
149
|
| 需求询问 | 逐项确认的关键问题清单(默认六个 spec 维度问题) | 已提交的 `doublecheck_spec` + `ask_user_question` 调用 | 无 |
|
|
146
|
-
| 测试证据 |
|
|
150
|
+
| 测试证据 | 最近运行颜色、绿色后的失败运行、可选覆盖率阈值、可选 dsh-eval 报告 | 会话日志中的 shell 测试运行(`[exit code: N]`、覆盖率百分比);开启 `gate.tests.evalReports.enabled` 时的 dsh-eval 报告文件 | 无 |
|
|
147
151
|
| 实现一致性 | 差异 ↔ 需求映射:每次改动都必须服务于某个 spec 维度 | 本地分叉评审者(结构化发现、只读工具) | 一个 subagent |
|
|
148
152
|
| 评审结论 | 交付裁决;`engine: auto` 在存在时消费 dsh-auto-review 的持久裁决记录,否则使用本地评审者 | `autoReview/verdict` / `autoReview/rejection` 事件,或本地分叉评审者 | 一个 subagent(本地) |
|
|
149
153
|
|
|
150
|
-
红灯是失败的检查(缺失 spec、最近运行失败、覆盖率低于下限、未映射的改动、blocker/major 发现)——每一项都附带返工建议。警告与跳过永远不会翻转裁决。门禁将 [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) 作为弱依赖集成:`review.engine: auto`
|
|
154
|
+
红灯是失败的检查(缺失 spec、最近运行失败、覆盖率低于下限、未映射的改动、blocker/major 发现)——每一项都附带返工建议。警告与跳过永远不会翻转裁决。门禁将 [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) 作为弱依赖集成:`review.engine: auto` 在存在时折叠其裁决记录,否则降级到本地评审者;`gate.tests.evalReports.enabled` 把其评测引擎的 dsh-eval 报告(prompt 回归 / 压测 / 公平性套件)并入测试证据,并在报告不存在时如实跳过。门禁从不合成审批请求。
|
|
151
155
|
|
|
152
156
|
## 示例报告
|
|
153
157
|
|
|
@@ -188,6 +192,19 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
188
192
|
- counts, ids, and verdicts only: no file contents or session text are embedded, and recognized secrets are redacted.
|
|
189
193
|
````
|
|
190
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
|
+
|
|
191
208
|
## 权限与数据
|
|
192
209
|
|
|
193
210
|
- **读取**:仅进程内读取会话日志(`tool/call` / `tool/result` / `tool/code-dispatch`、注入的 `user/message` 来源,以及外部的 `autoReview/*` 裁决记录);可选的计划模式服务状态。
|
|
@@ -208,6 +225,7 @@ dsh --profile web --dump-config | grep -E -A3 'id: doublecheck-(grill|guard)'
|
|
|
208
225
|
- **持久写入。** `/doublecheck on\|off` → `doublecheck/state` 与 `/gate run` → `doublecheck/gate` 需要宿主的 `ignorable` 追加接口(rc.6 之后),受支持宿主(≥ `0.1.1-rc.2`)均具备该接口。
|
|
209
226
|
- **可选接口。** `doublecheck.gate` 设置命名空间仅在挂载设置服务时注册;`/gate status` 的计划模式行读取可选的 `ctx.planMode`(没有则显示 `unknown`);对抗式评审需要 `ctx.subagents`;验证需要 `workflowEngine`。
|
|
210
227
|
- **本地降级。** 当 dsh-auto-review 缺失或本会话没有裁决记录时,`gate.review.engine: auto` 会降级到本地评审者——报告会写明原因,而不是捏造裁决。
|
|
228
|
+
- **dsh-eval 证据基于文件。** dsh-auto-review 评测引擎(`dsh-eval`)把其 prompt 回归 / 压测 / 公平性结果写入工作区报告文件,而非会话日志。`gate.tests.evalReports.enabled` 折叠该文件(默认关闭;缺失时跳过),折叠出的计数随持久 `doublecheck/gate` 记录保存,使已定的运行仍可重放。
|
|
211
229
|
|
|
212
230
|
## 开发
|
|
213
231
|
|
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));
|
package/lib/domain/gate.d.ts
CHANGED
|
@@ -156,6 +156,19 @@ export interface TestsGateConfig {
|
|
|
156
156
|
/** Minimum coverage percentage when coverage is required. */
|
|
157
157
|
minCoveragePct: number;
|
|
158
158
|
}
|
|
159
|
+
/** The dsh-eval evidence sub-config of the tests phase (weak dependency). */
|
|
160
|
+
export interface EvalReportsConfig {
|
|
161
|
+
/** Off keeps the tests phase purely session-log-derived (default). */
|
|
162
|
+
enabled: boolean;
|
|
163
|
+
/** Workspace-relative directory holding the engine's report file. */
|
|
164
|
+
dir: string;
|
|
165
|
+
/** Report file name inside {@link dir}. */
|
|
166
|
+
file: string;
|
|
167
|
+
/** A missing report is a red light exactly when true (a skip otherwise). */
|
|
168
|
+
required: boolean;
|
|
169
|
+
}
|
|
170
|
+
/** Default eval-evidence sub-config: off, so the gate never reads files by default. */
|
|
171
|
+
export declare const DEFAULT_EVAL_REPORTS_CONFIG: EvalReportsConfig;
|
|
159
172
|
/**
|
|
160
173
|
* Evaluate the tests phase: the latest run color, the open red window, and
|
|
161
174
|
* (optionally) the coverage percentage. Deterministic — no model calls.
|
|
@@ -164,6 +177,54 @@ export interface TestsGateConfig {
|
|
|
164
177
|
* @returns the settled phase result.
|
|
165
178
|
*/
|
|
166
179
|
export declare function evaluateTests(evidence: TestEvidence, config: TestsGateConfig): GatePhaseResult;
|
|
180
|
+
/** The dsh-eval report evidence folded from one report file (counts only). */
|
|
181
|
+
export interface EvalReportEvidence {
|
|
182
|
+
/** The suite name, or `dsh-eval` when the report omits it. */
|
|
183
|
+
suite: string;
|
|
184
|
+
/** Total cases in the suite. */
|
|
185
|
+
total: number;
|
|
186
|
+
/** Cases that passed. */
|
|
187
|
+
pass: number;
|
|
188
|
+
/** Cases that failed an assertion. */
|
|
189
|
+
fail: number;
|
|
190
|
+
/** Cases that errored before settling. */
|
|
191
|
+
error: number;
|
|
192
|
+
/** Cases cancelled. */
|
|
193
|
+
cancelled: number;
|
|
194
|
+
/** ISO finish timestamp, or '' when the report omits it. */
|
|
195
|
+
finishedAt: string;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse a dsh-eval `report.json` (the dsh-auto-review eval engine's machine
|
|
199
|
+
* report) into audit-safe counts. Structural and tolerant: the engine is a
|
|
200
|
+
* foreign weak dependency, so a malformed or unrelated file yields null
|
|
201
|
+
* instead of throwing. No case text or assertion detail is read — only the
|
|
202
|
+
* suite name, the five summary counts, and the finish timestamp.
|
|
203
|
+
* @param raw - the parsed JSON document.
|
|
204
|
+
* @returns the folded evidence, or null when the document is not a dsh-eval report.
|
|
205
|
+
*/
|
|
206
|
+
export declare function parseEvalReport(raw: unknown): EvalReportEvidence | null;
|
|
207
|
+
/**
|
|
208
|
+
* Evaluate the dsh-eval evidence check: a present report whose suite passed
|
|
209
|
+
* every case is a pass, a report with failing/erroring/cancelled cases is a
|
|
210
|
+
* red light, and a missing report is a skip (the weak dependency) — or a red
|
|
211
|
+
* light when `required` is set. Audit-safe: counts and the suite name only.
|
|
212
|
+
* @param evidence - the folded report, or null when none was readable.
|
|
213
|
+
* @param config - the eval-evidence sub-config.
|
|
214
|
+
* @param reason - why the report was unreadable (for the skip/red notice).
|
|
215
|
+
* @returns the eval-evidence check.
|
|
216
|
+
*/
|
|
217
|
+
export declare function evaluateEvalEvidence(evidence: EvalReportEvidence | null, config: EvalReportsConfig, reason: string): GateCheck;
|
|
218
|
+
/**
|
|
219
|
+
* Attach the eval-evidence check to a settled tests phase (the file fold is
|
|
220
|
+
* async and lives in the guard row, so the domain exposes the merge).
|
|
221
|
+
* @param result - the settled tests-phase result.
|
|
222
|
+
* @param evidence - the folded report, or null.
|
|
223
|
+
* @param config - the eval-evidence sub-config.
|
|
224
|
+
* @param reason - why the report was unreadable.
|
|
225
|
+
* @returns the tests-phase result with the eval check appended.
|
|
226
|
+
*/
|
|
227
|
+
export declare function attachEvalChecks(result: GatePhaseResult, evidence: EvalReportEvidence | null, config: EvalReportsConfig, reason: string): GatePhaseResult;
|
|
167
228
|
/** The structured output the consistency and local review subagents must satisfy. */
|
|
168
229
|
export declare const GATE_FINDINGS_SCHEMA: ObjectJsonSchema;
|
|
169
230
|
/**
|
|
@@ -261,3 +322,50 @@ export declare function renderGateReportMarkdown(state: GateState, planSuggestio
|
|
|
261
322
|
export declare function renderPhaseMarkdown(result: GatePhaseResult): string[];
|
|
262
323
|
/** Human-readable phase headings (stable English audit ids). */
|
|
263
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;
|
package/lib/domain/gate.js
CHANGED
|
@@ -241,6 +241,13 @@ export function evaluateRequirements(evidence, config) {
|
|
|
241
241
|
}
|
|
242
242
|
return { phase: 'requirements', enabled: true, status: worstStatus(checks), checks };
|
|
243
243
|
}
|
|
244
|
+
/** Default eval-evidence sub-config: off, so the gate never reads files by default. */
|
|
245
|
+
export const DEFAULT_EVAL_REPORTS_CONFIG = {
|
|
246
|
+
enabled: false,
|
|
247
|
+
dir: '.eval-reports',
|
|
248
|
+
file: 'report.json',
|
|
249
|
+
required: false,
|
|
250
|
+
};
|
|
244
251
|
/**
|
|
245
252
|
* Evaluate the tests phase: the latest run color, the open red window, and
|
|
246
253
|
* (optionally) the coverage percentage. Deterministic — no model calls.
|
|
@@ -304,6 +311,93 @@ export function evaluateTests(evidence, config) {
|
|
|
304
311
|
}
|
|
305
312
|
return { phase: 'tests', enabled: true, status: worstStatus(checks), checks };
|
|
306
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Parse a dsh-eval `report.json` (the dsh-auto-review eval engine's machine
|
|
316
|
+
* report) into audit-safe counts. Structural and tolerant: the engine is a
|
|
317
|
+
* foreign weak dependency, so a malformed or unrelated file yields null
|
|
318
|
+
* instead of throwing. No case text or assertion detail is read — only the
|
|
319
|
+
* suite name, the five summary counts, and the finish timestamp.
|
|
320
|
+
* @param raw - the parsed JSON document.
|
|
321
|
+
* @returns the folded evidence, or null when the document is not a dsh-eval report.
|
|
322
|
+
*/
|
|
323
|
+
export function parseEvalReport(raw) {
|
|
324
|
+
if (typeof raw !== 'object' || raw === null)
|
|
325
|
+
return null;
|
|
326
|
+
const report = raw;
|
|
327
|
+
const summary = report.summary;
|
|
328
|
+
if (typeof summary !== 'object' || summary === null)
|
|
329
|
+
return null;
|
|
330
|
+
const counts = summary;
|
|
331
|
+
const total = counts.total;
|
|
332
|
+
const pass = counts.pass;
|
|
333
|
+
const fail = counts.fail;
|
|
334
|
+
const error = counts.error;
|
|
335
|
+
const cancelled = counts.cancelled;
|
|
336
|
+
if (!Number.isInteger(total) || !Number.isInteger(pass) || !Number.isInteger(fail)
|
|
337
|
+
|| !Number.isInteger(error) || !Number.isInteger(cancelled)) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const finishedAt = report.finishedAt;
|
|
341
|
+
return {
|
|
342
|
+
suite: typeof report.suite === 'string' && report.suite.length > 0 ? report.suite : 'dsh-eval',
|
|
343
|
+
total: total,
|
|
344
|
+
pass: pass,
|
|
345
|
+
fail: fail,
|
|
346
|
+
error: error,
|
|
347
|
+
cancelled: cancelled,
|
|
348
|
+
finishedAt: typeof finishedAt === 'number'
|
|
349
|
+
? new Date(finishedAt).toISOString()
|
|
350
|
+
: typeof finishedAt === 'string' ? finishedAt : '',
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Evaluate the dsh-eval evidence check: a present report whose suite passed
|
|
355
|
+
* every case is a pass, a report with failing/erroring/cancelled cases is a
|
|
356
|
+
* red light, and a missing report is a skip (the weak dependency) — or a red
|
|
357
|
+
* light when `required` is set. Audit-safe: counts and the suite name only.
|
|
358
|
+
* @param evidence - the folded report, or null when none was readable.
|
|
359
|
+
* @param config - the eval-evidence sub-config.
|
|
360
|
+
* @param reason - why the report was unreadable (for the skip/red notice).
|
|
361
|
+
* @returns the eval-evidence check.
|
|
362
|
+
*/
|
|
363
|
+
export function evaluateEvalEvidence(evidence, config, reason) {
|
|
364
|
+
if (evidence === null) {
|
|
365
|
+
return {
|
|
366
|
+
id: 'eval-report',
|
|
367
|
+
phase: 'tests',
|
|
368
|
+
label: 'dsh-eval evidence (prompt regression / stress / fairness)',
|
|
369
|
+
status: config.required ? 'fail' : 'skip',
|
|
370
|
+
summary: `no dsh-eval report found${reason !== '' ? ` (${reason})` : ''}`,
|
|
371
|
+
suggestion: config.required
|
|
372
|
+
? 'run dsh-eval and write its report to the configured path before delivering'
|
|
373
|
+
: '',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const allPassed = evidence.total > 0 && evidence.fail === 0 && evidence.error === 0 && evidence.cancelled === 0;
|
|
377
|
+
return {
|
|
378
|
+
id: 'eval-report',
|
|
379
|
+
phase: 'tests',
|
|
380
|
+
label: 'dsh-eval evidence (prompt regression / stress / fairness)',
|
|
381
|
+
status: allPassed ? 'pass' : 'fail',
|
|
382
|
+
summary: allPassed
|
|
383
|
+
? `${evidence.pass}/${evidence.total} cases passed (dsh-eval suite "${evidence.suite}")`
|
|
384
|
+
: `${evidence.fail} failed, ${evidence.error} errored, ${evidence.cancelled} cancelled of ${evidence.total} cases (dsh-eval suite "${evidence.suite}")`,
|
|
385
|
+
suggestion: allPassed ? '' : 'fix the failing dsh-eval cases and re-run the suite',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Attach the eval-evidence check to a settled tests phase (the file fold is
|
|
390
|
+
* async and lives in the guard row, so the domain exposes the merge).
|
|
391
|
+
* @param result - the settled tests-phase result.
|
|
392
|
+
* @param evidence - the folded report, or null.
|
|
393
|
+
* @param config - the eval-evidence sub-config.
|
|
394
|
+
* @param reason - why the report was unreadable.
|
|
395
|
+
* @returns the tests-phase result with the eval check appended.
|
|
396
|
+
*/
|
|
397
|
+
export function attachEvalChecks(result, evidence, config, reason) {
|
|
398
|
+
const checks = [...result.checks, evaluateEvalEvidence(evidence, config, reason)];
|
|
399
|
+
return { ...result, checks, status: worstStatus(checks) };
|
|
400
|
+
}
|
|
307
401
|
/** The structured output the consistency and local review subagents must satisfy. */
|
|
308
402
|
export const GATE_FINDINGS_SCHEMA = {
|
|
309
403
|
type: 'object',
|
|
@@ -656,3 +750,87 @@ export function phaseLabel(phase) {
|
|
|
656
750
|
case 'review': return 'Review conclusion';
|
|
657
751
|
}
|
|
658
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.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands
|
|
|
29
29
|
import type { UserMessage } from '@deepseek-ai/dsh-llm';
|
|
30
30
|
import type Schema from '@deepseek-ai/schemastery';
|
|
31
31
|
import type { TestRunDetection } from '../domain/evidence.js';
|
|
32
|
-
import { type GateQuestion, type GateState, type GateVerdict } from '../domain/gate.js';
|
|
32
|
+
import { type EvalReportsConfig, type GateQuestion, type GateState, type GateVerdict } from '../domain/gate.js';
|
|
33
33
|
import type { GuardProse, ProseLanguage } from './prose.js';
|
|
34
34
|
/** The reviewer knobs shared by the consistency and review phases. */
|
|
35
35
|
export interface GateReviewerConfig {
|
|
@@ -76,6 +76,8 @@ export interface GateConfig {
|
|
|
76
76
|
minCoveragePct: number;
|
|
77
77
|
/** Regex (one capture group) parsing a coverage percentage from test output. */
|
|
78
78
|
coveragePattern: string;
|
|
79
|
+
/** The dsh-eval (dsh-auto-review eval engine) report fold. */
|
|
80
|
+
evalReports: EvalReportsConfig;
|
|
79
81
|
};
|
|
80
82
|
/** The implementation-consistency phase (local forked reviewer). */
|
|
81
83
|
consistency: GateReviewerConfig;
|
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 { countRedChecks, DEFAULT_COVERAGE_PATTERN, DEFAULT_GATE_QUESTIONS, deriveGateVerdict, evaluateConsistency, evaluateRequirements, evaluateReview, evaluateTests, foldAutoReviewEvidence, foldRequirementsEvidence, foldTestEvidence, GATE_FINDINGS_SCHEMA, 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. */
|
|
@@ -46,6 +46,7 @@ export const DEFAULT_GATE_CONFIG = {
|
|
|
46
46
|
requireCoverage: false,
|
|
47
47
|
minCoveragePct: 80,
|
|
48
48
|
coveragePattern: DEFAULT_COVERAGE_PATTERN,
|
|
49
|
+
evalReports: { ...DEFAULT_EVAL_REPORTS_CONFIG },
|
|
49
50
|
},
|
|
50
51
|
consistency: {
|
|
51
52
|
enabled: true,
|
|
@@ -91,7 +92,13 @@ export const GateConfigSchema = z.object({
|
|
|
91
92
|
requireCoverage: z.boolean().default(false),
|
|
92
93
|
minCoveragePct: z.number().default(80),
|
|
93
94
|
coveragePattern: z.string().min(1).default(DEFAULT_COVERAGE_PATTERN),
|
|
94
|
-
|
|
95
|
+
evalReports: z.object({
|
|
96
|
+
enabled: z.boolean().default(false),
|
|
97
|
+
dir: z.string().min(1).default('.eval-reports'),
|
|
98
|
+
file: z.string().min(1).default('report.json'),
|
|
99
|
+
required: z.boolean().default(false),
|
|
100
|
+
}).default({ ...DEFAULT_EVAL_REPORTS_CONFIG }),
|
|
101
|
+
}).default({ ...DEFAULT_GATE_CONFIG.tests, evalReports: { ...DEFAULT_EVAL_REPORTS_CONFIG } }),
|
|
95
102
|
consistency: z.object({
|
|
96
103
|
enabled: z.boolean().default(true),
|
|
97
104
|
provider: z.string().min(1).default('fork'),
|
|
@@ -176,6 +183,12 @@ export function validateGateConfig(gate) {
|
|
|
176
183
|
catch {
|
|
177
184
|
throw new Error(`dsh-doublecheck: invalid gate.tests.coveragePattern "${gate.tests.coveragePattern}"`);
|
|
178
185
|
}
|
|
186
|
+
if (gate.tests.evalReports.dir.trim() === '') {
|
|
187
|
+
throw new Error('dsh-doublecheck: gate.tests.evalReports.dir must not be empty');
|
|
188
|
+
}
|
|
189
|
+
if (gate.tests.evalReports.file.trim() === '') {
|
|
190
|
+
throw new Error('dsh-doublecheck: gate.tests.evalReports.file must not be empty');
|
|
191
|
+
}
|
|
179
192
|
for (const [label, reviewer] of [['consistency', gate.consistency], ['review', gate.review]]) {
|
|
180
193
|
if (!Number.isInteger(reviewer.maxFindings) || reviewer.maxFindings < 1 || reviewer.maxFindings > 20) {
|
|
181
194
|
throw new Error(`dsh-doublecheck: gate.${label}.maxFindings must be within 1..20`);
|
|
@@ -302,9 +315,16 @@ export async function runGate(ctx, config, detection, agent, signal, language) {
|
|
|
302
315
|
const requirements = config.requirements.enabled
|
|
303
316
|
? evaluateRequirements(foldRequirementsEvidence(events, config.requirements.interrogateTool), { checklist: config.requirements.checklist, minConfirmed: config.requirements.minConfirmed })
|
|
304
317
|
: skippedPhase('requirements');
|
|
305
|
-
|
|
318
|
+
let tests = config.tests.enabled
|
|
306
319
|
? evaluateTests(foldTestEvidence(events, detection, coverageRegex), config.tests)
|
|
307
320
|
: skippedPhase('tests');
|
|
321
|
+
if (config.tests.enabled && config.tests.evalReports.enabled) {
|
|
322
|
+
// Weak dependency on dsh-auto-review's eval engine: fold its report file
|
|
323
|
+
// (prompt-regression / stress / fairness suites) into the tests phase.
|
|
324
|
+
// A missing or malformed report settles as skip/red, never a throw.
|
|
325
|
+
const read = await readEvalEvidence(ctx, config.tests.evalReports, agent, signal);
|
|
326
|
+
tests = attachEvalChecks(tests, read.evidence, config.tests.evalReports, read.reason);
|
|
327
|
+
}
|
|
308
328
|
const prose = PROSE[language];
|
|
309
329
|
const consistencyTask = language === 'zh' ? CONSISTENCY_TASK_ZH : CONSISTENCY_TASK_EN;
|
|
310
330
|
const consistencyPromise = config.consistency.enabled
|
|
@@ -376,19 +396,63 @@ async function writeGateFile(ctx, filePath, state, agent, signal) {
|
|
|
376
396
|
const fs = ctx.get('fs');
|
|
377
397
|
if (fs === undefined)
|
|
378
398
|
return;
|
|
379
|
-
let target;
|
|
380
399
|
try {
|
|
381
|
-
|
|
400
|
+
const markdownTarget = await fs.resolve(filePath, {
|
|
401
|
+
...agent.session.header.cwd !== undefined ? { cwd: agent.session.header.cwd } : {},
|
|
402
|
+
signal,
|
|
403
|
+
});
|
|
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), {
|
|
382
409
|
...agent.session.header.cwd !== undefined ? { cwd: agent.session.header.cwd } : {},
|
|
383
410
|
signal,
|
|
384
411
|
});
|
|
385
|
-
await fs.writeText(
|
|
412
|
+
await fs.writeText(ciTarget, renderGateReportJson(state), undefined, signal);
|
|
386
413
|
}
|
|
387
414
|
catch (error) {
|
|
388
415
|
signal.throwIfAborted();
|
|
389
416
|
ctx.logger.debug(`dsh-doublecheck: gate report file write skipped: ${String(error)}`);
|
|
390
417
|
}
|
|
391
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
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Read and fold the dsh-eval report through the filesystem seam. A missing
|
|
425
|
+
* seam, a missing file, malformed JSON, or a wrong document shape all settle
|
|
426
|
+
* as `evidence: null` with a reason — never a throw (the weak dependency).
|
|
427
|
+
* @param ctx - plugin context carrying the fs seam.
|
|
428
|
+
* @param evalConfig - the eval-evidence sub-config.
|
|
429
|
+
* @param agent - the gated agent (its session cwd anchors the path).
|
|
430
|
+
* @param signal - the caller's cancellation signal.
|
|
431
|
+
* @returns the fold.
|
|
432
|
+
*/
|
|
433
|
+
async function readEvalEvidence(ctx, evalConfig, agent, signal) {
|
|
434
|
+
const fs = ctx.get('fs');
|
|
435
|
+
if (fs === undefined)
|
|
436
|
+
return { evidence: null, reason: 'the filesystem seam is not mounted' };
|
|
437
|
+
const dir = evalConfig.dir.replace(/\/+$/, '');
|
|
438
|
+
const reportPath = dir === '' || dir === '.' ? evalConfig.file : `${dir}/${evalConfig.file}`;
|
|
439
|
+
let target;
|
|
440
|
+
try {
|
|
441
|
+
target = await fs.resolve(reportPath, {
|
|
442
|
+
...agent.session.header.cwd !== undefined ? { cwd: agent.session.header.cwd } : {},
|
|
443
|
+
signal,
|
|
444
|
+
});
|
|
445
|
+
const text = await fs.readText(target, signal);
|
|
446
|
+
const evidence = parseEvalReport(JSON.parse(text));
|
|
447
|
+
if (evidence === null)
|
|
448
|
+
return { evidence: null, reason: `"${reportPath}" is not a dsh-eval report` };
|
|
449
|
+
return { evidence, reason: '' };
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
signal.throwIfAborted();
|
|
453
|
+
return { evidence: null, reason: `cannot read "${reportPath}": ${String(error)}` };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
392
456
|
/** The latest `doublecheck/gate` record of a session, or null before any run. */
|
|
393
457
|
function latestGateRecord(events) {
|
|
394
458
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
@@ -513,6 +577,6 @@ export function renderGateConfigMarkdown(config) {
|
|
|
513
577
|
config.requirements.checklist.forEach((question, index) => {
|
|
514
578
|
lines.push(`| ${index + 1} | ${question.id} | ${question.question} | ${question.specDimension ?? '—'} | ${question.required ? 'yes' : 'no'} |`);
|
|
515
579
|
});
|
|
516
|
-
lines.push('', `- minimum confirmed: ${config.requirements.minConfirmed}`, `- interrogation tool: ${config.requirements.interrogateTool}`, '', '## Test evidence', '', `- require passing run: ${config.tests.requirePassingRun}`, `- allowed failing runs after green: ${config.tests.allowFailingRuns}`, `- require coverage: ${config.tests.requireCoverage}`, `- minimum coverage: ${config.tests.minCoveragePct}%`, `- coverage pattern: \`${config.tests.coveragePattern}\``, '', '## Implementation consistency', '', `- enabled: ${config.consistency.enabled}`, `- reviewer provider: ${config.consistency.provider}`, `- max findings: ${config.consistency.maxFindings}`, `- tools: ${config.consistency.tools.join(', ')}`, '', '## Review conclusion', '', `- enabled: ${config.review.enabled}`, `- engine: ${config.review.engine} (auto = dsh-auto-review verdict records when present, else the local reviewer)`, `- reviewer provider: ${config.review.provider}`, `- max findings: ${config.review.maxFindings}`, `- tools: ${config.review.tools.join(', ')}`, '');
|
|
580
|
+
lines.push('', `- minimum confirmed: ${config.requirements.minConfirmed}`, `- interrogation tool: ${config.requirements.interrogateTool}`, '', '## Test evidence', '', `- require passing run: ${config.tests.requirePassingRun}`, `- allowed failing runs after green: ${config.tests.allowFailingRuns}`, `- require coverage: ${config.tests.requireCoverage}`, `- minimum coverage: ${config.tests.minCoveragePct}%`, `- coverage pattern: \`${config.tests.coveragePattern}\``, `- dsh-eval evidence: ${config.tests.evalReports.enabled ? `on (\`${config.tests.evalReports.dir}/${config.tests.evalReports.file}\`, required: ${config.tests.evalReports.required})` : 'off'}`, '', '## Implementation consistency', '', `- enabled: ${config.consistency.enabled}`, `- reviewer provider: ${config.consistency.provider}`, `- max findings: ${config.consistency.maxFindings}`, `- tools: ${config.consistency.tools.join(', ')}`, '', '## Review conclusion', '', `- enabled: ${config.review.enabled}`, `- engine: ${config.review.engine} (auto = dsh-auto-review verdict records when present, else the local reviewer)`, `- reviewer provider: ${config.review.provider}`, `- max findings: ${config.review.maxFindings}`, `- tools: ${config.review.tools.join(', ')}`, '');
|
|
517
581
|
return lines.join('\n');
|
|
518
582
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-doublecheck",
|
|
3
|
-
"version": "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",
|
package/strict.patch.yml
CHANGED
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
requireCoverage: true
|
|
54
54
|
minCoveragePct: 80
|
|
55
55
|
coveragePattern: 'coverage[^\\d]{0,40}(\\d+(?:\\.\\d+)?)\\s*%'
|
|
56
|
+
evalReports:
|
|
57
|
+
enabled: false
|
|
58
|
+
dir: '.eval-reports'
|
|
59
|
+
file: 'report.json'
|
|
60
|
+
required: false
|
|
56
61
|
consistency:
|
|
57
62
|
enabled: true
|
|
58
63
|
provider: fork
|