@deftai/directive-core 0.99.0 → 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 (102) 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 +157 -23
  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/doctor/checks.d.ts +44 -0
  13. package/dist/doctor/checks.js +428 -2
  14. package/dist/doctor/main.d.ts +6 -7
  15. package/dist/doctor/main.js +72 -79
  16. package/dist/doctor/which.d.ts +20 -1
  17. package/dist/doctor/which.js +67 -1
  18. package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
  19. package/dist/finish-loop/pr-finish-loop.js +116 -5
  20. package/dist/finish-loop/types.d.ts +1 -1
  21. package/dist/hooks/dispatcher.d.ts +9 -5
  22. package/dist/hooks/dispatcher.js +23 -15
  23. package/dist/hooks/readonly.d.ts +6 -3
  24. package/dist/hooks/readonly.js +44 -25
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/init-deposit/agent-hooks.d.ts +20 -0
  28. package/dist/init-deposit/agent-hooks.js +51 -31
  29. package/dist/intake/issue-ingest.js +29 -0
  30. package/dist/lifecycle/completed-consistency.d.ts +60 -0
  31. package/dist/lifecycle/completed-consistency.js +443 -0
  32. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
  33. package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
  34. package/dist/lifecycle/index.d.ts +2 -0
  35. package/dist/lifecycle/index.js +2 -0
  36. package/dist/literal-acceptance/capture.d.ts +52 -0
  37. package/dist/literal-acceptance/capture.js +658 -0
  38. package/dist/literal-acceptance/evaluate.d.ts +47 -0
  39. package/dist/literal-acceptance/evaluate.js +193 -0
  40. package/dist/literal-acceptance/index.d.ts +12 -0
  41. package/dist/literal-acceptance/index.js +12 -0
  42. package/dist/literal-acceptance/run.d.ts +38 -0
  43. package/dist/literal-acceptance/run.js +216 -0
  44. package/dist/literal-acceptance/safety.d.ts +24 -0
  45. package/dist/literal-acceptance/safety.js +251 -0
  46. package/dist/literal-acceptance/types.d.ts +82 -0
  47. package/dist/literal-acceptance/types.js +26 -0
  48. package/dist/policy/ceremony-dial.d.ts +67 -11
  49. package/dist/policy/ceremony-dial.js +174 -23
  50. package/dist/policy/index.d.ts +1 -0
  51. package/dist/policy/index.js +1 -0
  52. package/dist/policy/merge-approval-head.d.ts +129 -0
  53. package/dist/policy/merge-approval-head.js +461 -0
  54. package/dist/policy/require-human-merge.d.ts +5 -0
  55. package/dist/policy/require-human-merge.js +5 -0
  56. package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
  57. package/dist/pr-merge-readiness/ci-gate.js +65 -5
  58. package/dist/pr-merge-readiness/compute.d.ts +13 -0
  59. package/dist/pr-merge-readiness/compute.js +99 -14
  60. package/dist/pr-merge-readiness/gh.d.ts +49 -0
  61. package/dist/pr-merge-readiness/gh.js +268 -0
  62. package/dist/pr-merge-readiness/index.d.ts +2 -2
  63. package/dist/pr-merge-readiness/index.js +2 -2
  64. package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
  65. package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
  66. package/dist/pr-wait-mergeable/cascade.js +115 -1
  67. package/dist/pr-wait-mergeable/main.d.ts +4 -0
  68. package/dist/pr-wait-mergeable/main.js +4 -0
  69. package/dist/pr-wait-mergeable/types.d.ts +3 -1
  70. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
  71. package/dist/pr-wait-mergeable/wrappers.js +7 -1
  72. package/dist/preflight/evaluate.d.ts +15 -1
  73. package/dist/preflight/evaluate.js +33 -2
  74. package/dist/render/framework-commands.js +4 -0
  75. package/dist/scope/acceptance-evidence.d.ts +76 -0
  76. package/dist/scope/acceptance-evidence.js +348 -0
  77. package/dist/scope/coverage-map.d.ts +98 -0
  78. package/dist/scope/coverage-map.js +558 -0
  79. package/dist/scope/decompose.js +125 -11
  80. package/dist/scope/index.d.ts +3 -0
  81. package/dist/scope/index.js +3 -0
  82. package/dist/scope/parent-lineage.d.ts +125 -0
  83. package/dist/scope/parent-lineage.js +626 -0
  84. package/dist/scope/transition.d.ts +8 -0
  85. package/dist/scope/transition.js +61 -2
  86. package/dist/session/active-cli.d.ts +79 -0
  87. package/dist/session/active-cli.js +382 -0
  88. package/dist/session/effort-budget.d.ts +130 -0
  89. package/dist/session/effort-budget.js +334 -0
  90. package/dist/session/index.d.ts +2 -0
  91. package/dist/session/index.js +2 -0
  92. package/dist/session/session-start.d.ts +6 -0
  93. package/dist/session/session-start.js +53 -2
  94. package/dist/session/verify-session-ritual.d.ts +14 -0
  95. package/dist/session/verify-session-ritual.js +33 -0
  96. package/dist/story-ready/evaluate.d.ts +11 -0
  97. package/dist/story-ready/evaluate.js +41 -3
  98. package/dist/triage/actions/candidates-log.d.ts +1 -2
  99. package/dist/triage/actions/candidates-log.js +37 -6
  100. package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
  101. package/dist/xbrief-migrate/migrate-project.js +92 -3
  102. package/package.json +7 -3
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Ceremony dial (#3214): ritual depth = f(task size × model tier × project shape).
2
+ * Ceremony dial (#3214 / #3263): ritual depth = f(task size × model tier × project shape).
3
3
  *
4
4
  * Selection policy over existing pieces — not a new subsystem.
5
5
  * Composes:
@@ -7,11 +7,15 @@
7
7
  * - #3014 minimal consumer AGENTS profile (research pointer; not yet a shipped deposit)
8
8
  * - effort estimate (#1581), model routing (#1976/#818), host capability (#1461)
9
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)
10
+ * Two-stage dial (#3214 design note / #1581 ordering) with tier-conditional cold-start (#3263):
11
+ * cold-start / incomplete size → **tier-conditional**:
12
+ * frontier (or unknown tier) rapid; mid/low standard (not rapid)
13
+ * escalate when evidence arrives (provisional M/L size, full matrix)
13
14
  * Full matrix: S × frontier → rapid; non-project → minimal; else scales up.
14
15
  * Override always available via plan.policy.ceremonyDial.override; audited on write.
16
+ *
17
+ * Audit path (#3263): session:start records depth + provisional reasons on
18
+ * `.deft/ritual-state.json` under `ceremony_dial` — use `readCeremonyDialAudit`.
15
19
  */
16
20
  import { existsSync, readFileSync, statSync } from "node:fs";
17
21
  import { join } from "node:path";
@@ -45,6 +49,9 @@ export const CEREMONY_MINIMAL_AGENTS_PROFILE_POINTER = "docs/analysis/2026-07-31
45
49
  * (`verify:session-ritual --tier=gated`). Gated verify treats
46
50
  * `deferred_reason` as satisfied, so dial-driven deferral would skip gates.
47
51
  * ! `verify_tools` also remains required on every dial depth (not skipFatPath).
52
+ * ! Literal acceptance-command verification (#3267) is required at every depth
53
+ * (`literalAcceptanceRequired: true`), including rapid/minimal — capture exact
54
+ * stated commands at intake and run them verbatim before done. Extends #973.
48
55
  * ~ Rapid/minimal MAY lighten informational cold path only: triage welcome,
49
56
  * optional network/release probe, staleness tickler.
50
57
  */
@@ -55,6 +62,7 @@ const PROFILES = {
55
62
  autoDeferSteps: ["triage_welcome"],
56
63
  skipFatPath: true,
57
64
  lifecycleWrites: "minimal",
65
+ literalAcceptanceRequired: true,
58
66
  label: "minimal (non-project / #3014 direction)",
59
67
  },
60
68
  rapid: {
@@ -62,6 +70,7 @@ const PROFILES = {
62
70
  autoDeferSteps: ["triage_welcome"],
63
71
  skipFatPath: true,
64
72
  lifecycleWrites: "light",
73
+ literalAcceptanceRequired: true,
65
74
  label: "rapid (strategies/rapid.md light path)",
66
75
  },
67
76
  standard: {
@@ -69,6 +78,7 @@ const PROFILES = {
69
78
  autoDeferSteps: [],
70
79
  skipFatPath: false,
71
80
  lifecycleWrites: "full",
81
+ literalAcceptanceRequired: true,
72
82
  label: "standard (full session ritual)",
73
83
  },
74
84
  elevated: {
@@ -76,6 +86,7 @@ const PROFILES = {
76
86
  autoDeferSteps: [],
77
87
  skipFatPath: false,
78
88
  lifecycleWrites: "full",
89
+ literalAcceptanceRequired: true,
79
90
  label: "elevated (full ritual; prefer more gates for weaker tiers / large tasks)",
80
91
  },
81
92
  };
@@ -186,11 +197,29 @@ export function normalizeCeremonyProjectShape(raw) {
186
197
  return null;
187
198
  }
188
199
  /**
189
- * Two-stage / partial-evidence selection when size or tier is missing (#3214 design note).
200
+ * Tier-conditional cold-start depth when task size is incomplete (#3263).
201
+ *
202
+ * Mid/low models benefit from structure on hard tasks and should not cold-start
203
+ * at rapid (start-light / escalate-too-late signature). Frontier recovers from a
204
+ * light start. Unknown tier stays rapid (optimistic cold default; pass
205
+ * `--model-tier` / `DEFT_CEREMONY_MODEL_TIER` to unlock the mid/low floor).
206
+ *
207
+ * Escalate-on-evidence still applies when size later arrives (full matrix).
208
+ */
209
+ export function selectCeremonyColdStartDepth(modelTier) {
210
+ if (modelTier === "mid" || modelTier === "low") {
211
+ return "standard";
212
+ }
213
+ // frontier or unknown → rapid
214
+ return "rapid";
215
+ }
216
+ /**
217
+ * Two-stage / partial-evidence selection when size or tier is missing
218
+ * (#3214 design note / #3263 tier-conditional cold-start).
190
219
  *
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).
220
+ * Cold incomplete size is tier-conditional (#3263): mid/low standard, frontier
221
+ * or unknown rapid. When size is known without tier, escalate on substantial
222
+ * size (apps-bank safety). Does not invent plan-item effort (#1581 post-planning only).
194
223
  */
195
224
  export function selectCeremonyDepthFromPartialEvidence(inputs) {
196
225
  const size = inputs.taskSize;
@@ -203,26 +232,20 @@ export function selectCeremonyDepthFromPartialEvidence(inputs) {
203
232
  return "standard";
204
233
  return "elevated"; // L / XL
205
234
  }
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";
235
+ // Size incomplete (null), any tier including unknown: tier-conditional cold-start (#3263).
236
+ // Callers with both size and tier set use the full matrix instead.
237
+ return selectCeremonyColdStartDepth(tier);
216
238
  }
217
239
  /**
218
- * Pure default matrix (#3214 acceptance + two-stage design note):
240
+ * Pure default matrix (#3214 acceptance + #3263 tier-conditional cold-start):
219
241
  * - non-project → minimal (#3014 direction)
220
242
  * - S × frontier → rapid (strategies/rapid.md)
221
243
  * - S × mid → standard (mid-tier gains from structure)
222
244
  * - S × low → elevated
223
245
  * - M × low → elevated; M otherwise → standard
224
246
  * - L/XL → elevated (except L × frontier stays standard)
225
- * - Incomplete size/tiertwo-stage partial evidence (default rapid; escalate on M/L or mid/low)
247
+ * - Incomplete size → tier-conditional cold-start (#3263): mid/low standard; frontier/unknown rapid
248
+ * - Size known, tier missing → escalate on M/L (partial evidence)
226
249
  */
227
250
  export function selectCeremonyDepthFromMatrix(inputs) {
228
251
  const shape = inputs.projectShape ?? null;
@@ -279,7 +302,7 @@ function parseConfig(raw) {
279
302
  * Deterministic depth selection (pure; no IO).
280
303
  *
281
304
  * Precedence: disabled → standard; override → forced depth; else matrix on inputs;
282
- * empty inputs → **rapid** (two-stage cold default; escalate when evidence arrives).
305
+ * empty inputs → **rapid** (unknown-tier cold default; #3263 mid/low need modelTier set).
283
306
  */
284
307
  export function selectCeremonyDepth(options = {}) {
285
308
  const config = {
@@ -317,8 +340,8 @@ export function selectCeremonyDepth(options = {}) {
317
340
  }
318
341
  const hasAnyInput = inputs.taskSize !== null || inputs.modelTier !== null || inputs.projectShape !== null;
319
342
  if (!hasAnyInput) {
320
- // Two-stage dial cold default (#3214 design note / #1581 ordering).
321
- const depth = "rapid";
343
+ // Unknown-tier cold default (#3214 / #3263): rapid until modelTier or size arrives.
344
+ const depth = selectCeremonyColdStartDepth(null);
322
345
  return {
323
346
  depth,
324
347
  source: "default",
@@ -408,6 +431,7 @@ export function ceremonyDialToDict(selection) {
408
431
  autoDeferSteps: [...selection.profile.autoDeferSteps],
409
432
  skipFatPath: selection.profile.skipFatPath,
410
433
  lifecycleWrites: selection.profile.lifecycleWrites,
434
+ literalAcceptanceRequired: selection.profile.literalAcceptanceRequired,
411
435
  label: selection.profile.label,
412
436
  },
413
437
  composition: {
@@ -826,4 +850,131 @@ export function resolveSessionCeremonyDialInputs(projectRoot, explicit, options
826
850
  provisional,
827
851
  };
828
852
  }
853
+ function asOptionalDepth(value) {
854
+ return isCeremonyDepth(value) ? value : null;
855
+ }
856
+ function asOptionalTaskSize(value) {
857
+ return isTaskSize(value) ? value : null;
858
+ }
859
+ function asOptionalModelTier(value) {
860
+ return isModelTier(value) ? value : null;
861
+ }
862
+ function asOptionalProjectShape(value) {
863
+ return isProjectShape(value) ? value : null;
864
+ }
865
+ /**
866
+ * Read ceremony dial depth + provisional reasons from ritual-state (#3263 audit path).
867
+ * session:start already writes `ceremony_dial` (incl. provisional) — this is the
868
+ * operator/read path so failed runs can confirm start-light vs escalate timing.
869
+ *
870
+ * Returns `error` when ritual-state is missing/unreadable or lacks `ceremony_dial`;
871
+ * does not throw.
872
+ */
873
+ export function readCeremonyDialAudit(projectRoot) {
874
+ const path = join(projectRoot, ".deft", "ritual-state.json");
875
+ const empty = (error) => ({
876
+ path,
877
+ depth: null,
878
+ source: null,
879
+ inputs: null,
880
+ provisional: null,
881
+ raw: null,
882
+ error,
883
+ });
884
+ try {
885
+ if (!existsSync(path) || !statSync(path).isFile()) {
886
+ return empty(`ritual-state missing at ${path}`);
887
+ }
888
+ }
889
+ catch (exc) {
890
+ return empty(`ritual-state unreadable at ${path}: ${String(exc)}`);
891
+ }
892
+ let payload;
893
+ try {
894
+ payload = JSON.parse(readFileSync(path, { encoding: "utf8" }));
895
+ }
896
+ catch (exc) {
897
+ if (exc instanceof SyntaxError) {
898
+ return empty(`ritual-state is not valid JSON: ${exc.message}`);
899
+ }
900
+ return empty(`ritual-state cannot be read: ${String(exc)}`);
901
+ }
902
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
903
+ return empty("ritual-state top-level value must be an object");
904
+ }
905
+ const root = payload;
906
+ const dialRaw = root.ceremony_dial;
907
+ if (dialRaw === undefined || dialRaw === null) {
908
+ return empty("ritual-state has no ceremony_dial field (pre-#3214 or non-mutation session)");
909
+ }
910
+ if (typeof dialRaw !== "object" || Array.isArray(dialRaw)) {
911
+ return empty("ritual-state ceremony_dial must be an object");
912
+ }
913
+ const dial = dialRaw;
914
+ const depth = asOptionalDepth(dial.depth);
915
+ const source = typeof dial.source === "string" ? dial.source : null;
916
+ let inputs = null;
917
+ const inputsRaw = dial.inputs;
918
+ if (typeof inputsRaw === "object" && inputsRaw !== null && !Array.isArray(inputsRaw)) {
919
+ const rec = inputsRaw;
920
+ inputs = {
921
+ taskSize: asOptionalTaskSize(rec.taskSize),
922
+ modelTier: asOptionalModelTier(rec.modelTier),
923
+ projectShape: asOptionalProjectShape(rec.projectShape),
924
+ };
925
+ }
926
+ let provisional = null;
927
+ const provRaw = dial.provisional;
928
+ if (typeof provRaw === "object" && provRaw !== null && !Array.isArray(provRaw)) {
929
+ const rec = provRaw;
930
+ const reasons = Array.isArray(rec.reasons)
931
+ ? rec.reasons.filter((r) => typeof r === "string")
932
+ : [];
933
+ provisional = {
934
+ taskSize: asOptionalTaskSize(rec.taskSize),
935
+ modelTier: asOptionalModelTier(rec.modelTier),
936
+ projectShape: asOptionalProjectShape(rec.projectShape),
937
+ reasons,
938
+ };
939
+ }
940
+ return {
941
+ path,
942
+ depth,
943
+ source,
944
+ inputs,
945
+ provisional,
946
+ raw: { ...dial },
947
+ error: null,
948
+ };
949
+ }
950
+ /** Collapse whitespace so operator audit lines stay single-line (Greptile P2 #3263). */
951
+ function oneLineAuditToken(value) {
952
+ return value
953
+ .replace(/[\r\n\t]+/g, " ")
954
+ .replace(/ {2,}/g, " ")
955
+ .trim();
956
+ }
957
+ /** One-line operator summary for audit tooling / failed-task forensics (#3263). */
958
+ export function formatCeremonyDialAuditLine(audit) {
959
+ if (audit.error !== null) {
960
+ return `[deft ceremony-dial audit] error=${oneLineAuditToken(audit.error)}`;
961
+ }
962
+ const parts = [
963
+ `[deft ceremony-dial audit] depth=${audit.depth ?? "-"}`,
964
+ `source=${audit.source ?? "-"}`,
965
+ `taskSize=${audit.inputs?.taskSize ?? "-"}`,
966
+ `modelTier=${audit.inputs?.modelTier ?? "-"}`,
967
+ `projectShape=${audit.inputs?.projectShape ?? "-"}`,
968
+ ];
969
+ if (audit.provisional !== null) {
970
+ const reasons = audit.provisional.reasons.length > 0
971
+ ? audit.provisional.reasons
972
+ .map(oneLineAuditToken)
973
+ .filter((r) => r.length > 0)
974
+ .join("; ")
975
+ : "(none)";
976
+ parts.push(`provisional.reasons=${reasons.length > 0 ? reasons : "(none)"}`);
977
+ }
978
+ return parts.join(" ");
979
+ }
829
980
  //# sourceMappingURL=ceremony-dial.js.map
@@ -13,6 +13,7 @@ export * from "./host-hooks.js";
13
13
  export * from "./host-slash-commands.js";
14
14
  export * from "./hotfix-criteria.js";
15
15
  export * from "./intent-ceiling.js";
16
+ export * from "./merge-approval-head.js";
16
17
  export * from "./min-greptile-confidence.js";
17
18
  export * from "./no-deft-directive.js";
18
19
  export * from "./org-force-on-migration.js";
@@ -31,6 +31,7 @@ export * from "./host-hooks.js";
31
31
  export * from "./host-slash-commands.js";
32
32
  export * from "./hotfix-criteria.js";
33
33
  export * from "./intent-ceiling.js";
34
+ export * from "./merge-approval-head.js";
34
35
  export * from "./min-greptile-confidence.js";
35
36
  export * from "./no-deft-directive.js";
36
37
  export * from "./org-force-on-migration.js";
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Head-bound human merge approval (#3235).
3
+ *
4
+ * Phase 5 → 6 `plan:approved` events authorize one immutable PR head.
5
+ * When PR HEAD changes after approval (and GitHub auto-merge may still be
6
+ * enabled), Directive fails closed: treats the approval as stale, attempts to
7
+ * disable auto-merge, and emits recovery text requiring a fresh approval bound
8
+ * to the current head.
9
+ *
10
+ * Prefer this path from finish-loop / pr-wait-mergeable / require-human-merge
11
+ * consumers. Does not change consumer repository rulesets.
12
+ */
13
+ import { type BehavioralEventRecord } from "../lifecycle/events.js";
14
+ /** Event name recorded at Phase 5 → 6 approval. */
15
+ export declare const PLAN_APPROVED_EVENT = "plan:approved";
16
+ export type MergeApprovalHeadStatus =
17
+ /** approved_head_sha matches current_head_sha. */
18
+ "ok"
19
+ /** Approval bound to a different head than current. */
20
+ | "stale"
21
+ /** Approval exists but was not bound to a head SHA. */
22
+ | "missing_binding"
23
+ /** No plan:approved record for this PR. */
24
+ | "no_approval"
25
+ /** Gate intentionally skipped (e.g. no PR / no current head). */
26
+ | "skipped";
27
+ export interface PlanApprovedRecord {
28
+ readonly head_sha: string | null;
29
+ readonly pr_number: number | null;
30
+ readonly approver: string | null;
31
+ readonly plan_ref: string | null;
32
+ readonly detected_at: string | null;
33
+ readonly payload: Readonly<Record<string, unknown>>;
34
+ }
35
+ export interface MergeApprovalHeadResult {
36
+ readonly status: MergeApprovalHeadStatus;
37
+ /** False when merge must not proceed under head-bound contract. */
38
+ readonly allowed: boolean;
39
+ readonly approved_head_sha: string | null;
40
+ readonly current_head_sha: string | null;
41
+ readonly pr_number: number;
42
+ readonly require_human_merge: boolean;
43
+ readonly auto_merge_disabled: boolean | null;
44
+ readonly message: string;
45
+ readonly recovery: string | null;
46
+ }
47
+ export type DisableAutoMergeFn = (prNumber: number, repo: string | null) => {
48
+ readonly ok: boolean;
49
+ readonly stderr: string;
50
+ };
51
+ export type FetchPrHeadShaFn = (prNumber: number, repo: string | null) => string | null;
52
+ export type ReadEventsFn = (logPath?: string | null) => BehavioralEventRecord[];
53
+ /** Full Git object id length — approvals must bind to exact heads (#3235 / Greptile P1). */
54
+ export declare const FULL_HEAD_SHA_LEN = 40;
55
+ /**
56
+ * Exact full-SHA equality only (#3235).
57
+ * Prefix matching is forbidden: abbreviated approval SHAs must not authorize a later head.
58
+ */
59
+ export declare function headShaMatches(a: string | null, b: string | null): boolean;
60
+ /**
61
+ * Latest plan:approved for a PR (by log order; last write wins).
62
+ * When `repo` is set, prefer records whose repository/plan_ref matches (P2 #3235).
63
+ */
64
+ export declare function findLatestPlanApprovalForPr(prNumber: number, options?: {
65
+ readonly logPath?: string | null;
66
+ readonly readEventsFn?: ReadEventsFn;
67
+ readonly records?: readonly BehavioralEventRecord[];
68
+ /** owner/repo — scopes lookup when multiple remotes share a PR number. */
69
+ readonly repo?: string | null;
70
+ }): PlanApprovedRecord | null;
71
+ /** Recovery instructions when approval is stale or unbound. */
72
+ export declare function buildMergeApprovalRecovery(input: {
73
+ readonly prNumber: number;
74
+ readonly approvedHeadSha: string | null;
75
+ readonly currentHeadSha: string | null;
76
+ readonly status: MergeApprovalHeadStatus;
77
+ readonly autoMergeDisabled: boolean | null;
78
+ }): string;
79
+ export interface EvaluateMergeApprovalHeadInput {
80
+ readonly prNumber: number;
81
+ readonly currentHeadSha: string | null;
82
+ readonly projectRoot?: string;
83
+ readonly logPath?: string | null;
84
+ readonly requireHumanMerge?: boolean;
85
+ /** owner/repo for scoped approval lookup. */
86
+ readonly repo?: string | null;
87
+ /**
88
+ * When true (default), approval without head_sha fails closed under
89
+ * requireHumanMerge. When false, missing binding is advisory only.
90
+ */
91
+ readonly enforceStrictBinding?: boolean;
92
+ /**
93
+ * When true, run the gate whenever a plan:approved exists for the PR
94
+ * (default). When false, only under requireHumanMerge.
95
+ */
96
+ readonly enforceWhenApprovalPresent?: boolean;
97
+ readonly readEventsFn?: ReadEventsFn;
98
+ readonly records?: readonly BehavioralEventRecord[];
99
+ readonly approval?: PlanApprovedRecord | null;
100
+ }
101
+ /**
102
+ * Pure evaluation: compare recorded approval head to current PR HEAD.
103
+ */
104
+ export declare function evaluateMergeApprovalHead(input: EvaluateMergeApprovalHeadInput): MergeApprovalHeadResult;
105
+ /** Live `gh pr merge <N> --disable-auto` (best-effort). */
106
+ export declare function disablePullRequestAutoMerge(prNumber: number, repo: string | null, options?: {
107
+ readonly timeoutSec?: number;
108
+ }): {
109
+ readonly ok: boolean;
110
+ readonly stderr: string;
111
+ };
112
+ /** REST HEAD read via `gh api repos/.../pulls/N --jq .head.sha`. */
113
+ export declare function fetchPrHeadShaRest(prNumber: number, repo: string | null, options?: {
114
+ readonly timeoutSec?: number;
115
+ }): string | null;
116
+ export interface EnforceMergeApprovalHeadInput extends EvaluateMergeApprovalHeadInput {
117
+ readonly repo?: string | null;
118
+ /** When true (default on deny), attempt gh --disable-auto. */
119
+ readonly disableAutoMergeOnDeny?: boolean;
120
+ readonly disableAutoMergeFn?: DisableAutoMergeFn;
121
+ readonly fetchHeadShaFn?: FetchPrHeadShaFn;
122
+ }
123
+ /**
124
+ * Evaluate + on deny attempt to disable GitHub auto-merge and attach recovery.
125
+ */
126
+ export declare function enforceMergeApprovalHead(input: EnforceMergeApprovalHeadInput): MergeApprovalHeadResult;
127
+ /** Default event log path relative to project root. */
128
+ export declare function defaultMergeApprovalEventLog(projectRoot: string): string;
129
+ //# sourceMappingURL=merge-approval-head.d.ts.map