@nanobpm/nano-workforce 0.48.2 → 0.49.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.
@@ -15,7 +15,7 @@
15
15
  // Emitting an empty `waveTasks` is fine: the MI activity over an empty collection completes
16
16
  // immediately (the same 0-task path the flat fan-out already relied on).
17
17
  import type { AppJobHandler } from "@nanobpm/urban";
18
- import { planTaskDeps, planTasks } from "../../app/plan.ts";
18
+ import { plans, planTaskDeps, planTasks } from "../../app/plan.ts";
19
19
 
20
20
  interface In extends Record<string, unknown> {
21
21
  planKey: string;
@@ -47,6 +47,28 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
47
47
  const statusById = new Map<string, string>();
48
48
  for (const r of rows) statusById.set(r.task_id, r.status);
49
49
 
50
+ // Operator-visibility projection (issue #137): mark this as the wave the fleet is now
51
+ // implementing, so the epics-index can show wave X/N at a glance. wave_count is derivable from
52
+ // the levelized rows (max wave + 1), so the "X/N" label stays correct even if a re-levelize
53
+ // changed the total. Best-effort + idempotent (a retry re-writes the same value) and
54
+ // display-only — it must never gate control flow, which stays driven by the process
55
+ // `currentWave`/`waveCount`/`gate_wave` state.
56
+ const waveCount = rows.reduce((m, r) => Math.max(m, r.wave ?? 0), -1) + 1;
57
+ try {
58
+ await plans(app.data).update(planKey, {
59
+ // Keep the three progress fields consistent: with no levelized rows (waveCount 0) there is
60
+ // no wave to implement, so current_wave is NULL too — never a stray index against NULL N.
61
+ current_wave: waveCount > 0 ? currentWave : null,
62
+ wave_count: waveCount > 0 ? waveCount : null,
63
+ wave_label: waveCount > 0 ? `${currentWave + 1}/${waveCount}` : null,
64
+ updated_at: ts,
65
+ });
66
+ } catch (err) {
67
+ app.log.error(`select-wave: projecting current_wave failed for ${planKey}`, {
68
+ err: String(err),
69
+ });
70
+ }
71
+
50
72
  const deps = await planTaskDeps(app.data).find({ plan_key: planKey });
51
73
  const depsByTask = new Map<string, string[]>();
52
74
  for (const d of deps) {