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