@nanobpm/nano-workforce 0.73.0 → 0.73.1
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 +7 -0
- package/app/github.test.ts +24 -1
- package/app/github.ts +14 -0
- package/app/service.ts +18 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [0.73.1](https://github.com/nanobpm/nano-workforce/compare/v0.73.0...v0.73.1) (2026-08-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **merge:** treat a Depends-on ref that is not a PR as non-blocking ([#246](https://github.com/nanobpm/nano-workforce/issues/246)) ([c800f81](https://github.com/nanobpm/nano-workforce/commit/c800f81b02b3a9a787d79552f4e5eef9971e3edf)), closes [Magikcraft/nano-bpm#806](https://github.com/Magikcraft/nano-bpm/issues/806)
|
|
7
|
+
|
|
1
8
|
# [0.73.0](https://github.com/nanobpm/nano-workforce/compare/v0.72.1...v0.73.0) (2026-08-15)
|
|
2
9
|
|
|
3
10
|
|
package/app/github.test.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the merge-exclusion graph. Force the token transport and stub `globalThis.fetch`.
|
|
4
4
|
import { test } from "node:test";
|
|
5
5
|
import { assertEquals, assertRejects } from "#test-assert";
|
|
6
|
-
import { BaseBranchMustExistError, ensureBaseBranch, fetchPrFiles } from "./github.ts";
|
|
6
|
+
import { BaseBranchMustExistError, ensureBaseBranch, fetchPrFiles, isNotAPullRequestError } from "./github.ts";
|
|
7
7
|
|
|
8
8
|
// A fake `fetch` that serves `pages` of file batches; each page N (1-based) returns `pages[N-1]`
|
|
9
9
|
// files (named `f{index}`), setting a `Link: rel="next"` header whenever a later page exists.
|
|
@@ -236,3 +236,26 @@ test("ensureBaseBranch: missing non-epic/* branch throws BaseBranchMustExistErro
|
|
|
236
236
|
// A rejected non-epic/* base must never spawn a wrong-rooted branch.
|
|
237
237
|
assertEquals(state.creates.length, 0);
|
|
238
238
|
});
|
|
239
|
+
|
|
240
|
+
// A `Depends-on:` ref that resolves to an issue (or a non-existent number) can never merge, so the
|
|
241
|
+
// merge-poller's dependency gate must treat it as non-blocking rather than wedging forever — the
|
|
242
|
+
// exact wedge behind Magikcraft/nano-bpm#806 declaring `Depends-on:` its epic tracking *issue*
|
|
243
|
+
// #796. `isNotAPullRequestError` is the discriminator: it must fire for both transports' "not a PR"
|
|
244
|
+
// signals and stay false for transient failures (which must keep the dependency blocking).
|
|
245
|
+
test("isNotAPullRequestError: gh GraphQL 'not a PullRequest' → true", () => {
|
|
246
|
+
const err = new Error(
|
|
247
|
+
"GraphQL: Could not resolve to a PullRequest with the number of 796. (repository.pullRequest)",
|
|
248
|
+
);
|
|
249
|
+
assertEquals(isNotAPullRequestError(err), true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("isNotAPullRequestError: token-mode 404 → true", () => {
|
|
253
|
+
assertEquals(isNotAPullRequestError(new Error("github 404 Not Found")), true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("isNotAPullRequestError: transient failures stay blocking (false)", () => {
|
|
257
|
+
assertEquals(isNotAPullRequestError(new Error("github 502 Bad Gateway")), false);
|
|
258
|
+
assertEquals(isNotAPullRequestError(new Error("API rate limit exceeded")), false);
|
|
259
|
+
assertEquals(isNotAPullRequestError(new Error("fetch failed")), false);
|
|
260
|
+
assertEquals(isNotAPullRequestError(null), false);
|
|
261
|
+
});
|
package/app/github.ts
CHANGED
|
@@ -496,6 +496,20 @@ function allCheckNames(rollup: RollupEntry[]): string[] {
|
|
|
496
496
|
return names;
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
/** True when `err` is GitHub reporting that a ref which parsed as `owner/repo#N` is not a pull
|
|
500
|
+
* request — either it's an issue (issues and PRs share GitHub's number space, so an issue number
|
|
501
|
+
* is indistinguishable from a PR number by shape alone) or the number does not exist. Both
|
|
502
|
+
* transports surface here: `gh` mode throws the GraphQL message "Could not resolve to a
|
|
503
|
+
* PullRequest with the number of N", and token mode throws `github 404 …` from
|
|
504
|
+
* `GET /repos/{repo}/pulls/{N}`. A ref that is not a pull request can never merge, so a caller
|
|
505
|
+
* gating a merge queue on it (see `isDepMerged`) must treat it as non-blocking instead of wedging
|
|
506
|
+
* forever. Transient failures (rate-limit, 5xx, network) deliberately return `false` so the caller
|
|
507
|
+
* keeps waiting/retrying rather than silently clearing a real dependency. */
|
|
508
|
+
export function isNotAPullRequestError(err: unknown): boolean {
|
|
509
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
510
|
+
return /could not resolve to a pullrequest/i.test(msg) || /\bgithub 404\b/i.test(msg);
|
|
511
|
+
}
|
|
512
|
+
|
|
499
513
|
export async function fetchPrState(
|
|
500
514
|
repo: string,
|
|
501
515
|
number: number | string,
|
package/app/service.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
fetchPrReviews,
|
|
21
21
|
fetchPrState,
|
|
22
22
|
hasPendingCopilotReviewer,
|
|
23
|
+
isNotAPullRequestError,
|
|
23
24
|
type MergeMethod,
|
|
24
25
|
type PrState,
|
|
25
26
|
requestCopilotReview,
|
|
@@ -738,8 +739,23 @@ async function isDepMerged(data: DataLayer, depKey: string, token: string): Prom
|
|
|
738
739
|
if (tracked && tracked.status === "merged") return true;
|
|
739
740
|
const parsed = parsePr(depKey);
|
|
740
741
|
if (!parsed) return true; // unparseable dep can't be checked on GitHub → treat as cleared so it never wedges the PR
|
|
741
|
-
|
|
742
|
-
|
|
742
|
+
try {
|
|
743
|
+
const st = await fetchPrState(parsed.repo, parsed.number, token);
|
|
744
|
+
return st?.merged ?? false;
|
|
745
|
+
} catch (err) {
|
|
746
|
+
// A ref that GitHub cannot resolve to a *pull request* — it's an issue (issues and PRs share
|
|
747
|
+
// GitHub's number space) or the number doesn't exist — can never merge, so it cannot gate a
|
|
748
|
+
// merge queue. Treat it as cleared (non-blocking) rather than wedging the run at `wait-deps`
|
|
749
|
+
// forever, as happened when a PR body declared `Depends-on:` its epic tracking *issue*
|
|
750
|
+
// (Magikcraft/nano-bpm#806 → #796). Transient failures rethrow so the poller logs and retries.
|
|
751
|
+
if (isNotAPullRequestError(err)) {
|
|
752
|
+
console.warn(
|
|
753
|
+
`[poller] dep ${depKey} is not a mergeable pull request (issue or missing) — treating as non-blocking`,
|
|
754
|
+
);
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
throw err;
|
|
758
|
+
}
|
|
743
759
|
}
|
|
744
760
|
|
|
745
761
|
/** Flip a PR into the transient `merging` status and publish the correlating message, reverting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.73.
|
|
3
|
+
"version": "0.73.1",
|
|
4
4
|
"description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "main.ts",
|