dsh-doublecheck 0.5.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 +65 -0
- package/LICENSE +201 -0
- package/README.es.md +252 -0
- package/README.hi.md +252 -0
- package/README.md +268 -0
- package/README.pt.md +252 -0
- package/README.zh.md +266 -0
- package/cordis.patch.yml +19 -0
- package/lib/domain/evidence.d.ts +100 -0
- package/lib/domain/evidence.js +180 -0
- package/lib/domain/projection.d.ts +60 -0
- package/lib/domain/projection.js +132 -0
- package/lib/domain/report.d.ts +93 -0
- package/lib/domain/report.js +266 -0
- package/lib/domain/stages.d.ts +98 -0
- package/lib/domain/stages.js +192 -0
- package/lib/domain/vagueness.d.ts +25 -0
- package/lib/domain/vagueness.js +56 -0
- package/lib/domain/vocabulary.d.ts +50 -0
- package/lib/domain/vocabulary.js +16 -0
- package/lib/events.d.ts +155 -0
- package/lib/events.js +15 -0
- package/lib/grill/index.d.ts +49 -0
- package/lib/grill/index.js +495 -0
- package/lib/grill/provider.d.ts +56 -0
- package/lib/grill/provider.js +189 -0
- package/lib/guard/command.d.ts +47 -0
- package/lib/guard/command.js +132 -0
- package/lib/guard/index.d.ts +111 -0
- package/lib/guard/index.js +516 -0
- package/lib/guard/prose.d.ts +34 -0
- package/lib/guard/prose.js +80 -0
- package/lib/guard/review.d.ts +67 -0
- package/lib/guard/review.js +184 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +15 -0
- package/lib/invariant.d.ts +56 -0
- package/lib/invariant.js +112 -0
- package/lib/types.d.ts +39 -0
- package/lib/types.js +18 -0
- package/package.json +109 -0
- package/skills/delivery-proof/SKILL.md +52 -0
- package/skills/delivery-review/SKILL.md +49 -0
- package/skills/grill-requirements/SKILL.md +98 -0
- package/skills/red-green-tdd/SKILL.md +57 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-run evidence classification for the red/green gates.
|
|
3
|
+
*
|
|
4
|
+
* The durable session log is the only source of truth: a shell tool call
|
|
5
|
+
* (`bash` / `pwsh`, including Code Mode sub-dispatches) whose command matches
|
|
6
|
+
* the configured test patterns is a test run, and its rendered result text
|
|
7
|
+
* carries the exit facts (`[exit code: N]`, timeout, signal, sandbox-denial
|
|
8
|
+
* markers). A failing run is red evidence; a passing run is green evidence.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-doublecheck/domain/evidence
|
|
11
|
+
*/
|
|
12
|
+
/** Shared defaults: shell tools that can run tests. */
|
|
13
|
+
export const DEFAULT_TEST_TOOL_NAMES = ['bash', 'pwsh'];
|
|
14
|
+
/** Shared defaults: commands that count as test runs. */
|
|
15
|
+
export const DEFAULT_TEST_COMMAND_PATTERNS = [
|
|
16
|
+
'(?:^|[;&|]\\s*)(?:(?:pnpm|npm|npx|yarn|bun)(?:\\s+run)?\\s+(?:test|vitest|jest|mocha)(?:\\s|$))',
|
|
17
|
+
'(?:^|[;&|]\\s*)(?:(?:pytest|go\\s+test|cargo\\s+test|make\\s+test|ctest)(?:\\s|$))',
|
|
18
|
+
'(?:^|[;&|]\\s*)(?:node\\s+--test(?:\\s|$))',
|
|
19
|
+
];
|
|
20
|
+
/** Shared defaults: mutation tools and test-file path patterns. */
|
|
21
|
+
export const DEFAULT_MUTATION_TOOLS = ['edit', 'write'];
|
|
22
|
+
/** Shared defaults: paths that identify test files. */
|
|
23
|
+
export const DEFAULT_TEST_FILE_PATTERNS = [
|
|
24
|
+
'(^|[\\\\/])(tests?|__tests__|specs?)([\\\\/]|$)',
|
|
25
|
+
'\\.(test|spec)\\.[A-Za-z0-9]+$',
|
|
26
|
+
];
|
|
27
|
+
/** Background bash/pwsh acknowledgements are not test outcomes. */
|
|
28
|
+
const BACKGROUND_ACK = /^started background job \S+/;
|
|
29
|
+
/** The command never ran: a sandbox denial is not a failing test. */
|
|
30
|
+
const SANDBOX_DENIAL = /\[sandbox: [^\]]*denied under [^\]]* mode\]/;
|
|
31
|
+
const TIMED_OUT = /\[timed out after \d+ms\]/;
|
|
32
|
+
const KILLED_BY_SIGNAL = /\[killed by signal: [^\]]+\]/;
|
|
33
|
+
const EXIT_CODE = /\[exit code: (\d+)\]/m;
|
|
34
|
+
/**
|
|
35
|
+
* Compile the detection knobs, rejecting invalid regexes at load time.
|
|
36
|
+
* @param config - the raw string knobs from the guard config.
|
|
37
|
+
* @returns the compiled detection record.
|
|
38
|
+
*/
|
|
39
|
+
export function compileDetection(config) {
|
|
40
|
+
assertNonEmptyNames('testToolNames', config.testToolNames);
|
|
41
|
+
assertNonEmptyNames('guardTools', config.guardTools);
|
|
42
|
+
return {
|
|
43
|
+
testToolNames: [...config.testToolNames],
|
|
44
|
+
testCommandPatterns: config.testCommandPatterns.map(compilePattern),
|
|
45
|
+
mutationTools: [...config.guardTools],
|
|
46
|
+
testFilePatterns: config.testFilePatterns.map(compilePattern),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The detection record with every list empty: test-run evidence is ignored. */
|
|
50
|
+
export function emptyDetection() {
|
|
51
|
+
return {
|
|
52
|
+
testToolNames: [],
|
|
53
|
+
testCommandPatterns: [],
|
|
54
|
+
mutationTools: [],
|
|
55
|
+
testFilePatterns: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse a raw tool-call `arguments` value (a JSON string from the model, or an
|
|
60
|
+
* already-normalized object from a Code Mode sub-dispatch) into a plain record.
|
|
61
|
+
* @param raw - the raw arguments value from the durable event.
|
|
62
|
+
* @returns the parsed record, or `undefined` when the string is not valid JSON.
|
|
63
|
+
*/
|
|
64
|
+
export function parseRawArguments(raw) {
|
|
65
|
+
if (raw !== null && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
66
|
+
return raw;
|
|
67
|
+
}
|
|
68
|
+
if (typeof raw !== 'string')
|
|
69
|
+
return undefined;
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
73
|
+
return undefined;
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Malformed model JSON carries no usable command or path; it is not evidence.
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The shell command a tool call would execute, when the tool is a configured
|
|
83
|
+
* test runner.
|
|
84
|
+
* @param name - the called tool name.
|
|
85
|
+
* @param args - the parsed arguments record.
|
|
86
|
+
* @param detection - the compiled detection knobs.
|
|
87
|
+
* @returns the command text, or `undefined` when this call is not a shell run.
|
|
88
|
+
*/
|
|
89
|
+
export function shellCommand(name, args, detection) {
|
|
90
|
+
if (!detection.testToolNames.includes(name))
|
|
91
|
+
return undefined;
|
|
92
|
+
const command = args?.['command'];
|
|
93
|
+
return typeof command === 'string' && command.length > 0 ? command : undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a shell command is a test run under the configured patterns.
|
|
97
|
+
* @param command - the command text.
|
|
98
|
+
* @param detection - the compiled detection knobs.
|
|
99
|
+
* @returns true when at least one pattern matches.
|
|
100
|
+
*/
|
|
101
|
+
export function isTestCommand(command, detection) {
|
|
102
|
+
return detection.testCommandPatterns.some(pattern => pattern.test(command));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The mutation target path of a tool call, when the tool is a configured
|
|
106
|
+
* mutation tool and its arguments name a file.
|
|
107
|
+
* @param name - the called tool name.
|
|
108
|
+
* @param args - the parsed arguments record.
|
|
109
|
+
* @param detection - the compiled detection knobs.
|
|
110
|
+
* @returns the target path, or `undefined` when the call does not name one.
|
|
111
|
+
*/
|
|
112
|
+
export function mutationTargetPath(name, args, detection) {
|
|
113
|
+
if (!detection.mutationTools.includes(name))
|
|
114
|
+
return undefined;
|
|
115
|
+
const path = args?.['file_path'];
|
|
116
|
+
return typeof path === 'string' && path.length > 0 ? path : undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether a mutation target is itself a test file (writing the failing test
|
|
120
|
+
* is the red step, so test files stay editable).
|
|
121
|
+
* @param path - the mutation target path.
|
|
122
|
+
* @param detection - the compiled detection knobs.
|
|
123
|
+
* @returns true when the path matches a test-file pattern.
|
|
124
|
+
*/
|
|
125
|
+
export function isTestFilePath(path, detection) {
|
|
126
|
+
return detection.testFilePatterns.some(pattern => pattern.test(path));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Classify the durable result of one test run from its model-facing text.
|
|
130
|
+
* @param text - the joined text blocks of the rendered result.
|
|
131
|
+
* @param isError - whether the tool call failed at the infrastructure level.
|
|
132
|
+
* @returns red/green evidence, or `undefined` when the run proves nothing.
|
|
133
|
+
*/
|
|
134
|
+
export function testOutcome(text, isError) {
|
|
135
|
+
if (isError)
|
|
136
|
+
return undefined;
|
|
137
|
+
if (BACKGROUND_ACK.test(text))
|
|
138
|
+
return undefined;
|
|
139
|
+
if (SANDBOX_DENIAL.test(text))
|
|
140
|
+
return undefined;
|
|
141
|
+
if (TIMED_OUT.test(text) || KILLED_BY_SIGNAL.test(text))
|
|
142
|
+
return 'fail';
|
|
143
|
+
const exit = EXIT_CODE.exec(text);
|
|
144
|
+
if (exit !== null)
|
|
145
|
+
return exit[1] === '0' ? 'pass' : 'fail';
|
|
146
|
+
// A finished shell run with no failure markers settled with exit code 0.
|
|
147
|
+
return 'pass';
|
|
148
|
+
}
|
|
149
|
+
/** Join the text blocks of a rendered result into one searchable string. */
|
|
150
|
+
export function joinTextBlocks(content) {
|
|
151
|
+
const parts = [];
|
|
152
|
+
for (const block of content) {
|
|
153
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
154
|
+
parts.push(block.text);
|
|
155
|
+
}
|
|
156
|
+
else if (block.type === 'tool-result' && Array.isArray(block.content)) {
|
|
157
|
+
// A durable ToolResultMessage wraps its text blocks in one tool-result block.
|
|
158
|
+
parts.push(joinTextBlocks(block.content));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return parts.join('\n');
|
|
162
|
+
}
|
|
163
|
+
function compilePattern(source) {
|
|
164
|
+
try {
|
|
165
|
+
return new RegExp(source);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
throw new Error(`dsh-doublecheck: invalid regex pattern "${source}" in the red/green gate configuration`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function assertNonEmptyNames(field, names) {
|
|
172
|
+
if (names.length === 0)
|
|
173
|
+
throw new Error(`dsh-doublecheck: ${field} must not be empty`);
|
|
174
|
+
for (const name of names) {
|
|
175
|
+
if (name.length === 0)
|
|
176
|
+
throw new Error(`dsh-doublecheck: ${field} must not contain empty tool names`);
|
|
177
|
+
}
|
|
178
|
+
if (new Set(names).size !== names.length)
|
|
179
|
+
throw new Error(`dsh-doublecheck: ${field} must not contain duplicates`);
|
|
180
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `doublecheck` session projection: the discipline facts a client renders
|
|
3
|
+
* (Web UI status row), folded from the durable session log alone.
|
|
4
|
+
*
|
|
5
|
+
* The state is plain JSON (the persisted-cache precondition of the projection
|
|
6
|
+
* registry) and mirrors the guard's own fold: a successful `doublecheck_spec`
|
|
7
|
+
* pair commits the spec goal, test runs move the color, implementation edits
|
|
8
|
+
* bump the edit count, and a `doublecheck-review` source record marks the
|
|
9
|
+
* delivery reviewed. Resumed and forked sessions fold to the same view.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-doublecheck/domain/projection
|
|
12
|
+
*/
|
|
13
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
14
|
+
import { type TestRunDetection } from './evidence.ts';
|
|
15
|
+
import { type DisciplineStage, type TestColor } from './stages.ts';
|
|
16
|
+
import type { DoublecheckView } from '../types.ts';
|
|
17
|
+
/** The projection unit's plain-JSON fold state. */
|
|
18
|
+
export interface DoublecheckProjectionState {
|
|
19
|
+
/** The last discipline stage a successful call or test run reached. */
|
|
20
|
+
stage: DisciplineStage;
|
|
21
|
+
/** The latest test-run evidence: red = failing since the last pass. */
|
|
22
|
+
color: TestColor;
|
|
23
|
+
/** Whether a successful `doublecheck_spec` call exists in the log. */
|
|
24
|
+
hasSpec: boolean;
|
|
25
|
+
/** The goal of the latest committed spec, or '' before any spec. */
|
|
26
|
+
specGoal: string;
|
|
27
|
+
/** Whether an adversary review record exists in the log. */
|
|
28
|
+
reviewed: boolean;
|
|
29
|
+
/** Total implementation edits folded so far. */
|
|
30
|
+
editCount: number;
|
|
31
|
+
/** Spec-tool call ids (with their goals) whose results have not been folded yet. */
|
|
32
|
+
pendingSpecCalls: Record<string, string>;
|
|
33
|
+
/** Test-run call ids (with their commands) whose results have not been folded yet. */
|
|
34
|
+
pendingTestCalls: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
/** State for the empty log. */
|
|
37
|
+
export declare function emptyDoublecheckState(): DoublecheckProjectionState;
|
|
38
|
+
/**
|
|
39
|
+
* One-event transition of the `doublecheck` projection unit. Uninterested
|
|
40
|
+
* events return the same state reference (the registry's change gate).
|
|
41
|
+
* @param state - the folded state before `event`.
|
|
42
|
+
* @param event - one committed session event.
|
|
43
|
+
* @param detection - the compiled red/green evidence knobs.
|
|
44
|
+
* @returns the next state; the same reference when the event is not a
|
|
45
|
+
* discipline fact.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyDoublecheckEvent(state: DoublecheckProjectionState, event: SessionEvent, detection: TestRunDetection): DoublecheckProjectionState;
|
|
48
|
+
/**
|
|
49
|
+
* Fold a session log from scratch to the projection state.
|
|
50
|
+
* @param events - the session's append-only event log.
|
|
51
|
+
* @param detection - the compiled red/green evidence knobs.
|
|
52
|
+
* @returns the complete folded state.
|
|
53
|
+
*/
|
|
54
|
+
export declare function foldDoublecheckState(events: readonly SessionEvent[], detection: TestRunDetection): DoublecheckProjectionState;
|
|
55
|
+
/**
|
|
56
|
+
* Project the folded state to the wire payload.
|
|
57
|
+
* @param state - the folded state.
|
|
58
|
+
* @returns the whole current value for the `doublecheck` projection key.
|
|
59
|
+
*/
|
|
60
|
+
export declare function viewDoublecheck(state: DoublecheckProjectionState): DoublecheckView;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `doublecheck` session projection: the discipline facts a client renders
|
|
3
|
+
* (Web UI status row), folded from the durable session log alone.
|
|
4
|
+
*
|
|
5
|
+
* The state is plain JSON (the persisted-cache precondition of the projection
|
|
6
|
+
* registry) and mirrors the guard's own fold: a successful `doublecheck_spec`
|
|
7
|
+
* pair commits the spec goal, test runs move the color, implementation edits
|
|
8
|
+
* bump the edit count, and a `doublecheck-review` source record marks the
|
|
9
|
+
* delivery reviewed. Resumed and forked sessions fold to the same view.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-doublecheck/domain/projection
|
|
12
|
+
*/
|
|
13
|
+
import { isTestCommand, isTestFilePath, joinTextBlocks, mutationTargetPath, parseRawArguments, shellCommand, testOutcome, } from "./evidence.js";
|
|
14
|
+
import { SPEC_TOOL_NAME } from "./stages.js";
|
|
15
|
+
/** State for the empty log. */
|
|
16
|
+
export function emptyDoublecheckState() {
|
|
17
|
+
return {
|
|
18
|
+
stage: 'grill',
|
|
19
|
+
color: 'none',
|
|
20
|
+
hasSpec: false,
|
|
21
|
+
specGoal: '',
|
|
22
|
+
reviewed: false,
|
|
23
|
+
editCount: 0,
|
|
24
|
+
pendingSpecCalls: {},
|
|
25
|
+
pendingTestCalls: {},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One-event transition of the `doublecheck` projection unit. Uninterested
|
|
30
|
+
* events return the same state reference (the registry's change gate).
|
|
31
|
+
* @param state - the folded state before `event`.
|
|
32
|
+
* @param event - one committed session event.
|
|
33
|
+
* @param detection - the compiled red/green evidence knobs.
|
|
34
|
+
* @returns the next state; the same reference when the event is not a
|
|
35
|
+
* discipline fact.
|
|
36
|
+
*/
|
|
37
|
+
export function applyDoublecheckEvent(state, event, detection) {
|
|
38
|
+
switch (event.type) {
|
|
39
|
+
case 'tool/call': {
|
|
40
|
+
const args = parseRawArguments(event.data.arguments);
|
|
41
|
+
if (event.data.name === SPEC_TOOL_NAME) {
|
|
42
|
+
const goal = typeof args?.['goal'] === 'string' ? args['goal'] : '';
|
|
43
|
+
return { ...state, pendingSpecCalls: { ...state.pendingSpecCalls, [event.data.callId]: goal } };
|
|
44
|
+
}
|
|
45
|
+
const command = shellCommand(event.data.name, args, detection);
|
|
46
|
+
if (command !== undefined && isTestCommand(command, detection)) {
|
|
47
|
+
return { ...state, pendingTestCalls: { ...state.pendingTestCalls, [event.data.callId]: command } };
|
|
48
|
+
}
|
|
49
|
+
const path = mutationTargetPath(event.data.name, args, detection);
|
|
50
|
+
if (path !== undefined && !isTestFilePath(path, detection)) {
|
|
51
|
+
return { ...state, editCount: state.editCount + 1 };
|
|
52
|
+
}
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
case 'tool/result': {
|
|
56
|
+
const callId = event.data.message.source.callId;
|
|
57
|
+
if (event.data.error === undefined && callId in state.pendingSpecCalls) {
|
|
58
|
+
const pendingSpecCalls = { ...state.pendingSpecCalls };
|
|
59
|
+
const goal = pendingSpecCalls[callId] ?? '';
|
|
60
|
+
delete pendingSpecCalls[callId];
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
pendingSpecCalls,
|
|
64
|
+
hasSpec: true,
|
|
65
|
+
specGoal: goal,
|
|
66
|
+
stage: 'design',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (callId in state.pendingTestCalls) {
|
|
70
|
+
const pendingTestCalls = { ...state.pendingTestCalls };
|
|
71
|
+
delete pendingTestCalls[callId];
|
|
72
|
+
const outcome = testOutcome(joinTextBlocks(event.data.message.content), event.data.error !== undefined);
|
|
73
|
+
const next = { ...state, pendingTestCalls };
|
|
74
|
+
if (outcome === 'fail')
|
|
75
|
+
return { ...next, color: 'red', stage: 'red' };
|
|
76
|
+
if (outcome === 'pass')
|
|
77
|
+
return { ...next, color: 'green', stage: 'green' };
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
return state;
|
|
81
|
+
}
|
|
82
|
+
case 'tool/code-dispatch': {
|
|
83
|
+
const args = parseRawArguments(event.data.arguments);
|
|
84
|
+
const command = shellCommand(event.data.name, args, detection);
|
|
85
|
+
if (command !== undefined && isTestCommand(command, detection)) {
|
|
86
|
+
const outcome = testOutcome(joinTextBlocks(event.data.content), event.data.isError);
|
|
87
|
+
if (outcome === 'fail')
|
|
88
|
+
return { ...state, color: 'red', stage: 'red' };
|
|
89
|
+
if (outcome === 'pass')
|
|
90
|
+
return { ...state, color: 'green', stage: 'green' };
|
|
91
|
+
}
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
case 'user/message': {
|
|
95
|
+
const source = event.data.source;
|
|
96
|
+
if (source.kind === 'doublecheck-review' && !state.reviewed) {
|
|
97
|
+
return { ...state, reviewed: true };
|
|
98
|
+
}
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
return state;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Fold a session log from scratch to the projection state.
|
|
107
|
+
* @param events - the session's append-only event log.
|
|
108
|
+
* @param detection - the compiled red/green evidence knobs.
|
|
109
|
+
* @returns the complete folded state.
|
|
110
|
+
*/
|
|
111
|
+
export function foldDoublecheckState(events, detection) {
|
|
112
|
+
let state = emptyDoublecheckState();
|
|
113
|
+
for (const event of events) {
|
|
114
|
+
state = applyDoublecheckEvent(state, event, detection);
|
|
115
|
+
}
|
|
116
|
+
return state;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Project the folded state to the wire payload.
|
|
120
|
+
* @param state - the folded state.
|
|
121
|
+
* @returns the whole current value for the `doublecheck` projection key.
|
|
122
|
+
*/
|
|
123
|
+
export function viewDoublecheck(state) {
|
|
124
|
+
return {
|
|
125
|
+
stage: state.stage,
|
|
126
|
+
color: state.color,
|
|
127
|
+
hasSpec: state.hasSpec,
|
|
128
|
+
specGoal: state.specGoal,
|
|
129
|
+
reviewed: state.reviewed,
|
|
130
|
+
editCount: state.editCount,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The doublecheck report fold: consolidate the durable discipline evidence of
|
|
3
|
+
* one session into report facts, derive the delivery verdict, and render the
|
|
4
|
+
* model-facing report document.
|
|
5
|
+
*
|
|
6
|
+
* Everything here folds the session log alone — the spec (from the
|
|
7
|
+
* `doublecheck_spec` call arguments), the test-run timeline (red/green
|
|
8
|
+
* outcomes), the implementation edits, and the injected
|
|
9
|
+
* `doublecheck-review` source records — so a report re-derived after resume
|
|
10
|
+
* or fork reads identically.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-doublecheck/domain/report
|
|
13
|
+
*/
|
|
14
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
15
|
+
import type { ObjectJsonSchema } from '@deepseek-ai/dsh-tools';
|
|
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';
|
|
19
|
+
/** One timeline row of the report fold, in log order. */
|
|
20
|
+
export interface ReportTimelineEntry {
|
|
21
|
+
/** The evidence kind this row records. */
|
|
22
|
+
kind: 'spec' | 'red' | 'green' | 'review';
|
|
23
|
+
/** Short human-readable detail (command, spec goal preview, or verdict). */
|
|
24
|
+
detail: string;
|
|
25
|
+
}
|
|
26
|
+
/** The folded discipline facts of one session. */
|
|
27
|
+
export interface ReportFacts {
|
|
28
|
+
/** The latest committed spec, or null before any spec exists. */
|
|
29
|
+
spec: GrilledSpec | null;
|
|
30
|
+
/** Counts of failing and passing test runs on record. */
|
|
31
|
+
testRuns: {
|
|
32
|
+
failed: number;
|
|
33
|
+
passed: number;
|
|
34
|
+
};
|
|
35
|
+
/** Spec/test/review evidence in log order. */
|
|
36
|
+
timeline: ReportTimelineEntry[];
|
|
37
|
+
/** Implementation edits (non-test-file mutations) on record. */
|
|
38
|
+
edits: number;
|
|
39
|
+
/** The latest injected adversary review, or null when none ran. */
|
|
40
|
+
review: {
|
|
41
|
+
verdict: ReviewVerdict;
|
|
42
|
+
findings: ReviewFinding[];
|
|
43
|
+
} | null;
|
|
44
|
+
}
|
|
45
|
+
/** The full canonical report value the tool returns. */
|
|
46
|
+
export interface ReportData extends ReportFacts {
|
|
47
|
+
/** The derived delivery status. */
|
|
48
|
+
verdict: ReportVerdict;
|
|
49
|
+
/**
|
|
50
|
+
* Verification checks, when the verify workflow ran. `complete` is true
|
|
51
|
+
* only when every spec dimension returned a verdict, so `proven` never
|
|
52
|
+
* rests on silently missing dimensions.
|
|
53
|
+
*/
|
|
54
|
+
verification: {
|
|
55
|
+
checks: VerifyCheck[];
|
|
56
|
+
complete: boolean;
|
|
57
|
+
} | null;
|
|
58
|
+
/** Workspace markdown copy outcome. */
|
|
59
|
+
path: string | null;
|
|
60
|
+
written: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Empty facts: nothing evidenced yet. */
|
|
63
|
+
export declare function emptyReportFacts(): ReportFacts;
|
|
64
|
+
/**
|
|
65
|
+
* Fold a session log into report facts.
|
|
66
|
+
* @param events - the session's append-only event log.
|
|
67
|
+
* @param detection - the compiled test-run/mutation detection knobs.
|
|
68
|
+
* @returns the complete folded facts.
|
|
69
|
+
*/
|
|
70
|
+
export declare function foldReportFacts(events: readonly SessionEvent[], detection: TestRunDetection): ReportFacts;
|
|
71
|
+
/**
|
|
72
|
+
* Derive the delivery verdict from the folded facts, the optional review,
|
|
73
|
+
* and the optional verification outcome.
|
|
74
|
+
* @param facts - the folded session facts.
|
|
75
|
+
* @param verification - verification outcome when the verify workflow ran, else null.
|
|
76
|
+
* @returns the report verdict.
|
|
77
|
+
*/
|
|
78
|
+
export declare function deriveReportVerdict(facts: ReportFacts, verification: ReportData['verification']): ReportVerdict;
|
|
79
|
+
/** Render the report facts as the model-facing markdown document. */
|
|
80
|
+
export declare function renderReportMarkdown(data: ReportData): string;
|
|
81
|
+
/** The structured output each verification child must satisfy. */
|
|
82
|
+
export declare const VERIFY_CHECK_SCHEMA: ObjectJsonSchema;
|
|
83
|
+
/** How the verify workflow fans its checkers out. */
|
|
84
|
+
export type VerifyMode = 'all' | 'single';
|
|
85
|
+
/** The verify workflow identity block. */
|
|
86
|
+
export declare const VERIFY_META: WorkflowMeta;
|
|
87
|
+
/**
|
|
88
|
+
* Build the verify workflow script body.
|
|
89
|
+
* @param mode - `all` fans out one parallel checker per spec dimension;
|
|
90
|
+
* `single` runs one checker over every dimension (cheaper, one subagent).
|
|
91
|
+
* @returns the plain-JS script body for `WorkflowStartRequest.script`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildVerifyScript(mode: VerifyMode): string;
|