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.
@@ -33,6 +33,8 @@ import { emptyDisciplineState, foldDisciplineRange } from "../domain/stages.js";
33
33
  import { isVagueTask } from "../domain/vagueness.js";
34
34
  import { runAdversaryReview, reviewInjection } from "./review.js";
35
35
  import { PROSE } from "./prose.js";
36
+ import { gateHandler, gateInjection, GateConfigSchema, runGate, settleGate, validateGateConfig, } from "./gate.js";
37
+ import { countRedChecks } from "../domain/gate.js";
36
38
  import { applyDoublecheckEvent, emptyDoublecheckState, viewDoublecheck, } from "../domain/projection.js";
37
39
  import { doublecheckViewSchema } from "../types.js";
38
40
  import { installInvariant, PACKAGE_NAME } from "../invariant.js";
@@ -59,6 +61,7 @@ export const Config = z.object({
59
61
  testToolNames: z.array(z.string()).default([...DEFAULT_TEST_TOOL_NAMES]),
60
62
  testCommandPatterns: z.array(z.string()).default([...DEFAULT_TEST_COMMAND_PATTERNS]),
61
63
  testFilePatterns: z.array(z.string()).default([...DEFAULT_TEST_FILE_PATTERNS]),
64
+ gate: GateConfigSchema,
62
65
  });
63
66
  /**
64
67
  * Install the guard listeners.
@@ -68,6 +71,7 @@ export const Config = z.object({
68
71
  export function apply(ctx, config) {
69
72
  assertPositiveInteger('vagueTaskMaxChars', config.vagueTaskMaxChars);
70
73
  assertGuardTools(config.guardTools);
74
+ validateGateConfig(config.gate);
71
75
  if (config.modules.adversary) {
72
76
  assertBoundedInteger('adversaryMaxFindings', config.adversaryMaxFindings, 20);
73
77
  assertPositiveInteger('adversaryTimeoutMs', config.adversaryTimeoutMs);
@@ -130,6 +134,9 @@ export function apply(ctx, config) {
130
134
  snapshot.lastReviewSeq = event.seq;
131
135
  snapshot.editsAfterReview = 0;
132
136
  }
137
+ else if (source.kind === 'doublecheck-gate') {
138
+ snapshot.gateRedReminded = true;
139
+ }
133
140
  else if (source.kind === 'user') {
134
141
  const parts = [];
135
142
  for (const block of event.data.content) {
@@ -146,6 +153,14 @@ export function apply(ctx, config) {
146
153
  else if (event.type === 'doublecheck/state') {
147
154
  snapshot.stateEnabled = event.data.enabled;
148
155
  }
156
+ else if (event.type === 'doublecheck/gate') {
157
+ const gate = event.data;
158
+ snapshot.lastGate = {
159
+ seq: event.seq,
160
+ verdict: gate.verdict,
161
+ redCount: gate.phases === undefined ? 0 : countRedChecks(Object.values(gate.phases)),
162
+ };
163
+ }
149
164
  else if (event.type === 'tool/call' && event.seq > snapshot.lastReviewSeq) {
150
165
  // Implementation edits after the latest review re-arm the adversary
151
166
  // trigger for a second review round.
@@ -173,6 +188,8 @@ export function apply(ctx, config) {
173
188
  reportReminded: false,
174
189
  lastReviewSeq: -1,
175
190
  editsAfterReview: 0,
191
+ lastGate: null,
192
+ gateRedReminded: false,
176
193
  };
177
194
  foldEvents(snapshot, events, 0, events.length);
178
195
  snapshots.set(session, snapshot);
@@ -237,8 +254,66 @@ export function apply(ctx, config) {
237
254
  setLocalOverride: (session, enabled) => localOverrides.set(session, enabled),
238
255
  }),
239
256
  });
257
+ // The delivery gate panel: `/gate` stays available even when the gate or
258
+ // every discipline module is off — it is the user's inspection surface.
259
+ ctx.commands.register({
260
+ name: 'gate',
261
+ description: 'run the delivery quality gate and report the deliverable/rework decision',
262
+ input: { hint: 'status|run|config' },
263
+ handler: gateHandler({
264
+ config: config.gate,
265
+ detection,
266
+ prose,
267
+ runGate: (agent, signal) => runGate(ctx, config.gate, detection, agent, signal, config.language),
268
+ settleGate: (state, agent, signal) => settleGate(ctx, config.gate, state, agent, signal, hostStampsIgnorable()),
269
+ stampsIgnorable: hostStampsIgnorable,
270
+ planMode: ctx.get('planMode'),
271
+ }),
272
+ });
273
+ // The gate settings namespace: the pluggable checklist becomes editable
274
+ // through the harness settings surface when one is mounted (weak seam —
275
+ // a profile without the settings service simply has no settings page).
276
+ const settings = ctx.get('settings');
277
+ if (settings !== undefined) {
278
+ try {
279
+ settings.register('doublecheck.gate', GateConfigSchema, {
280
+ base: config.gate,
281
+ applies: 'restart',
282
+ expose: true,
283
+ });
284
+ }
285
+ catch (error) {
286
+ ctx.logger.warn(`dsh-doublecheck: gate settings namespace skipped: ${String(error)}`);
287
+ }
288
+ }
289
+ // The gate-red turn notice: once per session, a settled rework verdict
290
+ // suggests re-opening the work in plan mode. Installed even when every
291
+ // discipline module is off — the gate panel is advisory, not a gate.
292
+ if (config.gate.enabled) {
293
+ ctx.on('agent/turn-stopping', async ({ agent }) => {
294
+ if (!doublecheckEnabled(agent.session))
295
+ return;
296
+ const snapshot = snapshotOf(agent.session);
297
+ if (snapshot.gateRedReminded)
298
+ return;
299
+ const gate = snapshot.lastGate;
300
+ if (gate === null || gate.verdict !== 'rework')
301
+ return;
302
+ snapshot.gateRedReminded = true;
303
+ const text = prose.gateRedNotice(gate.redCount);
304
+ agent.inject(gateInjection(gate.verdict, gate.redCount, text));
305
+ ctx.emit('doublecheck/reminder', {
306
+ agent,
307
+ session: agent.session,
308
+ intensity: config.intensity,
309
+ gate: 'gate',
310
+ verdict: 'gate-red',
311
+ reminder: text,
312
+ });
313
+ });
314
+ }
240
315
  if (!config.modules.grill && !config.modules.tdd && !config.modules.adversary) {
241
- ctx.logger.info('dsh-doublecheck: all discipline gates disabled; only the /doublecheck command remains active');
316
+ ctx.logger.info('dsh-doublecheck: all discipline gates disabled; the /doublecheck and /gate commands remain active');
242
317
  return;
243
318
  }
244
319
  // The policy gate. Observe-and-decide: the grill gate owns vague,
@@ -456,17 +531,19 @@ export function apply(ctx, config) {
456
531
  init: emptyDoublecheckState,
457
532
  apply: (state, event) => applyDoublecheckEvent(state, event, detection),
458
533
  view: viewDoublecheck,
459
- stateVersion: 1,
534
+ stateVersion: 2,
460
535
  });
461
536
  });
462
537
  // The invariant companion, registered from the main plugin so its checks
463
538
  // see the live detection knobs. Activates only when an invariant registry
464
- // is composed.
539
+ // is composed. The registry binds its internal effect to the service
540
+ // context and throws on a duplicate package name, so the returned disposer
541
+ // is the only unregistration path: hold it on this inject scope's fiber.
465
542
  ctx.inject(['invariants'], (invariantCtx) => {
466
543
  const registry = invariantCtx.get('invariants');
467
544
  if (registry === undefined)
468
545
  return;
469
- registry.register(PACKAGE_NAME, installInvariant({ detection: () => detection }));
546
+ invariantCtx.effect(() => registry.register(PACKAGE_NAME, installInvariant({ detection: () => detection })), 'dsh-doublecheck: invariant companion');
470
547
  });
471
548
  }
472
549
  /** The adversary knobs in the shape the review runner reads. */
@@ -38,6 +38,18 @@ export interface GuardProse {
38
38
  reviewHeldBack: (held: number) => string;
39
39
  /** The critic's task prompt (model-facing behavior of the review run). */
40
40
  criticTask: string;
41
+ /** The gate-red notice injected at the turn boundary (role statement first). */
42
+ gateRedNotice: (redCount: number) => string;
43
+ /** The localized `/gate status` panel header. */
44
+ gateStatusHeader: string;
45
+ /** The localized `/gate status` usage hint. */
46
+ gateStatusHint: string;
47
+ /** The localized `/gate config` header. */
48
+ gateConfigHeader: string;
49
+ /** The localized `/gate` usage error. */
50
+ gateCommandUnknown: (input: string) => string;
51
+ /** The localized `/gate` no-agent error. */
52
+ gateCommandNoAgent: string;
41
53
  switchOnDurable: string;
42
54
  switchOnLocal: string;
43
55
  switchOffDurable: string;
@@ -76,6 +88,11 @@ export interface CommandStatusFacts {
76
88
  reviewed: boolean;
77
89
  /** Total implementation edits folded so far. */
78
90
  editCount: number;
91
+ /** The latest durable gate run, or null before any. */
92
+ gate: {
93
+ verdict: string;
94
+ redCount: number;
95
+ } | null;
79
96
  }
80
97
  /** The localized prose tables by language. */
81
98
  export declare const PROSE: Readonly<Record<ProseLanguage, GuardProse>>;
@@ -74,6 +74,12 @@ const EN = {
74
74
  + 'what in the session supports it. If — and only if — the evidence '
75
75
  + 'genuinely satisfies every dimension, return an empty findings list. Do '
76
76
  + 'not invent objections; the empty answer is correct when nothing is wrong.',
77
+ gateRedNotice: redCount => `You are the delivery gate panel for this session. The latest gate run is RED with ${redCount} red item(s). Re-open the work in plan mode to re-check, or run /gate run for the full checklist.`,
78
+ gateStatusHeader: 'Delivery gate panel — checklist progress for this session:',
79
+ gateStatusHint: 'Deterministic phases fold live; the reviewer phases show the latest /gate run. Run /gate run to settle everything; /gate config shows the checklist.',
80
+ gateConfigHeader: 'Delivery gate configuration for this session:',
81
+ gateCommandUnknown: input => `Unknown /gate argument "${input}". Usage: /gate status|run|config`,
82
+ gateCommandNoAgent: '/gate needs an agent session to inspect.',
77
83
  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
84
  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
85
  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.',
@@ -91,6 +97,7 @@ const EN = {
91
97
  `Modules: grill=${facts.modules.grill ? 'on' : 'off'}, tdd=${facts.modules.tdd ? 'on' : 'off'}, adversary=${facts.modules.adversary ? 'on' : 'off'} (cordis.yml).`,
92
98
  `Intensity: ${facts.intensity}. Default switch: ${facts.defaultEnabled ? 'on' : 'off'}. remindOnce: ${facts.remindOnce ? 'on' : 'off'}.`,
93
99
  `Stage: spec=${facts.hasSpec ? 'committed' : 'missing'}, tests=${facts.color}, review=${facts.reviewed ? 'on record' : 'not run'}, edits=${facts.editCount}.`,
100
+ `Gate: ${facts.gate === null ? 'not run' : `${facts.gate.verdict} (${facts.gate.redCount} red item(s))`} (/gate status|run|config).`,
94
101
  `Usage: /doublecheck status|report|on|off`,
95
102
  ].join('\n'),
96
103
  };
@@ -121,6 +128,12 @@ const ZH = {
121
128
  + '验收标准、范围或非目标被违反、未处理的失败模式。通过要求的结构化输出作答,'
122
129
  + '每条反对意见占一项,并引用会话中支撑它的内容。当且仅当证据确实满足每一个维度时,'
123
130
  + '才返回空的 findings 列表。不要编造反对意见;没有问题时空答案才是正确的。',
131
+ gateRedNotice: redCount => `你是本会话的交付门禁面板。最近一次门禁结论为红灯,共 ${redCount} 项红灯。建议转 plan mode 复查,或运行 /gate run 查看完整检查单。`,
132
+ gateStatusHeader: '交付门禁面板 — 本会话的检查单进度:',
133
+ gateStatusHint: '确定性阶段实时折叠;评审类阶段显示最近一次 /gate run 的结果。运行 /gate run 结算全部检查;/gate config 查看检查单配置。',
134
+ gateConfigHeader: '本会话的交付门禁配置:',
135
+ gateCommandUnknown: input => `未知的 /gate 参数 "${input}"。用法:/gate status|run|config`,
136
+ gateCommandNoAgent: '/gate 需要代理会话才能查看。',
124
137
  switchOnDurable: 'Double-check 纪律已对本会话开启(由用户切换):需求盘问、红绿门和交付审查恢复生效。',
125
138
  switchOnLocal: 'Double-check 纪律已对本进程开启(由用户切换;此 harness 版本早于 ignorable 追加接口,切换不持久):需求盘问、红绿门和交付审查恢复生效。',
126
139
  switchOffDurable: 'Double-check 纪律已对本会话关闭(由用户切换):在重新开启之前,纪律门让位于人工审批链。',
@@ -138,6 +151,7 @@ const ZH = {
138
151
  `模块:grill=${facts.modules.grill ? '开' : '关'}, tdd=${facts.modules.tdd ? '开' : '关'}, adversary=${facts.modules.adversary ? '开' : '关'}(cordis.yml)。`,
139
152
  `强度:${facts.intensity}。默认开关:${facts.defaultEnabled ? '开' : '关'}。remindOnce:${facts.remindOnce ? '开' : '关'}。`,
140
153
  `阶段:spec=${facts.hasSpec ? '已提交' : '缺失'}, tests=${facts.color}, review=${facts.reviewed ? '有记录' : '未运行'}, edits=${facts.editCount}。`,
154
+ `门禁:${facts.gate === null ? '未运行' : `${facts.gate.verdict}(${facts.gate.redCount} 项红灯)`}(/gate status|run|config)。`,
141
155
  '用法:/doublecheck status|report|on|off',
142
156
  ].join('\n'),
143
157
  };
@@ -16,8 +16,8 @@ import type { Context } from '@deepseek-ai/cordis';
16
16
  import type { Agent } from '@deepseek-ai/dsh-agent';
17
17
  import type { UserMessage } from '@deepseek-ai/dsh-llm';
18
18
  import type { ObjectJsonSchema } from '@deepseek-ai/dsh-tools';
19
- import type { ReviewFinding, ReviewVerdict } from '../domain/vocabulary.ts';
20
- import { type GuardProse, type ProseLanguage } from './prose.ts';
19
+ import type { ReviewFinding, ReviewVerdict } from '../domain/vocabulary.js';
20
+ import { type GuardProse, type ProseLanguage } from './prose.js';
21
21
  /** The adversary knobs the review runner reads. */
22
22
  export interface AdversaryConfig {
23
23
  adversaryProvider: string;
package/lib/index.d.ts CHANGED
@@ -6,12 +6,13 @@
6
6
  *
7
7
  * @module dsh-doublecheck
8
8
  */
9
- export type { GrilledSpec, GuardIntensity, GuardGate, GuardVerdict } from './events.ts';
10
- export * from './domain/stages.ts';
11
- export * from './domain/evidence.ts';
12
- export * from './domain/vagueness.ts';
13
- export * from './domain/vocabulary.ts';
14
- export * from './domain/report.ts';
15
- export { renderSpecMarkdown } from './grill/index.ts';
16
- export { BundledSkillProvider, parseSkillAsset, PROVIDER_NAME } from './grill/provider.ts';
17
- export type * from './types.ts';
9
+ export type { GrilledSpec, GuardIntensity, GuardGate, GuardVerdict } from './events.js';
10
+ export * from './domain/stages.js';
11
+ export * from './domain/evidence.js';
12
+ export * from './domain/vagueness.js';
13
+ export * from './domain/vocabulary.js';
14
+ export * from './domain/report.js';
15
+ export * from './domain/gate.js';
16
+ export { renderSpecMarkdown } from './grill/index.js';
17
+ export { BundledSkillProvider, parseSkillAsset, PROVIDER_NAME } from './grill/provider.js';
18
+ export type * from './types.js';
package/lib/index.js CHANGED
@@ -11,5 +11,6 @@ export * from "./domain/evidence.js";
11
11
  export * from "./domain/vagueness.js";
12
12
  export * from "./domain/vocabulary.js";
13
13
  export * from "./domain/report.js";
14
+ export * from "./domain/gate.js";
14
15
  export { renderSpecMarkdown } from "./grill/index.js";
15
16
  export { BundledSkillProvider, parseSkillAsset, PROVIDER_NAME } from "./grill/provider.js";
@@ -14,6 +14,8 @@
14
14
  * the tool result, not this event, so equality is not checkable there).
15
15
  * 3. Every `doublecheck/review` announcement's findings satisfy the
16
16
  * structured finding shape (severity enum, non-empty title and detail).
17
+ * 4. Every `doublecheck/gate` announcement's verdict matches the re-derivation
18
+ * from its own phase results, and all four phases are present.
17
19
  *
18
20
  * The guard registers a facts-bearing installer from its own context; the
19
21
  * `./invariant` export is the standalone companion usable through a separate
@@ -21,7 +23,7 @@
21
23
  * @module dsh-doublecheck/invariant
22
24
  */
23
25
  import type { Context } from '@deepseek-ai/cordis';
24
- import type { TestRunDetection } from './domain/evidence.ts';
26
+ import type { TestRunDetection } from './domain/evidence.js';
25
27
  /** Full npm package name owning the reported failures. */
26
28
  export declare const PACKAGE_NAME = "dsh-doublecheck";
27
29
  /** A package-attributed invariant failure reported by the host registry. */
package/lib/invariant.js CHANGED
@@ -14,6 +14,8 @@
14
14
  * the tool result, not this event, so equality is not checkable there).
15
15
  * 3. Every `doublecheck/review` announcement's findings satisfy the
16
16
  * structured finding shape (severity enum, non-empty title and detail).
17
+ * 4. Every `doublecheck/gate` announcement's verdict matches the re-derivation
18
+ * from its own phase results, and all four phases are present.
17
19
  *
18
20
  * The guard registers a facts-bearing installer from its own context; the
19
21
  * `./invariant` export is the standalone companion usable through a separate
@@ -22,6 +24,7 @@
22
24
  */
23
25
  import { SPEC_FIELD_NAMES } from "./domain/vocabulary.js";
24
26
  import { deriveReportVerdict, foldReportFacts } from "./domain/report.js";
27
+ import { deriveGateVerdict, GATE_PHASES } from "./domain/gate.js";
25
28
  /** Full npm package name owning the reported failures. */
26
29
  export const PACKAGE_NAME = 'dsh-doublecheck';
27
30
  /** Cordis companion plugin name. */
@@ -80,6 +83,18 @@ export function installInvariant(facts) {
80
83
  }
81
84
  }
82
85
  });
86
+ ctx.on('doublecheck/gate', (payload) => {
87
+ const state = payload.state;
88
+ const derived = deriveGateVerdict(GATE_PHASES.map(phase => state.phases[phase]).filter(phase => phase !== undefined));
89
+ if (derived !== state.verdict) {
90
+ fail(`doublecheck/gate announces verdict "${state.verdict}" but its phases derive "${derived}"`);
91
+ }
92
+ for (const phase of GATE_PHASES) {
93
+ if (state.phases[phase] === undefined) {
94
+ fail(`doublecheck/gate carries no "${phase}" phase result`);
95
+ }
96
+ }
97
+ });
83
98
  };
84
99
  }
85
100
  /**
package/lib/types.d.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  * @module dsh-doublecheck/types
8
8
  */
9
9
  import { z as zod } from 'zod';
10
- import type { DisciplineStage, TestColor } from './domain/stages.ts';
10
+ import type { DisciplineStage, TestColor } from './domain/stages.js';
11
+ import type { GateVerdict } from './domain/gate.js';
11
12
  /** The whole wire value of the `doublecheck` session projection. */
12
13
  export interface DoublecheckView {
13
14
  /** The last discipline stage reached on durable evidence. */
@@ -22,6 +23,10 @@ export interface DoublecheckView {
22
23
  reviewed: boolean;
23
24
  /** Total implementation edits folded so far. */
24
25
  editCount: number;
26
+ /** The latest delivery-gate verdict, or 'none' before any gate run. */
27
+ gateVerdict: 'none' | GateVerdict;
28
+ /** Red-light (failing) checks of the latest gate run. */
29
+ gateRedCount: number;
25
30
  }
26
31
  /** Validates the `doublecheck` projection's wire payload before it leaves the host. */
27
32
  export declare const doublecheckViewSchema: zod.ZodObject<{
@@ -31,6 +36,8 @@ export declare const doublecheckViewSchema: zod.ZodObject<{
31
36
  specGoal: zod.ZodString;
32
37
  reviewed: zod.ZodBoolean;
33
38
  editCount: zod.ZodNumber;
39
+ gateVerdict: zod.ZodUnion<readonly [zod.ZodLiteral<"none">, zod.ZodLiteral<"deliverable">, zod.ZodLiteral<"rework">]>;
40
+ gateRedCount: zod.ZodNumber;
34
41
  }, zod.core.$strip>;
35
42
  declare module '@deepseek-ai/dsh-session-projection/types' {
36
43
  interface SessionProjectionMap {
package/lib/types.js CHANGED
@@ -15,4 +15,6 @@ export const doublecheckViewSchema = zod.object({
15
15
  specGoal: zod.string(),
16
16
  reviewed: zod.boolean(),
17
17
  editCount: zod.number(),
18
+ gateVerdict: zod.union([zod.literal('none'), zod.literal('deliverable'), zod.literal('rework')]),
19
+ gateRedCount: zod.number(),
18
20
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-doublecheck",
3
- "version": "0.6.0",
4
- "description": "Double-check before you ship: grill the requirements, test the implementation, prove the delivery.",
3
+ "version": "0.7.1",
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": {
7
7
  "type": "git",
@@ -51,13 +51,17 @@
51
51
  "LICENSE"
52
52
  ],
53
53
  "scripts": {
54
- "build": "tsc --noEmitOnError",
55
- "prepare": "tsc --noEmitOnError",
56
- "prepublishOnly": "tsc --noEmitOnError && pnpm run test",
54
+ "build": "tsc --noEmitOnError && node scripts/fix-dts.mjs",
55
+ "prepare": "tsc --noEmitOnError && node scripts/fix-dts.mjs",
56
+ "prepublishOnly": "tsc --noEmitOnError && node scripts/fix-dts.mjs && pnpm run test",
57
57
  "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
58
58
  "lint": "eslint src tests",
59
59
  "test": "vitest run",
60
60
  "test:coverage": "vitest run --coverage",
61
+ "verify:skills": "node scripts/verify-skills.mjs",
62
+ "check:readmes": "node scripts/check-readme-sync.mjs",
63
+ "verify:self-contained": "node scripts/verify-self-contained.mjs",
64
+ "verify:artifacts": "node scripts/verify-artifacts.mjs",
61
65
  "pack:check": "pnpm run build && pnpm pack"
62
66
  },
63
67
  "keywords": [
@@ -67,7 +71,9 @@
67
71
  "engineering-discipline",
68
72
  "requirements",
69
73
  "guard",
70
- "skill"
74
+ "skill",
75
+ "quality-gate",
76
+ "delivery-gate"
71
77
  ],
72
78
  "author": "PerryLink",
73
79
  "license": "Apache-2.0",
@@ -79,6 +85,45 @@
79
85
  "patch": "./cordis.patch.yml"
80
86
  }
81
87
  },
88
+ "dshWorkshop": {
89
+ "schema": "omdsh-workshop-package/v1",
90
+ "type": "plugin",
91
+ "integration": {
92
+ "protocol": "harness-profile",
93
+ "artifact": "cordis.patch.yml"
94
+ },
95
+ "install": {
96
+ "mode": "transactional",
97
+ "adapter": "profile-bundle",
98
+ "failurePolicy": "generation-rollback",
99
+ "touchesCurrentBeforeActivation": false
100
+ },
101
+ "lifecycle": {
102
+ "activation": "restart-profile",
103
+ "dispose": "supported"
104
+ },
105
+ "permissions": [
106
+ "filesystem:read",
107
+ "filesystem:write"
108
+ ],
109
+ "compatibility": {
110
+ "dshVersions": [
111
+ "0.1.0-rc.6"
112
+ ]
113
+ },
114
+ "capability": {
115
+ "id": "doublecheck",
116
+ "kind": "command",
117
+ "invocation": "send /gate status in a session with the plugin rows loaded",
118
+ "expected": "a reply reporting the live checklist progress (requirements/test evidence phases), the latest gate run verdict with red items, and the plan-mode state"
119
+ },
120
+ "evidence": {
121
+ "install": null,
122
+ "failureIsolation": null,
123
+ "hotReload": null,
124
+ "remove": null
125
+ }
126
+ },
82
127
  "peerDependencies": {
83
128
  "@deepseek-ai/cordis": "^4.0.1",
84
129
  "@deepseek-ai/dsh-agent": "0.1.0-rc.6",
@@ -96,6 +141,7 @@
96
141
  },
97
142
  "devDependencies": {
98
143
  "@deepseek-ai/cordis": "^4.0.1",
144
+ "@deepseek-ai/cordis-plugin-loader": "1.0.2",
99
145
  "@deepseek-ai/dsh-agent": "0.1.0-rc.6",
100
146
  "@deepseek-ai/dsh-commands": "0.1.0-rc.6",
101
147
  "@deepseek-ai/dsh-fs": "0.1.0-rc.6",
@@ -32,6 +32,18 @@ for a proof, a delivery summary, or a "what did we actually do" record.
32
32
  proven and must not be described as such; `unverified` means not every
33
33
  dimension returned a verdict.
34
34
 
35
+ ## Passing the delivery gate
36
+
37
+ Before claiming the delivery is ready to ship, run the quality gate:
38
+
39
+ 1. Run `/gate run`. It settles the four-phase checklist — requirements
40
+ interrogation, test evidence, implementation consistency, and the review
41
+ conclusion — and returns a markdown report with a binary decision:
42
+ **deliverable** or **rework required**.
43
+ 2. On a rework verdict, fix the red items and re-run `/gate run`; the report
44
+ suggests re-opening the work in plan mode to re-check. Paste the final
45
+ report into the PR description when the decision is deliverable.
46
+
35
47
  ## Reading the verdict
36
48
 
37
49
  - **grill / draft / red** — the discipline loop is not complete. State what
package/strict.patch.yml CHANGED
@@ -31,3 +31,40 @@
31
31
  testFilePatterns:
32
32
  - '(^|[\\/])(tests?|__tests__|specs?)([\\/]|$)'
33
33
  - '\\.(test|spec)\\.[A-Za-z0-9]+$'
34
+ gate:
35
+ enabled: true
36
+ planSuggestion: true
37
+ reportFile: 'gate-report.md'
38
+ requirements:
39
+ enabled: true
40
+ checklist:
41
+ - { id: goal, question: 'What outcome must the delivery produce?', specDimension: goal, required: true }
42
+ - { id: scope, question: 'What is in scope, and what is out of scope?', specDimension: scope, required: true }
43
+ - { id: acceptance, question: 'Which observable checks prove the work is done?', specDimension: acceptanceCriteria, required: true }
44
+ - { id: failureModes, question: 'What can go wrong, and what is the correct behavior in each case?', specDimension: failureModes, required: true }
45
+ - { id: priorities, question: 'What is traded when goals conflict; what is optional?', specDimension: priorities, required: true }
46
+ - { id: nonGoals, question: 'What does the user explicitly not want?', specDimension: nonGoals, required: true }
47
+ minConfirmed: 6
48
+ interrogateTool: 'ask_user_question'
49
+ tests:
50
+ enabled: true
51
+ requirePassingRun: true
52
+ allowFailingRuns: 0
53
+ requireCoverage: true
54
+ minCoveragePct: 80
55
+ coveragePattern: 'coverage[^\\d]{0,40}(\\d+(?:\\.\\d+)?)\\s*%'
56
+ consistency:
57
+ enabled: true
58
+ provider: fork
59
+ model: null
60
+ tools: ['read', 'glob', 'grep']
61
+ timeoutMs: 120000
62
+ maxFindings: 5
63
+ review:
64
+ enabled: true
65
+ engine: auto
66
+ provider: fork
67
+ model: null
68
+ tools: ['read', 'glob', 'grep']
69
+ timeoutMs: 120000
70
+ maxFindings: 5