@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.
- package/dist/authz/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +157 -23
- package/dist/authz/decompose-apply.d.ts +67 -0
- package/dist/authz/decompose-apply.js +302 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +72 -79
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/intake/issue-ingest.js +29 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ceremony-dial.d.ts +67 -11
- package/dist/policy/ceremony-dial.js +174 -23
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/render/framework-commands.js +4 -0
- package/dist/scope/acceptance-evidence.d.ts +76 -0
- package/dist/scope/acceptance-evidence.js +348 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.js +125 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/effort-budget.d.ts +130 -0
- package/dist/session/effort-budget.js +334 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/session-start.d.ts +6 -0
- package/dist/session/session-start.js +53 -2
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- 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
|
|
12
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
192
|
-
*
|
|
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
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
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 +
|
|
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
|
|
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** (
|
|
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
|
-
//
|
|
321
|
-
const depth =
|
|
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
|
package/dist/policy/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/policy/index.js
CHANGED
|
@@ -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
|