@deftai/directive-core 0.101.0 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/authz/classify.js +97 -8
  2. package/dist/check/cached-orchestrator.d.ts +4 -0
  3. package/dist/check/cached-orchestrator.js +70 -5
  4. package/dist/check/cli-native-gates.d.ts +43 -0
  5. package/dist/check/cli-native-gates.js +84 -0
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.js +10 -0
  9. package/dist/doctor/checks.d.ts +5 -6
  10. package/dist/doctor/checks.js +13 -56
  11. package/dist/eval/later-graduation.d.ts +39 -0
  12. package/dist/eval/later-graduation.js +91 -0
  13. package/dist/eval/report.d.ts +6 -0
  14. package/dist/eval/report.js +27 -8
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/init-deposit/hygiene.d.ts +7 -1
  18. package/dist/init-deposit/hygiene.js +11 -3
  19. package/dist/init-deposit/scaffold.d.ts +12 -0
  20. package/dist/init-deposit/scaffold.js +81 -8
  21. package/dist/intake/issue-ingest.js +42 -0
  22. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  23. package/dist/policy/ceremony-dial-escalation.js +92 -0
  24. package/dist/policy/ceremony-dial.d.ts +3 -1
  25. package/dist/policy/ceremony-dial.js +52 -1
  26. package/dist/policy/check-resume.d.ts +9 -38
  27. package/dist/policy/check-resume.js +18 -156
  28. package/dist/policy/coverage-debt.d.ts +18 -37
  29. package/dist/policy/coverage-debt.js +38 -147
  30. package/dist/policy/index.d.ts +1 -1
  31. package/dist/policy/index.js +1 -1
  32. package/dist/product-first-done-gate/acceptance.js +22 -0
  33. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  34. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  35. package/dist/product-first-done-gate/evaluate.d.ts +40 -2
  36. package/dist/product-first-done-gate/evaluate.js +253 -61
  37. package/dist/product-first-done-gate/index.d.ts +2 -1
  38. package/dist/product-first-done-gate/index.js +2 -1
  39. package/dist/product-first-done-gate/types.d.ts +3 -0
  40. package/dist/product-first-done-gate/types.js +0 -7
  41. package/dist/run-summary/emit.d.ts +11 -1
  42. package/dist/run-summary/emit.js +48 -2
  43. package/dist/run-summary/index.d.ts +2 -1
  44. package/dist/run-summary/index.js +2 -1
  45. package/dist/run-summary/path.d.ts +1 -1
  46. package/dist/run-summary/path.js +1 -1
  47. package/dist/run-summary/share.d.ts +25 -0
  48. package/dist/run-summary/share.js +106 -0
  49. package/dist/run-summary/types.d.ts +60 -2
  50. package/dist/run-summary/types.js +7 -0
  51. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  52. package/dist/scope/acceptance-activate-gate.js +70 -0
  53. package/dist/scope/index.d.ts +1 -0
  54. package/dist/scope/index.js +1 -0
  55. package/dist/scope/transition.js +5 -0
  56. package/dist/session/index.d.ts +0 -1
  57. package/dist/session/index.js +0 -1
  58. package/dist/session/orientation-compression.js +19 -9
  59. package/dist/session/session-start.d.ts +6 -0
  60. package/dist/session/session-start.js +44 -22
  61. package/dist/session/toolchain-preflight.d.ts +15 -0
  62. package/dist/session/toolchain-preflight.js +65 -5
  63. package/dist/verify-ac/clauses.d.ts +51 -0
  64. package/dist/verify-ac/clauses.js +490 -0
  65. package/dist/verify-ac/evaluate.d.ts +73 -0
  66. package/dist/verify-ac/evaluate.js +167 -0
  67. package/dist/verify-ac/flag.d.ts +35 -0
  68. package/dist/verify-ac/flag.js +104 -0
  69. package/dist/verify-ac/index.d.ts +10 -0
  70. package/dist/verify-ac/index.js +10 -0
  71. package/package.json +7 -3
  72. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  73. package/dist/policy/coverage-check-resume-presets.js +0 -228
  74. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  75. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Ritual+gate share from a #3282 run-summary stream (#3320).
3
+ *
4
+ * Share is ritual+gate invocations over the emitted tool/turn denominator.
5
+ * Absence of the denominator is unevaluable — never invent a share from counts.
6
+ */
7
+ import { type RunSummaryLine } from "./types.js";
8
+ export interface RitualGateShare {
9
+ readonly evaluable: boolean;
10
+ readonly ritualGateCount: number;
11
+ readonly totalToolTurns: number | null;
12
+ readonly share: number | null;
13
+ }
14
+ /** True when n is a finite integer usable as a share denominator. */
15
+ export declare function isValidToolTurnDenominator(value: unknown): value is number;
16
+ /** Read total_tool_turns from a line (top-level field or payload). */
17
+ export declare function readToolTurnDenominator(line: unknown): number | undefined;
18
+ /** Parse JSONL (or DEFT-TLM: prefixed stdout capture) into run-summary lines. */
19
+ export declare function parseRunSummaryJsonl(text: string): RunSummaryLine[];
20
+ /**
21
+ * Compute ritual+gate share from summary lines alone.
22
+ * Uses the latest session in the stream. Missing/invalid denominator → unevaluable.
23
+ */
24
+ export declare function computeRitualGateShare(lines: readonly RunSummaryLine[]): RitualGateShare;
25
+ //# sourceMappingURL=share.d.ts.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Ritual+gate share from a #3282 run-summary stream (#3320).
3
+ *
4
+ * Share is ritual+gate invocations over the emitted tool/turn denominator.
5
+ * Absence of the denominator is unevaluable — never invent a share from counts.
6
+ */
7
+ import { RUN_SUMMARY_STDOUT_PREFIX } from "./types.js";
8
+ function asRecord(value) {
9
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
10
+ return null;
11
+ }
12
+ return value;
13
+ }
14
+ /** True when n is a finite integer usable as a share denominator. */
15
+ export function isValidToolTurnDenominator(value) {
16
+ return (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0);
17
+ }
18
+ /** Read total_tool_turns from a line (top-level field or payload). */
19
+ export function readToolTurnDenominator(line) {
20
+ const rec = asRecord(line);
21
+ if (rec === null) {
22
+ return undefined;
23
+ }
24
+ if (isValidToolTurnDenominator(rec.total_tool_turns)) {
25
+ return rec.total_tool_turns;
26
+ }
27
+ const payload = asRecord(rec.payload);
28
+ if (payload !== null && isValidToolTurnDenominator(payload.total_tool_turns)) {
29
+ return payload.total_tool_turns;
30
+ }
31
+ return undefined;
32
+ }
33
+ function parseLine(raw) {
34
+ const trimmed = raw.trim();
35
+ if (trimmed.length === 0) {
36
+ return null;
37
+ }
38
+ const body = trimmed.startsWith(RUN_SUMMARY_STDOUT_PREFIX)
39
+ ? trimmed.slice(RUN_SUMMARY_STDOUT_PREFIX.length)
40
+ : trimmed;
41
+ try {
42
+ const parsed = JSON.parse(body);
43
+ const rec = asRecord(parsed);
44
+ if (rec === null) {
45
+ return null;
46
+ }
47
+ if (typeof rec.event !== "string" || typeof rec.session_id !== "string") {
48
+ return null;
49
+ }
50
+ return parsed;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ /** Parse JSONL (or DEFT-TLM: prefixed stdout capture) into run-summary lines. */
57
+ export function parseRunSummaryJsonl(text) {
58
+ const lines = [];
59
+ for (const raw of text.split(/\r?\n/)) {
60
+ const line = parseLine(raw);
61
+ if (line !== null) {
62
+ lines.push(line);
63
+ }
64
+ }
65
+ return lines;
66
+ }
67
+ function lastSessionId(lines) {
68
+ let lastStart = null;
69
+ let lastAny = null;
70
+ for (const line of lines) {
71
+ lastAny = line.session_id;
72
+ if (line.event === "session_start") {
73
+ lastStart = line.session_id;
74
+ }
75
+ }
76
+ return lastStart ?? lastAny;
77
+ }
78
+ /**
79
+ * Compute ritual+gate share from summary lines alone.
80
+ * Uses the latest session in the stream. Missing/invalid denominator → unevaluable.
81
+ */
82
+ export function computeRitualGateShare(lines) {
83
+ const sessionId = lastSessionId(lines);
84
+ const sessionLines = sessionId === null ? [] : lines.filter((line) => line.session_id === sessionId);
85
+ let ritualGateCount = 0;
86
+ let totalToolTurns = null;
87
+ for (const line of sessionLines) {
88
+ if (line.event === "check_invocation") {
89
+ ritualGateCount += 1;
90
+ }
91
+ const denom = readToolTurnDenominator(line);
92
+ if (denom !== undefined) {
93
+ totalToolTurns = denom;
94
+ }
95
+ }
96
+ if (totalToolTurns === null) {
97
+ return { evaluable: false, ritualGateCount, totalToolTurns: null, share: null };
98
+ }
99
+ return {
100
+ evaluable: true,
101
+ ritualGateCount,
102
+ totalToolTurns,
103
+ share: ritualGateCount / totalToolTurns,
104
+ };
105
+ }
106
+ //# sourceMappingURL=share.js.map
@@ -7,13 +7,15 @@
7
7
  export declare const RUN_SUMMARY_SCHEMA_VERSION: 1;
8
8
  /** Env var that opts into / redirects run-summary JSONL (#3282). */
9
9
  export declare const ENV_RUN_SUMMARY_PATH = "DEFT_RUN_SUMMARY_PATH";
10
+ /** Harness-supplied session tool/turn total stamped onto run-summary lines (#3320). */
11
+ export declare const ENV_TOTAL_TOOL_TURNS = "DEFT_TOTAL_TOOL_TURNS";
10
12
  /** Repo-root default collectible path when gitignore coverage is present. */
11
13
  export declare const DEFAULT_RUN_SUMMARY_BASENAME = ".deft-run-summary.json";
12
14
  /** Prefix for stdout lines when `DEFT_RUN_SUMMARY_PATH=-`. */
13
15
  export declare const RUN_SUMMARY_STDOUT_PREFIX = "DEFT-TLM:";
14
16
  /** One warning when an explicitly requested write fails. */
15
17
  export declare const RUN_SUMMARY_WRITE_WARNING = "[deft run-summary] failed to write DEFT_RUN_SUMMARY_PATH telemetry (fail-open; exit codes unchanged)";
16
- export declare const RUN_SUMMARY_EVENT_KINDS: readonly ["session_start", "dial_transition", "check_invocation"];
18
+ export declare const RUN_SUMMARY_EVENT_KINDS: readonly ["session_start", "dial_transition", "dial_escalation_evaluation", "check_invocation", "tool_turn_denominator", "verification", "acceptance", "acceptance_stamp"];
17
19
  export type RunSummaryEventKind = (typeof RUN_SUMMARY_EVENT_KINDS)[number];
18
20
  export interface RunSummaryBaseFields {
19
21
  readonly schema_version: typeof RUN_SUMMARY_SCHEMA_VERSION;
@@ -23,6 +25,11 @@ export interface RunSummaryBaseFields {
23
25
  readonly seq: number;
24
26
  readonly ts: string;
25
27
  readonly event: RunSummaryEventKind;
28
+ /**
29
+ * Session total tool/turn count (#3320). Present so ritual+gate share is
30
+ * computable from the summary alone. Absence means the #3286 trigger is unevaluable.
31
+ */
32
+ readonly total_tool_turns?: number;
26
33
  }
27
34
  export interface SessionStartRunSummaryPayload {
28
35
  readonly ceremony_dial?: Record<string, unknown>;
@@ -43,6 +50,14 @@ export interface DialTransitionRunSummaryPayload {
43
50
  readonly reason: string;
44
51
  readonly evidence?: string;
45
52
  }
53
+ /** One evaluation of escalate-on-evidence (#3319). Absence of this event ≠ declined. */
54
+ export type DialEscalationEvaluationOutcome = "escalated" | "declined";
55
+ export interface DialEscalationEvaluationRunSummaryPayload {
56
+ /** Ceremony depth considered (the start/current tier under evaluation). */
57
+ readonly tier: string;
58
+ readonly outcome: DialEscalationEvaluationOutcome;
59
+ readonly reason: string;
60
+ }
46
61
  export interface CheckGateOutcome {
47
62
  readonly id: string;
48
63
  readonly status: "run" | "skipped" | "failed";
@@ -51,13 +66,56 @@ export interface CheckGateOutcome {
51
66
  readonly remedy?: string;
52
67
  readonly from_cache?: boolean;
53
68
  }
69
+ /** Per-clause coverage row carried on verify:ac telemetry (#3323). */
70
+ export interface AcceptanceClauseOutcomeRow {
71
+ readonly id: number;
72
+ readonly outcome: "verified" | "unverifiable" | "failed";
73
+ }
54
74
  export interface CheckInvocationRunSummaryPayload {
55
75
  readonly target: string;
56
76
  readonly exit_code: number;
57
77
  readonly degraded?: boolean;
58
78
  readonly gates: readonly CheckGateOutcome[];
79
+ /** Additive verify:ac fields (#3323). Consumers ignore unknown keys. */
80
+ readonly source_rung?: string;
81
+ readonly none_stated?: boolean;
82
+ readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
83
+ }
84
+ /** Total tool/turn count for the session (#3320). */
85
+ export interface ToolTurnDenominatorRunSummaryPayload {
86
+ readonly total_tool_turns: number;
87
+ }
88
+ /** Product-oracle attempt (#3322). Same emitter as #3319 / #3320. */
89
+ export type VerificationOutcome = "pass" | "fail";
90
+ /** verify:ac resolution telemetry (#3334). Distinct from verification pass/fail. */
91
+ export type AcceptanceRunSummaryOutcome = "verified-pass" | "empty-pass" | "soft_empty" | "fail";
92
+ export interface AcceptanceRunSummaryPayload {
93
+ readonly resolved_command_count: number;
94
+ readonly outcome: AcceptanceRunSummaryOutcome;
95
+ readonly source_rung?: string;
96
+ readonly none_stated?: boolean;
97
+ readonly clause_count?: number;
98
+ readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
99
+ }
100
+ /** Intake-time stamp: which rung locked, whether commands were stated, counts (#3323). */
101
+ export interface AcceptanceStampRunSummaryPayload {
102
+ readonly rung: string;
103
+ readonly none_stated: boolean;
104
+ readonly command_count: number;
105
+ readonly clause_count: number;
106
+ }
107
+ export interface VerificationRunSummaryPayload {
108
+ readonly check_id: string;
109
+ readonly method_fingerprint: string;
110
+ readonly outcome: VerificationOutcome;
111
+ /**
112
+ * True when both sides of the comparison were rebuilt from scratch by a
113
+ * different method than the one that failed. Required to resolve
114
+ * fail → method-change → pass (#3322).
115
+ */
116
+ readonly independent_rederivation?: boolean;
59
117
  }
60
- export type RunSummaryPayload = SessionStartRunSummaryPayload | DialTransitionRunSummaryPayload | CheckInvocationRunSummaryPayload;
118
+ export type RunSummaryPayload = SessionStartRunSummaryPayload | DialTransitionRunSummaryPayload | DialEscalationEvaluationRunSummaryPayload | CheckInvocationRunSummaryPayload | ToolTurnDenominatorRunSummaryPayload | VerificationRunSummaryPayload | AcceptanceRunSummaryPayload | AcceptanceStampRunSummaryPayload;
61
119
  export type RunSummaryLine = RunSummaryBaseFields & {
62
120
  readonly payload: RunSummaryPayload;
63
121
  };
@@ -7,6 +7,8 @@
7
7
  export const RUN_SUMMARY_SCHEMA_VERSION = 1;
8
8
  /** Env var that opts into / redirects run-summary JSONL (#3282). */
9
9
  export const ENV_RUN_SUMMARY_PATH = "DEFT_RUN_SUMMARY_PATH";
10
+ /** Harness-supplied session tool/turn total stamped onto run-summary lines (#3320). */
11
+ export const ENV_TOTAL_TOOL_TURNS = "DEFT_TOTAL_TOOL_TURNS";
10
12
  /** Repo-root default collectible path when gitignore coverage is present. */
11
13
  export const DEFAULT_RUN_SUMMARY_BASENAME = ".deft-run-summary.json";
12
14
  /** Prefix for stdout lines when `DEFT_RUN_SUMMARY_PATH=-`. */
@@ -16,6 +18,11 @@ export const RUN_SUMMARY_WRITE_WARNING = "[deft run-summary] failed to write DEF
16
18
  export const RUN_SUMMARY_EVENT_KINDS = [
17
19
  "session_start",
18
20
  "dial_transition",
21
+ "dial_escalation_evaluation",
19
22
  "check_invocation",
23
+ "tool_turn_denominator",
24
+ "verification",
25
+ "acceptance",
26
+ "acceptance_stamp",
20
27
  ];
21
28
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * scope:activate refuses acceptance-shaped narratives without plan.acceptance (#3334).
3
+ *
4
+ * Agents author Test / AcceptanceCriteria / Verification prose in narratives
5
+ * and then skip the executable plan.acceptance block. Activation fails closed
6
+ * with one remediation: stamp the acceptance block.
7
+ */
8
+ export interface AcceptanceActivateHit {
9
+ readonly key: string;
10
+ }
11
+ export interface AcceptanceActivateGateResult {
12
+ readonly ok: boolean;
13
+ readonly message: string;
14
+ readonly hits: readonly AcceptanceActivateHit[];
15
+ }
16
+ /** Narrative keys that look like authored acceptance (non-empty string values). */
17
+ export declare function collectAcceptanceShapedNarrativeKeys(narratives: Record<string, unknown>): AcceptanceActivateHit[];
18
+ /**
19
+ * Fail closed when narratives carry acceptance-shaped keys and plan.acceptance
20
+ * is absent. A stamped (even empty/none_stated) block is enough.
21
+ */
22
+ export declare function evaluateAcceptanceActivateGate(plan: Record<string, unknown>): AcceptanceActivateGateResult;
23
+ //# sourceMappingURL=acceptance-activate-gate.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * scope:activate refuses acceptance-shaped narratives without plan.acceptance (#3334).
3
+ *
4
+ * Agents author Test / AcceptanceCriteria / Verification prose in narratives
5
+ * and then skip the executable plan.acceptance block. Activation fails closed
6
+ * with one remediation: stamp the acceptance block.
7
+ */
8
+ import { validatePlanAcceptance } from "../product-first-done-gate/acceptance.js";
9
+ function asRecord(value) {
10
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
11
+ return value;
12
+ }
13
+ return null;
14
+ }
15
+ const ACCEPTANCE_SHAPED_KEYS = new Set(["test", "acceptancecriteria", "verification"]);
16
+ function normalizeNarrativeKey(key) {
17
+ return key.replace(/[\s_-]+/g, "").toLowerCase();
18
+ }
19
+ /** Narrative keys that look like authored acceptance (non-empty string values). */
20
+ export function collectAcceptanceShapedNarrativeKeys(narratives) {
21
+ const hits = [];
22
+ for (const key of Object.keys(narratives)) {
23
+ if (!ACCEPTANCE_SHAPED_KEYS.has(normalizeNarrativeKey(key))) {
24
+ continue;
25
+ }
26
+ const value = narratives[key];
27
+ if (typeof value !== "string" || value.trim().length === 0) {
28
+ continue;
29
+ }
30
+ hits.push({ key });
31
+ }
32
+ return hits;
33
+ }
34
+ /**
35
+ * Fail closed when narratives carry acceptance-shaped keys and plan.acceptance
36
+ * is absent. A stamped (even empty/none_stated) block is enough.
37
+ */
38
+ export function evaluateAcceptanceActivateGate(plan) {
39
+ const narratives = asRecord(plan.narratives);
40
+ if (narratives === null) {
41
+ return { ok: true, message: "", hits: [] };
42
+ }
43
+ const hits = collectAcceptanceShapedNarrativeKeys(narratives);
44
+ if (hits.length === 0) {
45
+ return { ok: true, message: "", hits: [] };
46
+ }
47
+ if (plan.acceptance !== undefined && plan.acceptance !== null) {
48
+ const schemaErrors = validatePlanAcceptance(plan.acceptance);
49
+ if (schemaErrors.length > 0) {
50
+ return {
51
+ ok: false,
52
+ message: `Refusing activate: plan.acceptance is present but invalid (#3334): ` +
53
+ `${schemaErrors.join("; ")}. Stamp a valid plan.acceptance block ` +
54
+ `(author commands or none_stated via intake capture / task issue:ingest).`,
55
+ hits,
56
+ };
57
+ }
58
+ return { ok: true, message: "", hits };
59
+ }
60
+ const keys = hits.map((h) => h.key).join(", ");
61
+ return {
62
+ ok: false,
63
+ message: `Refusing activate: narratives contain acceptance-shaped keys (${keys}) ` +
64
+ `but plan.acceptance is absent (#3334). Stamp plan.acceptance ` +
65
+ `(author commands or none_stated via intake capture / task issue:ingest) — ` +
66
+ `the criteria are in the wrong, non-executable field.`,
67
+ hits,
68
+ };
69
+ }
70
+ //# sourceMappingURL=acceptance-activate-gate.js.map
@@ -1,3 +1,4 @@
1
+ export * from "./acceptance-activate-gate.js";
1
2
  export * from "./acceptance-evidence.js";
2
3
  export * from "./audit-log.js";
3
4
  export * from "./batch-promote.js";
@@ -1,3 +1,4 @@
1
+ export * from "./acceptance-activate-gate.js";
1
2
  export * from "./acceptance-evidence.js";
2
3
  export * from "./audit-log.js";
3
4
  export * from "./batch-promote.js";
@@ -5,6 +5,7 @@ import { assertProjectionContained, ProjectionContainmentError, } from "../fs/pr
5
5
  import { hasArtifactSuffix } from "../layout/resolve.js";
6
6
  import { evaluateCompletedPlanConsistency } from "../lifecycle/completed-consistency.js";
7
7
  import { evaluateLiteralAcceptanceFromPlan } from "../literal-acceptance/index.js";
8
+ import { evaluateAcceptanceActivateGate } from "./acceptance-activate-gate.js";
8
9
  import { evaluateAcceptanceEvidenceGate, formatAcceptanceCompletionListing, } from "./acceptance-evidence.js";
9
10
  import { append, canonicalLogPath, newDecisionId } from "./audit-log.js";
10
11
  import { atomicWriteBrief, formatBriefJson, readBriefForMutation } from "./brief-io.js";
@@ -148,6 +149,10 @@ export function runTransition(action, filePath, now = new Date(), options = {})
148
149
  if (!effortGate.ok) {
149
150
  return { ok: false, message: effortGate.message };
150
151
  }
152
+ const acceptanceGate = evaluateAcceptanceActivateGate(planObj);
153
+ if (!acceptanceGate.ok) {
154
+ return { ok: false, message: acceptanceGate.message };
155
+ }
151
156
  }
152
157
  // #3041: fail closed before mutating a code-bearing complete without delivery evidence.
153
158
  if (act === "complete") {
@@ -1,7 +1,6 @@
1
1
  export * from "./ac-pass-banking.js";
2
2
  export * from "./active-cli.js";
3
3
  export * from "./compact-ritual.js";
4
- export * from "./coverage-check-resume-nudge.js";
5
4
  export * from "./deposit-sha.js";
6
5
  export * from "./effort-budget.js";
7
6
  export * from "./git.js";
@@ -1,7 +1,6 @@
1
1
  export * from "./ac-pass-banking.js";
2
2
  export * from "./active-cli.js";
3
3
  export * from "./compact-ritual.js";
4
- export * from "./coverage-check-resume-nudge.js";
5
4
  export * from "./deposit-sha.js";
6
5
  export * from "./effort-budget.js";
7
6
  export * from "./git.js";
@@ -6,6 +6,7 @@
6
6
  * agents:refresh and verify:cache-fresh; formats compact (opt-in) vs verbose
7
7
  * (default). Does NOT implement unified `deft orient` (Later remains open).
8
8
  */
9
+ import { isFrameworkRepoRoot } from "../check/context.js";
9
10
  import { decideThrottle, readState as readDoctorState, renderDoctorStatusLine, } from "../doctor/doctor-state.js";
10
11
  import { cmdDoctor } from "../doctor/main.js";
11
12
  import { applyAgentsRefresh } from "../platform/agents-md.js";
@@ -271,16 +272,23 @@ export function runCacheFreshOrientationSection(projectRoot, options) {
271
272
  void options.now;
272
273
  try {
273
274
  const evaluate = options.evaluateFn ?? evaluateCacheFresh;
274
- // #3286 Greptile: do not treat missing cache/candidates as a successful
275
- // cache-fresh for orientation / gated_steps (bootstrap must not look green).
276
- // Session:start still continues; gated verify will re-run cache_fresh when
277
- // ok is false.
275
+ const consumerDeposit = !isFrameworkRepoRoot(projectRoot);
276
+ // Consumer deposits often have no triage cache. Treat that as a named
277
+ // bootstrap cause (dirty), never a generic orientation `error` (#3335).
278
+ // Framework source still evaluates live; missing cache stays non-green
279
+ // (#3286) so gated verify re-runs cache_fresh.
278
280
  const result = evaluate(projectRoot, {
279
281
  allowMissingBootstrap: false,
280
282
  autoPopulateEmpty: false,
281
283
  ...options.evaluateOptions,
282
284
  });
283
285
  const ok = result.code === 0;
286
+ const missingCache = result.code === 2 && /not found|bootstrap/i.test(result.message);
287
+ const namedCause = missingCache
288
+ ? consumerDeposit
289
+ ? `[deft cache-fresh] triage cache not populated (consumer deposit) — not a toolchain failure. Recovery: run \`deft triage:bootstrap\` if you need the queue.`
290
+ : result.message
291
+ : result.message;
284
292
  // Tag as sha_match only when message already uses the canonical phrase
285
293
  // (e.g. tests inject it) — never invent a shortcut past evaluate.
286
294
  const shaMatch = ok && result.message.includes(DEPOSIT_SHA_MATCH_NOOP);
@@ -288,19 +296,21 @@ export function runCacheFreshOrientationSection(projectRoot, options) {
288
296
  ? "sha_match"
289
297
  : ok
290
298
  ? "ok"
291
- : result.code === 2
292
- ? "error"
293
- : "dirty";
299
+ : missingCache
300
+ ? "dirty"
301
+ : result.code === 2
302
+ ? "error"
303
+ : "dirty";
294
304
  return {
295
305
  name: "cache_fresh",
296
306
  status,
297
307
  ok,
298
308
  exitCode: result.code,
299
- lines: [result.message],
309
+ lines: [namedCause],
300
310
  shaMatch,
301
311
  depositSha: options.depositSha,
302
312
  durationMs: elapsedMs(started),
303
- detail: { code: result.code },
313
+ detail: { code: result.code, missing_cache: missingCache },
304
314
  };
305
315
  }
306
316
  catch (exc) {
@@ -1,6 +1,7 @@
1
1
  import { type HostContentSurfaceSeams } from "../platform/host-content-surface.js";
2
2
  import { type EnvironmentContext } from "../platform/shell-context.js";
3
3
  import { type CeremonyDialInputs, type CeremonyDialSelection, type ProvisionalCeremonyEstimateHints } from "../policy/ceremony-dial.js";
4
+ import { type CeremonyStartTierProvenance } from "../policy/ceremony-dial-escalation.js";
4
5
  import { type ProbeScmReadinessOptions, type ScmReadinessReport } from "../scm/readiness.js";
5
6
  import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
6
7
  import { type DetectHardEffortBudgetInput } from "./effort-budget.js";
@@ -125,6 +126,11 @@ export interface SessionStartOptions {
125
126
  * loads plan.policy.ceremonyDial and applies inputs (after provisional fill).
126
127
  */
127
128
  readonly ceremonyDial?: CeremonyDialSelection;
129
+ /**
130
+ * #3319: start-tier provenance hint. CLI `--ceremony-depth` / harness pin
131
+ * should pass `external-pin`. Policy override is inferred as `operator`.
132
+ */
133
+ readonly ceremonyDialStartTierProvenance?: CeremonyStartTierProvenance;
128
134
  /**
129
135
  * #3282: done-gate toolchain preflight seams (tests inject result or probe).
130
136
  * When omitted, runs live preflight after verify_tools on cold path.
@@ -7,6 +7,8 @@ import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposi
7
7
  import { hostContentSurfaceToDict, maybeFormatHostContentSurfaceLines, } from "../platform/host-content-surface.js";
8
8
  import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
9
9
  import { ceremonyDialToDict, formatCeremonyDialStatusLine, mergeCeremonyDialDeferrals, resolveCeremonyDial, resolveSessionCeremonyDialInputs, } from "../policy/ceremony-dial.js";
10
+ import { emitCeremonyDialEscalationEvaluation, evaluateSessionStartCeremonyDialEscalation, formatCeremonyDialPinBypassLine, isCeremonyStartTierPinned, resolveCeremonyStartTierProvenance, } from "../policy/ceremony-dial-escalation.js";
11
+ import { maybeFormatCoverageCheckResumeDisclosure } from "../policy/coverage-debt.js";
10
12
  import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
11
13
  import { disclosureLine } from "../policy/disclosure.js";
12
14
  import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
@@ -21,7 +23,6 @@ import { runDefaultMode } from "../triage/welcome/default-mode.js";
21
23
  import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
22
24
  import { emitSessionValueReadback } from "../value/readback.js";
23
25
  import { verifyRequiredTools } from "../verify-env/verify-tools.js";
24
- import { maybeFormatCoverageCheckResumeNudge } from "./coverage-check-resume-nudge.js";
25
26
  import { effortBudgetToDict, maybeFormatEffortBudgetLines, resolveProductionHostEffortDescriptor, } from "./effort-budget.js";
26
27
  import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
27
28
  import { runOrientationCompression, } from "./orientation-compression.js";
@@ -48,6 +49,18 @@ export const QUICK_STEPS = [
48
49
  "verify_tools",
49
50
  ];
50
51
  export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
52
+ /** Standing one-liner when coverageDebt/checkResume is non-default (#3314). */
53
+ function pushCoverageCheckResumeDisclosure(lines, projectRoot) {
54
+ try {
55
+ const line = maybeFormatCoverageCheckResumeDisclosure(projectRoot);
56
+ if (line !== null) {
57
+ lines.push(line);
58
+ }
59
+ }
60
+ catch {
61
+ // best-effort — ritual must not abort
62
+ }
63
+ }
51
64
  /** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
52
65
  export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
53
66
  /** Human-readable skip notice when optional network is off the hot path (#2991). */
@@ -527,16 +540,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
527
540
  if (humanMergeLine !== null) {
528
541
  lines.push(humanMergeLine);
529
542
  }
530
- // #3189: re-arm still surfaces undecided coverage/check-resume once per ritual.
531
- try {
532
- const coverageNudge = maybeFormatCoverageCheckResumeNudge({ projectRoot });
533
- if (coverageNudge.length > 0) {
534
- lines.push(coverageNudge.trimEnd());
535
- }
536
- }
537
- catch {
538
- // best-effort — re-arm must not abort
539
- }
543
+ pushCoverageCheckResumeDisclosure(lines, projectRoot);
540
544
  const priorQuick = eligibility.state.quickSteps;
541
545
  const priorTriage = priorQuick.triage_welcome ?? ritualStep({ ok: true, ts: instant });
542
546
  // Greptile P1: legacy ritual-state without verify_tools must re-run tools —
@@ -802,6 +806,11 @@ export function runSessionStart(projectRoot, options = {}) {
802
806
  env: options.env,
803
807
  });
804
808
  const ceremonyDialSelection = options.ceremonyDial ?? resolveCeremonyDial(projectRoot, { inputs: resolvedDialInputs });
809
+ const startTierProvenance = resolveCeremonyStartTierProvenance({
810
+ selection: ceremonyDialSelection,
811
+ injectedSelection: options.ceremonyDial !== undefined,
812
+ hint: options.ceremonyDialStartTierProvenance,
813
+ });
805
814
  const effectiveDeferrals = mergeCeremonyDialDeferrals(deferrals, ceremonyDialSelection);
806
815
  const skipFatPath = ceremonyDialSelection.profile.skipFatPath;
807
816
  const { head: gitHeadValue, error: gitError } = gitHead(projectRoot, runGit);
@@ -831,7 +840,13 @@ export function runSessionStart(projectRoot, options = {}) {
831
840
  const quickSteps = recordDeferredSteps(QUICK_STEPS, effectiveDeferrals, instant);
832
841
  const gatedSteps = recordDeferredSteps(GATED_STEPS, effectiveDeferrals, instant);
833
842
  const lines = [];
834
- lines.push(formatCeremonyDialStatusLine(ceremonyDialSelection));
843
+ lines.push(formatCeremonyDialStatusLine(ceremonyDialSelection, {
844
+ startTierProvenance,
845
+ }));
846
+ const pinBypassLine = formatCeremonyDialPinBypassLine(startTierProvenance);
847
+ if (pinBypassLine !== null) {
848
+ lines.push(pinBypassLine);
849
+ }
835
850
  if (provisionalDial.reasons.length > 0 && options.ceremonyDial === undefined) {
836
851
  lines.push(`[deft ceremony-dial] provisional: ${provisionalDial.reasons.join("; ")}`);
837
852
  }
@@ -919,6 +934,8 @@ export function runSessionStart(projectRoot, options = {}) {
919
934
  else {
920
935
  stepTimings.push({ name: "branch_policy", duration_ms: 0, skipped: true });
921
936
  }
937
+ // Standing disclosure is independent of branch_policy deferral (#3314 / Greptile).
938
+ pushCoverageCheckResumeDisclosure(lines, projectRoot);
922
939
  // #3214 / #3156: verify_tools is mutation readiness — always run, even under
923
940
  // rapid/minimal. Dial skipFatPath only lightens *ceremony* (triage welcome,
924
941
  // optional network, staleness tickler), never readiness gates.
@@ -1182,20 +1199,12 @@ export function runSessionStart(projectRoot, options = {}) {
1182
1199
  if (consentPrompt.length > 0) {
1183
1200
  lines.push(consentPrompt.trimEnd());
1184
1201
  }
1185
- // #3189: skippable coverageDebt/checkResume project-decision nudge (fail-open; never blocks).
1186
- try {
1187
- const coverageNudge = maybeFormatCoverageCheckResumeNudge({ projectRoot });
1188
- if (coverageNudge.length > 0) {
1189
- lines.push(coverageNudge.trimEnd());
1190
- }
1191
- }
1192
- catch {
1193
- // best-effort operator advisory — session start must not abort
1194
- }
1195
1202
  const writeStarted = performance.now();
1196
1203
  const coldSessionId = (options.newSessionId ?? randomUUID)();
1197
1204
  const dialDict = {
1198
1205
  ...ceremonyDialToDict(ceremonyDialSelection),
1206
+ start_tier: ceremonyDialSelection.depth,
1207
+ start_tier_provenance: startTierProvenance,
1199
1208
  provisional: {
1200
1209
  taskSize: provisionalDial.taskSize,
1201
1210
  modelTier: provisionalDial.modelTier,
@@ -1274,6 +1283,19 @@ export function runSessionStart(projectRoot, options = {}) {
1274
1283
  }
1275
1284
  : {}),
1276
1285
  });
1286
+ emitter.emitKnownToolTurnDenominator();
1287
+ // #3319: evaluate escalate-on-evidence only when #3274 is live. A pin
1288
+ // emits nothing so never-evaluated stays distinguishable from declined.
1289
+ if (!isCeremonyStartTierPinned(startTierProvenance)) {
1290
+ emitCeremonyDialEscalationEvaluation({
1291
+ projectRoot,
1292
+ sessionId: coldSessionId,
1293
+ env: options.env,
1294
+ evaluation: evaluateSessionStartCeremonyDialEscalation({
1295
+ selection: ceremonyDialSelection,
1296
+ }),
1297
+ });
1298
+ }
1277
1299
  }
1278
1300
  catch {
1279
1301
  // fail-open
@@ -8,9 +8,12 @@
8
8
  * Never bootstraps tooling the PRODUCT does not need. Failures name cause and
9
9
  * remedy without embedding env values.
10
10
  */
11
+ import { type CheckGateSpec } from "../check/gate-lists.js";
11
12
  export declare const TOOLCHAIN_PREFLIGHT_TOOLS: readonly ["task", "pnpm", "node", "git"];
12
13
  export type ToolchainPreflightTool = (typeof TOOLCHAIN_PREFLIGHT_TOOLS)[number];
13
14
  export type ToolchainPreflightStatus = "ok" | "missing" | "degraded";
15
+ /** How a missing tool affects check dispatch (#3335). */
16
+ export type ToolchainPreflightImpact = "none" | "degraded";
14
17
  export interface ToolchainPreflightFinding {
15
18
  readonly tool: ToolchainPreflightTool | "cli_dist";
16
19
  readonly present: boolean;
@@ -18,6 +21,11 @@ export interface ToolchainPreflightFinding {
18
21
  readonly cause: string | null;
19
22
  /** Operator/agent remedy command or instruction. */
20
23
  readonly remedy: string | null;
24
+ /**
25
+ * Missing-tool impact. `none` = present false but no install remedy and
26
+ * check does not skip CLI-native gates (#3335).
27
+ */
28
+ readonly impact?: ToolchainPreflightImpact | null;
21
29
  }
22
30
  export interface ToolchainPreflightResult {
23
31
  readonly status: ToolchainPreflightStatus;
@@ -46,6 +54,13 @@ export interface ToolchainPreflightOptions {
46
54
  * a global `deft`/`directive` is on PATH.
47
55
  */
48
56
  readonly probeCliDist?: boolean;
57
+ /** Active check composition. Defaults from consumer vs framework context. */
58
+ readonly composedGates?: readonly CheckGateSpec[];
59
+ /**
60
+ * Consumer deposit (not framework source). When omitted, inferred from
61
+ * frameworkRoot vs projectRoot (#3335 / #3324).
62
+ */
63
+ readonly consumerDeposit?: boolean;
49
64
  }
50
65
  /**
51
66
  * Gates that specifically need a package manager / built dist.