@deftai/directive-core 0.93.0 → 0.95.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/dist/authz/classify.js +485 -0
- package/dist/cache/fetch.d.ts +29 -0
- package/dist/cache/fetch.js +131 -4
- package/dist/cache/operations.d.ts +8 -0
- package/dist/cache/operations.js +67 -4
- package/dist/check/cached-orchestrator.js +11 -0
- package/dist/check/consumer-gate-integrity.d.ts +68 -0
- package/dist/check/consumer-gate-integrity.js +265 -0
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/orchestrator.js +10 -0
- package/dist/content-contracts/skills/greptile-detector.d.ts +9 -0
- package/dist/content-contracts/skills/greptile-detector.js +9 -2
- package/dist/doctor/main.js +98 -9
- package/dist/doctor/types.d.ts +2 -2
- package/dist/finish-loop/pr-finish-loop.js +1 -0
- package/dist/freshness/bind.d.ts +67 -0
- package/dist/freshness/bind.js +201 -0
- package/dist/freshness/cli.d.ts +30 -0
- package/dist/freshness/cli.js +180 -0
- package/dist/freshness/compare.d.ts +14 -0
- package/dist/freshness/compare.js +134 -0
- package/dist/freshness/generation.d.ts +44 -0
- package/dist/freshness/generation.js +172 -0
- package/dist/freshness/index.d.ts +13 -0
- package/dist/freshness/index.js +13 -0
- package/dist/freshness/report.d.ts +43 -0
- package/dist/freshness/report.js +139 -0
- package/dist/freshness/types.d.ts +70 -0
- package/dist/freshness/types.js +30 -0
- package/dist/handoff-evidence/index.d.ts +8 -0
- package/dist/handoff-evidence/index.js +7 -0
- package/dist/handoff-evidence/validate.d.ts +105 -0
- package/dist/handoff-evidence/validate.js +423 -0
- package/dist/hooks/dispatcher.d.ts +27 -2
- package/dist/hooks/dispatcher.js +134 -14
- package/dist/hooks/readonly.d.ts +14 -0
- package/dist/hooks/readonly.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +5 -2
- package/dist/init-deposit/init-deposit.d.ts +7 -1
- package/dist/init-deposit/init-deposit.js +24 -4
- package/dist/init-deposit/refresh.d.ts +9 -1
- package/dist/init-deposit/refresh.js +48 -5
- package/dist/intake/issue-ingest.d.ts +20 -0
- package/dist/intake/issue-ingest.js +58 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.js +50 -1
- package/dist/policy/min-greptile-confidence.d.ts +57 -0
- package/dist/policy/min-greptile-confidence.js +123 -0
- package/dist/pr-merge-readiness/compute.d.ts +11 -1
- package/dist/pr-merge-readiness/compute.js +17 -4
- package/dist/pr-merge-readiness/evaluate.d.ts +8 -1
- package/dist/pr-merge-readiness/evaluate.js +7 -4
- package/dist/pr-merge-readiness/mergeability.d.ts +7 -1
- package/dist/pr-merge-readiness/mergeability.js +14 -2
- package/dist/pr-monitor/main.d.ts +2 -0
- package/dist/pr-monitor/main.js +31 -1
- package/dist/pr-monitor/monitor.js +5 -1
- package/dist/pr-monitor/readiness.js +3 -1
- package/dist/pr-monitor/types.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -1
- package/dist/pr-wait-mergeable/main.d.ts +2 -0
- package/dist/pr-wait-mergeable/main.js +16 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +3 -0
- package/dist/pr-watch/constants.d.ts +4 -1
- package/dist/pr-watch/constants.js +5 -2
- package/dist/pr-watch/main.js +1 -0
- package/dist/pr-watch/probe.d.ts +1 -1
- package/dist/pr-watch/probe.js +4 -1
- package/dist/pr-watch/types.d.ts +7 -1
- package/dist/pr-watch/watch.js +3 -2
- package/dist/release-e2e/greenfield-python-free-smoke.js +7 -3
- package/dist/review-monitor/index.d.ts +1 -0
- package/dist/review-monitor/index.js +1 -0
- package/dist/review-monitor/l4-owner.d.ts +46 -0
- package/dist/review-monitor/l4-owner.js +262 -0
- package/dist/session/ritual-entrypoint.d.ts +6 -2
- package/dist/session/ritual-entrypoint.js +15 -2
- package/dist/session/session-ready.js +31 -14
- package/dist/session/session-start.d.ts +2 -1
- package/dist/session/session-start.js +66 -3
- package/dist/session/verify-session-ritual.d.ts +4 -2
- package/dist/session/verify-session-ritual.js +11 -11
- package/dist/slash/product-set.js +4 -1
- package/dist/swarm/verify-review-clean.d.ts +4 -3
- package/dist/swarm/verify-review-clean.js +28 -73
- package/dist/tool-events/classify.js +1 -0
- package/dist/triage/classify/index.d.ts +9 -0
- package/dist/triage/classify/index.js +34 -2
- package/dist/triage/classify/label-mirror.d.ts +143 -0
- package/dist/triage/classify/label-mirror.js +684 -0
- package/dist/triage/help/registry-data.d.ts +7 -7
- package/dist/triage/help/registry-data.js +27 -7
- package/dist/triage/queue/show.d.ts +1 -1
- package/dist/triage/queue/show.js +4 -2
- package/dist/triage/welcome/writers.js +16 -7
- package/dist/umbrella-current-shape/index.d.ts +45 -0
- package/dist/umbrella-current-shape/index.js +162 -8
- package/dist/vbrief-reconcile/index.d.ts +2 -2
- package/dist/vbrief-reconcile/index.js +1 -1
- package/dist/vbrief-reconcile/types.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +31 -2
- package/dist/vbrief-reconcile/umbrellas.js +231 -19
- package/dist/vbrief-validate/project-definition.js +1 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +42 -0
- package/dist/verify-env/agent-hook-readiness.js +150 -0
- package/dist/verify-env/agent-hooks-live-probe.d.ts +16 -4
- package/dist/verify-env/agent-hooks-live-probe.js +120 -107
- package/dist/verify-env/agent-hooks.js +11 -3
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer check-graph integrity (#3070).
|
|
3
|
+
*
|
|
4
|
+
* CONSUMER_CHECK_GATES lists Taskfile tasks that must resolve in a vendored
|
|
5
|
+
* deposit. When optional Taskfile includes silently omit `tasks/verify.yml`
|
|
6
|
+
* (etc.), go-task fails with opaque exit 200/201 ("Task does not exist").
|
|
7
|
+
*
|
|
8
|
+
* This module:
|
|
9
|
+
* 1. Maps each gate to its include namespace / root Taskfile surface
|
|
10
|
+
* 2. Proves shipped files define those tasks (static + runtime)
|
|
11
|
+
* 3. Emits a deposit-repair recovery message instead of opaque go-task errors
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { CONSUMER_CHECK_GATES, checkGateId } from "./gate-lists.js";
|
|
16
|
+
/** Include namespaces required by the consumer check graph (Taskfile includes). */
|
|
17
|
+
export const CHECK_GRAPH_REQUIRED_NAMESPACES = ["verify", "toolchain", "vbrief"];
|
|
18
|
+
export const CONSUMER_GATE_INTEGRITY_RECOVERY = "Incomplete Deft deposit: check-graph Taskfile include(s) missing or incomplete. " +
|
|
19
|
+
"Run `deft update` (or `npm i -g @deftai/directive@latest` then `deft update`) " +
|
|
20
|
+
"to restore `.deft/core/tasks/` (including `tasks/verify.yml` for `verify:orphan-active`). " +
|
|
21
|
+
"See UPGRADING.md.";
|
|
22
|
+
/** Namespace for a namespaced task (`verify:orphan-active` → `verify`); null for root tasks. */
|
|
23
|
+
export function gateNamespace(gateId) {
|
|
24
|
+
const colon = gateId.indexOf(":");
|
|
25
|
+
if (colon <= 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return gateId.slice(0, colon);
|
|
29
|
+
}
|
|
30
|
+
/** Local task name inside an include (`verify:orphan-active` → `orphan-active`). */
|
|
31
|
+
export function gateLocalName(gateId) {
|
|
32
|
+
const colon = gateId.indexOf(":");
|
|
33
|
+
if (colon <= 0) {
|
|
34
|
+
return gateId;
|
|
35
|
+
}
|
|
36
|
+
return gateId.slice(colon + 1);
|
|
37
|
+
}
|
|
38
|
+
/** Relative path of the include Taskfile for a namespace. */
|
|
39
|
+
export function includeTaskfileRel(namespace) {
|
|
40
|
+
return `tasks/${namespace}.yml`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Namespaces that CONSUMER_CHECK_GATES require via `ns:task` form.
|
|
44
|
+
* Root-only gates (doctor, verify-strategy-output) do not add a namespace.
|
|
45
|
+
*/
|
|
46
|
+
export function requiredNamespacesForGates(gates = CONSUMER_CHECK_GATES) {
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
for (const spec of gates) {
|
|
49
|
+
const ns = gateNamespace(checkGateId(spec));
|
|
50
|
+
if (ns !== null) {
|
|
51
|
+
seen.add(ns);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return [...seen].sort();
|
|
55
|
+
}
|
|
56
|
+
/** True when `localName` is a top-level task key under a Taskfile `tasks:` section. */
|
|
57
|
+
export function taskDefinedInTaskfileYaml(text, localName) {
|
|
58
|
+
// go-task task keys are indented two spaces under `tasks:`.
|
|
59
|
+
// Match ` orphan-active:` / ` check-consumer:` etc., not nested keys.
|
|
60
|
+
const escaped = localName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
61
|
+
const re = new RegExp(`^ {2}${escaped}\\s*:`, "m");
|
|
62
|
+
return re.test(text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse root Taskfile `includes:` entries: map namespace → { taskfile, optional }.
|
|
66
|
+
* Minimal line parser (avoids a YAML dependency); sufficient for go-task include shape.
|
|
67
|
+
*/
|
|
68
|
+
export function parseTaskfileIncludes(text) {
|
|
69
|
+
const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
70
|
+
const result = new Map();
|
|
71
|
+
let inIncludes = false;
|
|
72
|
+
let includesIndent = 0;
|
|
73
|
+
let currentNs = null;
|
|
74
|
+
let currentTaskfile = null;
|
|
75
|
+
let currentOptional = true; // go-task default when omitted is false, but our deposit historically used true
|
|
76
|
+
const flush = () => {
|
|
77
|
+
if (currentNs !== null && currentTaskfile !== null) {
|
|
78
|
+
result.set(currentNs, { taskfile: currentTaskfile, optional: currentOptional });
|
|
79
|
+
}
|
|
80
|
+
currentNs = null;
|
|
81
|
+
currentTaskfile = null;
|
|
82
|
+
currentOptional = false;
|
|
83
|
+
};
|
|
84
|
+
for (const raw of lines) {
|
|
85
|
+
const stripped = raw.trim();
|
|
86
|
+
if (!stripped || stripped.startsWith("#")) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const indent = raw.length - raw.trimStart().length;
|
|
90
|
+
if (!inIncludes) {
|
|
91
|
+
if (/^includes\s*:/.test(stripped) && indent === 0) {
|
|
92
|
+
inIncludes = true;
|
|
93
|
+
includesIndent = indent;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (indent <= includesIndent && !stripped.startsWith("#")) {
|
|
98
|
+
flush();
|
|
99
|
+
inIncludes = false;
|
|
100
|
+
// fall through if a new top-level key; re-check next iteration naturally
|
|
101
|
+
if (/^includes\s*:/.test(stripped)) {
|
|
102
|
+
inIncludes = true;
|
|
103
|
+
includesIndent = indent;
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Namespace key at includes+2 (typically 2 spaces): ` verify:`
|
|
108
|
+
if (indent === includesIndent + 2 && /^[A-Za-z_][\w-]*\s*:/.test(stripped)) {
|
|
109
|
+
flush();
|
|
110
|
+
currentNs = stripped.replace(/:\s*(?:#.*)?$/, "").trim();
|
|
111
|
+
currentTaskfile = null;
|
|
112
|
+
currentOptional = false;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (currentNs === null) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Properties at includes+4: taskfile / optional
|
|
119
|
+
if (indent >= includesIndent + 4) {
|
|
120
|
+
const taskfileMatch = stripped.match(/^taskfile\s*:\s*["']?([^"'#]+?)["']?\s*(?:#.*)?$/i);
|
|
121
|
+
if (taskfileMatch?.[1]) {
|
|
122
|
+
currentTaskfile = taskfileMatch[1].trim();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const optionalMatch = stripped.match(/^optional\s*:\s*(true|false)\s*(?:#.*)?$/i);
|
|
126
|
+
if (optionalMatch?.[1]) {
|
|
127
|
+
currentOptional = optionalMatch[1].toLowerCase() === "true";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
flush();
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Assert every consumer check gate resolves against `frameworkRoot` Taskfile + includes.
|
|
136
|
+
* When the root Taskfile is absent, returns ok with no findings (caller/spawn handles that).
|
|
137
|
+
*/
|
|
138
|
+
export function evaluateConsumerGateIntegrity(frameworkRoot, seams = {}) {
|
|
139
|
+
const root = resolve(frameworkRoot);
|
|
140
|
+
const exists = seams.exists ?? ((p) => existsSync(p));
|
|
141
|
+
const readText = seams.readText ??
|
|
142
|
+
((p) => {
|
|
143
|
+
try {
|
|
144
|
+
if (!existsSync(p)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return readFileSync(p, "utf8");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const gates = seams.gates ?? CONSUMER_CHECK_GATES;
|
|
154
|
+
const findings = [];
|
|
155
|
+
const rootTaskfile = join(root, "Taskfile.yml");
|
|
156
|
+
if (!exists(rootTaskfile)) {
|
|
157
|
+
// Incomplete / non-deposit path: do not invent a Taskfile; let spawn fail as before.
|
|
158
|
+
return { ok: true, findings: [], recovery: CONSUMER_GATE_INTEGRITY_RECOVERY };
|
|
159
|
+
}
|
|
160
|
+
const rootText = readText(rootTaskfile);
|
|
161
|
+
if (rootText === null) {
|
|
162
|
+
findings.push({
|
|
163
|
+
gateId: "*",
|
|
164
|
+
kind: "missing-root-taskfile",
|
|
165
|
+
detail: `Taskfile.yml unreadable at ${rootTaskfile}`,
|
|
166
|
+
expectedPath: rootTaskfile,
|
|
167
|
+
});
|
|
168
|
+
return { ok: false, findings, recovery: CONSUMER_GATE_INTEGRITY_RECOVERY };
|
|
169
|
+
}
|
|
170
|
+
const includes = parseTaskfileIncludes(rootText);
|
|
171
|
+
const yamlCache = new Map();
|
|
172
|
+
const readCached = (abs) => {
|
|
173
|
+
if (yamlCache.has(abs)) {
|
|
174
|
+
return yamlCache.get(abs) ?? null;
|
|
175
|
+
}
|
|
176
|
+
const t = readText(abs);
|
|
177
|
+
yamlCache.set(abs, t);
|
|
178
|
+
return t;
|
|
179
|
+
};
|
|
180
|
+
yamlCache.set(rootTaskfile, rootText);
|
|
181
|
+
for (const spec of gates) {
|
|
182
|
+
const gateId = checkGateId(spec);
|
|
183
|
+
const ns = gateNamespace(gateId);
|
|
184
|
+
const local = gateLocalName(gateId);
|
|
185
|
+
if (ns === null) {
|
|
186
|
+
// Root Taskfile task (doctor, verify-strategy-output, …)
|
|
187
|
+
if (!taskDefinedInTaskfileYaml(rootText, local)) {
|
|
188
|
+
findings.push({
|
|
189
|
+
gateId,
|
|
190
|
+
kind: "missing-task-definition",
|
|
191
|
+
detail: `Root task "${local}" not defined in Taskfile.yml (required by consumer check gate "${gateId}")`,
|
|
192
|
+
expectedPath: rootTaskfile,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const rel = includeTaskfileRel(ns);
|
|
198
|
+
const includeMeta = includes.get(ns);
|
|
199
|
+
const relFromInclude = includeMeta?.taskfile ? includeMeta.taskfile.replace(/^\.\//, "") : rel;
|
|
200
|
+
const includeAbs = join(root, relFromInclude);
|
|
201
|
+
if (!exists(includeAbs)) {
|
|
202
|
+
findings.push({
|
|
203
|
+
gateId,
|
|
204
|
+
kind: "missing-include-file",
|
|
205
|
+
detail: `Missing Taskfile include file for namespace "${ns}" (gate "${gateId}"): ` +
|
|
206
|
+
`${relFromInclude}. Optional silent omit of this include causes go-task ` +
|
|
207
|
+
`"Task \\"${gateId}\\" does not exist" (exit 200/201).`,
|
|
208
|
+
expectedPath: includeAbs,
|
|
209
|
+
});
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const includeText = readCached(includeAbs);
|
|
213
|
+
if (includeText === null) {
|
|
214
|
+
findings.push({
|
|
215
|
+
gateId,
|
|
216
|
+
kind: "missing-include-file",
|
|
217
|
+
detail: `Unreadable Taskfile include for namespace "${ns}": ${relFromInclude}`,
|
|
218
|
+
expectedPath: includeAbs,
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!taskDefinedInTaskfileYaml(includeText, local)) {
|
|
223
|
+
findings.push({
|
|
224
|
+
gateId,
|
|
225
|
+
kind: "missing-task-definition",
|
|
226
|
+
detail: `Task "${local}" not defined in ${relFromInclude} (required by consumer check gate "${gateId}")`,
|
|
227
|
+
expectedPath: includeAbs,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
ok: findings.length === 0,
|
|
233
|
+
findings,
|
|
234
|
+
recovery: CONSUMER_GATE_INTEGRITY_RECOVERY,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/** Format integrity failure for stderr (check orchestrator / doctor). */
|
|
238
|
+
export function formatConsumerGateIntegrityFailure(result) {
|
|
239
|
+
const lines = [
|
|
240
|
+
"check: consumer gate integrity failed (#3070)",
|
|
241
|
+
...result.findings.map((f) => ` - ${f.gateId}: ${f.detail}`),
|
|
242
|
+
` recovery: ${result.recovery}`,
|
|
243
|
+
];
|
|
244
|
+
return `${lines.join("\n")}\n`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Namespaces that must not be `optional: true` on the root Taskfile when the
|
|
248
|
+
* check graph depends on them — silent omit is the #3070 failure mode.
|
|
249
|
+
*/
|
|
250
|
+
export function checkGraphOptionalIncludeViolations(rootTaskfileText, namespaces = requiredNamespacesForGates()) {
|
|
251
|
+
const includes = parseTaskfileIncludes(rootTaskfileText);
|
|
252
|
+
const bad = [];
|
|
253
|
+
for (const ns of namespaces) {
|
|
254
|
+
const meta = includes.get(ns);
|
|
255
|
+
if (meta === undefined) {
|
|
256
|
+
bad.push(`${ns} (include entry missing)`);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (meta.optional) {
|
|
260
|
+
bad.push(`${ns} (optional: true — check-graph includes must fail loud when file is missing)`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return bad;
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=consumer-gate-integrity.js.map
|
package/dist/check/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
|
|
2
|
+
export { CHECK_GRAPH_REQUIRED_NAMESPACES, CONSUMER_GATE_INTEGRITY_RECOVERY, type ConsumerGateIntegrityFinding, type ConsumerGateIntegrityResult, type ConsumerGateIntegritySeams, checkGraphOptionalIncludeViolations, evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, gateLocalName, gateNamespace, includeTaskfileRel, parseTaskfileIncludes, requiredNamespacesForGates, taskDefinedInTaskfileYaml, } from "./consumer-gate-integrity.js";
|
|
2
3
|
export { type CheckGateSpec, CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, } from "./gate-lists.js";
|
|
3
4
|
export type { CheckOrchestratorOptions, CheckOrchestratorSeams } from "./orchestrator.js";
|
|
4
5
|
export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
|
package/dist/check/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
|
|
2
|
+
export { CHECK_GRAPH_REQUIRED_NAMESPACES, CONSUMER_GATE_INTEGRITY_RECOVERY, checkGraphOptionalIncludeViolations, evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, gateLocalName, gateNamespace, includeTaskfileRel, parseTaskfileIncludes, requiredNamespacesForGates, taskDefinedInTaskfileYaml, } from "./consumer-gate-integrity.js";
|
|
2
3
|
export { CONSUMER_CHECK_GATES, checkGateId, checkGateSpawnArgs, FRAMEWORK_CHECK_GATES, gatesForCheckTarget, } from "./gate-lists.js";
|
|
3
4
|
export { dispatchTaskCheck, isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget, } from "./orchestrator.js";
|
|
4
5
|
export { detectTestRunner, runnerDetectionTable, } from "./runner-detect.js";
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
16
|
import { dispatchCachedTaskCheck } from "./cached-orchestrator.js";
|
|
17
|
+
import { evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, } from "./consumer-gate-integrity.js";
|
|
17
18
|
import { resolveCheckTarget } from "./context.js";
|
|
18
19
|
export { isFrameworkRepoRoot, isFrameworkSourceContext, resolveCheckTarget } from "./context.js";
|
|
19
20
|
/**
|
|
@@ -35,6 +36,15 @@ export function dispatchTaskCheck(frameworkRoot, projectRoot, seams = {}) {
|
|
|
35
36
|
const taskBin = seams.taskBin ?? "task";
|
|
36
37
|
const target = resolveCheckTarget(resolvedFramework, resolvedProject);
|
|
37
38
|
const cwd = target === "check:framework-source" ? resolvedFramework : resolvedProject;
|
|
39
|
+
// #3070: pre-flight consumer check-graph integrity (same path as cached
|
|
40
|
+
// orchestrator) so uncached aggregate shelling also fails with recovery text.
|
|
41
|
+
if (target === "check:consumer") {
|
|
42
|
+
const integrity = evaluateConsumerGateIntegrity(resolvedFramework);
|
|
43
|
+
if (!integrity.ok) {
|
|
44
|
+
process.stderr.write(formatConsumerGateIntegrityFailure(integrity));
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
38
48
|
const spawn = seams.spawnFn ?? defaultSpawn;
|
|
39
49
|
const result = spawn(taskBin, [target, "--taskfile", taskfilePath], {
|
|
40
50
|
cwd,
|
|
@@ -34,6 +34,13 @@ export declare const BODY_AC4_INLINE_SHA_CLEAN: string;
|
|
|
34
34
|
export declare const BODY_AC4_THIRD_CONFIDENCE_FORM: string;
|
|
35
35
|
export declare const BODY_AC4_EMPTY = "";
|
|
36
36
|
export declare const BODY_AC4_TRUNCATED: string;
|
|
37
|
+
/**
|
|
38
|
+
* Fail-closed CLEAN gate shared by pr:watch, swarm poller, and content-contracts.
|
|
39
|
+
*
|
|
40
|
+
* `minConfidence` defaults to the consumer bar (4 == legacy confidence > 3).
|
|
41
|
+
* Directive dogfood and project policy resolve a higher floor via
|
|
42
|
+
* `resolveMinGreptileConfidence` (#3095).
|
|
43
|
+
*/
|
|
37
44
|
export declare function evaluateCleanGate(params: {
|
|
38
45
|
lastReviewedSha: string | null;
|
|
39
46
|
headSha: string;
|
|
@@ -42,6 +49,8 @@ export declare function evaluateCleanGate(params: {
|
|
|
42
49
|
ciFailures: number;
|
|
43
50
|
errored: boolean;
|
|
44
51
|
terminalCheckRun?: boolean;
|
|
52
|
+
/** Minimum confidence score (1–5) that CLEANs; score must be >= min. Default 4. */
|
|
53
|
+
minConfidence?: number;
|
|
45
54
|
}): [boolean, string | null];
|
|
46
55
|
type PollExitClass = "CLEAN" | "NEW_P0P1" | "ERRORED" | "STALL" | "RUNNING";
|
|
47
56
|
export declare function simulatePollLoop(params: {
|
|
@@ -277,15 +277,22 @@ export const BODY_AC4_THIRD_CONFIDENCE_FORM = "Greptile review of head 1234567\n
|
|
|
277
277
|
`Last reviewed commit: [fix: foo](https://github.com/deftai/directive/commit/${HEAD_SHA})\n`;
|
|
278
278
|
export const BODY_AC4_EMPTY = "";
|
|
279
279
|
export const BODY_AC4_TRUNCATED = "Greptile review of head 1234567\n" + "\n" + "## Confidence Score:";
|
|
280
|
+
/**
|
|
281
|
+
* Fail-closed CLEAN gate shared by pr:watch, swarm poller, and content-contracts.
|
|
282
|
+
*
|
|
283
|
+
* `minConfidence` defaults to the consumer bar (4 == legacy confidence > 3).
|
|
284
|
+
* Directive dogfood and project policy resolve a higher floor via
|
|
285
|
+
* `resolveMinGreptileConfidence` (#3095).
|
|
286
|
+
*/
|
|
280
287
|
export function evaluateCleanGate(params) {
|
|
281
|
-
const { lastReviewedSha, headSha, hasBlocking, confidence, ciFailures, errored, terminalCheckRun = true, } = params;
|
|
288
|
+
const { lastReviewedSha, headSha, hasBlocking, confidence, ciFailures, errored, terminalCheckRun = true, minConfidence = 4, } = params;
|
|
282
289
|
if (lastReviewedSha === null || lastReviewedSha !== headSha) {
|
|
283
290
|
return [false, "sha_match"];
|
|
284
291
|
}
|
|
285
292
|
if (hasBlocking) {
|
|
286
293
|
return [false, "has_blocking"];
|
|
287
294
|
}
|
|
288
|
-
if (confidence === null || confidence
|
|
295
|
+
if (confidence === null || confidence < minConfidence) {
|
|
289
296
|
return [false, "confidence"];
|
|
290
297
|
}
|
|
291
298
|
if (ciFailures > 0) {
|
package/dist/doctor/main.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { VBRIEF_VERSION } from "@deftai/directive-types";
|
|
4
4
|
import { evaluate as evaluateAgentsMdAdvisory } from "../agents-md-advisory/evaluate.js";
|
|
5
|
+
import { evaluateConsumerGateIntegrity, formatConsumerGateIntegrityFailure, } from "../check/consumer-gate-integrity.js";
|
|
5
6
|
import { contentRoot } from "../content-root.js";
|
|
6
7
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
7
8
|
import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, DEFT_DIRECTIVE_DISABLE_TRACKED_WARNING, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
|
|
@@ -389,6 +390,11 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
389
390
|
if (!jsonMode) {
|
|
390
391
|
sink.blank();
|
|
391
392
|
}
|
|
393
|
+
sink.info("Checking consumer check-graph integrity (verify:orphan-active and peers)...");
|
|
394
|
+
runConsumerCheckGraphIntegrity(projectRoot, frameworkRoot, sink, addFinding, seams);
|
|
395
|
+
if (!jsonMode) {
|
|
396
|
+
sink.blank();
|
|
397
|
+
}
|
|
392
398
|
sink.info("Checking OpenClaw always-pin skills...");
|
|
393
399
|
runOpenClawSkillPinsCheck(sink, addFinding, {
|
|
394
400
|
frameworkRoot,
|
|
@@ -511,8 +517,11 @@ export function runAgentHooksHealthCheck(projectRoot, consumerContext, sink, add
|
|
|
511
517
|
});
|
|
512
518
|
return false;
|
|
513
519
|
}
|
|
514
|
-
const
|
|
515
|
-
|
|
520
|
+
const codexEnabled = result.registrations.some((entry) => entry.host === "codex" && entry.status !== "disabled");
|
|
521
|
+
const message = `${checkName}: registered and structurally valid` +
|
|
522
|
+
(codexEnabled
|
|
523
|
+
? "; Codex trust is manual-review-required — open `/hooks` to review the project commands"
|
|
524
|
+
: "");
|
|
516
525
|
sink.success(message);
|
|
517
526
|
addFinding({
|
|
518
527
|
severity: "skip",
|
|
@@ -520,8 +529,11 @@ export function runAgentHooksHealthCheck(projectRoot, consumerContext, sink, add
|
|
|
520
529
|
check: checkName,
|
|
521
530
|
status: "registered",
|
|
522
531
|
registrations: result.registrations,
|
|
523
|
-
trust_status: "not-
|
|
524
|
-
trust_review:
|
|
532
|
+
trust_status: codexEnabled ? "manual-review-required" : "not-applicable",
|
|
533
|
+
trust_review: codexEnabled
|
|
534
|
+
? "Open `/hooks` in Codex and review the exact project hook commands."
|
|
535
|
+
: null,
|
|
536
|
+
interception_status: "not-directly-verified",
|
|
525
537
|
});
|
|
526
538
|
return true;
|
|
527
539
|
}
|
|
@@ -537,7 +549,25 @@ export function runAgentHooksLiveProbeCheck(projectRoot, sink, addFinding, seams
|
|
|
537
549
|
const liveCheckName = "agent-hooks-live-probe";
|
|
538
550
|
try {
|
|
539
551
|
const result = (seams.evaluateAgentHooks ?? evaluateAgentHooks)(projectRoot);
|
|
540
|
-
|
|
552
|
+
if (result.code !== 0) {
|
|
553
|
+
const message = `${checkName}: ${result.message.replace(/\s+/g, " ").trim()}`;
|
|
554
|
+
sink.warn(message);
|
|
555
|
+
addFinding({
|
|
556
|
+
severity: "warning",
|
|
557
|
+
message,
|
|
558
|
+
check: checkName,
|
|
559
|
+
status: result.code === 2 ? "unavailable" : "incomplete",
|
|
560
|
+
registrations: result.registrations,
|
|
561
|
+
suggestion: "deft update",
|
|
562
|
+
});
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const enabledHosts = result.registrations
|
|
566
|
+
.filter((entry) => entry.status !== "disabled")
|
|
567
|
+
.map((entry) => entry.host);
|
|
568
|
+
const liveResult = (seams.probeAgentHooksLive ?? probeAgentHooksLive)(projectRoot, {
|
|
569
|
+
hosts: enabledHosts,
|
|
570
|
+
});
|
|
541
571
|
if (liveResult.code !== 0) {
|
|
542
572
|
const message = `${liveCheckName}: ${liveResult.message.replace(/\s+/g, " ").trim()}`;
|
|
543
573
|
sink.warn(message);
|
|
@@ -551,8 +581,12 @@ export function runAgentHooksLiveProbeCheck(projectRoot, sink, addFinding, seams
|
|
|
551
581
|
});
|
|
552
582
|
return;
|
|
553
583
|
}
|
|
554
|
-
const
|
|
555
|
-
|
|
584
|
+
const codexEnabled = result.registrations.some((entry) => entry.host === "codex" && entry.status !== "disabled");
|
|
585
|
+
const message = `${checkName}: registered, structurally valid, and live probe passed` +
|
|
586
|
+
(codexEnabled
|
|
587
|
+
? "; Codex trust is manual-review-required — open `/hooks` to review the project commands"
|
|
588
|
+
: "") +
|
|
589
|
+
"; direct shim invocation does not verify host interception";
|
|
556
590
|
sink.success(message);
|
|
557
591
|
addFinding({
|
|
558
592
|
severity: "skip",
|
|
@@ -560,9 +594,13 @@ export function runAgentHooksLiveProbeCheck(projectRoot, sink, addFinding, seams
|
|
|
560
594
|
check: liveCheckName,
|
|
561
595
|
status: "registered-and-functional",
|
|
562
596
|
registrations: result.registrations,
|
|
563
|
-
trust_status: "not-
|
|
564
|
-
trust_review:
|
|
597
|
+
trust_status: codexEnabled ? "manual-review-required" : "not-applicable",
|
|
598
|
+
trust_review: codexEnabled
|
|
599
|
+
? "Open `/hooks` in Codex and review the exact project hook commands."
|
|
600
|
+
: null,
|
|
601
|
+
interception_status: "not-directly-verified",
|
|
565
602
|
live_probe: "passed",
|
|
603
|
+
live_probe_duration_ms: liveResult.durationMs,
|
|
566
604
|
});
|
|
567
605
|
}
|
|
568
606
|
catch (cause) {
|
|
@@ -763,6 +801,57 @@ function runAgentsMdAdvisoryCheck(projectRoot, sink, addFinding, seams) {
|
|
|
763
801
|
addFinding({ severity: "warning", message, check: checkName });
|
|
764
802
|
}
|
|
765
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* Consumer check-graph integrity (#3070): every CONSUMER_CHECK_GATES entry
|
|
806
|
+
* (including `verify:orphan-active`) must resolve against the deposited
|
|
807
|
+
* Taskfile includes. Missing `tasks/verify.yml` previously failed only at
|
|
808
|
+
* go-task shell time with opaque exit 200/201.
|
|
809
|
+
*/
|
|
810
|
+
function runConsumerCheckGraphIntegrity(projectRoot, frameworkRoot, sink, addFinding, seams) {
|
|
811
|
+
const checkName = "consumer-check-graph-integrity";
|
|
812
|
+
if (runningInsideDeftRepo(projectRoot, seams)) {
|
|
813
|
+
// Still prove the source checkout ships a resolvable consumer gate set.
|
|
814
|
+
const integrity = evaluateConsumerGateIntegrity(frameworkRoot);
|
|
815
|
+
if (integrity.ok) {
|
|
816
|
+
sink.success(`${checkName}: source checkout resolves CONSUMER_CHECK_GATES (incl. verify:orphan-active)`);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const message = formatConsumerGateIntegrityFailure(integrity).trim();
|
|
820
|
+
sink.error(message);
|
|
821
|
+
addFinding({
|
|
822
|
+
severity: "error",
|
|
823
|
+
message,
|
|
824
|
+
check: checkName,
|
|
825
|
+
suggestion: "deft update",
|
|
826
|
+
status: "fail",
|
|
827
|
+
});
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
const depositRoot = frameworkRoot;
|
|
832
|
+
const integrity = evaluateConsumerGateIntegrity(depositRoot);
|
|
833
|
+
if (integrity.ok) {
|
|
834
|
+
sink.success(`${checkName}: deposit resolves CONSUMER_CHECK_GATES (incl. verify:orphan-active)`);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
// Incomplete deposit is an error for consumers — check:consumer would hard-fail.
|
|
838
|
+
const message = formatConsumerGateIntegrityFailure(integrity).trim();
|
|
839
|
+
sink.error(message);
|
|
840
|
+
addFinding({
|
|
841
|
+
severity: "error",
|
|
842
|
+
message,
|
|
843
|
+
check: checkName,
|
|
844
|
+
suggestion: integrity.recovery,
|
|
845
|
+
status: "fail",
|
|
846
|
+
findings: integrity.findings,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
catch (exc) {
|
|
850
|
+
const message = `${checkName}: probe failed -- ${exc instanceof Error ? exc.name : "Error"}: ${exc}`;
|
|
851
|
+
sink.warn(message);
|
|
852
|
+
addFinding({ severity: "warning", message, check: checkName });
|
|
853
|
+
}
|
|
854
|
+
}
|
|
766
855
|
function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams) {
|
|
767
856
|
if (runningInsideDeftRepo(projectRoot, seams)) {
|
|
768
857
|
sink.info("Skipping Taskfile include check -- running inside the deft framework repo (the repo's own Taskfile.yml is the surface).");
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { EngineProbeResult } from "../resolution/classify.js";
|
|
|
4
4
|
import type { ResolutionMode } from "../resolution/index.js";
|
|
5
5
|
import type { ResolveUserMdResult } from "../user-config/resolve-user-md.js";
|
|
6
6
|
import type { AgentHookHealthResult } from "../verify-env/agent-hooks.js";
|
|
7
|
-
import type { AgentHookLiveProbeResult } from "../verify-env/agent-hooks-live-probe.js";
|
|
7
|
+
import type { AgentHookLiveProbeResult, AgentHookLiveProbeSeams } from "../verify-env/agent-hooks-live-probe.js";
|
|
8
8
|
export declare const EXIT_CLEAN = 0;
|
|
9
9
|
export declare const EXIT_DRIFT = 1;
|
|
10
10
|
export declare const EXIT_CONFIG_ERROR = 2;
|
|
@@ -153,7 +153,7 @@ export interface DoctorSeams {
|
|
|
153
153
|
/** Read-only agent-host hook registration probe (#2438). */
|
|
154
154
|
readonly evaluateAgentHooks?: (projectRoot: string) => AgentHookHealthResult;
|
|
155
155
|
/** Live hook spawn probe for doctor --full (#2852). */
|
|
156
|
-
readonly probeAgentHooksLive?: (projectRoot: string) => AgentHookLiveProbeResult;
|
|
156
|
+
readonly probeAgentHooksLive?: (projectRoot: string, seams?: AgentHookLiveProbeSeams) => AgentHookLiveProbeResult;
|
|
157
157
|
/**
|
|
158
158
|
* xBRIEF project-envelope staleness probe (#2971). Injected so doctor can
|
|
159
159
|
* fail closed on 0.6 project JSON under an xbrief/ layout without re-deriving
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session bind of live deposit generation (#3117).
|
|
3
|
+
*
|
|
4
|
+
* Host-agnostic storage:
|
|
5
|
+
* - Default (no sessionId): `.deft/session-bind.json` — single-operator convenience.
|
|
6
|
+
* - With sessionId: `.deft/session-binds/<safeId>.json` — isolated multi-session binds.
|
|
7
|
+
*
|
|
8
|
+
* Multi-agent hosts MUST pass a stable host session identity when binding and
|
|
9
|
+
* reporting so one session cannot certify another as current.
|
|
10
|
+
*/
|
|
11
|
+
import { type BoundGeneration, type LiveGeneration } from "./types.js";
|
|
12
|
+
/** Relative path for the default (no-sessionId) bind record. */
|
|
13
|
+
export declare const SESSION_BIND_REL: string;
|
|
14
|
+
/** Directory for per-session bind records. */
|
|
15
|
+
export declare const SESSION_BINDS_DIR_REL: string;
|
|
16
|
+
export declare function sessionBindPath(projectRoot: string, sessionId?: string | null): string;
|
|
17
|
+
/**
|
|
18
|
+
* Stable filesystem-safe file name for a host session id.
|
|
19
|
+
* Keeps a short prefix for debug, hashes the rest to avoid path injection.
|
|
20
|
+
* Character filter is O(n) (no regex) — CodeQL poly-redos on uncontrolled ids.
|
|
21
|
+
*/
|
|
22
|
+
export declare function safeSessionFileName(sessionId: string): string;
|
|
23
|
+
export interface BindSessionOptions {
|
|
24
|
+
readonly sessionId?: string | null;
|
|
25
|
+
readonly nowIso?: string;
|
|
26
|
+
/**
|
|
27
|
+
* When live generation is missing (legacy deposit), stamp generation 1 from
|
|
28
|
+
* contentVersion before binding. Default true.
|
|
29
|
+
*/
|
|
30
|
+
readonly ensureLive?: boolean;
|
|
31
|
+
/** Used only when ensuring a missing live token. */
|
|
32
|
+
readonly contentVersion?: string;
|
|
33
|
+
readonly stampedBy?: string;
|
|
34
|
+
/**
|
|
35
|
+
* When binding with a sessionId, also write the default bind path.
|
|
36
|
+
* Default **false** — multi-agent isolation requires hosts to report with
|
|
37
|
+
* `--session-id`. Enabling this reopens cross-session false-current (Greptile).
|
|
38
|
+
*/
|
|
39
|
+
readonly alsoWriteDefault?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Host attests that payload surfaces for the live generation were reloaded
|
|
42
|
+
* into the session. Required for trusted readiness. Default false.
|
|
43
|
+
*/
|
|
44
|
+
readonly payloadLoaded?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ReadBoundOptions {
|
|
47
|
+
/** When set, read only that session's bind (never the default). */
|
|
48
|
+
readonly sessionId?: string | null;
|
|
49
|
+
}
|
|
50
|
+
/** Parse a bound generation record (null if invalid). */
|
|
51
|
+
export declare function parseBoundGeneration(raw: unknown): BoundGeneration | null;
|
|
52
|
+
/** Read the session bind record (null when absent/unreadable). */
|
|
53
|
+
export declare function readBoundGeneration(projectRoot: string, options?: ReadBoundOptions): BoundGeneration | null;
|
|
54
|
+
/**
|
|
55
|
+
* Bind the current live generation into session context.
|
|
56
|
+
*
|
|
57
|
+
* Does not require restarting a shared host runtime — callers re-load payload
|
|
58
|
+
* surfaces into the session and call this (or `freshness:bind`) to rebind.
|
|
59
|
+
*
|
|
60
|
+
* Multi-agent hosts MUST supply `sessionId` so binds do not overwrite each other.
|
|
61
|
+
*/
|
|
62
|
+
export declare function bindSessionGeneration(projectRoot: string, options?: BindSessionOptions): {
|
|
63
|
+
bound: BoundGeneration;
|
|
64
|
+
live: LiveGeneration;
|
|
65
|
+
path: string;
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=bind.d.ts.map
|