dw-kit 1.9.2 → 1.10.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/.claude/rules/dw.md +14 -3
- package/.claude/skills/dw-flow/SKILL.md +35 -2
- package/.claude/skills/dw-goal/SKILL.md +22 -1
- package/.dw/config/config.schema.json +24 -0
- package/.dw/config/dw.config.yml +14 -0
- package/.dw/core/HARNESS.md +157 -0
- package/.dw/core/schemas/events/gate_auto_approved.schema.json +36 -0
- package/.dw/core/schemas/events/hard_stop.schema.json +35 -0
- package/.dw/core/schemas/events/index.json +28 -2
- package/.dw/core/schemas/events/overnight_wrapup.schema.json +29 -0
- package/.dw/core/schemas/events/parked.schema.json +39 -0
- package/.dw/core/schemas/events/subtask_completed.schema.json +35 -0
- package/.dw/core/schemas/overnight-digest.schema.json +113 -0
- package/.dw/core/templates/autopilot-cron.sh +28 -0
- package/README.md +152 -121
- package/package.json +4 -1
- package/src/cli.mjs +63 -0
- package/src/commands/autopilot.mjs +65 -0
- package/src/commands/doctor.mjs +17 -1
- package/src/commands/overnight.mjs +35 -0
- package/src/commands/trust.mjs +79 -0
- package/src/commands/voice.mjs +431 -2
- package/src/lib/autopilot-contract.mjs +97 -0
- package/src/lib/board-data.mjs +23 -57
- package/src/lib/config.mjs +56 -0
- package/src/lib/event-schema.mjs +28 -0
- package/src/lib/goal-driver.mjs +312 -0
- package/src/lib/goal-events.mjs +4 -1
- package/src/lib/goal-progress.mjs +193 -0
- package/src/lib/overnight-digest.mjs +241 -0
- package/src/lib/process-kill.mjs +77 -0
- package/src/lib/task-md-utils.mjs +78 -0
- package/src/lib/tls-helpers.mjs +28 -6
- package/src/lib/trust.mjs +139 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Trust Mode gate-resolution logic (ADR-0022).
|
|
2
|
+
//
|
|
3
|
+
// `dw:flow` human gates fall into two classes. Trust Mode may auto-approve the
|
|
4
|
+
// *convenience* gates (reversible review of intent/output) when the user opts
|
|
5
|
+
// in; it can NEVER relax the *safety* gates (irreversible / unsafe). This module
|
|
6
|
+
// is the single source of truth for that taxonomy, so the skill, the CLI, and
|
|
7
|
+
// the tests all agree on what is and isn't relaxable.
|
|
8
|
+
|
|
9
|
+
import { getTrustConfig } from './config.mjs';
|
|
10
|
+
import { logGoalEvent } from './goal-events.mjs';
|
|
11
|
+
|
|
12
|
+
// Canonical gate ids and their class. Gate ids are the stable contract the
|
|
13
|
+
// skill passes in (mapped from GATE A/B/C/D and the quick-depth Q1/Q2).
|
|
14
|
+
export const GATES = {
|
|
15
|
+
scope: { class: 'convenience', maps: 'GATE A / Q1', configKey: 'scope' },
|
|
16
|
+
plan: { class: 'convenience', maps: 'GATE C', configKey: 'plan' },
|
|
17
|
+
changes: { class: 'convenience', maps: 'GATE D / Q2', configKey: 'changes' },
|
|
18
|
+
arch: { class: 'safety', maps: 'GATE B (TL architecture sign-off)', configKey: null },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a one-shot `--trust` flag into a predicate over gate ids.
|
|
23
|
+
* Accepts:
|
|
24
|
+
* true → trust every convenience gate this run
|
|
25
|
+
* "plan" → trust only that gate
|
|
26
|
+
* "scope,plan" → trust those gates
|
|
27
|
+
* ["scope","plan"]→ same
|
|
28
|
+
* undefined/false → no one-shot trust
|
|
29
|
+
*/
|
|
30
|
+
export function parseTrustFlag(flag) {
|
|
31
|
+
if (flag === true) return { all: true, gates: new Set() };
|
|
32
|
+
if (!flag) return { all: false, gates: new Set() };
|
|
33
|
+
// CLI passes strings; treat "true"/"all" (the bare `--trust`) as "every convenience gate".
|
|
34
|
+
const raw = Array.isArray(flag) ? flag : String(flag).split(',');
|
|
35
|
+
const gates = new Set(raw.map((g) => g.trim().toLowerCase()).filter(Boolean));
|
|
36
|
+
if (gates.has('true') || gates.has('all')) return { all: true, gates: new Set() };
|
|
37
|
+
return { all: false, gates };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Decide whether a single gate may be auto-approved.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} params
|
|
44
|
+
* @param {string} params.gate canonical gate id (see GATES)
|
|
45
|
+
* @param {object} params.config loaded dw config (for workflow.trust)
|
|
46
|
+
* @param {*} [params.flag] one-shot `--trust` flag value (this run)
|
|
47
|
+
* @param {boolean}[params.hasCritical] true when a Critical review finding exists (GATE D)
|
|
48
|
+
* @param {object} [params.evidence] EVD verify (ADR-0023): {tests:{exit},review:{critical}} — when
|
|
49
|
+
* present, criticality/red-tests are derived from the evidence
|
|
50
|
+
* itself, not just the caller's flag.
|
|
51
|
+
* @returns {{ autoApprove: boolean, reason: string, gateClass: string }}
|
|
52
|
+
*/
|
|
53
|
+
export function resolveGate({ gate, config, flag, hasCritical = false, evidence, runCount } = {}) {
|
|
54
|
+
const meta = GATES[gate];
|
|
55
|
+
if (!meta) {
|
|
56
|
+
return { autoApprove: false, reason: `unknown gate "${gate}" — manual`, gateClass: 'unknown' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Invariant: safety hard-stops are structurally unreachable by Trust Mode.
|
|
60
|
+
if (meta.class === 'safety') {
|
|
61
|
+
return {
|
|
62
|
+
autoApprove: false,
|
|
63
|
+
reason: `safety gate (${meta.maps}) — never auto-approved`,
|
|
64
|
+
gateClass: 'safety',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// EVD verify (ADR-0023): when evidence is supplied for GATE D, derive the
|
|
69
|
+
// safety signals from the evidence — stronger than a bare --critical flag the
|
|
70
|
+
// caller might forget. Evidence that shows a Critical finding or red tests
|
|
71
|
+
// forces a manual stop even when `changes` is trusted.
|
|
72
|
+
const criticalFromEvidence = gate === 'changes' && evidence?.review && Number(evidence.review.critical) >= 1;
|
|
73
|
+
// Only treat tests as failed when a real exit code is present and non-zero.
|
|
74
|
+
// A `tests` object without `exit` must NOT block (round-2 verify: false positive).
|
|
75
|
+
const exitCode = Number(evidence?.tests?.exit);
|
|
76
|
+
const testsFailedFromEvidence = gate === 'changes' && evidence?.tests && Number.isFinite(exitCode) && exitCode !== 0;
|
|
77
|
+
|
|
78
|
+
// Invariant: a Critical review finding forces GATE D to a manual stop even
|
|
79
|
+
// when `changes` is trusted.
|
|
80
|
+
if (gate === 'changes' && (hasCritical || criticalFromEvidence)) {
|
|
81
|
+
return {
|
|
82
|
+
autoApprove: false,
|
|
83
|
+
reason: 'Critical review finding — manual approval required',
|
|
84
|
+
gateClass: 'convenience',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (testsFailedFromEvidence) {
|
|
88
|
+
return {
|
|
89
|
+
autoApprove: false,
|
|
90
|
+
reason: `tests not green (exit ${evidence.tests.exit}) — manual approval required`,
|
|
91
|
+
gateClass: 'convenience',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const trust = getTrustConfig(config);
|
|
96
|
+
|
|
97
|
+
// require_evidence (ADR-0023 follow-up): when set, GATE D will not auto-approve
|
|
98
|
+
// without an evidence object citing a REAL numeric review.critical count — an
|
|
99
|
+
// empty {review:{}} must NOT satisfy it (round-3 verify: bypass via empty object).
|
|
100
|
+
// Require a real numeric critical count — a boolean `false` or `[]` both coerce
|
|
101
|
+
// to 0 via Number(), so check the type, not just finiteness (final verify).
|
|
102
|
+
const hasReviewCount = typeof evidence?.review?.critical === 'number'
|
|
103
|
+
&& Number.isFinite(evidence.review.critical);
|
|
104
|
+
if (gate === 'changes' && trust.require_evidence && !hasReviewCount) {
|
|
105
|
+
return {
|
|
106
|
+
autoApprove: false,
|
|
107
|
+
reason: 'workflow.trust.require_evidence — supply --evidence with a numeric review.critical count',
|
|
108
|
+
gateClass: 'convenience',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const oneShot = parseTrustFlag(flag);
|
|
113
|
+
if (oneShot.all || oneShot.gates.has(gate)) {
|
|
114
|
+
return { autoApprove: true, reason: `--trust (this run): ${meta.maps}`, gateClass: 'convenience' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (trust.enabled && trust.auto_approve[meta.configKey]) {
|
|
118
|
+
// Budget: once an unattended run reaches its cap, stop auto-approving so the
|
|
119
|
+
// bound is real at the decision point, not just advisory (round-2 verify: W-3).
|
|
120
|
+
if (trust.max_auto_subtasks > 0 && Number.isInteger(runCount) && runCount >= trust.max_auto_subtasks) {
|
|
121
|
+
return { autoApprove: false, reason: `budget reached (${runCount}/${trust.max_auto_subtasks} auto-approved) — manual`, gateClass: 'convenience' };
|
|
122
|
+
}
|
|
123
|
+
return { autoApprove: true, reason: `workflow.trust.auto_approve.${meta.configKey}`, gateClass: 'convenience' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Invariant: default is fully manual.
|
|
127
|
+
return { autoApprove: false, reason: 'manual (Trust Mode off for this gate)', gateClass: 'convenience' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Audit an auto-approval to .dw/events-global.jsonl so the skipped human is
|
|
132
|
+
* always recoverable. Invariant 4 of ADR-0022.
|
|
133
|
+
*/
|
|
134
|
+
export function logTrustEvent({ gate, task, reason, evidence }, rootDir = process.cwd()) {
|
|
135
|
+
return logGoalEvent(
|
|
136
|
+
{ event: 'gate_auto_approved', actor: 'trust-mode', gate, task: task || null, reason: reason || null, evidence: evidence || null },
|
|
137
|
+
rootDir,
|
|
138
|
+
);
|
|
139
|
+
}
|