dsh-doublecheck 0.5.0 → 0.6.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 +17 -0
- package/README.es.md +39 -12
- package/README.hi.md +56 -21
- package/README.md +29 -13
- package/README.pt.md +39 -12
- package/README.zh.md +28 -12
- package/lib/domain/evidence.d.ts +4 -1
- package/lib/domain/evidence.js +7 -2
- package/lib/domain/vocabulary.d.ts +2 -0
- package/lib/domain/vocabulary.js +4 -0
- package/lib/grill/index.js +8 -1
- package/lib/guard/command.d.ts +7 -0
- package/lib/guard/command.js +25 -21
- package/lib/guard/index.d.ts +2 -0
- package/lib/guard/index.js +16 -5
- package/lib/guard/prose.d.ts +47 -0
- package/lib/guard/prose.js +65 -0
- package/lib/guard/review.d.ts +8 -0
- package/lib/guard/review.js +20 -20
- package/lib/invariant.js +2 -3
- package/package.json +13 -4
- package/strict.patch.yml +33 -0
package/lib/guard/prose.js
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* `green gate`) stay English: they are stable session-log ids the durable
|
|
6
6
|
* once-semantics fold matches, and translating them would silently break
|
|
7
7
|
* `remindOnce` after a language switch. Only the model-facing text localizes.
|
|
8
|
+
*
|
|
9
|
+
* Scope of localization: every model-visible string the package injects or
|
|
10
|
+
* answers with (gate prose, the critic task, `/doublecheck` replies, the
|
|
11
|
+
* switch notice) is localized. The workspace documents (`doublecheck-spec.md`
|
|
12
|
+
* / `doublecheck-report.md`) keep their English section headings: they are
|
|
13
|
+
* stable artifacts whose structure other tooling may match.
|
|
8
14
|
* @module dsh-doublecheck/guard/prose
|
|
9
15
|
*/
|
|
10
16
|
const EN = {
|
|
@@ -55,6 +61,38 @@ const EN = {
|
|
|
55
61
|
reviewUnavailableNoFindings: 'Adversary review returned no structured findings.',
|
|
56
62
|
reviewFindingsHeader: count => `Adversary review found ${count} objection(s) the delivery must answer:`,
|
|
57
63
|
reviewFindingsFooter: 'Answer each: fix what is real, and state plainly what is false.',
|
|
64
|
+
reviewHeldBack: held => `… ${held} further objection(s) held back by adversaryMaxFindings`,
|
|
65
|
+
criticTask: 'You are the delivery reviewer for this software-engineering session. The '
|
|
66
|
+
+ 'conversation you inherited contains a requirements spec recorded with the '
|
|
67
|
+
+ 'doublecheck_spec tool (six dimensions: goal, scope, acceptance criteria, '
|
|
68
|
+
+ 'failure modes, priorities, non-goals), followed by the implementation '
|
|
69
|
+
+ 'work and its test evidence. Assume the delivery FAILS its own spec. Hunt '
|
|
70
|
+
+ 'for the strongest objections you can actually support from this session: '
|
|
71
|
+
+ 'dimensions the work did not meet, acceptance criteria with no evidence, '
|
|
72
|
+
+ 'scope or non-goal violations, failure modes left unhandled. Answer '
|
|
73
|
+
+ 'through the required structured output, one entry per objection, citing '
|
|
74
|
+
+ 'what in the session supports it. If — and only if — the evidence '
|
|
75
|
+
+ 'genuinely satisfies every dimension, return an empty findings list. Do '
|
|
76
|
+
+ 'not invent objections; the empty answer is correct when nothing is wrong.',
|
|
77
|
+
switchOnDurable: 'Double-check discipline was switched ON for this session (changed by the user): the requirements grill, the red/green gates, and the delivery review are active again.',
|
|
78
|
+
switchOnLocal: 'Double-check discipline was switched ON for this process only (changed by the user; this harness build predates the ignorable append surface, so the switch is not durable): the requirements grill, the red/green gates, and the delivery review are active again.',
|
|
79
|
+
switchOffDurable: 'Double-check discipline was switched OFF for this session (changed by the user): the discipline gates defer to the human approval chain until switched back on.',
|
|
80
|
+
switchOffLocal: 'Double-check discipline was switched OFF for this process only (changed by the user; this harness build predates the ignorable append surface, so the switch is not durable): the discipline gates defer to the human approval chain until switched back on.',
|
|
81
|
+
commandNoAgent: '/doublecheck needs an agent session to inspect.',
|
|
82
|
+
commandUnknown: input => `Unknown /doublecheck argument "${input}". Usage: /doublecheck status|report|on|off`,
|
|
83
|
+
commandAlreadyOn: 'Double-check discipline is already ON for this session.',
|
|
84
|
+
commandAlreadyOff: 'Double-check discipline is already OFF for this session.',
|
|
85
|
+
commandOnDurable: 'Double-check discipline ON for this session.',
|
|
86
|
+
commandOnLocal: 'Double-check discipline ON for this process only (not durable on this harness).',
|
|
87
|
+
commandOffDurable: 'Double-check discipline OFF for this session.',
|
|
88
|
+
commandOffLocal: 'Double-check discipline OFF for this process only (not durable on this harness).',
|
|
89
|
+
commandStatus: facts => [
|
|
90
|
+
`Double-check discipline is ${facts.enabled ? 'ON' : 'OFF'} for this session. (/doublecheck on|off)`,
|
|
91
|
+
`Modules: grill=${facts.modules.grill ? 'on' : 'off'}, tdd=${facts.modules.tdd ? 'on' : 'off'}, adversary=${facts.modules.adversary ? 'on' : 'off'} (cordis.yml).`,
|
|
92
|
+
`Intensity: ${facts.intensity}. Default switch: ${facts.defaultEnabled ? 'on' : 'off'}. remindOnce: ${facts.remindOnce ? 'on' : 'off'}.`,
|
|
93
|
+
`Stage: spec=${facts.hasSpec ? 'committed' : 'missing'}, tests=${facts.color}, review=${facts.reviewed ? 'on record' : 'not run'}, edits=${facts.editCount}.`,
|
|
94
|
+
`Usage: /doublecheck status|report|on|off`,
|
|
95
|
+
].join('\n'),
|
|
58
96
|
};
|
|
59
97
|
const ZH = {
|
|
60
98
|
grillReminder: '交付前三查:这个任务描述很简短,本会话还没有记录 doublecheck spec。先停下编辑,把六个需求维度确认清楚——目标、范围、验收标准、失败模式、优先级、非目标。按 grill-requirements 技能执行:用 ask_user_question 向用户提问直到达成共识,用 doublecheck_spec 记录结果,然后再继续编辑。',
|
|
@@ -75,6 +113,33 @@ const ZH = {
|
|
|
75
113
|
reviewUnavailableNoFindings: '对抗式审查没有返回结构化发现。',
|
|
76
114
|
reviewFindingsHeader: count => `对抗式审查发现 ${count} 条反对意见,交付必须回应:`,
|
|
77
115
|
reviewFindingsFooter: '逐条回应:属实的修掉,不属实的明确说明。',
|
|
116
|
+
reviewHeldBack: held => `… 另有 ${held} 条反对意见因 adversaryMaxFindings 上限被保留`,
|
|
117
|
+
criticTask: '你是本次软件工程会话的交付审查者。你继承的会话里包含一份通过 '
|
|
118
|
+
+ 'doublecheck_spec 工具记录的需求 spec(六个维度:目标、范围、验收标准、'
|
|
119
|
+
+ '失败模式、优先级、非目标),其后是实现工作和测试证据。假设交付不满足自己的 '
|
|
120
|
+
+ 'spec。寻找你能从本会话证据中真正支撑的最强反对意见:未达到的维度、没有证据的'
|
|
121
|
+
+ '验收标准、范围或非目标被违反、未处理的失败模式。通过要求的结构化输出作答,'
|
|
122
|
+
+ '每条反对意见占一项,并引用会话中支撑它的内容。当且仅当证据确实满足每一个维度时,'
|
|
123
|
+
+ '才返回空的 findings 列表。不要编造反对意见;没有问题时空答案才是正确的。',
|
|
124
|
+
switchOnDurable: 'Double-check 纪律已对本会话开启(由用户切换):需求盘问、红绿门和交付审查恢复生效。',
|
|
125
|
+
switchOnLocal: 'Double-check 纪律已对本进程开启(由用户切换;此 harness 版本早于 ignorable 追加接口,切换不持久):需求盘问、红绿门和交付审查恢复生效。',
|
|
126
|
+
switchOffDurable: 'Double-check 纪律已对本会话关闭(由用户切换):在重新开启之前,纪律门让位于人工审批链。',
|
|
127
|
+
switchOffLocal: 'Double-check 纪律已对本进程关闭(由用户切换;此 harness 版本早于 ignorable 追加接口,切换不持久):在重新开启之前,纪律门让位于人工审批链。',
|
|
128
|
+
commandNoAgent: '/doublecheck 需要代理会话才能查看。',
|
|
129
|
+
commandUnknown: input => `未知的 /doublecheck 参数 "${input}"。用法:/doublecheck status|report|on|off`,
|
|
130
|
+
commandAlreadyOn: '本会话的 Double-check 纪律已经是开启状态。',
|
|
131
|
+
commandAlreadyOff: '本会话的 Double-check 纪律已经是关闭状态。',
|
|
132
|
+
commandOnDurable: 'Double-check 纪律已对本会话开启。',
|
|
133
|
+
commandOnLocal: 'Double-check 纪律已对本进程开启(此 harness 上不持久)。',
|
|
134
|
+
commandOffDurable: 'Double-check 纪律已对本会话关闭。',
|
|
135
|
+
commandOffLocal: 'Double-check 纪律已对本进程关闭(此 harness 上不持久)。',
|
|
136
|
+
commandStatus: facts => [
|
|
137
|
+
`本会话的 Double-check 纪律:${facts.enabled ? '已开启' : '已关闭'}。(/doublecheck on|off)`,
|
|
138
|
+
`模块:grill=${facts.modules.grill ? '开' : '关'}, tdd=${facts.modules.tdd ? '开' : '关'}, adversary=${facts.modules.adversary ? '开' : '关'}(cordis.yml)。`,
|
|
139
|
+
`强度:${facts.intensity}。默认开关:${facts.defaultEnabled ? '开' : '关'}。remindOnce:${facts.remindOnce ? '开' : '关'}。`,
|
|
140
|
+
`阶段:spec=${facts.hasSpec ? '已提交' : '缺失'}, tests=${facts.color}, review=${facts.reviewed ? '有记录' : '未运行'}, edits=${facts.editCount}。`,
|
|
141
|
+
'用法:/doublecheck status|report|on|off',
|
|
142
|
+
].join('\n'),
|
|
78
143
|
};
|
|
79
144
|
/** The localized prose tables by language. */
|
|
80
145
|
export const PROSE = { en: EN, zh: ZH };
|
package/lib/guard/review.d.ts
CHANGED
|
@@ -42,6 +42,14 @@ export interface ReviewOutcome {
|
|
|
42
42
|
export declare const REVIEW_OUTPUT_SCHEMA: ObjectJsonSchema;
|
|
43
43
|
/** Injected when the critic found nothing supportable (English contract text). */
|
|
44
44
|
export declare const CLEAN_TEXT: string;
|
|
45
|
+
/**
|
|
46
|
+
* Sort findings blocker-first, keeping the critic's original order within a
|
|
47
|
+
* severity. A deterministic order means the most threatening objection leads
|
|
48
|
+
* both the injected prose and the durable record.
|
|
49
|
+
* @param findings - the structured findings as produced by the critic.
|
|
50
|
+
* @returns the same findings, severity-ordered.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sortFindings(findings: readonly ReviewFinding[]): ReviewFinding[];
|
|
45
53
|
/** Render structured findings as the model-facing review text. */
|
|
46
54
|
export declare function renderFindings(findings: readonly ReviewFinding[], prose?: GuardProse, totalCount?: number): string;
|
|
47
55
|
/**
|
package/lib/guard/review.js
CHANGED
|
@@ -41,25 +41,25 @@ export const REVIEW_OUTPUT_SCHEMA = {
|
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
|
-
/**
|
|
45
|
-
* The critic's task, delivered as the forked child's user message. The child
|
|
46
|
-
* already holds the parent history; this prompt only sets the adversarial
|
|
47
|
-
* stance and the answer discipline.
|
|
48
|
-
*/
|
|
49
|
-
const CRITIC_TASK = 'You are the delivery reviewer for this software-engineering session. The '
|
|
50
|
-
+ 'conversation you inherited contains a requirements spec recorded with the '
|
|
51
|
-
+ 'doublecheck_spec tool (six dimensions: goal, scope, acceptance criteria, '
|
|
52
|
-
+ 'failure modes, priorities, non-goals), followed by the implementation '
|
|
53
|
-
+ 'work and its test evidence. Assume the delivery FAILS its own spec. Hunt '
|
|
54
|
-
+ 'for the strongest objections you can actually support from this session: '
|
|
55
|
-
+ 'dimensions the work did not meet, acceptance criteria with no evidence, '
|
|
56
|
-
+ 'scope or non-goal violations, failure modes left unhandled. Answer '
|
|
57
|
-
+ 'through the required structured output, one entry per objection, citing '
|
|
58
|
-
+ 'what in the session supports it. If — and only if — the evidence '
|
|
59
|
-
+ 'genuinely satisfies every dimension, return an empty findings list. Do '
|
|
60
|
-
+ 'not invent objections; the empty answer is correct when nothing is wrong.';
|
|
61
44
|
/** Injected when the critic found nothing supportable (English contract text). */
|
|
62
45
|
export const CLEAN_TEXT = PROSE.en.reviewClean;
|
|
46
|
+
/** Severity ranks for a deterministic, blocker-first findings order. */
|
|
47
|
+
const SEVERITY_RANK = {
|
|
48
|
+
blocker: 0,
|
|
49
|
+
major: 1,
|
|
50
|
+
minor: 2,
|
|
51
|
+
info: 3,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Sort findings blocker-first, keeping the critic's original order within a
|
|
55
|
+
* severity. A deterministic order means the most threatening objection leads
|
|
56
|
+
* both the injected prose and the durable record.
|
|
57
|
+
* @param findings - the structured findings as produced by the critic.
|
|
58
|
+
* @returns the same findings, severity-ordered.
|
|
59
|
+
*/
|
|
60
|
+
export function sortFindings(findings) {
|
|
61
|
+
return [...findings].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
|
|
62
|
+
}
|
|
63
63
|
/** Render structured findings as the model-facing review text. */
|
|
64
64
|
export function renderFindings(findings, prose = PROSE.en, totalCount = findings.length) {
|
|
65
65
|
const lines = [
|
|
@@ -71,7 +71,7 @@ export function renderFindings(findings, prose = PROSE.en, totalCount = findings
|
|
|
71
71
|
lines.push(` ${finding.detail}`);
|
|
72
72
|
}
|
|
73
73
|
if (totalCount > findings.length) {
|
|
74
|
-
lines.push(
|
|
74
|
+
lines.push(prose.reviewHeldBack(totalCount - findings.length));
|
|
75
75
|
}
|
|
76
76
|
lines.push('', prose.reviewFindingsFooter);
|
|
77
77
|
return lines.join('\n');
|
|
@@ -101,7 +101,7 @@ export async function runAdversaryReview(ctx, config, agent, turnSignal) {
|
|
|
101
101
|
try {
|
|
102
102
|
run = await subagents.start(config.adversaryProvider, {
|
|
103
103
|
label: 'doublecheck-adversary',
|
|
104
|
-
prompt: [{ type: 'text', text:
|
|
104
|
+
prompt: [{ type: 'text', text: prose.criticTask }],
|
|
105
105
|
parent: agent,
|
|
106
106
|
signal,
|
|
107
107
|
...config.adversaryModel !== null ? { agentOptions: { model: config.adversaryModel } } : {},
|
|
@@ -149,7 +149,7 @@ async function settleResult(run, signal, prose, maxFindings) {
|
|
|
149
149
|
if (!Array.isArray(raw)) {
|
|
150
150
|
return { verdict: 'unavailable', findings: [], text: outputText(result.output, prose.reviewUnavailableNoFindings) };
|
|
151
151
|
}
|
|
152
|
-
const findings = raw.slice(0, maxFindings);
|
|
152
|
+
const findings = sortFindings(raw).slice(0, maxFindings);
|
|
153
153
|
if (findings.length === 0)
|
|
154
154
|
return { verdict: 'clean', findings: [], text: prose.reviewClean };
|
|
155
155
|
return { verdict: 'findings', findings, text: renderFindings(findings, prose, raw.length) };
|
package/lib/invariant.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* profile row.
|
|
21
21
|
* @module dsh-doublecheck/invariant
|
|
22
22
|
*/
|
|
23
|
+
import { SPEC_FIELD_NAMES } from "./domain/vocabulary.js";
|
|
23
24
|
import { deriveReportVerdict, foldReportFacts } from "./domain/report.js";
|
|
24
25
|
/** Full npm package name owning the reported failures. */
|
|
25
26
|
export const PACKAGE_NAME = 'dsh-doublecheck';
|
|
@@ -28,9 +29,7 @@ export const name = 'dsh-doublecheck-invariant';
|
|
|
28
29
|
/** Service required before the companion can reserve package ownership. */
|
|
29
30
|
export const inject = ['invariants'];
|
|
30
31
|
/** All six spec fields, in declaration order. */
|
|
31
|
-
const SPEC_FIELDS =
|
|
32
|
-
'goal', 'scope', 'acceptanceCriteria', 'failureModes', 'priorities', 'nonGoals',
|
|
33
|
-
];
|
|
32
|
+
const SPEC_FIELDS = SPEC_FIELD_NAMES;
|
|
34
33
|
/**
|
|
35
34
|
* Build the installer over a facts source. The standalone companion and the
|
|
36
35
|
* main plugin share this body; only the facts differ.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-doublecheck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Double-check before you ship: grill the requirements, test the implementation, prove the delivery.",
|
|
5
5
|
"packageManager": "pnpm@11.7.0",
|
|
6
6
|
"repository": {
|
|
@@ -27,12 +27,21 @@
|
|
|
27
27
|
"types": "./lib/guard/index.d.ts",
|
|
28
28
|
"import": "./lib/guard/index.js"
|
|
29
29
|
},
|
|
30
|
+
"./invariant": {
|
|
31
|
+
"types": "./lib/invariant.d.ts",
|
|
32
|
+
"import": "./lib/invariant.js"
|
|
33
|
+
},
|
|
30
34
|
"./package.json": "./package.json"
|
|
31
35
|
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"sideEffects": false,
|
|
32
40
|
"files": [
|
|
33
41
|
"lib",
|
|
34
42
|
"skills",
|
|
35
43
|
"cordis.patch.yml",
|
|
44
|
+
"strict.patch.yml",
|
|
36
45
|
"CHANGELOG.md",
|
|
37
46
|
"README.md",
|
|
38
47
|
"README.zh.md",
|
|
@@ -82,7 +91,8 @@
|
|
|
82
91
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
83
92
|
},
|
|
84
93
|
"dependencies": {
|
|
85
|
-
"typescript": "^5.7.0"
|
|
94
|
+
"typescript": "^5.7.0",
|
|
95
|
+
"zod": "^4.4.3"
|
|
86
96
|
},
|
|
87
97
|
"devDependencies": {
|
|
88
98
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -103,7 +113,6 @@
|
|
|
103
113
|
"@vitest/coverage-v8": "^3.2.0",
|
|
104
114
|
"eslint": "^9.30.0",
|
|
105
115
|
"typescript-eslint": "^8.35.0",
|
|
106
|
-
"vitest": "^3.2.0"
|
|
107
|
-
"zod": "^4.4.3"
|
|
116
|
+
"vitest": "^3.2.0"
|
|
108
117
|
}
|
|
109
118
|
}
|
package/strict.patch.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# dsh-doublecheck strict overlay: every discipline gate ON at `block` intensity.
|
|
2
|
+
#
|
|
3
|
+
# Apply this patch AFTER the bundle patch (later layers win by position), e.g.:
|
|
4
|
+
# dsh --profile <name> --patch ./strict.patch.yml
|
|
5
|
+
# A patch replaces the row's WHOLE config — Schema defaults are NOT re-applied
|
|
6
|
+
# on override — so every key the guard row needs is restated here. The grill
|
|
7
|
+
# row keeps its defaults and is not restated.
|
|
8
|
+
- id: doublecheck-guard
|
|
9
|
+
config:
|
|
10
|
+
intensity: block
|
|
11
|
+
modules:
|
|
12
|
+
grill: true
|
|
13
|
+
tdd: true
|
|
14
|
+
adversary: true
|
|
15
|
+
adversaryModel: null
|
|
16
|
+
adversaryProvider: fork
|
|
17
|
+
adversaryMaxFindings: 5
|
|
18
|
+
adversaryTools: ['read', 'glob', 'grep']
|
|
19
|
+
adversaryTimeoutMs: 120000
|
|
20
|
+
guardTools: ['edit', 'write']
|
|
21
|
+
vagueTaskMaxChars: 200
|
|
22
|
+
remindOnce: true
|
|
23
|
+
language: en
|
|
24
|
+
enableByDefault: true
|
|
25
|
+
testToolNames: ['bash', 'pwsh']
|
|
26
|
+
testCommandPatterns:
|
|
27
|
+
- '(?:^|[;&|]\s*)(?:(?:pnpm|npm|npx|yarn|bun)(?:\s+run)?\s+(?:test|vitest|jest|mocha)(?:\s|$))'
|
|
28
|
+
- '(?:^|[;&|]\s*)(?:(?:pytest|go\s+test|cargo\s+test|make\s+test|ctest)(?:\s|$))'
|
|
29
|
+
- '(?:^|[;&|]\s*)(?:node\s+--test(?:\s|$))'
|
|
30
|
+
- '(?:^|[;&|]\s*)(?:deno\s+test|uv\s+run\s+pytest)(?:\s|$)'
|
|
31
|
+
testFilePatterns:
|
|
32
|
+
- '(^|[\\/])(tests?|__tests__|specs?)([\\/]|$)'
|
|
33
|
+
- '\\.(test|spec)\\.[A-Za-z0-9]+$'
|