dsh-doublecheck 0.6.0 → 0.7.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 +19 -0
- package/README.es.md +237 -39
- package/README.hi.md +231 -41
- package/README.md +221 -28
- package/README.pt.md +237 -39
- package/README.zh.md +313 -118
- package/lib/domain/gate.d.ts +263 -0
- package/lib/domain/gate.js +658 -0
- package/lib/domain/projection.d.ts +5 -0
- package/lib/domain/projection.js +12 -0
- package/lib/events.d.ts +44 -2
- package/lib/guard/command.js +3 -0
- package/lib/guard/gate.d.ts +170 -0
- package/lib/guard/gate.js +518 -0
- package/lib/guard/index.d.ts +12 -0
- package/lib/guard/index.js +77 -2
- package/lib/guard/prose.d.ts +17 -0
- package/lib/guard/prose.js +14 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/invariant.d.ts +2 -0
- package/lib/invariant.js +15 -0
- package/lib/types.d.ts +7 -0
- package/lib/types.js +2 -0
- package/package.json +44 -3
- package/skills/delivery-proof/SKILL.md +12 -0
- package/strict.patch.yml +37 -0
package/lib/domain/projection.js
CHANGED
|
@@ -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
|
}
|
package/lib/events.d.ts
CHANGED
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
16
16
|
import type { Session, SessionEventMap } from '@deepseek-ai/dsh-session';
|
|
17
17
|
import type { GrilledSpec, ReviewFinding, ReviewVerdict, ReportVerdict, VerifyCheck } from './domain/vocabulary.ts';
|
|
18
|
+
import type { GateState, GateVerdict } from './domain/gate.ts';
|
|
18
19
|
export type { GrilledSpec, ReviewFinding, ReviewVerdict, ReportVerdict, VerifyCheck } from './domain/vocabulary.ts';
|
|
20
|
+
export type { GateState } from './domain/gate.ts';
|
|
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
|
}
|
package/lib/guard/command.js
CHANGED
|
@@ -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.ts';
|
|
32
|
+
import { type GateQuestion, type GateState, type GateVerdict } from '../domain/gate.ts';
|
|
33
|
+
import type { GuardProse, ProseLanguage } from './prose.ts';
|
|
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;
|