forge-workflow 0.1.0-beta.4 → 0.1.0-beta.6
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/AGENTS.md +18 -7
- package/CHANGELOG.md +79 -1
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/README.md +6 -2
- package/bin/forge-cmd.js +20 -0
- package/bin/forge.js +28 -375
- package/docs/INDEX.md +1 -1
- package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
- package/docs/guides/MIGRATION.md +4 -4
- package/docs/guides/SETUP.md +16 -16
- package/docs/reference/COMMANDS.md +8 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSIGHTS_RECAP.md +9 -20
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/TOOLCHAIN.md +8 -0
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/protected-state-surfaces.md +4 -4
- package/docs/reference/shepherd.md +114 -35
- package/lefthook.yml +12 -0
- package/lib/activation/ensure-forge-home.js +33 -15
- package/lib/adapters/pr-state-adapter.js +359 -144
- package/lib/audit-evidence.js +71 -110
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/capped-jsonl-log.js +236 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +50 -20
- package/lib/commands/clean.js +252 -32
- package/lib/commands/dev.js +4 -33
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +276 -30
- package/lib/commands/insights.js +8 -3
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +1265 -58
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +3 -1
- package/lib/commands/preflight.js +21 -4
- package/lib/commands/prime.js +21 -8
- package/lib/commands/push.js +146 -54
- package/lib/commands/recall.js +127 -49
- package/lib/commands/recap.js +6 -1
- package/lib/commands/release.js +39 -3
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +323 -98
- package/lib/commands/shepherd.js +591 -73
- package/lib/commands/ship.js +36 -91
- package/lib/commands/skill.js +127 -11
- package/lib/commands/status.js +17 -1
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +187 -38
- package/lib/commands/validate.js +65 -21
- package/lib/commands/worktree.js +359 -45
- package/lib/core/runtime-graph.js +1 -1
- package/lib/doc-assertions.js +297 -0
- package/lib/existing-tdd-gate.js +253 -0
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-context.js +1 -4
- package/lib/forge-issues.js +134 -32
- package/lib/gate-events.js +98 -10
- package/lib/git-defaults.js +56 -0
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/harness-capability-matrix.js +3 -3
- package/lib/hook-renderer.js +122 -5
- package/lib/insights.js +96 -80
- package/lib/issue-render.js +19 -0
- package/lib/kernel/backing-issue.js +14 -2
- package/lib/kernel/broker.js +739 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/cli-broker-factory.js +12 -1
- package/lib/kernel/close-on-merge.js +154 -0
- package/lib/kernel/fs-class.js +42 -25
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3435 -172
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/lefthook-wiring.js +21 -1
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +110 -28
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +106 -15
- package/lib/memory-recall-events.js +145 -0
- package/lib/memory-recall.js +71 -10
- package/lib/merge-rules.js +143 -21
- package/lib/npm-publish-workflow.js +465 -0
- package/lib/orientation.js +68 -43
- package/lib/package-root.js +2 -0
- package/lib/plugin-catalog.js +14 -4
- package/lib/pr-bundle.js +5 -6
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +18 -39
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1129 -470
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +293 -0
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +38 -20
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +145 -27
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-pull.js +33 -14
- package/lib/pr-shepherd.js +51 -11
- package/lib/preflight/gates.js +65 -18
- package/lib/preflight/runner.js +5 -0
- package/lib/project-memory.js +178 -4
- package/lib/protected-state-authority.js +1100 -0
- package/lib/protected-state-surfaces.js +243 -45
- package/lib/release-readiness.js +53 -7
- package/lib/review-adapter.js +65 -0
- package/lib/shell-utils.js +1 -1
- package/lib/skills-sync.js +71 -35
- package/lib/smart-merge.js +28 -4
- package/lib/symlink-utils.js +74 -26
- package/lib/upgrade-safety.js +39 -0
- package/lib/using-forge.js +19 -6
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -0
- package/package.json +12 -9
- package/scripts/commitlint.js +13 -15
- package/scripts/doc-asserting-tests.js +158 -0
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runner.js +310 -0
- package/scripts/lib/behavioral-eval-runtime.js +457 -0
- package/scripts/lib/eval-evidence.js +328 -0
- package/scripts/lib/eval-runner.js +81 -41
- package/scripts/lib/immutable-eval-corpus.js +309 -0
- package/scripts/lib/promotion-evidence-loader.js +94 -0
- package/scripts/lib/promotion-scorecard.js +314 -0
- package/scripts/npm-release-receipt.js +134 -0
- package/scripts/process-tree.js +773 -0
- package/scripts/protected-state-check.js +479 -31
- package/scripts/run-command-eval.js +29 -1
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/sync-d20-audit.js +172 -0
- package/scripts/test-full-suite.js +935 -37
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +271 -57
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +6 -11
- package/skills/review/evals/scorecard.json +4 -4
- package/skills/rollback/SKILL.md +4 -11
- package/skills/rollback/evals/scorecard.json +3 -3
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +39 -16
- package/skills/shepherd/evals/scorecard.json +4 -4
- package/skills/ship/SKILL.md +4 -12
- package/skills/ship/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
- package/skills/worktree/SKILL.md +6 -1
- package/skills/worktree/evals/scorecard.json +2 -2
- package/lib/beads-setup.js +0 -538
- package/lib/beads-sync-scaffold.js +0 -189
- package/lib/pat-setup.js +0 -207
- package/lib/pr-monitor/render-sticky.js +0 -206
- package/lib/pr-monitor/upsert-sticky.js +0 -169
- package/scripts/beads-context.sh +0 -577
- package/scripts/beads-migrate-to-dolt.sh +0 -7
- package/scripts/beads-upgrade-smoke.sh +0 -284
- package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
package/lib/merge-rules.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* PR context shape (all fields optional; absence is treated fail-closed by the
|
|
23
23
|
* rules that need them):
|
|
24
|
-
* - checks: Array<{ name, conclusion }
|
|
24
|
+
* - checks: Array<{ name, appId, status, conclusion } | { name, appId:null, state }>
|
|
25
|
+
* - requiredChecks: Array<{ context, appId }> protected required set
|
|
25
26
|
* - requiredChecksKnown: boolean is the required set readable?
|
|
26
27
|
* - unresolvedThreads: number | Array open review threads
|
|
27
28
|
* - behindBase: number | boolean commits behind base
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
* - state: string (OPEN | MERGED | CLOSED) read by the command pre-flight, not a rule
|
|
35
36
|
*
|
|
36
37
|
* Built-in rule types (compose as a list, ANDed by default):
|
|
37
|
-
* checks_green | threads_resolved | not_behind | no_conflicts | not_draft |
|
|
38
|
+
* verdict_clean | checks_green | threads_resolved | not_behind | no_conflicts | not_draft |
|
|
38
39
|
* min_approvals:N | settle_min:N | idle_min:N | last_comment_by:X |
|
|
39
40
|
* approved_by:[..] | not_commented_by:[..]
|
|
40
41
|
* `checks_green` is bare (ALL checks must be green) or scoped:
|
|
@@ -48,14 +49,21 @@
|
|
|
48
49
|
* documented follow-up and intentionally NOT implemented here.
|
|
49
50
|
*
|
|
50
51
|
* Follow-ups (documented, NOT built here): an opt-in `auto_update` executor
|
|
51
|
-
* (update-branch when behind → wait for CI → re-check → merge);
|
|
52
|
-
* SCOPING for `checks_green` (read the branch-protection required set instead of
|
|
53
|
-
* requiring every rollup check green); a configurable merge `method`
|
|
52
|
+
* (update-branch when behind → wait for CI → re-check → merge); a configurable merge `method`
|
|
54
53
|
* (squash | merge | rebase); and post-merge branch deletion.
|
|
55
54
|
*/
|
|
56
55
|
|
|
57
|
-
const SUCCESS_CONCLUSIONS = new Set(['SUCCESS'
|
|
56
|
+
const SUCCESS_CONCLUSIONS = new Set(['SUCCESS']);
|
|
57
|
+
const SAFE_OPTIONAL_CONCLUSIONS = new Set(['SUCCESS', 'NEUTRAL', 'SKIPPED']);
|
|
58
58
|
const MS_PER_MIN = 60_000;
|
|
59
|
+
const {
|
|
60
|
+
normalizePrNumber,
|
|
61
|
+
normalizeRepository,
|
|
62
|
+
normalizeSha,
|
|
63
|
+
object,
|
|
64
|
+
readArrayItems,
|
|
65
|
+
readDataProperty,
|
|
66
|
+
} = require('./pr-monitor/verdict');
|
|
59
67
|
|
|
60
68
|
/** Coerce an epoch-ms number, a Date, or an ISO string to epoch ms (NaN if unparseable). */
|
|
61
69
|
function toMs(value) {
|
|
@@ -85,11 +93,15 @@ function toAccountList(arg) {
|
|
|
85
93
|
return [String(arg).trim().toLowerCase()];
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
/** A CI check is green
|
|
96
|
+
/** A CI check is green only when it is terminal and explicitly successful. */
|
|
89
97
|
function isCheckGreen(check) {
|
|
90
98
|
if (!check || typeof check !== 'object') return false;
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
if (Object.prototype.hasOwnProperty.call(check, 'state')
|
|
100
|
+
&& !Object.prototype.hasOwnProperty.call(check, 'status')) {
|
|
101
|
+
return String(check.state || '').toUpperCase() === 'SUCCESS';
|
|
102
|
+
}
|
|
103
|
+
return String(check.status || '').toUpperCase() === 'COMPLETED'
|
|
104
|
+
&& SUCCESS_CONCLUSIONS.has(String(check.conclusion || '').toUpperCase());
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
/** The most recent comment by timestamp; ties and unparseable stamps fall back to array order. */
|
|
@@ -123,16 +135,49 @@ function checkName(check) {
|
|
|
123
135
|
return String((check && check.name) || '');
|
|
124
136
|
}
|
|
125
137
|
|
|
138
|
+
function isCheckObservationWellFormed(check) {
|
|
139
|
+
if (!check || typeof check !== 'object' || typeof check.name !== 'string' || !check.name.trim()
|
|
140
|
+
|| !Object.prototype.hasOwnProperty.call(check, 'appId')) return false;
|
|
141
|
+
const hasState = Object.prototype.hasOwnProperty.call(check, 'state');
|
|
142
|
+
const hasStatus = Object.prototype.hasOwnProperty.call(check, 'status');
|
|
143
|
+
const hasConclusion = Object.prototype.hasOwnProperty.call(check, 'conclusion');
|
|
144
|
+
if (hasState) {
|
|
145
|
+
return !hasStatus && !hasConclusion && check.appId === null
|
|
146
|
+
&& typeof check.state === 'string' && Boolean(check.state.trim());
|
|
147
|
+
}
|
|
148
|
+
return hasStatus && hasConclusion && Number.isInteger(check.appId) && check.appId > 0
|
|
149
|
+
&& typeof check.status === 'string' && Boolean(check.status.trim())
|
|
150
|
+
&& (check.conclusion === null || typeof check.conclusion === 'string');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function normalizeRequiredCheck(check) {
|
|
154
|
+
if (!check || typeof check !== 'object' || typeof check.context !== 'string' || !check.context.trim()
|
|
155
|
+
|| !Object.prototype.hasOwnProperty.call(check, 'appId')
|
|
156
|
+
|| (check.appId !== null && (!Number.isInteger(check.appId) || check.appId <= 0))) return null;
|
|
157
|
+
return { context: check.context, appId: check.appId };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function matchesRequiredCheck(check, required) {
|
|
161
|
+
return checkName(check) === required.context
|
|
162
|
+
&& (required.appId === null || check.appId === required.appId);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isSafeOptionalCheck(check) {
|
|
166
|
+
if (Object.prototype.hasOwnProperty.call(check, 'state')) return isCheckGreen(check);
|
|
167
|
+
return String(check.status).toUpperCase() === 'COMPLETED'
|
|
168
|
+
&& SAFE_OPTIONAL_CONCLUSIONS.has(String(check.conclusion || '').toUpperCase());
|
|
169
|
+
}
|
|
170
|
+
|
|
126
171
|
/**
|
|
127
|
-
* `checks_green` —
|
|
128
|
-
*
|
|
129
|
-
* -
|
|
130
|
-
*
|
|
172
|
+
* `checks_green` — protected checks must be strict SUCCESS; optional check runs
|
|
173
|
+
* may also be terminal NEUTRAL or SKIPPED. Optionally scoped:
|
|
174
|
+
* - bare string / no arg → protected checks strict, optional checks safely terminal.
|
|
175
|
+
* - { ignore: ["A","B"] } → the named optional checks are EXEMPT;
|
|
176
|
+
* `ignore: []` is identical to the bare form.
|
|
131
177
|
* - { only: ["A"] } → ONLY the named checks must be green (present AND
|
|
132
|
-
* green per `isCheckGreen`); all others are ignored.
|
|
178
|
+
* green per `isCheckGreen`); all optional others are ignored.
|
|
133
179
|
* `ignore` + `only` together is malformed → fail-closed. Exact name match.
|
|
134
|
-
*
|
|
135
|
-
* documented follow-up, not built here.)
|
|
180
|
+
* The protected required set is always enforced before optional scoping.
|
|
136
181
|
*/
|
|
137
182
|
function checksGreen(ctx, arg) {
|
|
138
183
|
let ignore = [];
|
|
@@ -164,14 +209,42 @@ function checksGreen(ctx, arg) {
|
|
|
164
209
|
if (!Array.isArray(ctx.checks)) {
|
|
165
210
|
return fail('check results are missing from the PR context (fail-closed).');
|
|
166
211
|
}
|
|
212
|
+
if (!Array.isArray(ctx.requiredChecks)) {
|
|
213
|
+
return fail('required-check set is missing or malformed (fail-closed).');
|
|
214
|
+
}
|
|
215
|
+
if (ctx.checks.some((check) => !isCheckObservationWellFormed(check))) {
|
|
216
|
+
return fail('check results contain malformed observations (fail-closed).');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const required = ctx.requiredChecks.map(normalizeRequiredCheck);
|
|
220
|
+
if (required.some((check) => !check)) {
|
|
221
|
+
return fail('required-check set contains malformed entries (fail-closed).');
|
|
222
|
+
}
|
|
223
|
+
const requiredApps = new Map();
|
|
224
|
+
for (const check of required) {
|
|
225
|
+
if (!requiredApps.has(check.context)) requiredApps.set(check.context, new Set());
|
|
226
|
+
requiredApps.get(check.context).add(check.appId === null ? '*' : String(check.appId));
|
|
227
|
+
}
|
|
228
|
+
if ([...requiredApps.values()].some((apps) => apps.size > 1)) {
|
|
229
|
+
return fail('required-check set contains conflicting application identities (fail-closed).');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const requiredProblems = required.map((requiredCheck) => {
|
|
233
|
+
const matches = ctx.checks.filter((check) => matchesRequiredCheck(check, requiredCheck));
|
|
234
|
+
if (matches.length === 0) return `${requiredCheck.context} (missing)`;
|
|
235
|
+
if (matches.some((check) => !isCheckGreen(check))) return `${requiredCheck.context} (not successful)`;
|
|
236
|
+
return null;
|
|
237
|
+
}).filter(Boolean);
|
|
238
|
+
if (requiredProblems.length > 0) {
|
|
239
|
+
return fail(`protected required check(s) not green: ${requiredProblems.join(', ')}.`);
|
|
240
|
+
}
|
|
167
241
|
|
|
168
242
|
if (only) {
|
|
169
243
|
// Only the named checks matter — each must be PRESENT and green.
|
|
170
|
-
const byName = new Map(ctx.checks.map((c) => [checkName(c), c]));
|
|
171
244
|
const problems = only.map((name) => {
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
174
|
-
if (!isCheckGreen(check)) return `${name} (
|
|
245
|
+
const matches = ctx.checks.filter((check) => checkName(check) === name);
|
|
246
|
+
if (matches.length === 0) return `${name} (missing)`;
|
|
247
|
+
if (matches.some((check) => !isCheckGreen(check))) return `${name} (not green)`;
|
|
175
248
|
return null;
|
|
176
249
|
}).filter(Boolean);
|
|
177
250
|
if (problems.length > 0) return fail(`required check(s) not green: ${problems.join(', ')}.`);
|
|
@@ -180,7 +253,9 @@ function checksGreen(ctx, arg) {
|
|
|
180
253
|
|
|
181
254
|
// Default / ignore: every check EXCEPT the exempt ones must be green.
|
|
182
255
|
const exempt = new Set(ignore);
|
|
183
|
-
const notGreen = ctx.checks.filter((
|
|
256
|
+
const notGreen = ctx.checks.filter((check) => !exempt.has(checkName(check))
|
|
257
|
+
&& !required.some((requiredCheck) => matchesRequiredCheck(check, requiredCheck))
|
|
258
|
+
&& !isSafeOptionalCheck(check));
|
|
184
259
|
if (notGreen.length > 0) {
|
|
185
260
|
const names = notGreen.map((c) => (c && c.name) || '?').join(', ');
|
|
186
261
|
return fail(`${notGreen.length} check(s) not green: ${names}.`);
|
|
@@ -223,6 +298,52 @@ function notDraft(ctx) {
|
|
|
223
298
|
return fail('draft status is unknown (fail-closed).');
|
|
224
299
|
}
|
|
225
300
|
|
|
301
|
+
/** Require the pure reducer's positive verdict to match the caller's immutable head lease. */
|
|
302
|
+
function verdictClean(ctx) {
|
|
303
|
+
const verdict = object(readDataProperty(ctx, 'verdict').value);
|
|
304
|
+
if (!verdict) {
|
|
305
|
+
return fail('current-head verdict is missing or malformed (fail-closed).');
|
|
306
|
+
}
|
|
307
|
+
const repository = normalizeRepository(readDataProperty(ctx, 'repository').value);
|
|
308
|
+
const expectedRepository = normalizeRepository(readDataProperty(ctx, 'expectedRepository').value);
|
|
309
|
+
const prNumber = normalizePrNumber(readDataProperty(ctx, 'prNumber').value);
|
|
310
|
+
const expectedPrNumber = normalizePrNumber(readDataProperty(ctx, 'expectedPrNumber').value);
|
|
311
|
+
const head = normalizeSha(readDataProperty(ctx, 'headSha').value);
|
|
312
|
+
const expected = normalizeSha(readDataProperty(ctx, 'expectedHeadSha').value);
|
|
313
|
+
const base = normalizeSha(readDataProperty(ctx, 'baseSha').value);
|
|
314
|
+
const expectedBase = normalizeSha(readDataProperty(ctx, 'expectedBaseSha').value);
|
|
315
|
+
const verdictRepository = normalizeRepository(readDataProperty(verdict, 'repository').value);
|
|
316
|
+
const verdictPrNumber = normalizePrNumber(readDataProperty(verdict, 'prNumber').value);
|
|
317
|
+
const verdictHead = normalizeSha(readDataProperty(verdict, 'headSha').value);
|
|
318
|
+
const verdictBase = normalizeSha(readDataProperty(verdict, 'baseSha').value);
|
|
319
|
+
const verdictState = readDataProperty(verdict, 'state').value;
|
|
320
|
+
const verdictReasons = readArrayItems(readDataProperty(verdict, 'reasons').value);
|
|
321
|
+
if (!head || !expected || !base || !expectedBase || !verdictHead || !verdictBase) {
|
|
322
|
+
return fail('exact head lease, verdict head, or verdict base is missing or malformed (fail-closed).');
|
|
323
|
+
}
|
|
324
|
+
if (!repository || !expectedRepository || !prNumber || !expectedPrNumber
|
|
325
|
+
|| !verdictRepository || !verdictPrNumber) {
|
|
326
|
+
return fail('repository or PR authority is missing or malformed (fail-closed).');
|
|
327
|
+
}
|
|
328
|
+
if (repository !== expectedRepository || verdictRepository !== repository
|
|
329
|
+
|| prNumber !== expectedPrNumber || verdictPrNumber !== prNumber) {
|
|
330
|
+
return fail('current-head verdict does not match the exact repository and PR authority.');
|
|
331
|
+
}
|
|
332
|
+
if (head !== expected || verdictHead !== head) {
|
|
333
|
+
return fail('current-head verdict does not match the exact caller head lease.');
|
|
334
|
+
}
|
|
335
|
+
if (base !== expectedBase || verdictBase !== base) {
|
|
336
|
+
return fail('current-head verdict does not match the exact caller base lease.');
|
|
337
|
+
}
|
|
338
|
+
if (verdictState !== 'MERGE_READY') {
|
|
339
|
+
return fail(`current-head verdict is ${String(verdictState || 'INCOMPLETE')}, not MERGE_READY.`);
|
|
340
|
+
}
|
|
341
|
+
if (!verdictReasons || verdictReasons.length > 0) {
|
|
342
|
+
return fail('MERGE_READY verdict carries incomplete or contradictory reasons (fail-closed).');
|
|
343
|
+
}
|
|
344
|
+
return pass();
|
|
345
|
+
}
|
|
346
|
+
|
|
226
347
|
function minApprovals(ctx, arg) {
|
|
227
348
|
const need = Number.parseInt(arg, 10);
|
|
228
349
|
if (!Number.isFinite(need)) return fail('min_approvals requires a numeric argument, e.g. "min_approvals:2".');
|
|
@@ -306,6 +427,7 @@ function notWrapper(ctx, arg) {
|
|
|
306
427
|
}
|
|
307
428
|
|
|
308
429
|
const PREDICATES = {
|
|
430
|
+
verdict_clean: (ctx) => verdictClean(ctx),
|
|
309
431
|
checks_green: (ctx, arg) => checksGreen(ctx, arg),
|
|
310
432
|
threads_resolved: (ctx) => threadsResolved(ctx),
|
|
311
433
|
not_behind: (ctx) => notBehind(ctx),
|