@deftai/directive-core 0.92.0 → 0.94.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.
Files changed (111) hide show
  1. package/dist/cache/fetch.d.ts +29 -0
  2. package/dist/cache/fetch.js +131 -4
  3. package/dist/cache/operations.d.ts +8 -0
  4. package/dist/cache/operations.js +67 -4
  5. package/dist/check/cached-orchestrator.js +11 -0
  6. package/dist/check/consumer-gate-integrity.d.ts +68 -0
  7. package/dist/check/consumer-gate-integrity.js +265 -0
  8. package/dist/check/index.d.ts +1 -0
  9. package/dist/check/index.js +1 -0
  10. package/dist/check/orchestrator.js +10 -0
  11. package/dist/content-contracts/skills/greptile-detector.d.ts +9 -0
  12. package/dist/content-contracts/skills/greptile-detector.js +9 -2
  13. package/dist/doctor/index.d.ts +1 -0
  14. package/dist/doctor/index.js +1 -0
  15. package/dist/doctor/main.js +69 -0
  16. package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
  17. package/dist/doctor/openclaw-l2-adapter.js +199 -0
  18. package/dist/finish-loop/pr-finish-loop.js +1 -0
  19. package/dist/hooks/dispatcher.d.ts +24 -2
  20. package/dist/hooks/dispatcher.js +122 -11
  21. package/dist/hooks/readonly.d.ts +14 -0
  22. package/dist/hooks/readonly.js +126 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +2 -0
  25. package/dist/init-deposit/hygiene.js +22 -0
  26. package/dist/init-deposit/index.d.ts +3 -0
  27. package/dist/init-deposit/index.js +3 -0
  28. package/dist/init-deposit/init-deposit.js +10 -0
  29. package/dist/init-deposit/refresh.js +11 -0
  30. package/dist/init-deposit/scaffold.js +3 -133
  31. package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
  32. package/dist/init-deposit/skill-discovery-deposit.js +287 -0
  33. package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
  34. package/dist/init-deposit/skill-discovery-hosts.js +217 -0
  35. package/dist/init-deposit/slash-deposit.d.ts +46 -0
  36. package/dist/init-deposit/slash-deposit.js +174 -0
  37. package/dist/intake/issue-ingest.d.ts +20 -0
  38. package/dist/intake/issue-ingest.js +58 -0
  39. package/dist/policy/host-slash-commands.d.ts +28 -0
  40. package/dist/policy/host-slash-commands.js +103 -0
  41. package/dist/policy/index.d.ts +2 -0
  42. package/dist/policy/index.js +61 -7
  43. package/dist/policy/min-greptile-confidence.d.ts +57 -0
  44. package/dist/policy/min-greptile-confidence.js +123 -0
  45. package/dist/pr-merge-readiness/compute.d.ts +11 -1
  46. package/dist/pr-merge-readiness/compute.js +17 -4
  47. package/dist/pr-merge-readiness/evaluate.d.ts +8 -1
  48. package/dist/pr-merge-readiness/evaluate.js +7 -4
  49. package/dist/pr-merge-readiness/mergeability.d.ts +7 -1
  50. package/dist/pr-merge-readiness/mergeability.js +14 -2
  51. package/dist/pr-monitor/main.d.ts +2 -0
  52. package/dist/pr-monitor/main.js +31 -1
  53. package/dist/pr-monitor/monitor.js +5 -1
  54. package/dist/pr-monitor/readiness.js +3 -1
  55. package/dist/pr-monitor/types.d.ts +8 -0
  56. package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
  57. package/dist/pr-wait-mergeable/cascade.js +28 -1
  58. package/dist/pr-wait-mergeable/main.d.ts +2 -0
  59. package/dist/pr-wait-mergeable/main.js +16 -0
  60. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  61. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  62. package/dist/pr-wait-mergeable/wrappers.js +3 -0
  63. package/dist/pr-watch/constants.d.ts +4 -1
  64. package/dist/pr-watch/constants.js +5 -2
  65. package/dist/pr-watch/main.js +1 -0
  66. package/dist/pr-watch/probe.d.ts +1 -1
  67. package/dist/pr-watch/probe.js +4 -1
  68. package/dist/pr-watch/types.d.ts +7 -1
  69. package/dist/pr-watch/watch.js +3 -2
  70. package/dist/review-monitor/index.d.ts +1 -0
  71. package/dist/review-monitor/index.js +1 -0
  72. package/dist/review-monitor/l4-owner.d.ts +46 -0
  73. package/dist/review-monitor/l4-owner.js +262 -0
  74. package/dist/slash/emitters.d.ts +102 -0
  75. package/dist/slash/emitters.js +148 -0
  76. package/dist/slash/generator.d.ts +98 -0
  77. package/dist/slash/generator.js +145 -0
  78. package/dist/slash/index.d.ts +16 -0
  79. package/dist/slash/index.js +16 -0
  80. package/dist/slash/openclaw-adapter.d.ts +64 -0
  81. package/dist/slash/openclaw-adapter.js +198 -0
  82. package/dist/slash/openclaw-deposit.d.ts +73 -0
  83. package/dist/slash/openclaw-deposit.js +279 -0
  84. package/dist/slash/openclaw-slugs.d.ts +52 -0
  85. package/dist/slash/openclaw-slugs.js +126 -0
  86. package/dist/slash/product-set.d.ts +50 -0
  87. package/dist/slash/product-set.js +142 -0
  88. package/dist/tool-events/classify.js +1 -0
  89. package/dist/triage/welcome/writers.js +16 -7
  90. package/dist/umbrella-current-shape/index.d.ts +45 -0
  91. package/dist/umbrella-current-shape/index.js +162 -8
  92. package/dist/vbrief-reconcile/index.d.ts +2 -2
  93. package/dist/vbrief-reconcile/index.js +1 -1
  94. package/dist/vbrief-reconcile/types.d.ts +14 -0
  95. package/dist/vbrief-reconcile/umbrellas.d.ts +31 -2
  96. package/dist/vbrief-reconcile/umbrellas.js +231 -19
  97. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  98. package/dist/vbrief-validate/plan-hooks.js +50 -0
  99. package/dist/xbrief/create.d.ts +36 -0
  100. package/dist/xbrief/create.js +285 -0
  101. package/dist/xbrief/index.d.ts +14 -0
  102. package/dist/xbrief/index.js +42 -0
  103. package/dist/xbrief/paths.d.ts +37 -0
  104. package/dist/xbrief/paths.js +123 -0
  105. package/dist/xbrief/styles.d.ts +36 -0
  106. package/dist/xbrief/styles.js +235 -0
  107. package/dist/xbrief/types.d.ts +50 -0
  108. package/dist/xbrief/types.js +17 -0
  109. package/dist/xbrief/verify.d.ts +30 -0
  110. package/dist/xbrief/verify.js +251 -0
  111. package/package.json +11 -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
@@ -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";
@@ -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 <= 3) {
295
+ if (confidence === null || confidence < minConfidence) {
289
296
  return [false, "confidence"];
290
297
  }
291
298
  if (ciFailures > 0) {
@@ -7,6 +7,7 @@ export * from "./json.js";
7
7
  export * from "./main.js";
8
8
  export * from "./manifest.js";
9
9
  export * from "./npm-registry.js";
10
+ export * from "./openclaw-l2-adapter.js";
10
11
  export * from "./openclaw-skills.js";
11
12
  export * from "./paths.js";
12
13
  export * from "./payload-staleness.js";
@@ -7,6 +7,7 @@ export * from "./json.js";
7
7
  export * from "./main.js";
8
8
  export * from "./manifest.js";
9
9
  export * from "./npm-registry.js";
10
+ export * from "./openclaw-l2-adapter.js";
10
11
  export * from "./openclaw-skills.js";
11
12
  export * from "./paths.js";
12
13
  export * from "./payload-staleness.js";
@@ -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";
@@ -22,6 +23,7 @@ import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } fro
22
23
  import { pythonJsonDump } from "./json.js";
23
24
  import { parseInstallRootFromAgentsMd } from "./manifest.js";
24
25
  import { runNpmRegistryMirrorCheck } from "./npm-registry.js";
26
+ import { runOpenClawL2AdapterCheck } from "./openclaw-l2-adapter.js";
25
27
  import { runOpenClawSkillPinsCheck } from "./openclaw-skills.js";
26
28
  import { createPlainSink } from "./output.js";
27
29
  import { readTextSafe, resolveFrameworkRootForProject, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
@@ -388,6 +390,11 @@ export function cmdDoctor(args, seams = {}) {
388
390
  if (!jsonMode) {
389
391
  sink.blank();
390
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
+ }
391
398
  sink.info("Checking OpenClaw always-pin skills...");
392
399
  runOpenClawSkillPinsCheck(sink, addFinding, {
393
400
  frameworkRoot,
@@ -409,6 +416,17 @@ export function cmdDoctor(args, seams = {}) {
409
416
  if (!jsonMode) {
410
417
  sink.blank();
411
418
  }
419
+ sink.info("Checking OpenClaw L2 product-command skills...");
420
+ runOpenClawL2AdapterCheck(sink, addFinding, {
421
+ projectRoot,
422
+ fixMode,
423
+ jsonMode,
424
+ allAgents: flags.openclawAllAgents,
425
+ seams,
426
+ });
427
+ if (!jsonMode) {
428
+ sink.blank();
429
+ }
412
430
  sink.info("Checking xBRIEF project envelope version...");
413
431
  runXbriefEnvelopeVersionCheck(projectRoot, sink, addFinding, seams);
414
432
  let resolution = null;
@@ -751,6 +769,57 @@ function runAgentsMdAdvisoryCheck(projectRoot, sink, addFinding, seams) {
751
769
  addFinding({ severity: "warning", message, check: checkName });
752
770
  }
753
771
  }
772
+ /**
773
+ * Consumer check-graph integrity (#3070): every CONSUMER_CHECK_GATES entry
774
+ * (including `verify:orphan-active`) must resolve against the deposited
775
+ * Taskfile includes. Missing `tasks/verify.yml` previously failed only at
776
+ * go-task shell time with opaque exit 200/201.
777
+ */
778
+ function runConsumerCheckGraphIntegrity(projectRoot, frameworkRoot, sink, addFinding, seams) {
779
+ const checkName = "consumer-check-graph-integrity";
780
+ if (runningInsideDeftRepo(projectRoot, seams)) {
781
+ // Still prove the source checkout ships a resolvable consumer gate set.
782
+ const integrity = evaluateConsumerGateIntegrity(frameworkRoot);
783
+ if (integrity.ok) {
784
+ sink.success(`${checkName}: source checkout resolves CONSUMER_CHECK_GATES (incl. verify:orphan-active)`);
785
+ return;
786
+ }
787
+ const message = formatConsumerGateIntegrityFailure(integrity).trim();
788
+ sink.error(message);
789
+ addFinding({
790
+ severity: "error",
791
+ message,
792
+ check: checkName,
793
+ suggestion: "deft update",
794
+ status: "fail",
795
+ });
796
+ return;
797
+ }
798
+ try {
799
+ const depositRoot = frameworkRoot;
800
+ const integrity = evaluateConsumerGateIntegrity(depositRoot);
801
+ if (integrity.ok) {
802
+ sink.success(`${checkName}: deposit resolves CONSUMER_CHECK_GATES (incl. verify:orphan-active)`);
803
+ return;
804
+ }
805
+ // Incomplete deposit is an error for consumers — check:consumer would hard-fail.
806
+ const message = formatConsumerGateIntegrityFailure(integrity).trim();
807
+ sink.error(message);
808
+ addFinding({
809
+ severity: "error",
810
+ message,
811
+ check: checkName,
812
+ suggestion: integrity.recovery,
813
+ status: "fail",
814
+ findings: integrity.findings,
815
+ });
816
+ }
817
+ catch (exc) {
818
+ const message = `${checkName}: probe failed -- ${exc instanceof Error ? exc.name : "Error"}: ${exc}`;
819
+ sink.warn(message);
820
+ addFinding({ severity: "warning", message, check: checkName });
821
+ }
822
+ }
754
823
  function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams) {
755
824
  if (runningInsideDeftRepo(projectRoot, seams)) {
756
825
  sink.info("Skipping Taskfile include check -- running inside the deft framework repo (the repo's own Taskfile.yml is the surface).");
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Doctor check + --fix wire for OpenClaw L2 product-command skills (#3064 D5).
3
+ *
4
+ * Primary operator recovery path when OpenClaw is detected. Deposits thin
5
+ * user-invocable router + 13 product skills into workspace skills roots.
6
+ * Fail-closed when OC signals are absent. Policy opt-out:
7
+ * `plan.policy.openClawProductCommands: false`.
8
+ */
9
+ import { type OpenClawL2DepositResult } from "../slash/openclaw-deposit.js";
10
+ import type { OutputSink } from "./output.js";
11
+ import type { DoctorSeams, Finding } from "./types.js";
12
+ /** Stable doctor check id for JSON findings. */
13
+ export declare const OPENCLAW_L2_ADAPTER_CHECK = "openclaw-l2-product-commands";
14
+ export interface RunOpenClawL2AdapterOptions {
15
+ readonly projectRoot: string;
16
+ readonly fixMode: boolean;
17
+ readonly jsonMode: boolean;
18
+ readonly allAgents: boolean;
19
+ readonly seams?: DoctorSeams;
20
+ }
21
+ /**
22
+ * Doctor check: OpenClaw L2 product-command skills present and current.
23
+ * No-ops when OpenClaw is not detected. Fix mode deposits managed skills.
24
+ */
25
+ export declare function runOpenClawL2AdapterCheck(sink: OutputSink, addFinding: (finding: Finding) => void, options: RunOpenClawL2AdapterOptions): OpenClawL2DepositResult | null;
26
+ //# sourceMappingURL=openclaw-l2-adapter.d.ts.map