@deftai/directive-core 0.101.0 → 0.103.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 (75) hide show
  1. package/dist/authz/classify.js +97 -8
  2. package/dist/check/cached-orchestrator.d.ts +4 -0
  3. package/dist/check/cached-orchestrator.js +70 -5
  4. package/dist/check/cli-native-gates.d.ts +43 -0
  5. package/dist/check/cli-native-gates.js +84 -0
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.js +10 -0
  9. package/dist/doctor/checks.d.ts +5 -6
  10. package/dist/doctor/checks.js +13 -56
  11. package/dist/eval/later-graduation.d.ts +39 -0
  12. package/dist/eval/later-graduation.js +91 -0
  13. package/dist/eval/report.d.ts +6 -0
  14. package/dist/eval/report.js +27 -8
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/init-deposit/hygiene.d.ts +7 -1
  18. package/dist/init-deposit/hygiene.js +11 -3
  19. package/dist/init-deposit/scaffold.d.ts +12 -0
  20. package/dist/init-deposit/scaffold.js +81 -8
  21. package/dist/intake/issue-ingest.js +42 -0
  22. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  23. package/dist/policy/ceremony-dial-escalation.js +92 -0
  24. package/dist/policy/ceremony-dial.d.ts +3 -1
  25. package/dist/policy/ceremony-dial.js +52 -1
  26. package/dist/policy/check-resume.d.ts +9 -38
  27. package/dist/policy/check-resume.js +18 -156
  28. package/dist/policy/coverage-debt.d.ts +18 -37
  29. package/dist/policy/coverage-debt.js +38 -147
  30. package/dist/policy/index.d.ts +1 -1
  31. package/dist/policy/index.js +1 -1
  32. package/dist/product-first-done-gate/acceptance.js +22 -0
  33. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  34. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  35. package/dist/product-first-done-gate/evaluate.d.ts +40 -2
  36. package/dist/product-first-done-gate/evaluate.js +253 -61
  37. package/dist/product-first-done-gate/index.d.ts +2 -1
  38. package/dist/product-first-done-gate/index.js +2 -1
  39. package/dist/product-first-done-gate/types.d.ts +3 -0
  40. package/dist/product-first-done-gate/types.js +0 -7
  41. package/dist/run-summary/emit.d.ts +11 -1
  42. package/dist/run-summary/emit.js +48 -2
  43. package/dist/run-summary/index.d.ts +2 -1
  44. package/dist/run-summary/index.js +2 -1
  45. package/dist/run-summary/path.d.ts +1 -1
  46. package/dist/run-summary/path.js +1 -1
  47. package/dist/run-summary/share.d.ts +25 -0
  48. package/dist/run-summary/share.js +106 -0
  49. package/dist/run-summary/types.d.ts +60 -2
  50. package/dist/run-summary/types.js +7 -0
  51. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  52. package/dist/scope/acceptance-activate-gate.js +70 -0
  53. package/dist/scope/index.d.ts +1 -0
  54. package/dist/scope/index.js +1 -0
  55. package/dist/scope/transition.js +5 -0
  56. package/dist/session/index.d.ts +0 -1
  57. package/dist/session/index.js +0 -1
  58. package/dist/session/orientation-compression.js +19 -9
  59. package/dist/session/session-start.d.ts +6 -0
  60. package/dist/session/session-start.js +44 -22
  61. package/dist/session/toolchain-preflight.d.ts +15 -0
  62. package/dist/session/toolchain-preflight.js +65 -5
  63. package/dist/verify-ac/clauses.d.ts +51 -0
  64. package/dist/verify-ac/clauses.js +490 -0
  65. package/dist/verify-ac/evaluate.d.ts +73 -0
  66. package/dist/verify-ac/evaluate.js +167 -0
  67. package/dist/verify-ac/flag.d.ts +35 -0
  68. package/dist/verify-ac/flag.js +104 -0
  69. package/dist/verify-ac/index.d.ts +10 -0
  70. package/dist/verify-ac/index.js +10 -0
  71. package/package.json +7 -3
  72. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  73. package/dist/policy/coverage-check-resume-presets.js +0 -228
  74. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  75. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -3,14 +3,19 @@
3
3
  *
4
4
  * Runs plan.acceptance.commands (or #3267 literal ledger) via the shared
5
5
  * literal-acceptance runner. Records source_rung in the result message.
6
- * Project floor with empty commands is a soft pass (suite gates own the floor
7
- * inside full `task check`); standalone done paths still surface the rung.
6
+ * Project floor with empty commands is a soft pass only when a suite floor
7
+ * exists (suite gates own the floor inside full `task check`). With no suite
8
+ * floor, empty resolution is soft_empty — not a green run (#3334).
8
9
  */
9
10
  import { existsSync, readFileSync } from "node:fs";
10
- import { basename, resolve } from "node:path";
11
+ import { basename, isAbsolute, relative, resolve } from "node:path";
11
12
  import { evaluateLiteralAcceptanceFromPlan, runLiteralAcceptanceCommands, } from "../literal-acceptance/index.js";
13
+ import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
12
14
  import { maybeBankOnAcPass } from "../session/ac-pass-banking.js";
15
+ import { formatClauseWalkMessage, walkAcceptanceClauses, } from "../verify-ac/clauses.js";
16
+ import { emitVerifyAcAttempts, evaluateProductOracleIntegrity, mergeOracleVerdict, } from "../verify-ac/evaluate.js";
13
17
  import { readPlanAcceptance, validatePlanAcceptance } from "./acceptance.js";
18
+ import { formatSoftEmptyMessage, isEmptyAcResolution, projectHasSuiteFloor, } from "./empty-resolution.js";
14
19
  function asRecord(value) {
15
20
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
16
21
  return value;
@@ -21,11 +26,16 @@ function asRecord(value) {
21
26
  * Evaluate product AC from an in-memory plan.
22
27
  */
23
28
  export function evaluateVerifyAcFromPlan(plan, options = {}) {
29
+ const planId = typeof plan.id === "string" && plan.id.trim() ? plan.id.trim() : null;
30
+ const optionsWithScope = {
31
+ ...options,
32
+ oracleScopeKey: options.oracleScopeKey?.trim() || planId || null,
33
+ };
24
34
  const acceptance = readPlanAcceptance(plan);
25
35
  const schemaErrors = validatePlanAcceptance(plan.acceptance ?? acceptance);
26
36
  // Only hard-fail schema when an explicit plan.acceptance object exists.
27
37
  if (plan.acceptance !== undefined && schemaErrors.length > 0) {
28
- return {
38
+ return applyOracle({
29
39
  ok: false,
30
40
  code: 2,
31
41
  message: `verify:ac config error (#3284): ${schemaErrors.join("; ")}`,
@@ -34,9 +44,11 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
34
44
  sourceRung: acceptance.source_rung,
35
45
  noneStated: acceptance.none_stated,
36
46
  acceptance,
37
- };
47
+ resolution: "config",
48
+ resolvedCommandCount: 0,
49
+ }, optionsWithScope);
38
50
  }
39
- const projectRoot = resolve(options.projectRoot ?? process.cwd());
51
+ const projectRoot = resolve(optionsWithScope.projectRoot ?? process.cwd());
40
52
  // Prefer shared literal-acceptance path so safety / promotion rules stay one place.
41
53
  // Empty plan.acceptance.commands still consults the #3267 rejected ledger
42
54
  // (Greptile P1: rejected stated AC must never soft-pass).
@@ -44,9 +56,13 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
44
56
  // if the literal ledger is empty of executables.
45
57
  const base = evaluateLiteralAcceptanceFromPlan(plan, {
46
58
  projectRoot,
47
- runner: options.runner,
48
- captureFromNarratives: options.captureFromNarratives,
49
- quiet: options.quiet,
59
+ runner: optionsWithScope.runner,
60
+ // Check composition uses the stamped ledger only. Re-scanning issue prose
61
+ // during `task check` re-captures backtick `verify:ac` lines as rejected
62
+ // and deadlocks the graph (#3323 / #3284 check-integrated).
63
+ captureFromNarratives: optionsWithScope.captureFromNarratives ??
64
+ (optionsWithScope.checkIntegrated === true ? false : undefined),
65
+ quiet: optionsWithScope.quiet,
50
66
  });
51
67
  // When literal path had nothing executable but plan.acceptance has derived commands,
52
68
  // run them directly with source=explicit semantics.
@@ -54,7 +70,7 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
54
70
  base.runs.length === 0 &&
55
71
  acceptance.commands.length > 0 &&
56
72
  (acceptance.source_rung === "derived" || acceptance.source_rung === "project_floor")) {
57
- const runner = options.runner;
73
+ const runner = optionsWithScope.runner;
58
74
  const direct = runLiteralAcceptanceCommands(acceptance.commands.map((c) => ({
59
75
  command: c.command,
60
76
  cwd: c.cwd ?? null,
@@ -62,25 +78,29 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
62
78
  expectedExitCode: c.expectedExitCode ?? 0,
63
79
  source: "explicit",
64
80
  sourceSpan: "plan.acceptance.commands",
65
- })), { projectRoot, runner, allowTaskStatement: options.allowTaskStatement });
66
- return annotate(direct, acceptance, options.quiet);
81
+ })), {
82
+ projectRoot,
83
+ runner,
84
+ allowTaskStatement: optionsWithScope.allowTaskStatement,
85
+ });
86
+ return applyOracle(annotate(direct, acceptance, optionsWithScope.quiet), optionsWithScope);
67
87
  }
68
88
  // Check composition: mid-story unpromoted capture-only may soft-pass so the
69
89
  // framework graph is not deadlocked before agents promote peers.
70
90
  // Greptile P1 #3284: safety-rejected stated commands NEVER soft-pass — they
71
91
  // block product verification until a safe alternative is promoted.
72
- if (options.checkIntegrated === true && !base.ok && base.runs.length === 0) {
92
+ if (optionsWithScope.checkIntegrated === true && !base.ok && base.runs.length === 0) {
73
93
  const hasRejected = (base.rejected?.length ?? 0) > 0;
74
94
  if (hasRejected) {
75
- return annotate(base, acceptance, options.quiet);
95
+ return applyOracle(annotate(base, acceptance, optionsWithScope.quiet), optionsWithScope);
76
96
  }
77
97
  const unpromoted = /capture-only|task_statement|no matching agent-promoted/i.test(base.message) ||
78
98
  (base.commands.length > 0 && base.commands.every((c) => c.source === "task_statement"));
79
99
  if (unpromoted || base.commands.length === 0) {
80
- return {
100
+ return applyOracle({
81
101
  ok: true,
82
102
  code: 0,
83
- message: options.quiet
103
+ message: optionsWithScope.quiet
84
104
  ? ""
85
105
  : `verify:ac advisory (#3284 check-integrated): no executable AC peers yet ` +
86
106
  `(capture-only / empty). Done-gate standalone verify:ac still requires promotion. ` +
@@ -92,10 +112,165 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
92
112
  sourceRung: acceptance.source_rung,
93
113
  noneStated: acceptance.none_stated,
94
114
  acceptance,
95
- };
115
+ resolution: classifyResolution({
116
+ ok: true,
117
+ code: 0,
118
+ runsLength: 0,
119
+ commandCount: base.commands.length,
120
+ rejectedCount: base.rejected?.length ?? 0,
121
+ }),
122
+ resolvedCommandCount: base.commands.length,
123
+ }, optionsWithScope);
124
+ }
125
+ }
126
+ return applyOracle(annotate(base, acceptance, optionsWithScope.quiet), optionsWithScope);
127
+ }
128
+ function classifyResolution(input) {
129
+ if (input.resolution !== undefined) {
130
+ return input.resolution;
131
+ }
132
+ if (input.code === 2) {
133
+ return "config";
134
+ }
135
+ if (!input.ok) {
136
+ return "fail";
137
+ }
138
+ if (input.runsLength > 0) {
139
+ return "verified-pass";
140
+ }
141
+ if (isEmptyAcResolution(input)) {
142
+ return "empty-pass";
143
+ }
144
+ return "empty-pass";
145
+ }
146
+ function applyEmptyFloorPolicy(result, options) {
147
+ if (!isEmptyAcResolution({
148
+ ok: result.ok,
149
+ code: result.code,
150
+ runsLength: result.runs.length,
151
+ commandCount: Math.max(result.commands.length, result.acceptance.commands.length),
152
+ rejectedCount: result.rejected?.length ?? 0,
153
+ resolution: result.resolution,
154
+ })) {
155
+ return result;
156
+ }
157
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
158
+ const suiteFloor = options.hasSuiteFloor ?? projectHasSuiteFloor(projectRoot);
159
+ if (suiteFloor) {
160
+ return {
161
+ ...result,
162
+ resolution: "empty-pass",
163
+ resolvedCommandCount: 0,
164
+ };
165
+ }
166
+ return {
167
+ ...result,
168
+ ok: false,
169
+ code: 1,
170
+ message: formatSoftEmptyMessage(result.acceptance),
171
+ resolution: "soft_empty",
172
+ resolvedCommandCount: 0,
173
+ };
174
+ }
175
+ function acceptanceOutcomeOf(result) {
176
+ if (result.resolution === "verified-pass")
177
+ return "verified-pass";
178
+ if (result.resolution === "empty-pass")
179
+ return "empty-pass";
180
+ if (result.resolution === "soft_empty")
181
+ return "soft_empty";
182
+ if (result.resolution === "fail")
183
+ return "fail";
184
+ return null;
185
+ }
186
+ function emitAcceptanceOutcome(result, options, projectRoot) {
187
+ if (options.env === undefined) {
188
+ return;
189
+ }
190
+ const dest = options.env[ENV_RUN_SUMMARY_PATH];
191
+ if (dest === undefined || dest.trim().length === 0) {
192
+ return;
193
+ }
194
+ const outcome = acceptanceOutcomeOf(result);
195
+ if (outcome === null) {
196
+ return;
197
+ }
198
+ try {
199
+ const emitter = new RunSummaryEmitter({
200
+ projectRoot,
201
+ sessionId: (typeof options.env.DEFT_SESSION_ID === "string" && options.env.DEFT_SESSION_ID.trim()) ||
202
+ "verify-ac",
203
+ env: options.env,
204
+ });
205
+ emitter.emitAcceptance({
206
+ resolved_command_count: result.resolvedCommandCount,
207
+ outcome,
208
+ source_rung: result.sourceRung,
209
+ none_stated: result.noneStated,
210
+ clause_count: result.clauseOutcomes?.length,
211
+ clause_outcomes: result.clauseOutcomes?.map((row) => ({
212
+ id: row.id,
213
+ outcome: row.outcome,
214
+ })),
215
+ });
216
+ }
217
+ catch {
218
+ // fail-open
219
+ }
220
+ }
221
+ function applyClauseWalk(result, options) {
222
+ const clauses = result.acceptance.clauses ?? [];
223
+ if (clauses.length === 0 || result.resolution === "config" || result.resolution === "skipped") {
224
+ return result;
225
+ }
226
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
227
+ const report = walkAcceptanceClauses(clauses, projectRoot);
228
+ const message = options.quiet ? result.message : formatClauseWalkMessage(report, result.message);
229
+ const ok = result.ok && report.ok;
230
+ return {
231
+ ...result,
232
+ ok,
233
+ code: ok ? result.code : result.code === 2 ? 2 : 1,
234
+ message,
235
+ resolution: ok
236
+ ? result.resolution === "empty-pass"
237
+ ? "verified-pass"
238
+ : result.resolution
239
+ : "fail",
240
+ clauseOutcomes: report.clauses,
241
+ clauseWalked: true,
242
+ };
243
+ }
244
+ function applyOracle(result, options) {
245
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
246
+ const walked = applyClauseWalk(result, options);
247
+ const gated = walked.clauseWalked === true ? walked : applyEmptyFloorPolicy(walked, options);
248
+ // Emit/read disk only when the caller supplied env (CLI passes process.env).
249
+ // Tests stay isolated unless they opt in with env or runSummaryText.
250
+ if (options.env !== undefined) {
251
+ emitVerifyAcAttempts({
252
+ projectRoot,
253
+ runs: gated.runs,
254
+ env: options.env,
255
+ scopeKey: options.oracleScopeKey,
256
+ });
257
+ }
258
+ let next = gated;
259
+ if (options.applyOracleIntegrity !== false) {
260
+ const verdict = evaluateProductOracleIntegrity({
261
+ projectRoot,
262
+ runSummaryText: options.runSummaryText,
263
+ env: options.env,
264
+ });
265
+ next = mergeOracleVerdict(gated, verdict);
266
+ if (!verdict.ok && next.resolution !== "soft_empty" && next.resolution !== "config") {
267
+ next = { ...next, resolution: "fail" };
96
268
  }
97
269
  }
98
- return annotate(base, acceptance, options.quiet);
270
+ if (options.skipAcceptanceEmit !== true) {
271
+ emitAcceptanceOutcome(next, options, projectRoot);
272
+ }
273
+ return next;
99
274
  }
100
275
  function annotate(result, acceptance, quiet) {
101
276
  const prefix = result.ok
@@ -116,12 +291,21 @@ function annotate(result, acceptance, quiet) {
116
291
  else if (result.ok) {
117
292
  message = "";
118
293
  }
294
+ const commandCount = Math.max(result.commands.length, acceptance.commands.length);
119
295
  return {
120
296
  ...result,
121
297
  message,
122
298
  sourceRung: acceptance.source_rung,
123
299
  noneStated: acceptance.none_stated,
124
300
  acceptance,
301
+ resolution: classifyResolution({
302
+ ok: result.ok,
303
+ code: result.code,
304
+ runsLength: result.runs.length,
305
+ commandCount,
306
+ rejectedCount: result.rejected?.length ?? 0,
307
+ }),
308
+ resolvedCommandCount: result.runs.length > 0 ? result.runs.length : commandCount,
125
309
  };
126
310
  }
127
311
  /**
@@ -131,18 +315,9 @@ export function evaluateVerifyAcFromPath(xbriefPath, options = {}) {
131
315
  const abs = resolve(xbriefPath);
132
316
  if (!existsSync(abs)) {
133
317
  if (options.softMissingXbrief) {
134
- return softSkip(`xBRIEF not found: ${abs}`, options.quiet);
318
+ return applyOracle(softSkip(`xBRIEF not found: ${abs}`, options.quiet), options);
135
319
  }
136
- return {
137
- ok: false,
138
- code: 2,
139
- message: `verify:ac: xBRIEF not found: ${abs}`,
140
- commands: [],
141
- runs: [],
142
- sourceRung: "project_floor",
143
- noneStated: true,
144
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
145
- };
320
+ return applyOracle(configResult(`verify:ac: xBRIEF not found: ${abs}`), options);
146
321
  }
147
322
  let parsed;
148
323
  try {
@@ -150,45 +325,45 @@ export function evaluateVerifyAcFromPath(xbriefPath, options = {}) {
150
325
  }
151
326
  catch (err) {
152
327
  const msg = err instanceof Error ? err.message : String(err);
153
- return {
154
- ok: false,
155
- code: 2,
156
- message: `verify:ac: unreadable xBRIEF (${msg}): ${abs}`,
157
- commands: [],
158
- runs: [],
159
- sourceRung: "project_floor",
160
- noneStated: true,
161
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
162
- };
328
+ return applyOracle(configResult(`verify:ac: unreadable xBRIEF (${msg}): ${abs}`), options);
163
329
  }
164
330
  const data = asRecord(parsed);
165
331
  if (data === null) {
166
- return {
167
- ok: false,
168
- code: 2,
169
- message: `verify:ac: xBRIEF top-level is not an object: ${abs}`,
170
- commands: [],
171
- runs: [],
172
- sourceRung: "project_floor",
173
- noneStated: true,
174
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
175
- };
332
+ return applyOracle(configResult(`verify:ac: xBRIEF top-level is not an object: ${abs}`), options);
176
333
  }
177
334
  const plan = asRecord(data.plan);
178
335
  if (plan === null) {
179
- return {
180
- ok: false,
181
- code: 2,
182
- message: `verify:ac: xBRIEF missing plan object: ${abs}`,
183
- commands: [],
184
- runs: [],
185
- sourceRung: "project_floor",
186
- noneStated: true,
187
- acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
188
- };
336
+ return applyOracle(configResult(`verify:ac: xBRIEF missing plan object: ${abs}`), options);
337
+ }
338
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
339
+ // Path-relative keys stay unique across xbrief/ vs vbrief/ and duplicate plan.id (#3337 Greptile).
340
+ const oracleScopeKey = options.oracleScopeKey?.trim() || resolveOracleScopeKey(plan, abs, projectRoot);
341
+ const result = evaluateVerifyAcFromPlan(plan, {
342
+ ...options,
343
+ skipAcceptanceEmit: true,
344
+ oracleScopeKey,
345
+ });
346
+ const banked = maybeAttachAcPassBank(result, plan, abs, options);
347
+ emitAcceptanceOutcome(banked, options, projectRoot);
348
+ return banked;
349
+ }
350
+ /**
351
+ * Unique product-oracle scope key for one active xBRIEF path (#3337).
352
+ * Relative path is always unique across active roots; plan.id alone is not
353
+ * (duplicate ids / same stem in xbrief+vbrief). Prefer `id@relPath` when both exist.
354
+ */
355
+ export function resolveOracleScopeKey(plan, xbriefPath, projectRoot) {
356
+ const abs = resolve(xbriefPath);
357
+ const root = resolve(projectRoot);
358
+ let rel = relative(root, abs).replace(/\\/g, "/");
359
+ if (rel.length === 0 || rel.startsWith("..") || isAbsolute(rel)) {
360
+ rel = basename(abs);
361
+ }
362
+ const planId = typeof plan.id === "string" && plan.id.trim() ? plan.id.trim() : null;
363
+ if (planId !== null) {
364
+ return `${planId}@${rel}`;
189
365
  }
190
- const result = evaluateVerifyAcFromPlan(plan, options);
191
- return maybeAttachAcPassBank(result, plan, abs, options);
366
+ return rel;
192
367
  }
193
368
  /**
194
369
  * After executable AC pass, FINALIZE the banking checkpoint (#3285).
@@ -231,11 +406,26 @@ function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
231
406
  ...result,
232
407
  ok: false,
233
408
  code: 1,
409
+ resolution: "fail",
234
410
  message: `verify:ac bank checkpoint failed (#3285): ${msg}` +
235
411
  (result.message ? `\n${result.message}` : ""),
236
412
  };
237
413
  }
238
414
  }
415
+ function configResult(message) {
416
+ return {
417
+ ok: false,
418
+ code: 2,
419
+ message,
420
+ commands: [],
421
+ runs: [],
422
+ sourceRung: "project_floor",
423
+ noneStated: true,
424
+ acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
425
+ resolution: "config",
426
+ resolvedCommandCount: 0,
427
+ };
428
+ }
239
429
  function softSkip(detail, quiet) {
240
430
  return {
241
431
  ok: true,
@@ -246,6 +436,8 @@ function softSkip(detail, quiet) {
246
436
  sourceRung: "project_floor",
247
437
  noneStated: true,
248
438
  acceptance: { commands: [], none_stated: true, source_rung: "project_floor" },
439
+ resolution: "skipped",
440
+ resolvedCommandCount: 0,
249
441
  };
250
442
  }
251
443
  /** Pure: product AC is required at every ceremony depth (#3284 / #3267 / #3156). */
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { attachPlanAcceptance, buildAcceptanceFromIntakeCapture, readPlanAcceptance, stampAcceptanceFromLiteralCapture, validatePlanAcceptance, } from "./acceptance.js";
8
8
  export { applyProductFirstGateMode, isHygieneGate, isProductAcGate, type ProductFirstCheckModeResolution, type ResolveProductFirstCheckModeInput, resolveProductFirstCheckMode, } from "./check-mode.js";
9
- export { type EvaluateVerifyAcOptions, evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, type VerifyAcResult, } from "./evaluate.js";
9
+ export { EMPTY_AC_CAUSE, EMPTY_AC_OUTCOME, EMPTY_AC_REMEDY, formatSoftEmptyMessage, isEmptyAcResolution, isSoftEmptyAcText, projectHasSuiteFloor, type VerifyAcResolution, } from "./empty-resolution.js";
10
+ export { type EvaluateVerifyAcOptions, evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, resolveOracleScopeKey, type VerifyAcResult, } from "./evaluate.js";
10
11
  export { type AcceptanceCommand, type AcSourceRung, ENV_CHECK_AC_ONLY, ENV_CHECK_MODE, ENV_HYGIENE_ADVISORY, HYGIENE_GATE_ID_PREFIXES, PLAN_ACCEPTANCE_KEY, type PlanAcceptance, PRODUCT_AC_GATE_ID, type ProductFirstCheckMode, } from "./types.js";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { attachPlanAcceptance, buildAcceptanceFromIntakeCapture, readPlanAcceptance, stampAcceptanceFromLiteralCapture, validatePlanAcceptance, } from "./acceptance.js";
8
8
  export { applyProductFirstGateMode, isHygieneGate, isProductAcGate, resolveProductFirstCheckMode, } from "./check-mode.js";
9
- export { evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, } from "./evaluate.js";
9
+ export { EMPTY_AC_CAUSE, EMPTY_AC_OUTCOME, EMPTY_AC_REMEDY, formatSoftEmptyMessage, isEmptyAcResolution, isSoftEmptyAcText, projectHasSuiteFloor, } from "./empty-resolution.js";
10
+ export { evaluateVerifyAcFromPath, evaluateVerifyAcFromPlan, isVerifyAcRequiredAtCeremonyDepth, resolveOracleScopeKey, } from "./evaluate.js";
10
11
  export { ENV_CHECK_AC_ONLY, ENV_CHECK_MODE, ENV_HYGIENE_ADVISORY, HYGIENE_GATE_ID_PREFIXES, PLAN_ACCEPTANCE_KEY, PRODUCT_AC_GATE_ID, } from "./types.js";
11
12
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
+ import type { AcceptanceClause } from "../verify-ac/clauses.js";
1
2
  /**
2
3
  * Product-first done-gate (#3284): stated acceptance criteria run FIRST and are
3
4
  * never skippable; hygiene gates run second and may degrade under pressure.
@@ -31,6 +32,8 @@ export interface PlanAcceptance {
31
32
  readonly source_rung: AcSourceRung;
32
33
  /** Why derived/floor was chosen (required for derived when recorded). */
33
34
  readonly derived_reason?: string | null;
35
+ /** Rung-2 independently testable clauses (#3323). Parallel to commands[]. */
36
+ readonly clauses?: readonly AcceptanceClause[];
34
37
  }
35
38
  /** How `task check` composes product vs hygiene gates under pressure (#3284). */
36
39
  export type ProductFirstCheckMode =
@@ -1,10 +1,3 @@
1
- /**
2
- * Product-first done-gate (#3284): stated acceptance criteria run FIRST and are
3
- * never skippable; hygiene gates run second and may degrade under pressure.
4
- *
5
- * Architecture for #3267 (literal acceptance-command mechanism). Composes
6
- * #3214 ceremony dial (rapid = AC-only) and #3266 bank-the-pass.
7
- */
8
1
  /** Canonical product AC gate id used in check gate lists. */
9
2
  export const PRODUCT_AC_GATE_ID = "verify:ac";
10
3
  /** plan.acceptance object key on the plan root. */
@@ -6,7 +6,7 @@
6
6
  * Symlink destinations are refused (no-follow / containment) — fail-open.
7
7
  */
8
8
  import { type ResolveRunSummaryDestinationOptions } from "./path.js";
9
- import { type CheckInvocationRunSummaryPayload, type DialTransitionRunSummaryPayload, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload } from "./types.js";
9
+ import { type AcceptanceRunSummaryPayload, type AcceptanceStampRunSummaryPayload, type CheckInvocationRunSummaryPayload, type DialEscalationEvaluationRunSummaryPayload, type DialTransitionRunSummaryPayload, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, type ToolTurnDenominatorRunSummaryPayload, type VerificationRunSummaryPayload } from "./types.js";
10
10
  export interface RunSummaryEmitterOptions extends ResolveRunSummaryDestinationOptions {
11
11
  readonly projectRoot: string;
12
12
  readonly sessionId: string;
@@ -36,6 +36,7 @@ export declare class RunSummaryEmitter {
36
36
  private readonly frameworkVersion;
37
37
  private readonly now;
38
38
  private readonly destination;
39
+ private readonly env;
39
40
  private readonly writeStdout;
40
41
  private readonly writeStderr;
41
42
  constructor(options: RunSummaryEmitterOptions);
@@ -43,8 +44,17 @@ export declare class RunSummaryEmitter {
43
44
  emit(event: RunSummaryEventKind, payload: RunSummaryPayload): EmitRunSummaryResult;
44
45
  emitSessionStart(payload: SessionStartRunSummaryPayload): EmitRunSummaryResult;
45
46
  emitDialTransition(payload: DialTransitionRunSummaryPayload): EmitRunSummaryResult;
47
+ emitDialEscalationEvaluation(payload: DialEscalationEvaluationRunSummaryPayload): EmitRunSummaryResult;
46
48
  emitCheckInvocation(payload: CheckInvocationRunSummaryPayload): EmitRunSummaryResult;
49
+ emitToolTurnDenominator(payload: ToolTurnDenominatorRunSummaryPayload): EmitRunSummaryResult;
50
+ emitVerification(payload: VerificationRunSummaryPayload): EmitRunSummaryResult;
51
+ emitAcceptance(payload: AcceptanceRunSummaryPayload): EmitRunSummaryResult;
52
+ emitAcceptanceStamp(payload: AcceptanceStampRunSummaryPayload): EmitRunSummaryResult;
53
+ /** Emit the harness-supplied denominator when DEFT_TOTAL_TOOL_TURNS is set. */
54
+ emitKnownToolTurnDenominator(): EmitRunSummaryResult;
47
55
  }
56
+ /** Parse harness-supplied DEFT_TOTAL_TOOL_TURNS (integer > 0). Invalid/unset → omit. */
57
+ export declare function readEnvToolTurnDenominator(env: NodeJS.ProcessEnv): number | undefined;
48
58
  /**
49
59
  * One-shot helper for call sites that do not hold an emitter (e.g. dial escalate).
50
60
  * Still fail-open; creates a fresh seq=1 emitter unless `seqSeed` is passed via reuse.
@@ -11,7 +11,7 @@ import { readCorePackageVersion } from "../engine-version.js";
11
11
  import { containedWrite } from "../fs/contained-write.js";
12
12
  import { assertWriteTargetSafe } from "../fs/projection-containment.js";
13
13
  import { resolveRunSummaryDestination } from "./path.js";
14
- import { RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
14
+ import { ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
15
15
  function sortKeysDeep(value) {
16
16
  if (Array.isArray(value)) {
17
17
  return value.map(sortKeysDeep);
@@ -90,6 +90,7 @@ export class RunSummaryEmitter {
90
90
  frameworkVersion;
91
91
  now;
92
92
  destination;
93
+ env;
93
94
  writeStdout;
94
95
  writeStderr;
95
96
  constructor(options) {
@@ -97,10 +98,11 @@ export class RunSummaryEmitter {
97
98
  this.sessionId = options.sessionId;
98
99
  this.frameworkVersion = options.frameworkVersion ?? readCorePackageVersion();
99
100
  this.now = options.now ?? (() => new Date());
101
+ this.env = options.env ?? process.env;
100
102
  this.destination =
101
103
  options.destination ??
102
104
  resolveRunSummaryDestination(options.projectRoot, {
103
- env: options.env,
105
+ env: this.env,
104
106
  gitignoreCovers: options.gitignoreCovers,
105
107
  });
106
108
  this.writeStdout = options.writeStdout ?? ((line) => process.stdout.write(`${line}\n`));
@@ -115,6 +117,7 @@ export class RunSummaryEmitter {
115
117
  return { emitted: false, destination: this.destination, line: null, warning: false };
116
118
  }
117
119
  this.seq += 1;
120
+ const denominator = readPayloadToolTurnDenominator(payload) ?? readEnvToolTurnDenominator(this.env);
118
121
  const line = {
119
122
  schema_version: RUN_SUMMARY_SCHEMA_VERSION,
120
123
  session_id: this.sessionId,
@@ -123,6 +126,7 @@ export class RunSummaryEmitter {
123
126
  ts: this.now().toISOString(),
124
127
  event,
125
128
  payload,
129
+ ...(denominator !== undefined ? { total_tool_turns: denominator } : {}),
126
130
  };
127
131
  const text = lineToJson(line);
128
132
  if (this.destination.kind === "stdout") {
@@ -156,9 +160,51 @@ export class RunSummaryEmitter {
156
160
  emitDialTransition(payload) {
157
161
  return this.emit("dial_transition", payload);
158
162
  }
163
+ emitDialEscalationEvaluation(payload) {
164
+ return this.emit("dial_escalation_evaluation", payload);
165
+ }
159
166
  emitCheckInvocation(payload) {
160
167
  return this.emit("check_invocation", payload);
161
168
  }
169
+ emitToolTurnDenominator(payload) {
170
+ return this.emit("tool_turn_denominator", payload);
171
+ }
172
+ emitVerification(payload) {
173
+ return this.emit("verification", payload);
174
+ }
175
+ emitAcceptance(payload) {
176
+ return this.emit("acceptance", payload);
177
+ }
178
+ emitAcceptanceStamp(payload) {
179
+ return this.emit("acceptance_stamp", payload);
180
+ }
181
+ /** Emit the harness-supplied denominator when DEFT_TOTAL_TOOL_TURNS is set. */
182
+ emitKnownToolTurnDenominator() {
183
+ const n = readEnvToolTurnDenominator(this.env);
184
+ if (n === undefined) {
185
+ return { emitted: false, destination: this.destination, line: null, warning: false };
186
+ }
187
+ return this.emitToolTurnDenominator({ total_tool_turns: n });
188
+ }
189
+ }
190
+ function readPayloadToolTurnDenominator(payload) {
191
+ if (!("total_tool_turns" in payload)) {
192
+ return undefined;
193
+ }
194
+ const value = payload.total_tool_turns;
195
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
196
+ }
197
+ /** Parse harness-supplied DEFT_TOTAL_TOOL_TURNS (integer > 0). Invalid/unset → omit. */
198
+ export function readEnvToolTurnDenominator(env) {
199
+ const raw = env[ENV_TOTAL_TOOL_TURNS];
200
+ if (raw === undefined || raw.trim().length === 0) {
201
+ return undefined;
202
+ }
203
+ const n = Number(raw.trim());
204
+ if (!Number.isInteger(n) || !Number.isFinite(n) || n <= 0) {
205
+ return undefined;
206
+ }
207
+ return n;
162
208
  }
163
209
  /**
164
210
  * One-shot helper for call sites that do not hold an emitter (e.g. dial escalate).
@@ -1,4 +1,5 @@
1
1
  export { type EmitRunSummaryResult, emitRunSummaryEvent, RunSummaryEmitter, type RunSummaryEmitterOptions, } from "./emit.js";
2
2
  export { gitignoreCoversRunSummary, type ResolveRunSummaryDestinationOptions, resolveRunSummaryDestination, } from "./path.js";
3
- export { type CheckGateOutcome, type CheckInvocationRunSummaryPayload, DEFAULT_RUN_SUMMARY_BASENAME, type DialTransitionRunSummaryPayload, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, type RunSummaryBaseFields, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, } from "./types.js";
3
+ export { computeRitualGateShare, parseRunSummaryJsonl, type RitualGateShare, readToolTurnDenominator, } from "./share.js";
4
+ export { type AcceptanceClauseOutcomeRow, type AcceptanceRunSummaryOutcome, type AcceptanceRunSummaryPayload, type AcceptanceStampRunSummaryPayload, type CheckGateOutcome, type CheckInvocationRunSummaryPayload, DEFAULT_RUN_SUMMARY_BASENAME, type DialEscalationEvaluationOutcome, type DialEscalationEvaluationRunSummaryPayload, type DialTransitionRunSummaryPayload, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, type RunSummaryBaseFields, type RunSummaryDestination, type RunSummaryEventKind, type RunSummaryLine, type RunSummaryPayload, type SessionStartRunSummaryPayload, type ToolTurnDenominatorRunSummaryPayload, type VerificationOutcome, type VerificationRunSummaryPayload, } from "./types.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  export { emitRunSummaryEvent, RunSummaryEmitter, } from "./emit.js";
2
2
  export { gitignoreCoversRunSummary, resolveRunSummaryDestination, } from "./path.js";
3
- export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
3
+ export { computeRitualGateShare, parseRunSummaryJsonl, readToolTurnDenominator, } from "./share.js";
4
+ export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_EVENT_KINDS, RUN_SUMMARY_SCHEMA_VERSION, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -6,7 +6,7 @@
6
6
  * - unset → repo-root `.deft-run-summary.json` only when .gitignore covers it; else silent
7
7
  */
8
8
  import { type RunSummaryDestination } from "./types.js";
9
- export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
9
+ export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
10
10
  /**
11
11
  * True when a .gitignore line covers the default run-summary basename.
12
12
  * Matches exact line or trailing slash variants; ignores comments.
@@ -8,7 +8,7 @@
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { isAbsolute, join, resolve } from "node:path";
10
10
  import { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, } from "./types.js";
11
- export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
11
+ export { DEFAULT_RUN_SUMMARY_BASENAME, ENV_RUN_SUMMARY_PATH, ENV_TOTAL_TOOL_TURNS, RUN_SUMMARY_STDOUT_PREFIX, RUN_SUMMARY_WRITE_WARNING, } from "./types.js";
12
12
  /**
13
13
  * True when a .gitignore line covers the default run-summary basename.
14
14
  * Matches exact line or trailing slash variants; ignores comments.