@nanobpm/nano-workforce 0.35.2 → 0.37.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.
Files changed (75) hide show
  1. package/.github/workflows/ci.yml +10 -17
  2. package/AGENTS.md +8 -7
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +1 -21
  5. package/actions/abandon.test.ts +8 -16
  6. package/actions/blackboard.test.ts +13 -21
  7. package/actions/blackboard.ts +1 -0
  8. package/actions/feature-answer-hook.ts +1 -0
  9. package/actions/plan-hook.ts +2 -1
  10. package/actions/webhook-submit.ts +2 -1
  11. package/app/abandon.test.ts +10 -15
  12. package/app/baseGuard.test.ts +7 -6
  13. package/app/blackboard.test.ts +22 -32
  14. package/app/blackboard.ts +7 -6
  15. package/app/ensure-pr.test.ts +10 -12
  16. package/app/github.test.ts +9 -8
  17. package/app/github.ts +24 -22
  18. package/app/instance-tracking.test.ts +8 -6
  19. package/app/mergeExclusion.test.ts +11 -20
  20. package/app/mergeExclusion.ts +8 -3
  21. package/app/mergeProtocol.test.ts +14 -13
  22. package/app/mergeProtocol.ts +1 -0
  23. package/app/mergeRebaseArm.test.ts +8 -6
  24. package/app/mergeTrain.test.ts +10 -9
  25. package/app/persist-escalation.test.ts +9 -30
  26. package/app/persist-round.test.ts +6 -19
  27. package/app/plan.test.ts +19 -42
  28. package/app/plan.ts +1 -1
  29. package/app/record-plan-review.test.ts +8 -7
  30. package/app/retro.test.ts +24 -48
  31. package/app/retro.ts +4 -2
  32. package/app/reviewWait.test.ts +12 -11
  33. package/app/rounds.test.ts +13 -12
  34. package/app/service.test.ts +14 -27
  35. package/app/service.ts +8 -4
  36. package/app/taskDelta.test.ts +8 -17
  37. package/app/taskDelta.ts +1 -0
  38. package/app/trialMerge.test.ts +4 -3
  39. package/app/version.ts +9 -21
  40. package/app/waves.test.ts +17 -16
  41. package/biome.json +143 -0
  42. package/operations/getVersion.test.ts +8 -12
  43. package/operations/getVersion.ts +2 -3
  44. package/operations/listActivePrs.test.ts +8 -14
  45. package/operations/listActivePrs.ts +2 -3
  46. package/operations/postMessage.ts +3 -3
  47. package/operations/startAndMessage.test.ts +6 -12
  48. package/package.json +9 -4
  49. package/plugins/no-unsafe-type-assertion.grit +12 -0
  50. package/scripts/check-agent-prompts.test.ts +15 -11
  51. package/scripts/check-agent-prompts.ts +4 -2
  52. package/scripts/layout-bpmn.ts +6 -26
  53. package/scripts/pages-contract.test.ts +14 -13
  54. package/scripts/purge-db.ts +3 -2
  55. package/scripts/upgrade-from-pack.ts +9 -5
  56. package/test/assert.ts +74 -0
  57. package/tsconfig.json +4 -1
  58. package/workers/finalize/worker.ts +2 -1
  59. package/workers/merge/worker.ts +3 -3
  60. package/workers/persist-escalation/worker.ts +2 -1
  61. package/workers/persist-round/worker.ts +2 -1
  62. package/workers/record-plan-review/worker.test.ts +6 -9
  63. package/workers/record-plan-review/worker.ts +2 -1
  64. package/workers/record-results/worker.test.ts +5 -8
  65. package/workers/record-results/worker.ts +2 -1
  66. package/workers/record-trial-merge/worker.test.ts +7 -18
  67. package/workers/record-trial-merge/worker.ts +6 -6
  68. package/workers/record-wave/worker.test.ts +13 -20
  69. package/workers/record-wave/worker.ts +7 -8
  70. package/workers/retro-gather/worker.test.ts +4 -17
  71. package/workers/retro-record/worker.test.ts +8 -27
  72. package/workers/retro-record/worker.ts +3 -3
  73. package/workers/select-wave/worker.test.ts +5 -8
  74. package/deno.json +0 -24
  75. package/deno.lock +0 -1776
@@ -1,35 +1,28 @@
1
- import { assertEquals } from "jsr:@std/assert@1";
1
+ import { test } from "node:test";
2
+ import { assertEquals } from "#test-assert";
2
3
  import handler from "./worker.ts";
3
4
 
4
5
  function fakeApp() {
5
- // deno-lint-ignore no-explicit-any
6
6
  const stores: Record<string, any[]> = { plan_retros: [] };
7
7
  const seq: Record<string, number> = {};
8
8
  function tbl(name: string, pk = "id") {
9
- // deno-lint-ignore no-explicit-any
10
9
  const rows = (stores[name] ??= [] as any[]);
11
- // deno-lint-ignore no-explicit-any
12
10
  const match = (r: any, where: any) => Object.entries(where).every(([k, v]) => r[k] === v);
13
11
  return {
14
- // deno-lint-ignore no-explicit-any require-await
15
12
  async insert(row: any) {
16
13
  const id = (seq[name] = (seq[name] ?? 0) + 1);
17
14
  rows.push(pk === "id" ? { id, ...row } : { ...row });
18
15
  return pk === "id" ? id : row[pk];
19
16
  },
20
- // deno-lint-ignore no-explicit-any require-await
21
17
  async find(where: any = {}) {
22
18
  return rows.filter((r) => match(r, where));
23
19
  },
24
- // deno-lint-ignore no-explicit-any require-await
25
20
  async findOne(where: any = {}) {
26
21
  return rows.find((r) => match(r, where));
27
22
  },
28
- // deno-lint-ignore no-explicit-any require-await
29
23
  async get(id: any) {
30
24
  return rows.find((row) => row[pk] === id);
31
25
  },
32
- // deno-lint-ignore no-explicit-any require-await
33
26
  async update(id: any, patch: any) {
34
27
  const r = rows.find((row) => row[pk] === id);
35
28
  if (r) Object.assign(r, patch);
@@ -40,10 +33,9 @@ function fakeApp() {
40
33
  return { app, stores };
41
34
  }
42
35
 
43
- Deno.test("retro-record: persists a filed retro from hoisted result vars", async () => {
36
+ test("retro-record: persists a filed retro from hoisted result vars", async () => {
44
37
  const { app, stores } = fakeApp();
45
38
  await handler(
46
- // deno-lint-ignore no-explicit-any
47
39
  {
48
40
  variables: {
49
41
  planKey: "o/r#5",
@@ -54,7 +46,6 @@ Deno.test("retro-record: persists a filed retro from hoisted result vars", async
54
46
  "io.nanobpm.agentResult": { output: "the full report" },
55
47
  },
56
48
  } as any,
57
- // deno-lint-ignore no-explicit-any
58
49
  app as any,
59
50
  );
60
51
 
@@ -67,59 +58,49 @@ Deno.test("retro-record: persists a filed retro from hoisted result vars", async
67
58
  assertEquals(row.report, "the full report");
68
59
  });
69
60
 
70
- Deno.test("retro-record: defaults to skipped when the agent filed no PR", async () => {
61
+ test("retro-record: defaults to skipped when the agent filed no PR", async () => {
71
62
  const { app, stores } = fakeApp();
72
63
  await handler(
73
- // deno-lint-ignore no-explicit-any
74
64
  { variables: { planKey: "o/r#6", summary: "nothing durable" } } as any,
75
- // deno-lint-ignore no-explicit-any
76
65
  app as any,
77
66
  );
78
67
  assertEquals(stores.plan_retros[0].status, "skipped");
79
68
  assertEquals(stores.plan_retros[0].pr_key, null);
80
69
  });
81
70
 
82
- Deno.test("retro-record: honours an explicit blocked status", async () => {
71
+ test("retro-record: honours an explicit blocked status", async () => {
83
72
  const { app, stores } = fakeApp();
84
73
  await handler(
85
- // deno-lint-ignore no-explicit-any
86
74
  { variables: { planKey: "o/r#7", status: "blocked", summary: "no write access" } } as any,
87
- // deno-lint-ignore no-explicit-any
88
75
  app as any,
89
76
  );
90
77
  assertEquals(stores.plan_retros[0].status, "blocked");
91
78
  });
92
79
 
93
- Deno.test("retro-record: ignores a PR unless the status is filed", async () => {
80
+ test("retro-record: ignores a PR unless the status is filed", async () => {
94
81
  const { app, stores } = fakeApp();
95
82
  await handler(
96
- // deno-lint-ignore no-explicit-any
97
83
  { variables: { planKey: "o/r#8", status: "skipped", pr: "o/r#43", summary: "not durable" } } as any,
98
- // deno-lint-ignore no-explicit-any
99
84
  app as any,
100
85
  );
101
86
  assertEquals(stores.plan_retros[0].status, "skipped");
102
87
  assertEquals(stores.plan_retros[0].pr_key, null);
103
88
  });
104
89
 
105
- Deno.test("retro-record: defaults invalid status from PR presence", async () => {
90
+ test("retro-record: defaults invalid status from PR presence", async () => {
106
91
  const { app, stores } = fakeApp();
107
92
  await handler(
108
- // deno-lint-ignore no-explicit-any
109
93
  { variables: { planKey: "o/r#9", status: "done", pr: "o/r#44" } } as any,
110
- // deno-lint-ignore no-explicit-any
111
94
  app as any,
112
95
  );
113
96
  assertEquals(stores.plan_retros[0].status, "filed");
114
97
  assertEquals(stores.plan_retros[0].pr_key, "o/r#44");
115
98
  });
116
99
 
117
- Deno.test("retro-record: coerces filed without a PR to skipped", async () => {
100
+ test("retro-record: coerces filed without a PR to skipped", async () => {
118
101
  const { app, stores } = fakeApp();
119
102
  await handler(
120
- // deno-lint-ignore no-explicit-any
121
103
  { variables: { planKey: "o/r#10", status: "filed", summary: "forgot the PR" } } as any,
122
- // deno-lint-ignore no-explicit-any
123
104
  app as any,
124
105
  );
125
106
  assertEquals(stores.plan_retros[0].status, "skipped");
@@ -10,7 +10,6 @@ import type { AppJobHandler } from "@nanobpm/urban";
10
10
  import { recordRetro } from "../../app/retro.ts";
11
11
 
12
12
  const AGENT_RESULT_KEY = "io.nanobpm.agentResult";
13
- const VALID_STATUSES = new Set(["filed", "skipped", "blocked"]);
14
13
 
15
14
  interface In extends Record<string, unknown> {
16
15
  planKey: string;
@@ -26,9 +25,9 @@ function asStr(v: unknown): string | null {
26
25
 
27
26
  function asStatus(v: unknown, hasPr: boolean): "filed" | "skipped" | "blocked" {
28
27
  const s = asStr(v);
29
- if (s && VALID_STATUSES.has(s)) {
28
+ if (s === "filed" || s === "skipped" || s === "blocked") {
30
29
  if (s === "filed" && !hasPr) return "skipped";
31
- return s as "filed" | "skipped" | "blocked";
30
+ return s;
32
31
  }
33
32
  return hasPr ? "filed" : "skipped";
34
33
  }
@@ -42,6 +41,7 @@ const handler: AppJobHandler<In> = async (job, app) => {
42
41
  const prKey = status === "filed" ? rawPrKey : null;
43
42
  const summary = asStr(job.variables.summary);
44
43
 
44
+ // biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
45
45
  const env = job.variables[AGENT_RESULT_KEY] as { output?: unknown } | undefined;
46
46
  const report = typeof env?.output === "string" ? env.output : null;
47
47
 
@@ -3,7 +3,8 @@
3
3
  // A predecessor parked behind a merge lane is not a failed slice: dependents must remain pending
4
4
  // so a later retry can dispatch them once the lane clears. Real non-open failures still cascade
5
5
  // to `skipped` as before.
6
- import { assertEquals } from "jsr:@std/assert@1";
6
+ import { test } from "node:test";
7
+ import { assertEquals } from "#test-assert";
7
8
  import handler from "./worker.ts";
8
9
  import type { PlanTaskStatus } from "../../app/plan.ts";
9
10
 
@@ -30,7 +31,6 @@ function fakeApp(rows: Row[], deps: DepRow[]) {
30
31
  table(name: string, key: string) {
31
32
  const store = name === "plan_tasks" ? rows : deps;
32
33
  return {
33
- // deno-lint-ignore no-explicit-any
34
34
  find: (q: any) =>
35
35
  Promise.resolve(
36
36
  store.filter((r) =>
@@ -39,7 +39,6 @@ function fakeApp(rows: Row[], deps: DepRow[]) {
39
39
  )
40
40
  ),
41
41
  ),
42
- // deno-lint-ignore no-explicit-any
43
42
  update: (k: any, patch: any) => {
44
43
  const row = store.find((r) =>
45
44
  ((r as unknown) as Record<string, unknown>)[key] === k
@@ -50,20 +49,18 @@ function fakeApp(rows: Row[], deps: DepRow[]) {
50
49
  };
51
50
  },
52
51
  },
53
- // deno-lint-ignore no-explicit-any
54
52
  } as any;
55
53
  }
56
54
 
57
55
  async function selectWave(rows: Row[], deps: DepRow[]) {
58
56
  const out = await handler(
59
- // deno-lint-ignore no-explicit-any
60
57
  { variables: { planKey: "owner/repo#63", currentWave: 1 } } as any,
61
58
  fakeApp(rows, deps),
62
59
  );
63
60
  return out as { waveTasks: unknown[] };
64
61
  }
65
62
 
66
- Deno.test("select-wave leaves dependents pending behind a waiting-for-lane dependency", async () => {
63
+ test("select-wave leaves dependents pending behind a waiting-for-lane dependency", async () => {
67
64
  const rows: Row[] = [
68
65
  {
69
66
  id: 1,
@@ -95,9 +92,9 @@ Deno.test("select-wave leaves dependents pending behind a waiting-for-lane depen
95
92
  assertEquals(rows[1].summary, undefined);
96
93
  });
97
94
 
98
- Deno.test("select-wave still skips dependents behind failed or otherwise non-open dependencies", async (t) => {
95
+ test("select-wave still skips dependents behind failed or otherwise non-open dependencies", async (t) => {
99
96
  for (const depStatus of ["blocked", "skipped", "pending"] as const) {
100
- await t.step(depStatus, async () => {
97
+ await t.test(depStatus, async () => {
101
98
  const rows: Row[] = [
102
99
  {
103
100
  id: 1,
package/deno.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": [
4
- "deno.window",
5
- "deno.ns"
6
- ]
7
- },
8
- "imports": {
9
- "@nanobpm/urban": "npm:@nanobpm/urban@^0.33.0"
10
- },
11
- "tasks": {
12
- "start": "deno run --allow-net --allow-read --allow-write --allow-run=gh --allow-env main.ts",
13
- "purge": "deno run --allow-read --allow-write --allow-env scripts/purge-db.ts",
14
- "check": "deno run -A @nanobpm/urban check",
15
- "check:prompts": "deno run -A scripts/check-agent-prompts.ts",
16
- "gen": "deno run -A @nanobpm/urban gen",
17
- "gen:check": "deno run -A @nanobpm/urban gen --check",
18
- "layout": "deno run -A scripts/layout-bpmn.ts",
19
- "layout:check": "deno run -A scripts/layout-bpmn.ts --check",
20
- "dev": "deno run -A @nanobpm/urban dev",
21
- "compile": "deno compile --allow-net --allow-read --allow-write --allow-run=gh --allow-env -o dist/nano-workforce main.ts",
22
- "test": "deno test -A"
23
- }
24
- }