@deftai/directive-core 0.98.1 → 0.100.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 (144) hide show
  1. package/dist/authz/actions.d.ts +6 -0
  2. package/dist/authz/actions.js +3 -0
  3. package/dist/authz/classify.js +401 -75
  4. package/dist/authz/decompose-apply.d.ts +67 -0
  5. package/dist/authz/decompose-apply.js +302 -0
  6. package/dist/authz/index.d.ts +3 -1
  7. package/dist/authz/index.js +3 -1
  8. package/dist/authz/store.d.ts +53 -0
  9. package/dist/authz/store.js +219 -1
  10. package/dist/authz/types.d.ts +19 -2
  11. package/dist/authz/types.js +5 -1
  12. package/dist/consumer-check-contract/evaluate.d.ts +40 -0
  13. package/dist/consumer-check-contract/evaluate.js +188 -3
  14. package/dist/consumer-check-contract/index.d.ts +1 -1
  15. package/dist/consumer-check-contract/index.js +1 -1
  16. package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
  17. package/dist/content-contracts/skills/greptile-detector.js +202 -4
  18. package/dist/decision/index.d.ts +17 -0
  19. package/dist/decision/index.js +35 -0
  20. package/dist/decision/list.d.ts +47 -0
  21. package/dist/decision/list.js +250 -0
  22. package/dist/decision/schema.d.ts +88 -0
  23. package/dist/decision/schema.js +293 -0
  24. package/dist/decision/write.d.ts +82 -0
  25. package/dist/decision/write.js +427 -0
  26. package/dist/doctor/checks.d.ts +44 -0
  27. package/dist/doctor/checks.js +428 -2
  28. package/dist/doctor/main.d.ts +6 -7
  29. package/dist/doctor/main.js +72 -79
  30. package/dist/doctor/which.d.ts +20 -1
  31. package/dist/doctor/which.js +67 -1
  32. package/dist/eval/report.d.ts +29 -0
  33. package/dist/eval/report.js +69 -0
  34. package/dist/eval/run.d.ts +9 -0
  35. package/dist/eval/run.js +40 -4
  36. package/dist/eval/version-pin.d.ts +99 -0
  37. package/dist/eval/version-pin.js +181 -0
  38. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  39. package/dist/finish-loop/pr-finish-loop.js +116 -5
  40. package/dist/finish-loop/types.d.ts +1 -1
  41. package/dist/hooks/dispatcher.d.ts +9 -5
  42. package/dist/hooks/dispatcher.js +23 -15
  43. package/dist/hooks/readonly.d.ts +6 -3
  44. package/dist/hooks/readonly.js +44 -25
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +2 -0
  47. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  48. package/dist/init-deposit/agent-hooks.js +51 -31
  49. package/dist/intake/issue-ingest.js +29 -0
  50. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  51. package/dist/lifecycle/completed-consistency.js +443 -0
  52. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  53. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  54. package/dist/lifecycle/index.d.ts +2 -0
  55. package/dist/lifecycle/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +52 -0
  57. package/dist/literal-acceptance/capture.js +658 -0
  58. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  59. package/dist/literal-acceptance/evaluate.js +193 -0
  60. package/dist/literal-acceptance/index.d.ts +12 -0
  61. package/dist/literal-acceptance/index.js +12 -0
  62. package/dist/literal-acceptance/run.d.ts +38 -0
  63. package/dist/literal-acceptance/run.js +216 -0
  64. package/dist/literal-acceptance/safety.d.ts +24 -0
  65. package/dist/literal-acceptance/safety.js +251 -0
  66. package/dist/literal-acceptance/types.d.ts +82 -0
  67. package/dist/literal-acceptance/types.js +26 -0
  68. package/dist/platform/host-content-surface.d.ts +74 -0
  69. package/dist/platform/host-content-surface.js +214 -0
  70. package/dist/platform/index.d.ts +1 -0
  71. package/dist/platform/index.js +1 -0
  72. package/dist/policy/ceremony-dial.d.ts +289 -0
  73. package/dist/policy/ceremony-dial.js +980 -0
  74. package/dist/policy/deft-directive-disable.js +12 -2
  75. package/dist/policy/index.d.ts +2 -0
  76. package/dist/policy/index.js +16 -1
  77. package/dist/policy/merge-approval-head.d.ts +129 -0
  78. package/dist/policy/merge-approval-head.js +461 -0
  79. package/dist/policy/require-human-merge.d.ts +5 -0
  80. package/dist/policy/require-human-merge.js +5 -0
  81. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  82. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  83. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  84. package/dist/pr-merge-readiness/compute.js +99 -14
  85. package/dist/pr-merge-readiness/evaluate.js +10 -0
  86. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  87. package/dist/pr-merge-readiness/gh.js +268 -0
  88. package/dist/pr-merge-readiness/index.d.ts +2 -2
  89. package/dist/pr-merge-readiness/index.js +2 -2
  90. package/dist/pr-merge-readiness/mergeability.js +5 -0
  91. package/dist/pr-merge-readiness/output.js +2 -0
  92. package/dist/pr-merge-readiness/parse.js +4 -0
  93. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  94. package/dist/pr-merge-readiness/types.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  96. package/dist/pr-wait-mergeable/cascade.js +115 -1
  97. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  98. package/dist/pr-wait-mergeable/main.js +4 -0
  99. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  100. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  101. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  102. package/dist/preflight/evaluate.d.ts +15 -1
  103. package/dist/preflight/evaluate.js +33 -2
  104. package/dist/render/framework-commands.js +4 -0
  105. package/dist/scope/acceptance-evidence.d.ts +76 -0
  106. package/dist/scope/acceptance-evidence.js +348 -0
  107. package/dist/scope/coverage-map.d.ts +98 -0
  108. package/dist/scope/coverage-map.js +558 -0
  109. package/dist/scope/decompose.js +125 -11
  110. package/dist/scope/effort-activate-gate.d.ts +28 -0
  111. package/dist/scope/effort-activate-gate.js +64 -0
  112. package/dist/scope/index.d.ts +4 -0
  113. package/dist/scope/index.js +4 -0
  114. package/dist/scope/parent-lineage.d.ts +125 -0
  115. package/dist/scope/parent-lineage.js +626 -0
  116. package/dist/scope/transition.d.ts +8 -0
  117. package/dist/scope/transition.js +69 -2
  118. package/dist/session/active-cli.d.ts +79 -0
  119. package/dist/session/active-cli.js +382 -0
  120. package/dist/session/effort-budget.d.ts +130 -0
  121. package/dist/session/effort-budget.js +334 -0
  122. package/dist/session/index.d.ts +2 -0
  123. package/dist/session/index.js +2 -0
  124. package/dist/session/session-start.d.ts +30 -1
  125. package/dist/session/session-start.js +234 -26
  126. package/dist/session/verify-session-ritual.d.ts +14 -0
  127. package/dist/session/verify-session-ritual.js +33 -0
  128. package/dist/story-ready/evaluate.d.ts +11 -0
  129. package/dist/story-ready/evaluate.js +41 -3
  130. package/dist/swarm/index.d.ts +2 -0
  131. package/dist/swarm/index.js +2 -0
  132. package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
  133. package/dist/swarm/pre-dispatch-cli.js +143 -0
  134. package/dist/swarm/pre-dispatch.d.ts +87 -0
  135. package/dist/swarm/pre-dispatch.js +373 -0
  136. package/dist/triage/actions/candidates-log.d.ts +1 -2
  137. package/dist/triage/actions/candidates-log.js +37 -6
  138. package/dist/vbrief-activate/activate.js +6 -0
  139. package/dist/vbrief-validate/constants.d.ts +2 -0
  140. package/dist/vbrief-validate/constants.js +2 -0
  141. package/dist/vbrief-validate/schema.js +4 -1
  142. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  143. package/dist/xbrief-migrate/migrate-project.js +92 -3
  144. package/package.json +19 -3
@@ -0,0 +1,980 @@
1
+ /**
2
+ * Ceremony dial (#3214 / #3263): ritual depth = f(task size × model tier × project shape).
3
+ *
4
+ * Selection policy over existing pieces — not a new subsystem.
5
+ * Composes:
6
+ * - strategies/rapid.md (light / rapid path)
7
+ * - #3014 minimal consumer AGENTS profile (research pointer; not yet a shipped deposit)
8
+ * - effort estimate (#1581), model routing (#1976/#818), host capability (#1461)
9
+ *
10
+ * Two-stage dial (#3214 design note / #1581 ordering) with tier-conditional cold-start (#3263):
11
+ * cold-start / incomplete size → **tier-conditional**:
12
+ * frontier (or unknown tier) → rapid; mid/low → standard (not rapid)
13
+ * escalate when evidence arrives (provisional M/L size, full matrix)
14
+ * Full matrix: S × frontier → rapid; non-project → minimal; else scales up.
15
+ * Override always available via plan.policy.ceremonyDial.override; audited on write.
16
+ *
17
+ * Audit path (#3263): session:start records depth + provisional reasons on
18
+ * `.deft/ritual-state.json` under `ceremony_dial` — use `readCeremonyDialAudit`.
19
+ */
20
+ import { existsSync, readFileSync, statSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
23
+ import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
24
+ import { policyColonInvocation } from "./policy-invocation.js";
25
+ import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
26
+ /** Canonical dotted policy field name. */
27
+ export const FIELD_CEREMONY_DIAL = "plan.policy.ceremonyDial";
28
+ /** Short alias for `policy:show --field=ceremonyDial`. */
29
+ export const FIELD_CEREMONY_DIAL_CLI_ALIAS = "ceremonyDial";
30
+ /** Depth ladder: less ceremony → more ceremony. */
31
+ export const CEREMONY_DEPTHS = ["minimal", "rapid", "standard", "elevated"];
32
+ /** Task size band (aligns with swarm size / effort estimate vocabulary). */
33
+ export const CEREMONY_TASK_SIZES = ["S", "M", "L", "XL"];
34
+ /**
35
+ * Model capability tier for dial selection.
36
+ * `frontier` = strongest class; `mid` benefits from structure; `low` needs more ceremony.
37
+ */
38
+ export const CEREMONY_MODEL_TIERS = ["frontier", "mid", "low"];
39
+ /** Whether the session is project-shaped (Directive repo/work) or ad-hoc non-project. */
40
+ export const CEREMONY_PROJECT_SHAPES = ["project", "non-project"];
41
+ /** Pointers for composition (not loaded as a subsystem — discovery only). */
42
+ export const CEREMONY_RAPID_STRATEGY_POINTER = "content/strategies/rapid.md";
43
+ export const CEREMONY_MINIMAL_AGENTS_PROFILE_POINTER = "docs/analysis/2026-07-31-minimal-consumer-agents-profile-research.md (#3014)";
44
+ /**
45
+ * Profiles scale *cold ceremony* only — verification depth is constant (#3156).
46
+ *
47
+ * ! Do NOT auto-defer gated readiness steps (`doctor`, `cache_fresh`,
48
+ * `agent_hooks`) — those stay required for mutation authorization
49
+ * (`verify:session-ritual --tier=gated`). Gated verify treats
50
+ * `deferred_reason` as satisfied, so dial-driven deferral would skip gates.
51
+ * ! `verify_tools` also remains required on every dial depth (not skipFatPath).
52
+ * ! Literal acceptance-command verification (#3267) is required at every depth
53
+ * (`literalAcceptanceRequired: true`), including rapid/minimal — capture exact
54
+ * stated commands at intake and run them verbatim before done. Extends #973.
55
+ * ~ Rapid/minimal MAY lighten informational cold path only: triage welcome,
56
+ * optional network/release probe, staleness tickler.
57
+ */
58
+ const PROFILES = {
59
+ minimal: {
60
+ depth: "minimal",
61
+ // Cold-path only: never defer gated readiness (Greptile P1 #3214).
62
+ autoDeferSteps: ["triage_welcome"],
63
+ skipFatPath: true,
64
+ lifecycleWrites: "minimal",
65
+ literalAcceptanceRequired: true,
66
+ label: "minimal (non-project / #3014 direction)",
67
+ },
68
+ rapid: {
69
+ depth: "rapid",
70
+ autoDeferSteps: ["triage_welcome"],
71
+ skipFatPath: true,
72
+ lifecycleWrites: "light",
73
+ literalAcceptanceRequired: true,
74
+ label: "rapid (strategies/rapid.md light path)",
75
+ },
76
+ standard: {
77
+ depth: "standard",
78
+ autoDeferSteps: [],
79
+ skipFatPath: false,
80
+ lifecycleWrites: "full",
81
+ literalAcceptanceRequired: true,
82
+ label: "standard (full session ritual)",
83
+ },
84
+ elevated: {
85
+ depth: "elevated",
86
+ autoDeferSteps: [],
87
+ skipFatPath: false,
88
+ lifecycleWrites: "full",
89
+ literalAcceptanceRequired: true,
90
+ label: "elevated (full ritual; prefer more gates for weaker tiers / large tasks)",
91
+ },
92
+ };
93
+ export function ceremonyDialProfile(depth) {
94
+ return PROFILES[depth];
95
+ }
96
+ function compositionFor(depth) {
97
+ return {
98
+ rapidStrategy: depth === "rapid" ? CEREMONY_RAPID_STRATEGY_POINTER : null,
99
+ minimalAgentsProfile: depth === "minimal" ? CEREMONY_MINIMAL_AGENTS_PROFILE_POINTER : null,
100
+ };
101
+ }
102
+ function isCeremonyDepth(value) {
103
+ return typeof value === "string" && CEREMONY_DEPTHS.includes(value);
104
+ }
105
+ function isTaskSize(value) {
106
+ return typeof value === "string" && CEREMONY_TASK_SIZES.includes(value);
107
+ }
108
+ function isModelTier(value) {
109
+ return typeof value === "string" && CEREMONY_MODEL_TIERS.includes(value);
110
+ }
111
+ function isProjectShape(value) {
112
+ return (typeof value === "string" && CEREMONY_PROJECT_SHAPES.includes(value));
113
+ }
114
+ /** Validate a typed `plan.policy.ceremonyDial` payload. */
115
+ export function validateCeremonyDial(value) {
116
+ if (value === null || value === undefined) {
117
+ return [];
118
+ }
119
+ if (typeof value !== "object" || Array.isArray(value)) {
120
+ return [`${FIELD_CEREMONY_DIAL} must be an object; got ${typeof value}`];
121
+ }
122
+ const rec = value;
123
+ const errors = [];
124
+ if ("enabled" in rec && typeof rec.enabled !== "boolean") {
125
+ errors.push(`${FIELD_CEREMONY_DIAL}.enabled must be a boolean`);
126
+ }
127
+ if ("override" in rec && rec.override !== null && rec.override !== undefined) {
128
+ if (!isCeremonyDepth(rec.override)) {
129
+ errors.push(`${FIELD_CEREMONY_DIAL}.override must be one of ${CEREMONY_DEPTHS.join("|")} or null`);
130
+ }
131
+ }
132
+ return errors;
133
+ }
134
+ /**
135
+ * Normalize free-form size tokens (swarm size vocabulary) into dial bands.
136
+ * Accepts S|M|L|XL and small|medium|large|extra-large (case-insensitive).
137
+ */
138
+ export function normalizeCeremonyTaskSize(raw) {
139
+ if (typeof raw !== "string")
140
+ return null;
141
+ const t = raw.trim();
142
+ if (isTaskSize(t))
143
+ return t;
144
+ const low = t.toLowerCase();
145
+ if (low === "s" || low === "small" || low === "xs" || low === "tiny")
146
+ return "S";
147
+ if (low === "m" || low === "medium" || low === "med")
148
+ return "M";
149
+ if (low === "l" || low === "large")
150
+ return "L";
151
+ if (low === "xl" || low === "extra-large" || low === "x-large" || low === "huge")
152
+ return "XL";
153
+ return null;
154
+ }
155
+ /**
156
+ * Normalize model tier tokens. Frontier covers high/strong; mid covers medium;
157
+ * low covers weak/economy.
158
+ */
159
+ export function normalizeCeremonyModelTier(raw) {
160
+ if (typeof raw !== "string")
161
+ return null;
162
+ const t = raw.trim();
163
+ if (isModelTier(t))
164
+ return t;
165
+ const low = t.toLowerCase();
166
+ if (low === "frontier" ||
167
+ low === "high" ||
168
+ low === "strong" ||
169
+ low === "opus" ||
170
+ low === "sota") {
171
+ return "frontier";
172
+ }
173
+ if (low === "mid" || low === "medium" || low === "default" || low === "balanced") {
174
+ return "mid";
175
+ }
176
+ if (low === "low" || low === "weak" || low === "economy" || low === "fast" || low === "cheap") {
177
+ return "low";
178
+ }
179
+ return null;
180
+ }
181
+ export function normalizeCeremonyProjectShape(raw) {
182
+ if (typeof raw !== "string")
183
+ return null;
184
+ const t = raw.trim();
185
+ if (isProjectShape(t))
186
+ return t;
187
+ const low = t.toLowerCase();
188
+ if (low === "project" || low === "repo" || low === "directive")
189
+ return "project";
190
+ if (low === "non-project" ||
191
+ low === "nonproject" ||
192
+ low === "ad-hoc" ||
193
+ low === "adhoc" ||
194
+ low === "scratch") {
195
+ return "non-project";
196
+ }
197
+ return null;
198
+ }
199
+ /**
200
+ * Tier-conditional cold-start depth when task size is incomplete (#3263).
201
+ *
202
+ * Mid/low models benefit from structure on hard tasks and should not cold-start
203
+ * at rapid (start-light / escalate-too-late signature). Frontier recovers from a
204
+ * light start. Unknown tier stays rapid (optimistic cold default; pass
205
+ * `--model-tier` / `DEFT_CEREMONY_MODEL_TIER` to unlock the mid/low floor).
206
+ *
207
+ * Escalate-on-evidence still applies when size later arrives (full matrix).
208
+ */
209
+ export function selectCeremonyColdStartDepth(modelTier) {
210
+ if (modelTier === "mid" || modelTier === "low") {
211
+ return "standard";
212
+ }
213
+ // frontier or unknown → rapid
214
+ return "rapid";
215
+ }
216
+ /**
217
+ * Two-stage / partial-evidence selection when size or tier is missing
218
+ * (#3214 design note / #3263 tier-conditional cold-start).
219
+ *
220
+ * Cold incomplete size is tier-conditional (#3263): mid/low → standard, frontier
221
+ * or unknown → rapid. When size is known without tier, escalate on substantial
222
+ * size (apps-bank safety). Does not invent plan-item effort (#1581 post-planning only).
223
+ */
224
+ export function selectCeremonyDepthFromPartialEvidence(inputs) {
225
+ const size = inputs.taskSize;
226
+ const tier = inputs.modelTier;
227
+ // Size known, tier unknown — escalate on substantial size.
228
+ if (size !== null && tier === null) {
229
+ if (size === "S")
230
+ return "rapid";
231
+ if (size === "M")
232
+ return "standard";
233
+ return "elevated"; // L / XL
234
+ }
235
+ // Size incomplete (null), any tier including unknown: tier-conditional cold-start (#3263).
236
+ // Callers with both size and tier set use the full matrix instead.
237
+ return selectCeremonyColdStartDepth(tier);
238
+ }
239
+ /**
240
+ * Pure default matrix (#3214 acceptance + #3263 tier-conditional cold-start):
241
+ * - non-project → minimal (#3014 direction)
242
+ * - S × frontier → rapid (strategies/rapid.md)
243
+ * - S × mid → standard (mid-tier gains from structure)
244
+ * - S × low → elevated
245
+ * - M × low → elevated; M otherwise → standard
246
+ * - L/XL → elevated (except L × frontier stays standard)
247
+ * - Incomplete size → tier-conditional cold-start (#3263): mid/low → standard; frontier/unknown → rapid
248
+ * - Size known, tier missing → escalate on M/L (partial evidence)
249
+ */
250
+ export function selectCeremonyDepthFromMatrix(inputs) {
251
+ const shape = inputs.projectShape ?? null;
252
+ if (shape === "non-project") {
253
+ return "minimal";
254
+ }
255
+ const size = inputs.taskSize ?? null;
256
+ const tier = inputs.modelTier ?? null;
257
+ // Two-stage dial: incomplete matrix starts light; escalates on partial evidence.
258
+ // ⊗ Do not default incomplete → standard (that was the #1581-ordering anti-pattern).
259
+ if (size === null || tier === null) {
260
+ return selectCeremonyDepthFromPartialEvidence({ taskSize: size, modelTier: tier });
261
+ }
262
+ if (size === "S" && tier === "frontier")
263
+ return "rapid";
264
+ if (size === "S" && tier === "mid")
265
+ return "standard";
266
+ if (size === "S" && tier === "low")
267
+ return "elevated";
268
+ if (size === "M" && tier === "low")
269
+ return "elevated";
270
+ if (size === "M")
271
+ return "standard";
272
+ if (size === "L" && tier === "frontier")
273
+ return "standard";
274
+ return "elevated";
275
+ }
276
+ function parseConfig(raw) {
277
+ const errors = validateCeremonyDial(raw);
278
+ if (errors.length > 0) {
279
+ return {
280
+ config: { enabled: true, override: null },
281
+ error: errors[0] ?? "invalid ceremonyDial",
282
+ };
283
+ }
284
+ if (raw === null || raw === undefined) {
285
+ return { config: { enabled: true, override: null }, error: null };
286
+ }
287
+ if (typeof raw !== "object" || Array.isArray(raw)) {
288
+ return { config: { enabled: true, override: null }, error: null };
289
+ }
290
+ const rec = raw;
291
+ const enabled = typeof rec.enabled === "boolean" ? rec.enabled : true;
292
+ let override = null;
293
+ if (isCeremonyDepth(rec.override)) {
294
+ override = rec.override;
295
+ }
296
+ else if (rec.override === null) {
297
+ override = null;
298
+ }
299
+ return { config: { enabled, override }, error: null };
300
+ }
301
+ /**
302
+ * Deterministic depth selection (pure; no IO).
303
+ *
304
+ * Precedence: disabled → standard; override → forced depth; else matrix on inputs;
305
+ * empty inputs → **rapid** (unknown-tier cold default; #3263 mid/low need modelTier set).
306
+ */
307
+ export function selectCeremonyDepth(options = {}) {
308
+ const config = {
309
+ enabled: options.config?.enabled !== false,
310
+ override: options.config?.override ?? null,
311
+ };
312
+ const inputs = {
313
+ taskSize: options.inputs?.taskSize ?? null,
314
+ modelTier: options.inputs?.modelTier ?? null,
315
+ projectShape: options.inputs?.projectShape ?? null,
316
+ };
317
+ if (config.enabled === false) {
318
+ const depth = "standard";
319
+ return {
320
+ depth,
321
+ source: "disabled",
322
+ inputs,
323
+ profile: ceremonyDialProfile(depth),
324
+ composition: compositionFor(depth),
325
+ config,
326
+ error: null,
327
+ };
328
+ }
329
+ if (config.override !== null && config.override !== undefined) {
330
+ const depth = config.override;
331
+ return {
332
+ depth,
333
+ source: "override",
334
+ inputs,
335
+ profile: ceremonyDialProfile(depth),
336
+ composition: compositionFor(depth),
337
+ config,
338
+ error: null,
339
+ };
340
+ }
341
+ const hasAnyInput = inputs.taskSize !== null || inputs.modelTier !== null || inputs.projectShape !== null;
342
+ if (!hasAnyInput) {
343
+ // Unknown-tier cold default (#3214 / #3263): rapid until modelTier or size arrives.
344
+ const depth = selectCeremonyColdStartDepth(null);
345
+ return {
346
+ depth,
347
+ source: "default",
348
+ inputs,
349
+ profile: ceremonyDialProfile(depth),
350
+ composition: compositionFor(depth),
351
+ config,
352
+ error: null,
353
+ };
354
+ }
355
+ const depth = selectCeremonyDepthFromMatrix(inputs);
356
+ return {
357
+ depth,
358
+ source: "matrix",
359
+ inputs,
360
+ profile: ceremonyDialProfile(depth),
361
+ composition: compositionFor(depth),
362
+ config,
363
+ error: null,
364
+ };
365
+ }
366
+ /**
367
+ * Resolve ceremony dial from PROJECT-DEFINITION policy + session inputs (#3214).
368
+ */
369
+ export function resolveCeremonyDial(projectRoot, options = {}) {
370
+ if (options.config !== undefined) {
371
+ return selectCeremonyDepth({ config: options.config, inputs: options.inputs });
372
+ }
373
+ const [data, err] = loadProjectDefinition(projectRoot);
374
+ if (data === null) {
375
+ const selected = selectCeremonyDepth({ inputs: options.inputs });
376
+ return {
377
+ ...selected,
378
+ source: err ? "default-on-error" : selected.source,
379
+ error: err,
380
+ };
381
+ }
382
+ const policyBlock = readPlanPolicy(data.plan);
383
+ if (typeof policyBlock !== "object" ||
384
+ policyBlock === null ||
385
+ Array.isArray(policyBlock) ||
386
+ !("ceremonyDial" in policyBlock)) {
387
+ return selectCeremonyDepth({ inputs: options.inputs });
388
+ }
389
+ const raw = policyBlock.ceremonyDial;
390
+ const { config, error } = parseConfig(raw);
391
+ if (error !== null) {
392
+ const selected = selectCeremonyDepth({ inputs: options.inputs });
393
+ return { ...selected, source: "default-on-error", error };
394
+ }
395
+ const selected = selectCeremonyDepth({ config, inputs: options.inputs });
396
+ // Preserve typed presence when matrix/default still applies.
397
+ if (selected.source === "default" || selected.source === "matrix") {
398
+ return { ...selected, source: selected.source === "matrix" ? "matrix" : "typed" };
399
+ }
400
+ return selected;
401
+ }
402
+ /** Human-readable status line for session:start / policy:show. */
403
+ export function formatCeremonyDialStatusLine(selection) {
404
+ const parts = [
405
+ `[deft ceremony-dial] depth=${selection.depth}`,
406
+ `source=${selection.source}`,
407
+ `taskSize=${selection.inputs.taskSize ?? "-"}`,
408
+ `modelTier=${selection.inputs.modelTier ?? "-"}`,
409
+ `projectShape=${selection.inputs.projectShape ?? "-"}`,
410
+ ];
411
+ if (selection.composition.rapidStrategy) {
412
+ parts.push(`compose=${selection.composition.rapidStrategy}`);
413
+ }
414
+ if (selection.composition.minimalAgentsProfile) {
415
+ parts.push(`compose=${selection.composition.minimalAgentsProfile}`);
416
+ }
417
+ return parts.join(" ");
418
+ }
419
+ /** JSON-serializable dial snapshot for ritual-state / session payload. */
420
+ export function ceremonyDialToDict(selection) {
421
+ return {
422
+ depth: selection.depth,
423
+ source: selection.source,
424
+ inputs: {
425
+ taskSize: selection.inputs.taskSize,
426
+ modelTier: selection.inputs.modelTier,
427
+ projectShape: selection.inputs.projectShape,
428
+ },
429
+ profile: {
430
+ depth: selection.profile.depth,
431
+ autoDeferSteps: [...selection.profile.autoDeferSteps],
432
+ skipFatPath: selection.profile.skipFatPath,
433
+ lifecycleWrites: selection.profile.lifecycleWrites,
434
+ literalAcceptanceRequired: selection.profile.literalAcceptanceRequired,
435
+ label: selection.profile.label,
436
+ },
437
+ composition: {
438
+ rapidStrategy: selection.composition.rapidStrategy,
439
+ minimalAgentsProfile: selection.composition.minimalAgentsProfile,
440
+ },
441
+ config: {
442
+ enabled: selection.config.enabled !== false,
443
+ override: selection.config.override ?? null,
444
+ },
445
+ error: selection.error,
446
+ };
447
+ }
448
+ const DEFAULT_CONFIG = { enabled: true, override: null };
449
+ /** Inspector row for `task policy:show --field=ceremonyDial`. */
450
+ export function inspectCeremonyDial(data, projectRoot) {
451
+ const defaults = { ...DEFAULT_CONFIG };
452
+ if (data === null) {
453
+ return {
454
+ name: FIELD_CEREMONY_DIAL,
455
+ current: { ...defaults, selectedDepth: "rapid" },
456
+ default: defaults,
457
+ source: "default",
458
+ };
459
+ }
460
+ const policyBlock = readPlanPolicy(data.plan);
461
+ let config = defaults;
462
+ let source = "default";
463
+ let error = null;
464
+ if (typeof policyBlock === "object" &&
465
+ policyBlock !== null &&
466
+ !Array.isArray(policyBlock) &&
467
+ "ceremonyDial" in policyBlock) {
468
+ const parsed = parseConfig(policyBlock.ceremonyDial);
469
+ config = parsed.config;
470
+ error = parsed.error;
471
+ source = parsed.error !== null ? "default-on-error" : "typed";
472
+ }
473
+ const selection = projectRoot !== undefined && projectRoot.length > 0
474
+ ? resolveCeremonyDial(projectRoot)
475
+ : selectCeremonyDepth({ config });
476
+ let fieldSource = source;
477
+ if (error !== null) {
478
+ fieldSource = "default-on-error";
479
+ }
480
+ else if (source === "typed" && selection.source === "override") {
481
+ fieldSource = "override";
482
+ }
483
+ return {
484
+ name: FIELD_CEREMONY_DIAL,
485
+ current: {
486
+ enabled: config.enabled !== false,
487
+ override: config.override ?? null,
488
+ selectedDepth: selection.depth,
489
+ },
490
+ default: defaults,
491
+ source: fieldSource,
492
+ };
493
+ }
494
+ /**
495
+ * Persist plan.policy.ceremonyDial (override + enabled) with audit trail (#3214).
496
+ * Requires --confirm for override writes (capability-cost style disclosure).
497
+ */
498
+ export function setCeremonyDial(projectRoot, options) {
499
+ if (options.confirm !== true) {
500
+ return {
501
+ exitCode: 1,
502
+ stdout: "Ceremony dial changes ritual/gate depth for session:start.\n" +
503
+ " Re-run with --confirm to apply.\n" +
504
+ ` Inspect: ${policyColonInvocation("show", " --field=ceremonyDial")}\n`,
505
+ changed: false,
506
+ };
507
+ }
508
+ const path = projectDefinitionPath(projectRoot);
509
+ try {
510
+ const { changed } = projectDefinitionMutationLock(projectRoot, () => {
511
+ const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
512
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
513
+ throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
514
+ }
515
+ const data = parsed;
516
+ if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
517
+ if (data.plan === undefined) {
518
+ data.plan = {};
519
+ }
520
+ else {
521
+ throw new Error("PROJECT-DEFINITION 'plan' is not an object");
522
+ }
523
+ }
524
+ const plan = data.plan;
525
+ migrateLegacyPolicyKey(plan);
526
+ const existingPolicy = plan[PLAN_POLICY_KEY];
527
+ if (typeof existingPolicy !== "object" ||
528
+ existingPolicy === null ||
529
+ Array.isArray(existingPolicy)) {
530
+ if (existingPolicy === undefined) {
531
+ plan[PLAN_POLICY_KEY] = {};
532
+ }
533
+ else {
534
+ throw new Error("plan.policy is not an object");
535
+ }
536
+ }
537
+ const policyBlock = plan[PLAN_POLICY_KEY];
538
+ const previous = policyBlock.ceremonyDial;
539
+ const prevObj = typeof previous === "object" && previous !== null && !Array.isArray(previous)
540
+ ? previous
541
+ : {};
542
+ const nextEnabled = typeof options.enabled === "boolean"
543
+ ? options.enabled
544
+ : typeof prevObj.enabled === "boolean"
545
+ ? prevObj.enabled
546
+ : true;
547
+ let nextOverride = null;
548
+ if (options.override !== undefined) {
549
+ nextOverride = options.override;
550
+ }
551
+ else if (isCeremonyDepth(prevObj.override)) {
552
+ nextOverride = prevObj.override;
553
+ }
554
+ else if (prevObj.override === null) {
555
+ nextOverride = null;
556
+ }
557
+ const nextBlock = {
558
+ enabled: nextEnabled,
559
+ override: nextOverride,
560
+ };
561
+ const prevParsed = parseConfig(previous).config;
562
+ const changedFlag = prevParsed.enabled !== nextBlock.enabled || prevParsed.override !== nextBlock.override;
563
+ policyBlock.ceremonyDial = nextBlock;
564
+ if (changedFlag) {
565
+ atomicWriteProjectDefinition(path, data);
566
+ }
567
+ const actor = options.actor ?? policyColonInvocation("set-ceremony-dial");
568
+ const note = options.note ?? "";
569
+ const parts = [
570
+ `actor=${actor}`,
571
+ `ceremonyDial.enabled=${String(nextBlock.enabled)}`,
572
+ `ceremonyDial.override=${nextBlock.override ?? "null"}`,
573
+ `previous=${JSON.stringify(previous ?? null)}`,
574
+ ];
575
+ if (note) {
576
+ parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
577
+ }
578
+ appendAuditLog(projectRoot, parts.join(" "));
579
+ return { changed: changedFlag };
580
+ });
581
+ const resolved = resolveCeremonyDial(projectRoot);
582
+ const lines = [
583
+ `\u2713 ${FIELD_CEREMONY_DIAL} updated.`,
584
+ changed
585
+ ? " audit: meta/policy-changes.log updated."
586
+ : " no-op: value already matched (audit entry still appended for trail).",
587
+ formatCeremonyDialStatusLine(resolved),
588
+ ];
589
+ return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
590
+ }
591
+ catch (err) {
592
+ const message = err instanceof Error ? err.message : String(err);
593
+ if (message.includes("PROJECT-DEFINITION not found")) {
594
+ return { exitCode: 2, stdout: `\u274c ${message}\n`, changed: false };
595
+ }
596
+ return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
597
+ }
598
+ }
599
+ /**
600
+ * Merge dial auto-defer steps into an existing deferrals map without clobbering
601
+ * operator-supplied reasons.
602
+ */
603
+ export function mergeCeremonyDialDeferrals(existing, selection) {
604
+ const out = { ...existing };
605
+ const reason = `ceremony-dial depth=${selection.depth}`;
606
+ for (const step of selection.profile.autoDeferSteps) {
607
+ if (!(step in out)) {
608
+ out[step] = reason;
609
+ }
610
+ }
611
+ return out;
612
+ }
613
+ // ---------------------------------------------------------------------------
614
+ // Intake-time provisional estimate (#3214 design note option 1 / #1581 ordering)
615
+ // ---------------------------------------------------------------------------
616
+ /** Env keys for headless dial inputs (no operator confirmation). */
617
+ export const ENV_CEREMONY_TASK_SIZE = "DEFT_CEREMONY_TASK_SIZE";
618
+ export const ENV_CEREMONY_MODEL_TIER = "DEFT_CEREMONY_MODEL_TIER";
619
+ export const ENV_CEREMONY_PROJECT_SHAPE = "DEFT_CEREMONY_PROJECT_SHAPE";
620
+ /** Optional host model hint used when DEFT_CEREMONY_MODEL_TIER is unset. */
621
+ export const ENV_CEREMONY_MODEL_HINT = "DEFT_MODEL";
622
+ const SMALL_VERBS = new Set([
623
+ "fix",
624
+ "typo",
625
+ "docs",
626
+ "doc",
627
+ "chore",
628
+ "lint",
629
+ "format",
630
+ "rename",
631
+ "bump",
632
+ "patch",
633
+ "tweak",
634
+ "nit",
635
+ "spellcheck",
636
+ ]);
637
+ const MEDIUM_VERBS = new Set([
638
+ "implement",
639
+ "add",
640
+ "feature",
641
+ "update",
642
+ "ship",
643
+ "build",
644
+ "wire",
645
+ "land",
646
+ "extend",
647
+ "improve",
648
+ "refine",
649
+ ]);
650
+ const LARGE_VERBS = new Set([
651
+ "refactor",
652
+ "migrate",
653
+ "swarm",
654
+ "epic",
655
+ "redesign",
656
+ "rewrite",
657
+ "overhaul",
658
+ "rearchitect",
659
+ "through-merge",
660
+ "through_merge",
661
+ "port",
662
+ "extract",
663
+ ]);
664
+ const XL_PROMPT_MARKERS = /\b(multi[- ]?repo|platform[- ]wide|full rewrite|architecture overhaul|cohort|through[- ]merge)\b/i;
665
+ const LARGE_PROMPT_MARKERS = /\b(refactor|migrate|swarm|epic|redesign|rearchitect|umbrella|multi[- ]file|large)\b/i;
666
+ const SMALL_PROMPT_MARKERS = /\b(typo|nit|docs? only|one[- ]liner|single[- ]line|trivial|cosmetic|spelling)\b/i;
667
+ /**
668
+ * Detect project shape from deposit layout (headless, no prompts).
669
+ * Returns `project` when vanilla deposit markers exist; **null** when unknown
670
+ * (two-stage cold default stays rapid — do not invent non-project without evidence).
671
+ * Explicit `non-project` comes from CLI/env only.
672
+ */
673
+ export function detectCeremonyProjectShape(projectRoot) {
674
+ try {
675
+ const pd = join(projectRoot, "xbrief", "PROJECT-DEFINITION.xbrief.json");
676
+ if (existsSync(pd) && statSync(pd).isFile()) {
677
+ return "project";
678
+ }
679
+ const agents = join(projectRoot, "AGENTS.md");
680
+ const xbriefDir = join(projectRoot, "xbrief");
681
+ if (existsSync(agents) && existsSync(xbriefDir) && statSync(xbriefDir).isDirectory()) {
682
+ return "project";
683
+ }
684
+ const deftCore = join(projectRoot, ".deft", "core");
685
+ if (existsSync(deftCore) && statSync(deftCore).isDirectory()) {
686
+ return "project";
687
+ }
688
+ }
689
+ catch {
690
+ // unknown on IO errors — stay null so cold default remains rapid
691
+ }
692
+ return null;
693
+ }
694
+ function rankSize(size) {
695
+ switch (size) {
696
+ case "S":
697
+ return 0;
698
+ case "M":
699
+ return 1;
700
+ case "L":
701
+ return 2;
702
+ case "XL":
703
+ return 3;
704
+ default:
705
+ return 0;
706
+ }
707
+ }
708
+ function maxSize(a, b) {
709
+ if (a === null)
710
+ return b;
711
+ if (b === null)
712
+ return a;
713
+ return rankSize(a) >= rankSize(b) ? a : b;
714
+ }
715
+ function sizeFromFileCount(count) {
716
+ if (count <= 0)
717
+ return null;
718
+ if (count <= 2)
719
+ return "S";
720
+ if (count <= 10)
721
+ return "M";
722
+ if (count <= 40)
723
+ return "L";
724
+ return "XL";
725
+ }
726
+ function sizeFromVerb(verb) {
727
+ const v = verb.trim().toLowerCase().replace(/_/g, "-");
728
+ if (v.length === 0)
729
+ return null;
730
+ if (SMALL_VERBS.has(v))
731
+ return "S";
732
+ if (MEDIUM_VERBS.has(v))
733
+ return "M";
734
+ if (LARGE_VERBS.has(v))
735
+ return "L";
736
+ const first = v.split(/[\s:/]+/)[0] ?? "";
737
+ if (SMALL_VERBS.has(first))
738
+ return "S";
739
+ if (MEDIUM_VERBS.has(first))
740
+ return "M";
741
+ if (LARGE_VERBS.has(first))
742
+ return "L";
743
+ return null;
744
+ }
745
+ function sizeFromPrompt(text) {
746
+ const t = text.trim();
747
+ if (t.length === 0)
748
+ return null;
749
+ if (XL_PROMPT_MARKERS.test(t) || t.length > 4000)
750
+ return "XL";
751
+ if (LARGE_PROMPT_MARKERS.test(t) || t.length > 1500)
752
+ return "L";
753
+ if (SMALL_PROMPT_MARKERS.test(t) && t.length < 400)
754
+ return "S";
755
+ if (t.length > 600)
756
+ return "M";
757
+ if (t.length < 120)
758
+ return "S";
759
+ return null;
760
+ }
761
+ /**
762
+ * Headless-safe provisional S/M/L (+ optional tier/shape) **before** ritual.
763
+ * No operator confirmation. Plan-item effort (#1581) later confirms/corrects.
764
+ *
765
+ * Signals (max-wins for size): env → verb class → file-scope count → prompt shape.
766
+ * Project shape: env, else deposit detection when projectRoot is set.
767
+ */
768
+ export function estimateProvisionalCeremonyInputs(hints = {}) {
769
+ const env = hints.env ?? process.env;
770
+ const reasons = [];
771
+ let taskSize = normalizeCeremonyTaskSize(env[ENV_CEREMONY_TASK_SIZE] ?? env.DEFT_TASK_SIZE);
772
+ if (taskSize !== null) {
773
+ reasons.push(`taskSize=${taskSize} from env`);
774
+ }
775
+ const modelTier = normalizeCeremonyModelTier(env[ENV_CEREMONY_MODEL_TIER] ?? env[ENV_CEREMONY_MODEL_HINT]);
776
+ if (modelTier !== null) {
777
+ reasons.push(`modelTier=${modelTier} from env`);
778
+ }
779
+ let projectShape = normalizeCeremonyProjectShape(env[ENV_CEREMONY_PROJECT_SHAPE]);
780
+ if (projectShape !== null) {
781
+ reasons.push(`projectShape=${projectShape} from env`);
782
+ }
783
+ if (taskSize === null && typeof hints.verb === "string") {
784
+ const fromVerb = sizeFromVerb(hints.verb);
785
+ if (fromVerb !== null) {
786
+ taskSize = fromVerb;
787
+ reasons.push(`taskSize=${fromVerb} from verb=${hints.verb.trim()}`);
788
+ }
789
+ }
790
+ {
791
+ let fileCount = typeof hints.fileCount === "number" ? hints.fileCount : null;
792
+ if (fileCount === null && Array.isArray(hints.filePaths)) {
793
+ fileCount = hints.filePaths.length;
794
+ }
795
+ if (fileCount !== null && fileCount > 0) {
796
+ const fromFiles = sizeFromFileCount(fileCount);
797
+ if (fromFiles !== null) {
798
+ const next = maxSize(taskSize, fromFiles);
799
+ if (next !== taskSize) {
800
+ taskSize = next;
801
+ reasons.push(`taskSize=${next} from fileCount=${fileCount}`);
802
+ }
803
+ }
804
+ }
805
+ }
806
+ if (typeof hints.promptText === "string" && hints.promptText.trim().length > 0) {
807
+ const fromPrompt = sizeFromPrompt(hints.promptText);
808
+ if (fromPrompt !== null) {
809
+ const next = maxSize(taskSize, fromPrompt);
810
+ if (next !== taskSize) {
811
+ taskSize = next;
812
+ reasons.push(`taskSize=${next} from prompt shape`);
813
+ }
814
+ }
815
+ }
816
+ if (projectShape === null &&
817
+ typeof hints.projectRoot === "string" &&
818
+ hints.projectRoot.length > 0) {
819
+ const detected = detectCeremonyProjectShape(hints.projectRoot);
820
+ if (detected !== null) {
821
+ projectShape = detected;
822
+ reasons.push(`projectShape=${detected} from deposit layout`);
823
+ }
824
+ }
825
+ return { taskSize, modelTier, projectShape, reasons };
826
+ }
827
+ /**
828
+ * Merge explicit dial inputs over provisional estimates. Explicit non-null wins.
829
+ * Used by session:start so vanilla deposit gets provisional fill without policy opt-in.
830
+ */
831
+ export function mergeCeremonyDialInputsWithProvisional(explicit, provisional) {
832
+ return {
833
+ taskSize: explicit?.taskSize ?? provisional.taskSize ?? null,
834
+ modelTier: explicit?.modelTier ?? provisional.modelTier ?? null,
835
+ projectShape: explicit?.projectShape ?? provisional.projectShape ?? null,
836
+ };
837
+ }
838
+ /**
839
+ * Resolve dial inputs for session:start: explicit CLI/options first, then
840
+ * headless provisional classifier (env / verb / files / prompt / deposit).
841
+ * ⊗ Does not read plan-item effort (#1581) — that lands post-planning only.
842
+ */
843
+ export function resolveSessionCeremonyDialInputs(projectRoot, explicit, options = {}) {
844
+ const provisional = estimateProvisionalCeremonyInputs({
845
+ ...options,
846
+ projectRoot,
847
+ });
848
+ return {
849
+ inputs: mergeCeremonyDialInputsWithProvisional(explicit, provisional),
850
+ provisional,
851
+ };
852
+ }
853
+ function asOptionalDepth(value) {
854
+ return isCeremonyDepth(value) ? value : null;
855
+ }
856
+ function asOptionalTaskSize(value) {
857
+ return isTaskSize(value) ? value : null;
858
+ }
859
+ function asOptionalModelTier(value) {
860
+ return isModelTier(value) ? value : null;
861
+ }
862
+ function asOptionalProjectShape(value) {
863
+ return isProjectShape(value) ? value : null;
864
+ }
865
+ /**
866
+ * Read ceremony dial depth + provisional reasons from ritual-state (#3263 audit path).
867
+ * session:start already writes `ceremony_dial` (incl. provisional) — this is the
868
+ * operator/read path so failed runs can confirm start-light vs escalate timing.
869
+ *
870
+ * Returns `error` when ritual-state is missing/unreadable or lacks `ceremony_dial`;
871
+ * does not throw.
872
+ */
873
+ export function readCeremonyDialAudit(projectRoot) {
874
+ const path = join(projectRoot, ".deft", "ritual-state.json");
875
+ const empty = (error) => ({
876
+ path,
877
+ depth: null,
878
+ source: null,
879
+ inputs: null,
880
+ provisional: null,
881
+ raw: null,
882
+ error,
883
+ });
884
+ try {
885
+ if (!existsSync(path) || !statSync(path).isFile()) {
886
+ return empty(`ritual-state missing at ${path}`);
887
+ }
888
+ }
889
+ catch (exc) {
890
+ return empty(`ritual-state unreadable at ${path}: ${String(exc)}`);
891
+ }
892
+ let payload;
893
+ try {
894
+ payload = JSON.parse(readFileSync(path, { encoding: "utf8" }));
895
+ }
896
+ catch (exc) {
897
+ if (exc instanceof SyntaxError) {
898
+ return empty(`ritual-state is not valid JSON: ${exc.message}`);
899
+ }
900
+ return empty(`ritual-state cannot be read: ${String(exc)}`);
901
+ }
902
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
903
+ return empty("ritual-state top-level value must be an object");
904
+ }
905
+ const root = payload;
906
+ const dialRaw = root.ceremony_dial;
907
+ if (dialRaw === undefined || dialRaw === null) {
908
+ return empty("ritual-state has no ceremony_dial field (pre-#3214 or non-mutation session)");
909
+ }
910
+ if (typeof dialRaw !== "object" || Array.isArray(dialRaw)) {
911
+ return empty("ritual-state ceremony_dial must be an object");
912
+ }
913
+ const dial = dialRaw;
914
+ const depth = asOptionalDepth(dial.depth);
915
+ const source = typeof dial.source === "string" ? dial.source : null;
916
+ let inputs = null;
917
+ const inputsRaw = dial.inputs;
918
+ if (typeof inputsRaw === "object" && inputsRaw !== null && !Array.isArray(inputsRaw)) {
919
+ const rec = inputsRaw;
920
+ inputs = {
921
+ taskSize: asOptionalTaskSize(rec.taskSize),
922
+ modelTier: asOptionalModelTier(rec.modelTier),
923
+ projectShape: asOptionalProjectShape(rec.projectShape),
924
+ };
925
+ }
926
+ let provisional = null;
927
+ const provRaw = dial.provisional;
928
+ if (typeof provRaw === "object" && provRaw !== null && !Array.isArray(provRaw)) {
929
+ const rec = provRaw;
930
+ const reasons = Array.isArray(rec.reasons)
931
+ ? rec.reasons.filter((r) => typeof r === "string")
932
+ : [];
933
+ provisional = {
934
+ taskSize: asOptionalTaskSize(rec.taskSize),
935
+ modelTier: asOptionalModelTier(rec.modelTier),
936
+ projectShape: asOptionalProjectShape(rec.projectShape),
937
+ reasons,
938
+ };
939
+ }
940
+ return {
941
+ path,
942
+ depth,
943
+ source,
944
+ inputs,
945
+ provisional,
946
+ raw: { ...dial },
947
+ error: null,
948
+ };
949
+ }
950
+ /** Collapse whitespace so operator audit lines stay single-line (Greptile P2 #3263). */
951
+ function oneLineAuditToken(value) {
952
+ return value
953
+ .replace(/[\r\n\t]+/g, " ")
954
+ .replace(/ {2,}/g, " ")
955
+ .trim();
956
+ }
957
+ /** One-line operator summary for audit tooling / failed-task forensics (#3263). */
958
+ export function formatCeremonyDialAuditLine(audit) {
959
+ if (audit.error !== null) {
960
+ return `[deft ceremony-dial audit] error=${oneLineAuditToken(audit.error)}`;
961
+ }
962
+ const parts = [
963
+ `[deft ceremony-dial audit] depth=${audit.depth ?? "-"}`,
964
+ `source=${audit.source ?? "-"}`,
965
+ `taskSize=${audit.inputs?.taskSize ?? "-"}`,
966
+ `modelTier=${audit.inputs?.modelTier ?? "-"}`,
967
+ `projectShape=${audit.inputs?.projectShape ?? "-"}`,
968
+ ];
969
+ if (audit.provisional !== null) {
970
+ const reasons = audit.provisional.reasons.length > 0
971
+ ? audit.provisional.reasons
972
+ .map(oneLineAuditToken)
973
+ .filter((r) => r.length > 0)
974
+ .join("; ")
975
+ : "(none)";
976
+ parts.push(`provisional.reasons=${reasons.length > 0 ? reasons : "(none)"}`);
977
+ }
978
+ return parts.join(" ");
979
+ }
980
+ //# sourceMappingURL=ceremony-dial.js.map