@nanobpm/nano-workforce 0.203.0 → 0.204.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.204.0](https://github.com/nanobpm/nano-workforce/compare/v0.203.1...v0.204.0) (2026-11-04)
2
+
3
+ ### Features
4
+
5
+ * **feature-run:** post one idempotent "PR opened" comment on the claimed issue ([#912](https://github.com/nanobpm/nano-workforce/issues/912)) ([6c9219a](https://github.com/nanobpm/nano-workforce/commit/6c9219a8e2ca8ba3c495e07c20ee4ae9c3bf60be)), closes [camunda/camunda#19090](https://github.com/camunda/camunda/issues/19090) [#n](https://github.com/nanobpm/nano-workforce/issues/n) [#907](https://github.com/nanobpm/nano-workforce/issues/907) [#907](https://github.com/nanobpm/nano-workforce/issues/907)
6
+
7
+ ## [0.203.1](https://github.com/nanobpm/nano-workforce/compare/v0.203.0...v0.203.1) (2026-11-03)
8
+
9
+ ### Documentation
10
+
11
+ * shorten the operator guide and skill (64 KB → 26 KB) ([#909](https://github.com/nanobpm/nano-workforce/issues/909)) ([3a0be17](https://github.com/nanobpm/nano-workforce/commit/3a0be17b9f061c006e718f38773ebfffdc690cb4)), closes [sibling/#NNN](https://github.com/sibling/nano-workforce/issues/NNN) [#908](https://github.com/nanobpm/nano-workforce/issues/908) [#666](https://github.com/nanobpm/nano-workforce/issues/666)
12
+
1
13
  ## [0.203.0](https://github.com/nanobpm/nano-workforce/compare/v0.202.0...v0.203.0) (2026-10-13)
2
14
 
3
15
  ### Features
package/app/feature.ts CHANGED
@@ -74,6 +74,15 @@ export interface FeatureRun {
74
74
  * outcome is written to `status` itself; this carries the sub-state / note (e.g. "merged",
75
75
  * "waiting_review", or "operator: <note>"). */
76
76
  delivery_label: string | null;
77
+ /** CONFIRMED "PR opened" link (issue #907, app/issuePrLink.ts): the `owner/repo#N` PR key whose
78
+ * comment is known to be on the claimed issue (set only after a successful POST or marker match). */
79
+ pr_linked_pr_key?: string | null;
80
+ /** The PR key the claimed issue is OWED a "PR opened" link for (set by pr.record-feature for a
81
+ * claimed single-issue run only). Outstanding while it differs from `pr_linked_pr_key`; the
82
+ * poller's `pollIssuePrLinks` pass retries it until confirmed. */
83
+ pr_link_want_pr_key?: string | null;
84
+ /** In-flight link lease (ISO time) — a CAS-claimed, EXPIRING claim before any GitHub write. */
85
+ pr_link_lease_at?: string | null;
77
86
  /** Timestamp an operator dismissed a TERMINAL run (§5, `acknowledge-done`); NULL until then. When
78
87
  * set on a terminal row, `list_bucket` flips from 'active' to 'history'. Projection surface. */
79
88
  acknowledged_at: string | null;
package/app/github.ts CHANGED
@@ -40,14 +40,16 @@ export function githubTransport(): GithubTransport {
40
40
 
41
41
  /** Run the host `gh` CLI with the given args (no shell — args are passed as a vector, so a
42
42
  * `repo`/`number` from the datastore cannot inject a command). Resolves stdout, rejects on a
43
- * non-zero exit with stderr as the message. */
44
- async function runGh(args: string[]): Promise<string> {
43
+ * non-zero exit with stderr as the message. Exported so sibling host-glue modules (e.g.
44
+ * app/issuePrLink.ts) ride the ONE gh transport instead of re-deriving it. */
45
+ export async function runGh(args: string[], opts: { timeoutMs?: number } = {}): Promise<string> {
45
46
  const { execFile } = await import("node:child_process");
46
47
  return await new Promise<string>((resolve, reject) => {
47
48
  execFile(
48
49
  "gh",
49
50
  args,
50
- { maxBuffer: 16 * 1024 * 1024 },
51
+ // `timeout` (0 = none) kills a hung `gh` so a bounded caller's deadline is actually enforced.
52
+ { maxBuffer: 16 * 1024 * 1024, timeout: opts.timeoutMs ?? 0 },
51
53
  (err, stdout, stderr) => {
52
54
  if (err) reject(new Error(String(stderr || "").trim() || err.message));
53
55
  else resolve(String(stdout));
@@ -714,8 +716,9 @@ export async function requestCopilotReview(
714
716
  // The same two-transport model (gh | token) backs the merge stage: read a PR's merge state to
715
717
  // decide when it is landable, and perform the merge (directly or via the repo's merge queue).
716
718
 
717
- /** Whether to use the `gh` CLI for this pass, honouring `NANO_PR_GITHUB_TRANSPORT`. */
718
- async function useGh(): Promise<boolean> {
719
+ /** Whether to use the `gh` CLI for this pass, honouring `NANO_PR_GITHUB_TRANSPORT`. Exported so
720
+ * sibling host-glue modules (e.g. app/issuePrLink.ts) resolve the SAME transport selection. */
721
+ export async function useGh(): Promise<boolean> {
719
722
  const mode = githubTransport();
720
723
  return mode === "gh" || (mode === "auto" && (await isGhAvailable()));
721
724
  }
@@ -739,9 +742,13 @@ export async function fetchPrMeta(
739
742
  repo: string,
740
743
  number: number | string,
741
744
  token: string,
745
+ timeoutMs?: number,
742
746
  ): Promise<PrMeta | null> {
743
747
  if (await useGh()) {
744
- const out = await runGh(["pr", "view", String(number), "--repo", repo, "--json", "title,body,headRefName,baseRefName"]);
748
+ const out = await runGh(
749
+ ["pr", "view", String(number), "--repo", repo, "--json", "title,body,headRefName,baseRefName"],
750
+ timeoutMs === undefined ? undefined : { timeoutMs },
751
+ );
745
752
  // biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
746
753
  const j = JSON.parse(out) as { title?: string; body?: string; headRefName?: string | null; baseRefName?: string | null };
747
754
  return { title: j.title ?? null, body: j.body ?? "", headRef: j.headRefName ?? null, baseRef: j.baseRefName ?? null };
@@ -749,6 +756,7 @@ export async function fetchPrMeta(
749
756
  if (!token) return null;
750
757
  const r = await fetch(`https://api.github.com/repos/${repo}/pulls/${number}`, {
751
758
  headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json" },
759
+ ...(timeoutMs === undefined ? {} : { signal: AbortSignal.timeout(timeoutMs) }),
752
760
  });
753
761
  if (!r.ok) throw new Error(`github ${r.status} ${r.statusText}`.trim());
754
762
  // biome-ignore lint/plugin: runtime/framework contract boundary for external data shape