@goodtek/vibeops 3.1.8 → 3.1.9
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/CHANGELOG.md +6 -0
- package/README.md +1 -0
- package/dist/lib/check-rollup.js +9 -0
- package/dist/lib/github-actions-gate.js +14 -1
- package/dist/lib/pr-create.js +11 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to VibeOps are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 3.1.9 - 2026-10-17
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **The merge gate was blocked by GitHub's dynamic workflow runs.** A failed Dependabot run on the head commit (`event: dynamic`, `dynamic/dependabot/dependabot-updates`) refused a PR whose CI was green — and such runs cannot be re-run. Dynamic runs and `(dynamic)` check runs are now ignored; a `(dynamic)` check named in `requiredChecks` is still required (D-011, TASK-043).
|
|
12
|
+
|
|
7
13
|
## 3.1.8 - 2026-10-17
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/README.md
CHANGED
|
@@ -218,6 +218,7 @@ Before calling `gh pr merge` / `glab mr merge`:
|
|
|
218
218
|
- **Expected workflows:** every `.github/workflows/*.yml|yaml` at the head commit whose `on:` triggers `pull_request` for this PR, using GitHub's filter rules — `branches` / `branches-ignore` against the base branch, `paths` / `paths-ignore` against the PR's changed files (first 300, like GitHub), and `types` (a custom list must contain both `opened` and `synchronize`). Disabled workflows are skipped; `pull_request_target`, `workflow_call` and other events are not awaited. Each expected workflow must have a `pull_request` run for the head SHA — until it appears the gate **waits** (`workflow … (pull_request) has not started for <sha> yet`), then refuses at `waitTimeoutSeconds`.
|
|
219
219
|
- **All runs for the head SHA** (any event, latest run per workflow + event) must be `completed` with `success`, `neutral` or `skipped`. A queued / in-progress / waiting run waits; any other conclusion refuses immediately — including `startup_failure` (no jobs, so nothing in the rollup) and `action_required` (fork PR awaiting approval).
|
|
220
220
|
- The Actions API is required: if it cannot be read, the gate keeps waiting (back-off) and refuses at the timeout — it never merges blind.
|
|
221
|
+
- **Dynamic workflows are not CI:** runs GitHub generates on the fly — `event: dynamic`, e.g. `dynamic/dependabot/dependabot-updates`, the jobs that open Dependabot PRs — and check runs suffixed `(dynamic)` are ignored. GitHub documents them as "generated for a specific run and … not stored in your repository's `.github/workflows` directory" ([Dependabot on GitHub Actions runners](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/about-dependabot-on-github-actions-runners)); a failed one says nothing about the commit and cannot be re-run. A `(dynamic)` check named in `requiredChecks` is still required.
|
|
221
222
|
- **GitLab:** the MR head pipeline must be `success` or `skipped`. `failed`, `canceled`, `manual` and unknown statuses refuse; `created` / `pending` / `running` / `scheduled` / … wait.
|
|
222
223
|
- **Host output is verified:** if `gh pr view` lacks or has empty `state`, `mergeable`, `headRefOid`, or `statusCheckRollup` is not a list (GitLab: `state`, `sha`, `merge_status`, `detailed_merge_status`, `head_pipeline`), the merge is refused.
|
|
223
224
|
- **Failed check → refuse immediately** (exit code 1, checks named). **Pending → wait** up to `waitTimeoutSeconds`, then refuse. There is no override flag: fix CI (re-run `task ship`) and rerun `task merge`.
|
package/dist/lib/check-rollup.js
CHANGED
|
@@ -94,6 +94,15 @@ export function checkNameMatches(name, pattern) {
|
|
|
94
94
|
* - any pending, or a required check not (yet) present → pending
|
|
95
95
|
* - otherwise green (including "no checks at all" when nothing is required)
|
|
96
96
|
*/
|
|
97
|
+
/**
|
|
98
|
+
* Check runs GitHub suffixes with " (dynamic)" belong to dynamic workflows (see
|
|
99
|
+
* `isDynamicRun` in github-actions-gate.ts), not to the commit's CI: dropped unless a required
|
|
100
|
+
* check pattern names them.
|
|
101
|
+
*/
|
|
102
|
+
export function withoutDynamicChecks(checks, requiredChecks = []) {
|
|
103
|
+
return checks.filter((c) => !(c.kind === "CheckRun" && c.name.endsWith(" (dynamic)")) ||
|
|
104
|
+
requiredChecks.some((p) => checkNameMatches(c.name, p)));
|
|
105
|
+
}
|
|
97
106
|
export function evaluateCheckGate(checks, requiredChecks = []) {
|
|
98
107
|
const failed = checks.filter((c) => c.verdict === "failed");
|
|
99
108
|
const pending = checks.filter((c) => c.verdict === "pending");
|
|
@@ -171,6 +171,17 @@ export async function workflowExpectedForPullRequest(yamlText, ctx) {
|
|
|
171
171
|
return { expected: true };
|
|
172
172
|
}
|
|
173
173
|
const RUN_PASS = new Set(["success", "neutral", "skipped"]);
|
|
174
|
+
/**
|
|
175
|
+
* GitHub-generated runs that are not the commit's CI: "dynamic workflows are generated for a
|
|
176
|
+
* specific run and are not stored in your repository's `.github/workflows` directory" — e.g.
|
|
177
|
+
* the jobs that generate Dependabot pull requests, `dynamic/dependabot/dependabot-updates`
|
|
178
|
+
* (docs.github.com/code-security/dependabot/working-with-dependabot/about-dependabot-on-github-actions-runners).
|
|
179
|
+
* Their result says nothing about the head commit, and a failed one cannot be re-run, so the
|
|
180
|
+
* gate ignores them (required checks named in `.vibeops.json` are still judged by name).
|
|
181
|
+
*/
|
|
182
|
+
export function isDynamicRun(run) {
|
|
183
|
+
return run.event === "dynamic" || run.path.startsWith("dynamic/");
|
|
184
|
+
}
|
|
174
185
|
function runPath(run) {
|
|
175
186
|
return run.path.split("@")[0];
|
|
176
187
|
}
|
|
@@ -180,11 +191,13 @@ function describeRun(run, detail) {
|
|
|
180
191
|
/**
|
|
181
192
|
* Latest run per (workflow, event) must be completed and passing; every
|
|
182
193
|
* expected workflow must have a `pull_request` run. Unknown statuses /
|
|
183
|
-
* conclusions fail closed.
|
|
194
|
+
* conclusions fail closed. Dynamic runs ({@link isDynamicRun}) are ignored.
|
|
184
195
|
*/
|
|
185
196
|
export function evaluateActionsRuns(runs, expectedPaths) {
|
|
186
197
|
const latest = new Map();
|
|
187
198
|
for (const run of runs) {
|
|
199
|
+
if (isDynamicRun(run))
|
|
200
|
+
continue;
|
|
188
201
|
const key = `${runPath(run)}\u0000${run.event}`;
|
|
189
202
|
const prev = latest.get(key);
|
|
190
203
|
if (prev === undefined || run.id > prev.id)
|
package/dist/lib/pr-create.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { mergeRequestLabel } from "./git-host.js";
|
|
4
|
-
import { classifyRollup, evaluateCheckGate, formatCheckGateProblems, } from "./check-rollup.js";
|
|
4
|
+
import { classifyRollup, evaluateCheckGate, withoutDynamicChecks, formatCheckGateProblems, } from "./check-rollup.js";
|
|
5
5
|
import { isMergeRequestReadyToMerge, isPipelineActive, pipelineGateState, pipelineStatusFromHost, } from "./merge-request-readiness.js";
|
|
6
|
-
import { evaluateActionsRuns, listExpectedPullRequestWorkflows, listWorkflowRunsForSha, parseGitHubPullUrl, } from "./github-actions-gate.js";
|
|
6
|
+
import { evaluateActionsRuns, isDynamicRun, listExpectedPullRequestWorkflows, listWorkflowRunsForSha, parseGitHubPullUrl, } from "./github-actions-gate.js";
|
|
7
7
|
import { dim, log } from "./logger.js";
|
|
8
8
|
import { checkBaseInHead } from "./merge-base-freshness.js";
|
|
9
9
|
import { MERGE_GATE_DEFAULTS, mergeGateTiming } from "./merge-config.js";
|
|
@@ -292,7 +292,10 @@ export async function readMergeRequestDetails(cwd, host, url, options) {
|
|
|
292
292
|
throw new MergeRequestShapeError("gh", problems);
|
|
293
293
|
}
|
|
294
294
|
const checks = classifyRollup(parsed.statusCheckRollup);
|
|
295
|
-
|
|
295
|
+
// Dynamic workflows' check runs are not the commit's CI (see `withoutDynamicChecks`);
|
|
296
|
+
// required ones are still judged by name in the merge gate.
|
|
297
|
+
const ciChecks = withoutDynamicChecks(checks);
|
|
298
|
+
const gate = evaluateCheckGate(ciChecks);
|
|
296
299
|
let pipelineStatus = "none";
|
|
297
300
|
if (gate.state === "red") {
|
|
298
301
|
pipelineStatus = "failed";
|
|
@@ -300,7 +303,7 @@ export async function readMergeRequestDetails(cwd, host, url, options) {
|
|
|
300
303
|
else if (gate.state === "pending") {
|
|
301
304
|
pipelineStatus = "running";
|
|
302
305
|
}
|
|
303
|
-
else if (
|
|
306
|
+
else if (ciChecks.length > 0) {
|
|
304
307
|
pipelineStatus = "success";
|
|
305
308
|
}
|
|
306
309
|
const mergeable = parsed.mergeable?.trim().toUpperCase() ?? "";
|
|
@@ -340,11 +343,12 @@ export class MergeGateError extends Error {
|
|
|
340
343
|
}
|
|
341
344
|
function evaluateDetailsGate(details, requiredChecks) {
|
|
342
345
|
if (details.checks !== null) {
|
|
343
|
-
const
|
|
346
|
+
const checks = withoutDynamicChecks(details.checks, requiredChecks);
|
|
347
|
+
const gate = evaluateCheckGate(checks, requiredChecks);
|
|
344
348
|
return {
|
|
345
349
|
state: gate.state,
|
|
346
350
|
problems: formatCheckGateProblems(gate),
|
|
347
|
-
noChecks:
|
|
351
|
+
noChecks: checks.length === 0,
|
|
348
352
|
};
|
|
349
353
|
}
|
|
350
354
|
const state = pipelineGateState(details.pipelineStatus);
|
|
@@ -383,7 +387,7 @@ async function evaluateGitHubActions(cwd, url, details, expectedCache) {
|
|
|
383
387
|
expected = await listExpectedPullRequestWorkflows(cwd, pr, sha, details.baseBranch);
|
|
384
388
|
expectedCache.set(sha, expected);
|
|
385
389
|
}
|
|
386
|
-
const runs = await listWorkflowRunsForSha(cwd, pr, sha);
|
|
390
|
+
const runs = (await listWorkflowRunsForSha(cwd, pr, sha)).filter((r) => !isDynamicRun(r));
|
|
387
391
|
return {
|
|
388
392
|
result: evaluateActionsRuns(runs, expected.map((w) => w.path)),
|
|
389
393
|
runCount: runs.length,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodtek/vibeops",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.9",
|
|
4
4
|
"description": "Minimal CLI rails for Cursor-based TASK-driven vibe coding.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@9.15.9",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@eslint/js": "10.0.1",
|
|
64
64
|
"@types/node": "^20.14.0",
|
|
65
|
-
"eslint": "10.
|
|
65
|
+
"eslint": "10.12.0",
|
|
66
66
|
"tsx": "^4.23.15",
|
|
67
67
|
"typescript": "^5.5.4",
|
|
68
68
|
"typescript-eslint": "8.71.0"
|