@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,130 @@
1
+ /**
2
+ * Hard effort-budget detection and bank-the-pass guidance (#3266).
3
+ *
4
+ * When a host/harness declares max-turns or max-cost, agents must bank the
5
+ * *stated* acceptance pass before self-imposed deepening. Detection is
6
+ * advisory at session start (env / flags / host capability pointer #1461);
7
+ * skill text in deft-directive-build and pre-pr owns the behavioral rule.
8
+ *
9
+ * Composes dual-stop (#2442): dual-stop is the failure/budget envelope;
10
+ * this module is the success-side analog (do not gold-plate past the bar
11
+ * when remaining budget cannot also fund a fix for deeper findings).
12
+ *
13
+ * Fail-loud (#1006): when deepening is skipped for budget, summaries must
14
+ * say so — never silent gold-plate or silent skip.
15
+ */
16
+ /** Canonical max-turns env (hard turn budget). */
17
+ export declare const ENV_MAX_TURNS = "DEFT_MAX_TURNS";
18
+ /** Canonical max cost/budget env (opaque cost units from the host). */
19
+ export declare const ENV_MAX_BUDGET = "DEFT_MAX_BUDGET";
20
+ /** Remaining turns when the host updates mid-run. */
21
+ export declare const ENV_REMAINING_TURNS = "DEFT_REMAINING_TURNS";
22
+ /** Remaining cost units when the host updates mid-run. */
23
+ export declare const ENV_REMAINING_BUDGET = "DEFT_REMAINING_BUDGET";
24
+ /**
25
+ * Explicit hard-cap flag when the host has a limit but does not expose a number
26
+ * (truthy → hard-capped with unknown numeric ceiling).
27
+ */
28
+ export declare const ENV_HARD_BUDGET = "DEFT_HARD_BUDGET";
29
+ /**
30
+ * Optional JSON host capability fragment for effort caps (#1461 / #3266).
31
+ * Example: `{"maxTurns":120,"maxBudget":10,"hardBudget":true}`.
32
+ * Production session:start merges this when `effortBudgetSeams.hostDescriptor` is unset.
33
+ */
34
+ export declare const ENV_HOST_EFFORT_BUDGET = "DEFT_HOST_EFFORT_BUDGET";
35
+ /** Additional env keys accepted as max-turns aliases (harness / CLI common). */
36
+ export declare const MAX_TURNS_ENV_ALIASES: readonly ["DEFT_MAX_TURNS", "MAX_TURNS", "AGENT_MAX_TURNS", "DEFT_AGENT_MAX_TURNS", "CURSOR_MAX_TURNS"];
37
+ /** Additional env keys accepted as max-budget/cost aliases. */
38
+ export declare const MAX_BUDGET_ENV_ALIASES: readonly ["DEFT_MAX_BUDGET", "MAX_BUDGET", "AGENT_BUDGET", "DEFT_AGENT_BUDGET", "AGENT_MAX_BUDGET"];
39
+ export type EffortBudgetKind = "none" | "max-turns" | "max-cost" | "both" | "hard-flag";
40
+ /** Whether the session is under a detectable hard cap. */
41
+ export type EffortBudgetPosture = "unbounded" | "hard-capped";
42
+ /**
43
+ * Recommended self-verification depth given budget + stated-AC status.
44
+ * - stated-only: bank the stated bar; do not start self-imposed suites
45
+ * - stated-then-deepen: stated bar first; deepen only with remaining budget
46
+ * - unconstrained-deepen: no hard budget signal; dual-stop still applies
47
+ */
48
+ export type VerificationDepthPolicy = "stated-only" | "stated-then-deepen" | "unconstrained-deepen";
49
+ export interface HardEffortBudget {
50
+ readonly detected: boolean;
51
+ readonly posture: EffortBudgetPosture;
52
+ readonly kind: EffortBudgetKind;
53
+ /** Hard max turns when known; null if only a flag or cost cap is present. */
54
+ readonly maxTurns: number | null;
55
+ /** Hard max cost units when known (opaque host units). */
56
+ readonly maxBudget: number | null;
57
+ /** Remaining turns when the host exposes them; else equals maxTurns. */
58
+ readonly remainingTurns: number | null;
59
+ /** Remaining cost units when exposed; else equals maxBudget. */
60
+ readonly remainingBudget: number | null;
61
+ /** Env keys / host signals that contributed. */
62
+ readonly sources: readonly string[];
63
+ }
64
+ export interface DetectHardEffortBudgetInput {
65
+ readonly environ?: Readonly<Record<string, string | undefined>>;
66
+ /**
67
+ * Optional host capability descriptor fragment (#1461 pointer).
68
+ * Accepts maxTurns / max_turns / maxBudget / max_budget / hardBudget.
69
+ */
70
+ readonly hostDescriptor?: Readonly<Record<string, unknown>> | null;
71
+ }
72
+ export interface RecommendVerificationDepthInput {
73
+ readonly budget: HardEffortBudget;
74
+ /** Whether stated acceptance criteria (issue/xBRIEF AC) are already met. */
75
+ readonly statedAcceptanceMet: boolean;
76
+ /**
77
+ * Reserve for a fix after a deeper finding (turns). Default 3 — if remaining
78
+ * is below this, do not start self-imposed deepening.
79
+ */
80
+ readonly deepenReserveTurns?: number;
81
+ /** Reserve for a fix after a deeper finding (cost units). Default null (ignore). */
82
+ readonly deepenReserveBudget?: number | null;
83
+ }
84
+ /**
85
+ * Parse host effort-budget descriptor from env JSON (#1461 / #3266 production wire).
86
+ * Returns null when unset or unparseable (fail-open).
87
+ */
88
+ export declare function parseHostEffortBudgetEnv(environ?: Readonly<Record<string, string | undefined>>): Readonly<Record<string, unknown>> | null;
89
+ /**
90
+ * Production host-adapter: translate host/harness native signals into a descriptor
91
+ * object for detectHardEffortBudget (#1461 / #3266).
92
+ *
93
+ * Sources (later keys fill gaps only):
94
+ * 1. `DEFT_HOST_EFFORT_BUDGET` JSON (`maxTurns` / `maxBudget` / `hardBudget`)
95
+ * 2. Common harness env aliases already accepted as max-turns / max-budget
96
+ * 3. Explicit hard-budget flag env
97
+ *
98
+ * session:start CLI and resolveEffortBudget always call this so descriptor-only
99
+ * host caps are not lost when the host never set DEFT_HOST_EFFORT_BUDGET.
100
+ */
101
+ export declare function resolveProductionHostEffortDescriptor(environ?: Readonly<Record<string, string | undefined>>): Readonly<Record<string, unknown>>;
102
+ /**
103
+ * Detect a hard turn/cost budget from env and optional host descriptor (#3266).
104
+ * Defaults to unbounded when no signal is present.
105
+ * When `hostDescriptor` is omitted, falls back to `DEFT_HOST_EFFORT_BUDGET` JSON.
106
+ */
107
+ export declare function detectHardEffortBudget(input?: DetectHardEffortBudgetInput): HardEffortBudget;
108
+ /**
109
+ * Recommend self-verification depth: bank stated AC before deepening (#3266).
110
+ */
111
+ export declare function recommendVerificationDepth(input: RecommendVerificationDepthInput): VerificationDepthPolicy;
112
+ /**
113
+ * Operator-visible note when self-imposed deepening is skipped for budget (#1006).
114
+ */
115
+ export declare function formatDeepeningSkippedNote(budget: HardEffortBudget, reason?: string): string;
116
+ /** Session-start / JSON payload shape. */
117
+ export declare function effortBudgetToDict(budget: HardEffortBudget): Record<string, unknown>;
118
+ /**
119
+ * Format operator-facing effort-budget lines for session:start (#3266).
120
+ * Always emits one summary line; adds bank-the-pass guidance when hard-capped.
121
+ */
122
+ export declare function formatEffortBudgetLines(budget: HardEffortBudget): string[];
123
+ /**
124
+ * Detect + format; fail-open for session-start callers.
125
+ */
126
+ export declare function maybeFormatEffortBudgetLines(input?: DetectHardEffortBudgetInput): {
127
+ budget: HardEffortBudget;
128
+ lines: string[];
129
+ };
130
+ //# sourceMappingURL=effort-budget.d.ts.map
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Hard effort-budget detection and bank-the-pass guidance (#3266).
3
+ *
4
+ * When a host/harness declares max-turns or max-cost, agents must bank the
5
+ * *stated* acceptance pass before self-imposed deepening. Detection is
6
+ * advisory at session start (env / flags / host capability pointer #1461);
7
+ * skill text in deft-directive-build and pre-pr owns the behavioral rule.
8
+ *
9
+ * Composes dual-stop (#2442): dual-stop is the failure/budget envelope;
10
+ * this module is the success-side analog (do not gold-plate past the bar
11
+ * when remaining budget cannot also fund a fix for deeper findings).
12
+ *
13
+ * Fail-loud (#1006): when deepening is skipped for budget, summaries must
14
+ * say so — never silent gold-plate or silent skip.
15
+ */
16
+ /** Canonical max-turns env (hard turn budget). */
17
+ export const ENV_MAX_TURNS = "DEFT_MAX_TURNS";
18
+ /** Canonical max cost/budget env (opaque cost units from the host). */
19
+ export const ENV_MAX_BUDGET = "DEFT_MAX_BUDGET";
20
+ /** Remaining turns when the host updates mid-run. */
21
+ export const ENV_REMAINING_TURNS = "DEFT_REMAINING_TURNS";
22
+ /** Remaining cost units when the host updates mid-run. */
23
+ export const ENV_REMAINING_BUDGET = "DEFT_REMAINING_BUDGET";
24
+ /**
25
+ * Explicit hard-cap flag when the host has a limit but does not expose a number
26
+ * (truthy → hard-capped with unknown numeric ceiling).
27
+ */
28
+ export const ENV_HARD_BUDGET = "DEFT_HARD_BUDGET";
29
+ /**
30
+ * Optional JSON host capability fragment for effort caps (#1461 / #3266).
31
+ * Example: `{"maxTurns":120,"maxBudget":10,"hardBudget":true}`.
32
+ * Production session:start merges this when `effortBudgetSeams.hostDescriptor` is unset.
33
+ */
34
+ export const ENV_HOST_EFFORT_BUDGET = "DEFT_HOST_EFFORT_BUDGET";
35
+ /** Additional env keys accepted as max-turns aliases (harness / CLI common). */
36
+ export const MAX_TURNS_ENV_ALIASES = [
37
+ ENV_MAX_TURNS,
38
+ "MAX_TURNS",
39
+ "AGENT_MAX_TURNS",
40
+ "DEFT_AGENT_MAX_TURNS",
41
+ "CURSOR_MAX_TURNS",
42
+ ];
43
+ /** Additional env keys accepted as max-budget/cost aliases. */
44
+ export const MAX_BUDGET_ENV_ALIASES = [
45
+ ENV_MAX_BUDGET,
46
+ "MAX_BUDGET",
47
+ "AGENT_BUDGET",
48
+ "DEFT_AGENT_BUDGET",
49
+ "AGENT_MAX_BUDGET",
50
+ ];
51
+ const TRUTHY = new Set(["1", "true", "yes", "on"]);
52
+ function envTruthy(value) {
53
+ return TRUTHY.has((value ?? "").trim().toLowerCase());
54
+ }
55
+ function parsePositiveNumber(raw) {
56
+ if (raw === undefined)
57
+ return null;
58
+ const text = raw.trim();
59
+ if (!text)
60
+ return null;
61
+ const n = Number(text);
62
+ if (!Number.isFinite(n) || n < 0)
63
+ return null;
64
+ return n;
65
+ }
66
+ function firstNumericFromEnv(environ, keys) {
67
+ for (const key of keys) {
68
+ const value = parsePositiveNumber(environ[key]);
69
+ if (value !== null) {
70
+ return { value, source: `env:${key}` };
71
+ }
72
+ }
73
+ return { value: null, source: null };
74
+ }
75
+ function hostNumeric(host, keys) {
76
+ if (!host)
77
+ return { value: null, source: null };
78
+ for (const key of keys) {
79
+ const raw = host[key];
80
+ if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) {
81
+ return { value: raw, source: `host:${key}` };
82
+ }
83
+ if (typeof raw === "string") {
84
+ const n = parsePositiveNumber(raw);
85
+ if (n !== null)
86
+ return { value: n, source: `host:${key}` };
87
+ }
88
+ }
89
+ return { value: null, source: null };
90
+ }
91
+ function hostTruthy(host, keys) {
92
+ if (!host)
93
+ return null;
94
+ for (const key of keys) {
95
+ const raw = host[key];
96
+ if (raw === true)
97
+ return `host:${key}`;
98
+ if (typeof raw === "string" && envTruthy(raw))
99
+ return `host:${key}`;
100
+ if (typeof raw === "number" && raw === 1)
101
+ return `host:${key}`;
102
+ }
103
+ return null;
104
+ }
105
+ /**
106
+ * Parse host effort-budget descriptor from env JSON (#1461 / #3266 production wire).
107
+ * Returns null when unset or unparseable (fail-open).
108
+ */
109
+ export function parseHostEffortBudgetEnv(environ = process.env) {
110
+ const raw = (environ[ENV_HOST_EFFORT_BUDGET] ?? "").trim();
111
+ if (!raw)
112
+ return null;
113
+ try {
114
+ const parsed = JSON.parse(raw);
115
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
116
+ return parsed;
117
+ }
118
+ }
119
+ catch {
120
+ // fail-open
121
+ }
122
+ return null;
123
+ }
124
+ /**
125
+ * Production host-adapter: translate host/harness native signals into a descriptor
126
+ * object for detectHardEffortBudget (#1461 / #3266).
127
+ *
128
+ * Sources (later keys fill gaps only):
129
+ * 1. `DEFT_HOST_EFFORT_BUDGET` JSON (`maxTurns` / `maxBudget` / `hardBudget`)
130
+ * 2. Common harness env aliases already accepted as max-turns / max-budget
131
+ * 3. Explicit hard-budget flag env
132
+ *
133
+ * session:start CLI and resolveEffortBudget always call this so descriptor-only
134
+ * host caps are not lost when the host never set DEFT_HOST_EFFORT_BUDGET.
135
+ */
136
+ export function resolveProductionHostEffortDescriptor(environ = process.env) {
137
+ const out = {
138
+ ...(parseHostEffortBudgetEnv(environ) ?? {}),
139
+ };
140
+ if (out.maxTurns === undefined) {
141
+ const turns = firstNumericFromEnv(environ, MAX_TURNS_ENV_ALIASES);
142
+ if (turns.value !== null)
143
+ out.maxTurns = turns.value;
144
+ }
145
+ if (out.maxBudget === undefined) {
146
+ const budget = firstNumericFromEnv(environ, MAX_BUDGET_ENV_ALIASES);
147
+ if (budget.value !== null)
148
+ out.maxBudget = budget.value;
149
+ }
150
+ if (out.hardBudget === undefined && envTruthy(environ[ENV_HARD_BUDGET])) {
151
+ out.hardBudget = true;
152
+ }
153
+ // Remaining counters as soft descriptor fields (detection also reads them).
154
+ if (out.remainingTurns === undefined) {
155
+ const rem = firstNumericFromEnv(environ, [ENV_REMAINING_TURNS, "REMAINING_TURNS"]);
156
+ if (rem.value !== null)
157
+ out.remainingTurns = rem.value;
158
+ }
159
+ if (out.remainingBudget === undefined) {
160
+ const rem = firstNumericFromEnv(environ, [ENV_REMAINING_BUDGET, "REMAINING_BUDGET"]);
161
+ if (rem.value !== null)
162
+ out.remainingBudget = rem.value;
163
+ }
164
+ return out;
165
+ }
166
+ /**
167
+ * Detect a hard turn/cost budget from env and optional host descriptor (#3266).
168
+ * Defaults to unbounded when no signal is present.
169
+ * When `hostDescriptor` is omitted, falls back to `DEFT_HOST_EFFORT_BUDGET` JSON.
170
+ */
171
+ export function detectHardEffortBudget(input = {}) {
172
+ const environ = input.environ ?? process.env;
173
+ const host = input.hostDescriptor !== undefined
174
+ ? (input.hostDescriptor ?? null)
175
+ : parseHostEffortBudgetEnv(environ);
176
+ const sources = [];
177
+ if (host !== null && input.hostDescriptor === undefined) {
178
+ sources.push(`env:${ENV_HOST_EFFORT_BUDGET}`);
179
+ }
180
+ const turnsEnv = firstNumericFromEnv(environ, MAX_TURNS_ENV_ALIASES);
181
+ const budgetEnv = firstNumericFromEnv(environ, MAX_BUDGET_ENV_ALIASES);
182
+ const turnsHost = hostNumeric(host, ["maxTurns", "max_turns", "maxTurnLimit"]);
183
+ const budgetHost = hostNumeric(host, ["maxBudget", "max_budget", "maxCost", "max_cost"]);
184
+ // Prefer explicit DEFT_* then first alias; host fills gaps only.
185
+ const maxTurns = turnsEnv.value ?? turnsHost.value;
186
+ const maxBudget = budgetEnv.value ?? budgetHost.value;
187
+ if (turnsEnv.source)
188
+ sources.push(turnsEnv.source);
189
+ else if (turnsHost.source)
190
+ sources.push(turnsHost.source);
191
+ if (budgetEnv.source)
192
+ sources.push(budgetEnv.source);
193
+ else if (budgetHost.source)
194
+ sources.push(budgetHost.source);
195
+ const remainingTurnsEnv = firstNumericFromEnv(environ, [ENV_REMAINING_TURNS, "REMAINING_TURNS"]);
196
+ const remainingBudgetEnv = firstNumericFromEnv(environ, [
197
+ ENV_REMAINING_BUDGET,
198
+ "REMAINING_BUDGET",
199
+ ]);
200
+ if (remainingTurnsEnv.source)
201
+ sources.push(remainingTurnsEnv.source);
202
+ if (remainingBudgetEnv.source)
203
+ sources.push(remainingBudgetEnv.source);
204
+ const remainingTurns = remainingTurnsEnv.value ?? maxTurns;
205
+ const remainingBudget = remainingBudgetEnv.value ?? maxBudget;
206
+ let hardFlag = false;
207
+ if (envTruthy(environ[ENV_HARD_BUDGET])) {
208
+ hardFlag = true;
209
+ sources.push(`env:${ENV_HARD_BUDGET}`);
210
+ }
211
+ else {
212
+ const hostFlag = hostTruthy(host, ["hardBudget", "hard_budget", "hasHardBudget"]);
213
+ if (hostFlag) {
214
+ hardFlag = true;
215
+ sources.push(hostFlag);
216
+ }
217
+ }
218
+ // Remaining-only signals still imply a hard cap even when max is unknown (#3266 Greptile).
219
+ const hasTurns = maxTurns !== null || remainingTurnsEnv.value !== null;
220
+ const hasCost = maxBudget !== null || remainingBudgetEnv.value !== null;
221
+ let kind = "none";
222
+ if (hasTurns && hasCost)
223
+ kind = "both";
224
+ else if (hasTurns)
225
+ kind = "max-turns";
226
+ else if (hasCost)
227
+ kind = "max-cost";
228
+ else if (hardFlag)
229
+ kind = "hard-flag";
230
+ const detected = kind !== "none";
231
+ return {
232
+ detected,
233
+ posture: detected ? "hard-capped" : "unbounded",
234
+ kind,
235
+ maxTurns,
236
+ maxBudget,
237
+ remainingTurns: detected ? remainingTurns : null,
238
+ remainingBudget: detected ? remainingBudget : null,
239
+ sources,
240
+ };
241
+ }
242
+ /**
243
+ * Recommend self-verification depth: bank stated AC before deepening (#3266).
244
+ */
245
+ export function recommendVerificationDepth(input) {
246
+ const { budget, statedAcceptanceMet } = input;
247
+ if (!budget.detected) {
248
+ return "unconstrained-deepen";
249
+ }
250
+ if (!statedAcceptanceMet) {
251
+ // Hard budget + AC open → only work that banks the stated pass.
252
+ return "stated-only";
253
+ }
254
+ const reserveTurns = input.deepenReserveTurns ?? 3;
255
+ const reserveBudget = input.deepenReserveBudget ?? null;
256
+ const turnsOk = budget.remainingTurns === null || budget.remainingTurns >= reserveTurns;
257
+ const costOk = reserveBudget === null ||
258
+ budget.remainingBudget === null ||
259
+ budget.remainingBudget >= reserveBudget;
260
+ // hard-flag with no numbers: after AC is met, allow stated-then-deepen only
261
+ // when remaining is unknown — agent still must fail-loud if it skips.
262
+ if (budget.kind === "hard-flag") {
263
+ return "stated-then-deepen";
264
+ }
265
+ if (turnsOk && costOk) {
266
+ return "stated-then-deepen";
267
+ }
268
+ return "stated-only";
269
+ }
270
+ /**
271
+ * Operator-visible note when self-imposed deepening is skipped for budget (#1006).
272
+ */
273
+ export function formatDeepeningSkippedNote(budget, reason) {
274
+ const detail = reason?.trim() ||
275
+ "remaining budget is insufficient to both deepen verification and fix a found defect";
276
+ const sources = budget.sources.length > 0 ? ` sources=${budget.sources.join(",")}` : "";
277
+ const rem = budget.remainingTurns !== null
278
+ ? ` remaining_turns=${budget.remainingTurns}`
279
+ : budget.remainingBudget !== null
280
+ ? ` remaining_budget=${budget.remainingBudget}`
281
+ : "";
282
+ return (`[deft effort-budget] deepening_skipped=true reason=${detail}` +
283
+ ` posture=${budget.posture} kind=${budget.kind}${rem}${sources} (#3266/#1006)`);
284
+ }
285
+ /** Session-start / JSON payload shape. */
286
+ export function effortBudgetToDict(budget) {
287
+ return {
288
+ detected: budget.detected,
289
+ posture: budget.posture,
290
+ kind: budget.kind,
291
+ max_turns: budget.maxTurns,
292
+ max_budget: budget.maxBudget,
293
+ remaining_turns: budget.remainingTurns,
294
+ remaining_budget: budget.remainingBudget,
295
+ sources: [...budget.sources],
296
+ };
297
+ }
298
+ /**
299
+ * Format operator-facing effort-budget lines for session:start (#3266).
300
+ * Always emits one summary line; adds bank-the-pass guidance when hard-capped.
301
+ */
302
+ export function formatEffortBudgetLines(budget) {
303
+ if (!budget.detected) {
304
+ return [
305
+ "[deft effort-budget] posture=unbounded — no hard max-turns/max-budget signal " +
306
+ `(env ${ENV_MAX_TURNS}/${ENV_MAX_BUDGET} or host descriptor) (#3266)`,
307
+ ];
308
+ }
309
+ const parts = [`[deft effort-budget] posture=${budget.posture}`, `kind=${budget.kind}`];
310
+ if (budget.maxTurns !== null)
311
+ parts.push(`max_turns=${budget.maxTurns}`);
312
+ if (budget.remainingTurns !== null && budget.remainingTurns !== budget.maxTurns) {
313
+ parts.push(`remaining_turns=${budget.remainingTurns}`);
314
+ }
315
+ if (budget.maxBudget !== null)
316
+ parts.push(`max_budget=${budget.maxBudget}`);
317
+ if (budget.remainingBudget !== null && budget.remainingBudget !== budget.maxBudget) {
318
+ parts.push(`remaining_budget=${budget.remainingBudget}`);
319
+ }
320
+ if (budget.sources.length > 0)
321
+ parts.push(`sources=${budget.sources.join(",")}`);
322
+ const lines = [`${parts.join(" ")} (#3266)`];
323
+ lines.push("[deft effort-budget] bank stated acceptance pass before self-imposed deepening; " +
324
+ "scale verification depth with remaining budget; fail-loud when deepening is skipped (#3266/#1006)");
325
+ return lines;
326
+ }
327
+ /**
328
+ * Detect + format; fail-open for session-start callers.
329
+ */
330
+ export function maybeFormatEffortBudgetLines(input = {}) {
331
+ const budget = detectHardEffortBudget(input);
332
+ return { budget, lines: formatEffortBudgetLines(budget) };
333
+ }
334
+ //# sourceMappingURL=effort-budget.js.map
@@ -1,5 +1,7 @@
1
+ export * from "./active-cli.js";
1
2
  export * from "./compact-ritual.js";
2
3
  export * from "./coverage-check-resume-nudge.js";
4
+ export * from "./effort-budget.js";
3
5
  export * from "./git.js";
4
6
  export * from "./json.js";
5
7
  export * from "./openclaw-soft-rebind-deposit.js";
@@ -1,5 +1,7 @@
1
+ export * from "./active-cli.js";
1
2
  export * from "./compact-ritual.js";
2
3
  export * from "./coverage-check-resume-nudge.js";
4
+ export * from "./effort-budget.js";
3
5
  export * from "./git.js";
4
6
  export * from "./json.js";
5
7
  export * from "./openclaw-soft-rebind-deposit.js";
@@ -1,6 +1,9 @@
1
+ import { type HostContentSurfaceSeams } from "../platform/host-content-surface.js";
1
2
  import { type EnvironmentContext } from "../platform/shell-context.js";
3
+ import { type CeremonyDialInputs, type CeremonyDialSelection, type ProvisionalCeremonyEstimateHints } from "../policy/ceremony-dial.js";
2
4
  import { type ProbeScmReadinessOptions, type ScmReadinessReport } from "../scm/readiness.js";
3
5
  import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
6
+ import { type DetectHardEffortBudgetInput } from "./effort-budget.js";
4
7
  import type { GitRunner } from "./git.js";
5
8
  import { type ReleaseAvailabilityProbeOptions } from "./release-availability.js";
6
9
  import { type RitualState, ritualStatePath } from "./ritual-sentinel.js";
@@ -15,7 +18,7 @@ export declare const SESSION_CEREMONY_TIERS: readonly ["cold", "rearm"];
15
18
  export type SessionCeremonyTier = (typeof SESSION_CEREMONY_TIERS)[number];
16
19
  export declare const COLD_CEREMONY_TIER: SessionCeremonyTier;
17
20
  export declare const REARM_CEREMONY_TIER: SessionCeremonyTier;
18
- export declare const QUICK_STEPS: readonly ["alignment", "branch_policy", "triage_welcome"];
21
+ export declare const QUICK_STEPS: readonly ["alignment", "branch_policy", "triage_welcome", "verify_tools"];
19
22
  export declare const GATED_STEPS: readonly ["agent_hooks", "doctor", "cache_fresh"];
20
23
  export type GatedStepName = (typeof GATED_STEPS)[number];
21
24
  /** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
@@ -69,6 +72,16 @@ export interface SessionStartOptions {
69
72
  };
70
73
  readonly resolveUserMd?: (projectRoot: string) => ResolveUserMdResult;
71
74
  readonly probeEnvironment?: () => EnvironmentContext;
75
+ /**
76
+ * #3162: host content-surface class + managed-section drift seams (tests inject).
77
+ * Fail-open advisory only — never blocks session:start.
78
+ */
79
+ readonly hostContentSurfaceSeams?: HostContentSurfaceSeams;
80
+ /**
81
+ * #3266: hard effort-budget detection seams (env / host descriptor).
82
+ * Fail-open advisory only — never blocks session:start.
83
+ */
84
+ readonly effortBudgetSeams?: DetectHardEffortBudgetInput;
72
85
  readonly probeReleaseAvailability?: (projectRoot: string, options: ReleaseAvailabilityProbeOptions) => {
73
86
  lines: readonly string[];
74
87
  };
@@ -94,6 +107,22 @@ export interface SessionStartOptions {
94
107
  * network is enabled. Inject in tests.
95
108
  */
96
109
  readonly probeScm?: (options: ProbeScmReadinessOptions) => ScmReadinessReport;
110
+ /**
111
+ * #3214: ceremony dial inputs (task size × model tier × project shape).
112
+ * Missing fields are filled by the headless provisional classifier
113
+ * (env / verb / file-scope / deposit layout) — no plan-item effort (#1581).
114
+ */
115
+ readonly ceremonyDialInputs?: CeremonyDialInputs;
116
+ /**
117
+ * #3214: optional intake hints for provisional size (prompt/verb/files).
118
+ * Vanilla deposit session:start runs provisional fill without policy opt-in.
119
+ */
120
+ readonly ceremonyDialHints?: Omit<ProvisionalCeremonyEstimateHints, "projectRoot" | "env">;
121
+ /**
122
+ * #3214: optional pre-resolved dial (tests). When omitted, resolveCeremonyDial
123
+ * loads plan.policy.ceremonyDial and applies inputs (after provisional fill).
124
+ */
125
+ readonly ceremonyDial?: CeremonyDialSelection;
97
126
  }
98
127
  /** Format preferred `session:start` recovery command for cold vs re-arm (#2992). */
99
128
  export declare function formatSessionStartRecoveryCommand(tier?: SessionCeremonyTier): string;