dsh-doublecheck 0.6.0 → 0.7.1

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.
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { isTestCommand, isTestFilePath, joinTextBlocks, mutationTargetPath, parseRawArguments, shellCommand, testOutcome, } from "./evidence.js";
14
14
  import { SPEC_TOOL_NAME } from "./stages.js";
15
+ import { countRedChecks } from "./gate.js";
15
16
  /** State for the empty log. */
16
17
  export function emptyDoublecheckState() {
17
18
  return {
@@ -21,6 +22,8 @@ export function emptyDoublecheckState() {
21
22
  specGoal: '',
22
23
  reviewed: false,
23
24
  editCount: 0,
25
+ gateVerdict: 'none',
26
+ gateRedCount: 0,
24
27
  pendingSpecCalls: {},
25
28
  pendingTestCalls: {},
26
29
  };
@@ -98,6 +101,13 @@ export function applyDoublecheckEvent(state, event, detection) {
98
101
  }
99
102
  return state;
100
103
  }
104
+ case 'doublecheck/gate': {
105
+ const gate = event.data;
106
+ const redCount = countRedChecks(Object.values(gate.phases));
107
+ if (state.gateVerdict === gate.verdict && state.gateRedCount === redCount)
108
+ return state;
109
+ return { ...state, gateVerdict: gate.verdict, gateRedCount: redCount };
110
+ }
101
111
  default:
102
112
  return state;
103
113
  }
@@ -128,5 +138,7 @@ export function viewDoublecheck(state) {
128
138
  specGoal: state.specGoal,
129
139
  reviewed: state.reviewed,
130
140
  editCount: state.editCount,
141
+ gateVerdict: state.gateVerdict,
142
+ gateRedCount: state.gateRedCount,
131
143
  };
132
144
  }
@@ -14,8 +14,8 @@
14
14
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
15
15
  import type { ObjectJsonSchema } from '@deepseek-ai/dsh-tools';
16
16
  import type { WorkflowMeta } from '@deepseek-ai/dsh-workflow';
17
- import { type TestRunDetection } from './evidence.ts';
18
- import { type GrilledSpec, type ReportVerdict, type ReviewFinding, type ReviewVerdict, type VerifyCheck } from './vocabulary.ts';
17
+ import { type TestRunDetection } from './evidence.js';
18
+ import { type GrilledSpec, type ReportVerdict, type ReviewFinding, type ReviewVerdict, type VerifyCheck } from './vocabulary.js';
19
19
  /** One timeline row of the report fold, in log order. */
20
20
  export interface ReportTimelineEntry {
21
21
  /** The evidence kind this row records. */
@@ -15,7 +15,7 @@
15
15
  * @module dsh-doublecheck/domain/stages
16
16
  */
17
17
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
18
- import { type TestRunDetection } from './evidence.ts';
18
+ import { type TestRunDetection } from './evidence.js';
19
19
  /** The discipline stages, in execution order. */
20
20
  export type DisciplineStage = 'grill' | 'design' | 'red' | 'green' | 'review' | 'verify';
21
21
  /** All discipline stages in order. */
package/lib/events.d.ts CHANGED
@@ -14,14 +14,16 @@
14
14
  */
15
15
  import type { Agent } from '@deepseek-ai/dsh-agent';
16
16
  import type { Session, SessionEventMap } from '@deepseek-ai/dsh-session';
17
- import type { GrilledSpec, ReviewFinding, ReviewVerdict, ReportVerdict, VerifyCheck } from './domain/vocabulary.ts';
18
- export type { GrilledSpec, ReviewFinding, ReviewVerdict, ReportVerdict, VerifyCheck } from './domain/vocabulary.ts';
17
+ import type { GrilledSpec, ReviewFinding, ReviewVerdict, ReportVerdict, VerifyCheck } from './domain/vocabulary.js';
18
+ import type { GateState, GateVerdict } from './domain/gate.js';
19
+ export type { GrilledSpec, ReviewFinding, ReviewVerdict, ReportVerdict, VerifyCheck } from './domain/vocabulary.js';
20
+ export type { GateState } from './domain/gate.js';
19
21
  /** Guard enforcement strength. */
20
22
  export type GuardIntensity = 'remind' | 'warn' | 'block';
21
23
  /** Which discipline gate produced a guard reaction. */
22
- export type GuardGate = 'grill' | 'tdd' | 'delivery';
24
+ export type GuardGate = 'grill' | 'tdd' | 'delivery' | 'gate';
23
25
  /** Policy outcome of one guard reaction. */
24
- export type GuardVerdict = 'reminded' | 'held' | 'denied' | 'green-pending' | 'report-expected';
26
+ export type GuardVerdict = 'reminded' | 'held' | 'denied' | 'green-pending' | 'report-expected' | 'gate-red';
25
27
  declare module '@deepseek-ai/dsh-session' {
26
28
  interface SessionEventMap {
27
29
  /**
@@ -34,6 +36,14 @@ declare module '@deepseek-ai/dsh-session' {
34
36
  'doublecheck/state': {
35
37
  enabled: boolean;
36
38
  };
39
+ /**
40
+ * One settled delivery-gate run written by `/gate run`: the full gate
41
+ * state (checklist statuses, red items, verdict, engine, timestamp).
42
+ * Audit-safe by construction — counts, ids, and verdicts only — and the
43
+ * replayable source the `/gate status` panel and the turn-boundary red
44
+ * notice re-derive from. Rides `ignorable: true` like the state switch.
45
+ */
46
+ 'doublecheck/gate': GateState;
37
47
  }
38
48
  }
39
49
  /**
@@ -46,6 +56,15 @@ export interface StateAppend {
46
56
  ignorable: true;
47
57
  }): void;
48
58
  }
59
+ /**
60
+ * `Session.append` narrowed to the `doublecheck/gate` event, with the same
61
+ * `ignorable: true` envelope marker as the state switch.
62
+ */
63
+ export interface GateAppend {
64
+ (type: 'doublecheck/gate', data: SessionEventMap['doublecheck/gate'], options: {
65
+ ignorable: true;
66
+ }): void;
67
+ }
49
68
  declare module '@deepseek-ai/dsh-llm' {
50
69
  interface MessageSourceMap {
51
70
  /**
@@ -59,6 +78,17 @@ declare module '@deepseek-ai/dsh-llm' {
59
78
  verdict: ReviewVerdict;
60
79
  findings: ReviewFinding[];
61
80
  };
81
+ /**
82
+ * The delivery-gate red notice injected at the turn boundary: the
83
+ * model-facing text is the short gate notice (role statement first), and
84
+ * the structured verdict + red count ride this source so the durable
85
+ * once-semantics and the report folds read them without re-parsing prose.
86
+ */
87
+ 'doublecheck-gate': {
88
+ kind: 'doublecheck-gate';
89
+ verdict: GateVerdict;
90
+ redCount: number;
91
+ };
62
92
  }
63
93
  }
64
94
  declare module '@deepseek-ai/cordis' {
@@ -151,5 +181,17 @@ declare module '@deepseek-ai/cordis' {
151
181
  path: string | null;
152
182
  written: boolean;
153
183
  }): void;
184
+ /**
185
+ * One delivery-gate run settled: the full gate state the `/gate run`
186
+ * command derived, after the durable `doublecheck/gate` session event was
187
+ * appended. Observability only — listeners must not veto or reroute.
188
+ * @param payload.session - the gated session.
189
+ * @param payload.state - the settled gate state (redacted by construction).
190
+ * @mode emit
191
+ */
192
+ 'doublecheck/gate'(payload: {
193
+ session: Session;
194
+ state: GateState;
195
+ }): void;
154
196
  }
155
197
  }
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import type { Context } from '@deepseek-ai/cordis';
16
16
  import type Schema from '@deepseek-ai/schemastery';
17
- import type { GrilledSpec } from '../events.ts';
17
+ import type { GrilledSpec } from '../events.js';
18
18
  export declare const name = "doublecheck-grill";
19
19
  export declare const inject: string[];
20
20
  /** Grill module configuration. */
@@ -16,8 +16,8 @@
16
16
  import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands';
17
17
  import { Session } from '@deepseek-ai/dsh-session';
18
18
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
19
- import type { TestRunDetection } from '../domain/evidence.ts';
20
- import type { Config, Snapshot } from './index.ts';
19
+ import type { TestRunDetection } from '../domain/evidence.js';
20
+ import type { Config, Snapshot } from './index.js';
21
21
  export declare function hostStampsIgnorable(): boolean;
22
22
  /**
23
23
  * The session's doublecheck master switch: the last `doublecheck/state` event,
@@ -93,6 +93,9 @@ export function doublecheckHandler(deps) {
93
93
  color: discipline.color,
94
94
  reviewed: snapshot.lastReviewSeq >= 0,
95
95
  editCount: discipline.editCount,
96
+ gate: snapshot.lastGate === null
97
+ ? null
98
+ : { verdict: snapshot.lastGate.verdict, redCount: snapshot.lastGate.redCount },
96
99
  }),
97
100
  };
98
101
  }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The delivery gate runner: orchestrates the four-phase quality checklist
3
+ * over one session's durable evidence and settles the deliverable/rework
4
+ * decision.
5
+ *
6
+ * - **requirements** + **tests** — deterministic folds of the session log
7
+ * (no model calls): the key-question checklist against the committed
8
+ * spec, and the test-run/coverage evidence.
9
+ * - **consistency** — a local forked reviewer audits the diff ↔ requirement
10
+ * mapping (every edit must serve a spec dimension).
11
+ * - **review** — the delivery conclusion. With `engine: auto`, the
12
+ * dsh-auto-review engine's durable verdict records (`autoReview/verdict` /
13
+ * `autoReview/rejection`) are the evidence; when the engine is not
14
+ * installed or has no records this session, the phase degrades to the
15
+ * local forked reviewer and says so. `engine: local` always uses the
16
+ * local reviewer. The gate never synthesizes approval requests: that
17
+ * chain may reach a human.
18
+ *
19
+ * Everything a run settles is audit-safe: counts, ids, verdicts, and
20
+ * redacted findings only. The settled state rides the durable
21
+ * `doublecheck/gate` session event (replay IS the state), the workspace
22
+ * `gate-report.md`, and the `/gate run` command result.
23
+ *
24
+ * @module dsh-doublecheck/guard/gate
25
+ */
26
+ import type { Context } from '@deepseek-ai/cordis';
27
+ import type { Agent } from '@deepseek-ai/dsh-agent';
28
+ import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands';
29
+ import type { UserMessage } from '@deepseek-ai/dsh-llm';
30
+ import type Schema from '@deepseek-ai/schemastery';
31
+ import type { TestRunDetection } from '../domain/evidence.js';
32
+ import { type GateQuestion, type GateState, type GateVerdict } from '../domain/gate.js';
33
+ import type { GuardProse, ProseLanguage } from './prose.js';
34
+ /** The reviewer knobs shared by the consistency and review phases. */
35
+ export interface GateReviewerConfig {
36
+ enabled: boolean;
37
+ /** Subagent provider name the local reviewer runs on (default `fork`). */
38
+ provider: string;
39
+ /** Model route for the local reviewer; null means the main model reviews. */
40
+ model: string | null;
41
+ /** Tools the local reviewer may call (read-only by default). */
42
+ tools: string[];
43
+ /** Hard time budget for one local reviewer run. */
44
+ timeoutMs: number;
45
+ /** Maximum structured findings per phase. */
46
+ maxFindings: number;
47
+ }
48
+ /** The delivery-gate configuration block of the guard row. */
49
+ export interface GateConfig {
50
+ /** Master switch for the gate panel and the turn-boundary red notice. */
51
+ enabled: boolean;
52
+ /** Suggest re-opening in plan mode when the verdict is rework. */
53
+ planSuggestion: boolean;
54
+ /** Workspace file (relative to the session cwd) receiving the gate report. */
55
+ reportFile: string;
56
+ /** The requirements-interrogation phase. */
57
+ requirements: {
58
+ enabled: boolean;
59
+ /** The configurable key-question checklist. */
60
+ checklist: GateQuestion[];
61
+ /** Minimum required questions that must be confirmed for the phase to pass. */
62
+ minConfirmed: number;
63
+ /** Tool name whose calls count as user interrogation evidence. */
64
+ interrogateTool: string;
65
+ };
66
+ /** The test-evidence phase. */
67
+ tests: {
68
+ enabled: boolean;
69
+ /** Require a passing test run as the latest evidence. */
70
+ requirePassingRun: boolean;
71
+ /** Failing runs after the latest green allowed before the gate turns red. */
72
+ allowFailingRuns: number;
73
+ /** Require coverage evidence in the test output. */
74
+ requireCoverage: boolean;
75
+ /** Minimum coverage percentage when coverage is required. */
76
+ minCoveragePct: number;
77
+ /** Regex (one capture group) parsing a coverage percentage from test output. */
78
+ coveragePattern: string;
79
+ };
80
+ /** The implementation-consistency phase (local forked reviewer). */
81
+ consistency: GateReviewerConfig;
82
+ /** The review-conclusion phase (engine verdict records + local degrade). */
83
+ review: GateReviewerConfig & {
84
+ /** `auto` = dsh-auto-review verdict records when present, else local. */
85
+ engine: 'auto' | 'local';
86
+ };
87
+ }
88
+ /** The default gate configuration: every phase on, the six key questions required. */
89
+ export declare const DEFAULT_GATE_CONFIG: GateConfig;
90
+ /** The delivery-gate configuration schema (the pluggable checklist + knobs). */
91
+ export declare const GateConfigSchema: Schema<GateConfig>;
92
+ /**
93
+ * Validate the gate configuration fail-loud: checklist ids unique and
94
+ * non-empty, valid spec-dimension bindings, bounded thresholds, compilable
95
+ * coverage regex, and sound reviewer knobs. A bad gate config must not
96
+ * silently produce an uncheckable checklist.
97
+ * @param gate - the gate config block.
98
+ */
99
+ export declare function validateGateConfig(gate: GateConfig): void;
100
+ /**
101
+ * Run the full delivery gate over the agent's session and settle the state.
102
+ * The deterministic phases fold first; the two reviewer phases run in
103
+ * parallel. Failures degrade to honest skip notices, never to throws.
104
+ * @param ctx - plugin context (subagents, commands, fs seams).
105
+ * @param config - the gate configuration.
106
+ * @param detection - the compiled test-run detection knobs.
107
+ * @param agent - the gated agent.
108
+ * @param signal - the caller's cancellation signal.
109
+ * @param language - prose language for the reviewer tasks.
110
+ * @returns the settled gate state.
111
+ */
112
+ export declare function runGate(ctx: Context, config: GateConfig, detection: TestRunDetection, agent: Agent, signal: AbortSignal, language: ProseLanguage): Promise<GateState>;
113
+ /**
114
+ * Settle one gate run's side effects: the durable `doublecheck/gate` session
115
+ * event (when the host stamps `ignorable`), the observability announcement,
116
+ * and the workspace `gate-report.md` copy. Failures of the file write never
117
+ * fail the run — the command result and the durable event remain.
118
+ * @param ctx - plugin context.
119
+ * @param config - the gate configuration.
120
+ * @param state - the settled gate state.
121
+ * @param agent - the gated agent.
122
+ * @param signal - the caller's cancellation signal.
123
+ * @param stampsIgnorable - whether the host stamps the `ignorable` marker.
124
+ */
125
+ export declare function settleGate(ctx: Context, config: GateConfig, state: GateState, agent: Agent, signal: AbortSignal, stampsIgnorable: boolean): Promise<void>;
126
+ /** The closures the `/gate` command reads; all live inside the guard's `apply` scope. */
127
+ export interface GateCommandDeps {
128
+ config: GateConfig;
129
+ detection: TestRunDetection;
130
+ prose: GuardProse;
131
+ /** Runs the full gate and returns the settled state. */
132
+ runGate: (agent: Agent, signal: AbortSignal) => Promise<GateState>;
133
+ /** Settles the durable append, the announcement, and the workspace file. */
134
+ settleGate: (state: GateState, agent: Agent, signal: AbortSignal) => Promise<void>;
135
+ /** Whether the host stamps the `ignorable` marker (the durable write gate). */
136
+ stampsIgnorable: () => boolean;
137
+ /** The optional plan-mode service (weak seam: `ctx.get('planMode')`). */
138
+ planMode?: {
139
+ get(agent: Agent): {
140
+ active?: boolean;
141
+ } | undefined;
142
+ };
143
+ }
144
+ /**
145
+ * The `/gate` command handler: `status` renders the live checklist progress
146
+ * plus the latest settled run; `run` settles the full gate and returns the
147
+ * PR-ready markdown report; `config` renders the effective checklist.
148
+ * @param deps - the guard closures the handler reads.
149
+ * @returns the command handler for `ctx.commands.register`.
150
+ */
151
+ export declare function gateHandler(deps: GateCommandDeps): (invocation: CommandInvocation) => CommandResult | Promise<CommandResult>;
152
+ /**
153
+ * The injected gate-red notice message: short model-facing prose (role
154
+ * statement first) with the structured verdict and red count riding the
155
+ * durable `doublecheck-gate` message source, so the durable once-semantics
156
+ * and the report folds read the record without re-parsing the prose.
157
+ * @param verdict - the settled verdict.
158
+ * @param redCount - the red-item count.
159
+ * @param text - the model-facing notice text.
160
+ * @returns the injection-ready user message.
161
+ */
162
+ export declare function gateInjection(verdict: GateVerdict, redCount: number, text: string): UserMessage;
163
+ /**
164
+ * Render the effective gate configuration as markdown — the `/gate config`
165
+ * output: the pluggable checklist, the thresholds, and the reviewer knobs.
166
+ * Configuration contains no secrets by construction; nothing is redacted.
167
+ * @param config - the gate configuration.
168
+ * @returns the configuration document.
169
+ */
170
+ export declare function renderGateConfigMarkdown(config: GateConfig): string;