Establish CONTRACTS.md at the EVOLV root as the canonical map of where every contract, rule, and standard lives. Surface it from CLAUDE.md so every fresh agent or colleague lands there first. Reshape .claude/refactor/ to reflect that the platform refactor is done: live standards stay at the top level; the plan artifacts (CONTINUE_HERE.md, TASKS.md) move into Archive/ with WARNING banners. Drop content that drifted out of date or duplicated the new standards stack: - docs/DEVELOPER_GUIDE.md (pre-refactor walkthrough; superseded by wiki/Architecture, wiki/Getting-Started, .claude/rules/node-architecture, .claude/refactor/MODULE_SPLIT + per-node CONTRACT.md + src/commands/). - .agents/decisions/ (15 DECISION files): load-bearing decisions belong in commit messages and PR descriptions; live open items in OPEN_QUESTIONS.md. - .agents/improvements/TOP10_*.md: moved to Archive/. Bump generalFunctions to 49c77f2 — adds CONTRACT.md inside the library: different shape from per-node CONTRACT.md files (library API, not msg.topic), with stability tags and pointers to .claude/refactor/CONTRACTS.md §N. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
Task list — ARCHIVED
Warning
ARCHIVED — Phases 1–11 landed on
developmentin May 2026. This file is the original phased plan and is retained for history. For deferred / open work, see../OPEN_QUESTIONS.md. For current standards, start at../../../CONTRACTS.md(EVOLV root).
Phased and ordered. The TaskCreate tracker mirrors this list and is the active, mutable view; this file is the durable plan.
A task is done when:
- The code matches the contracts in
CONTRACTS.md. - All the affected node's tests are green (
node --test test/basic test/integration test/edge). - A short note is appended in the task tracker if anything was deferred
to
OPEN_QUESTIONS.md.
Phase 1 — generalFunctions additive infra
Goal: add the new platform pieces. Nothing is removed; nothing existing changes shape. All existing nodes continue to work unchanged.
| # | Task | Notes |
|---|---|---|
| 1.1 | Add src/domain/UnitPolicy.js + tests |
Extracted from rotatingMachine._buildUnitPolicy. |
| 1.2 | Add src/domain/ChildRouter.js + tests |
Built on existing childRegistrationUtils. |
| 1.3 | Add src/domain/LatestWinsGate.js + tests |
Extracted from MGC _dispatchInFlight/_delayedCall. |
| 1.4 | Add src/domain/HealthStatus.js + tests |
Standardise the {level, flags, message, source} shape. |
| 1.5 | Add src/domain/BaseDomain.js + tests |
Constructor boilerplate; calls subclass configure()/_init(). |
| 1.6 | Add src/nodered/commandRegistry.js + tests |
Topic dispatch + alias warnings. |
| 1.7 | Add src/nodered/statusBadge.js + tests |
compose, error, idle, byState helpers. |
| 1.8 | Add src/nodered/statusUpdater.js + tests |
1 Hz poller calling source.getStatusBadge(). |
| 1.9 | Add src/nodered/BaseNodeAdapter.js + tests |
The thing every nodeClass extends. |
| 1.10 | Add src/stats/index.js + tests |
Promote mean/stdDev/median/mad/lerp from measurement. |
| 1.11 | Update generalFunctions/index.js (additive) |
New exports under existing pattern. |
| 1.12 | Run all 12 nodes' tests against the bumped generalFunctions |
Sanity gate before phase 2. |
Phase-1 commit cadence: one commit per task on the development branch
of generalFunctions. Submodule pointer in parent EVOLV bumps once
at end of phase.
Phase 2 — pumpingStation pilot
Goal: prove the new infrastructure end-to-end. Pumping station is a mid-complexity node — bigger than measurement, smaller than the curve-driven nodes.
| # | Task | Notes |
|---|---|---|
| 2.1 | Move standalone demo from specificClass.js to examples/standalone-demo.js |
Pure deletion + move; tests unchanged. |
| 2.2 | Extract basin/ (BasinGeometry + thresholdValidator) |
Pure functions. |
| 2.3 | Extract measurement/flowAggregator.js (incl. _updatePredictedVolume) |
Centerpiece of the tick loop. |
| 2.4 | Extract measurement/measurementRouter.js + measurement/calibration.js |
|
| 2.5 | Extract control/ strategies + dispatcher |
levelBased, flowBased (stub), manual. |
| 2.6 | Extract safety/safetyController.js |
dryRunRule + overfillRule split internally. |
| 2.7 | Add getStatusBadge() on PumpingStation; remove badge logic from nodeClass |
|
| 2.8 | Convert nodeClass.js to extend BaseNodeAdapter |
|
| 2.9 | Convert specificClass.js to extend BaseDomain |
Use ChildRouter, UnitPolicy. |
| 2.10 | Extract commands/ registry + handlers |
Old topic names become aliases. |
| 2.11 | Extract editor.js from pumpingStation.html (the SVG redraw logic) |
Served via a /pumpingStation/editor.js admin endpoint. |
| 2.12 | Generate CONTRACT.md from commands/ + handwritten events section |
|
| 2.13 | Tests: 3-tier per extracted module + the existing suite still green | Add edge tests for any regression discovered. |
| 2.14 | Docker E2E (deploy 01-basic/02-integration/03-dashboard flows on a running Node-RED) |
Required for "trial-ready" claim. |
Phase 3 — measurement
| # | Task | Notes |
|---|---|---|
| 3.1 | Promote stats helpers to generalFunctions/src/stats/ (already done in 1.10) |
|
| 3.2 | Convert analog mode to use Channel internally (with key=null) |
Removes the ~400-line inline pipeline duplication. |
| 3.3 | Extract simulation/simulator.js |
|
| 3.4 | Extract calibration/calibrator.js |
|
| 3.5 | Add getStatusBadge() on Measurement |
|
| 3.6 | Convert nodeClass.js to BaseNodeAdapter; specificClass.js to BaseDomain |
|
| 3.7 | Extract commands/ |
|
| 3.8 | CONTRACT.md |
|
| 3.9 | Tests + Docker E2E |
Phase 4 — machineGroupControl
| # | Task | Notes |
|---|---|---|
| 4.1 | Extract groupOps/ (groupOperatingPoint + groupCurves) |
The cluster of _group* helpers. |
| 4.2 | Extract totals/totalsCalculator.js |
|
| 4.3 | Extract combinatorics/pumpCombinations.js |
|
| 4.4 | Extract optimizer/bestCombination.js + optimizer/bepGravitation.js |
|
| 4.5 | Extract efficiency/groupEfficiency.js |
|
| 4.6 | Extract dispatch/demandDispatcher.js using LatestWinsGate |
Replaces _dispatchInFlight/_delayedCall directly. |
| 4.7 | Add getStatusBadge() |
|
| 4.8 | Convert nodeClass + specificClass to base classes; use ChildRouter |
|
| 4.9 | commands/ + CONTRACT.md |
|
| 4.10 | Tests + Docker E2E |
Phase 5 — rotatingMachine
| # | Task | Notes |
|---|---|---|
| 5.1 | Extract curves/ (loader + normalizer + reverseCurve) |
|
| 5.2 | Extract prediction/ (predictors + groupPredictors + operatingPoint) |
|
| 5.3 | Extract drift/ using HealthStatus |
|
| 5.4 | Extract pressure/ (virtual children + initialization + router) |
|
| 5.5 | Extract state/stateBindings.js (adapter to existing generalFunctions/state) |
|
| 5.6 | Extract measurement/measurementHandlers.js |
|
| 5.7 | Extract flow/flowController.js |
|
| 5.8 | Extract display/workingCurves.js |
|
| 5.9 | Add getStatusBadge() (replaces the 100-line nodeClass version) |
|
| 5.10 | Convert nodeClass + specificClass | |
| 5.11 | commands/ + CONTRACT.md |
|
| 5.12 | Tests + Docker E2E |
Phase 6 — remaining nodes
For each: skeleton refactor only — extend BaseNodeAdapter + BaseDomain, use ChildRouter, move the input switch to commands/, add
getStatusBadge(). Domain-specific module split only if specificClass > 300 lines after the platform refactor.
| # | Task |
|---|---|
| 6.1 | valve |
| 6.2 | valveGroupControl |
| 6.3 | diffuser |
| 6.4 | monster |
| 6.5 | settler |
| 6.6 | reactor |
| 6.7 | dashboardAPI (special — likely no BaseDomain, it's a passive HTTP server) |
These are parallelisable — each can be its own agent.
Phase 7 — remove legacy topic aliases
Note: canonical names (
set.*,cmd.*,data.*,child.*,query.*,evt.*) are used from Phase 1 onwards — seeCONTRACTS.md §1. Eachcommands/index.jsdeclares the canonical name astopicand lists pre-refactor names inaliases. So Phase 7 is just the deprecation-window sweep.
| # | Task | Notes |
|---|---|---|
| 7.1 | Audit aliases across all commands/ files; confirm one release cycle has elapsed |
If any alias was added recently, defer that node's removal another cycle. |
| 7.2 | Remove aliases entries; canonical name only |
Each removal is a single PR. |
| 7.3 | Update example flows that still used legacy names | Should already have been updated in their phase. |
| 7.4 | Document the removal in each CONTRACT.md |
"Removed legacy topic X (replaced by canonical Y) on YYYY-MM-DD". |
Phase 8 — promotion to main
When every node is on the new infra and Docker E2E green:
- Bump submodule pointers in parent EVOLV
development. - Open a PR per submodule (
development→main). - Open the parent EVOLV PR last (
development→main). - Merge in dependency order (
generalFunctionsfirst, then nodes that depend on it, finallyEVOLV).
Phase 8.5 — generalFunctions deprecated path cleanup
Removes the deprecated paths flagged in OPEN_QUESTIONS.md. Runs after
promotion to main (so callers have stopped depending on the old
paths via the platform's own consumers).
Targets to remove
| Path | Replaced by | First flagged |
|---|---|---|
src/helper/menuUtils_DEPRECATED.js |
src/menu/ (the active menu manager) |
pre-refactor |
loadCurve export (in index.js + datasets/assetData/curves/) |
loadModel |
pre-refactor |
Any *_DEPRECATED.* file added during the refactor |
(per-file note) | refactor |
Tasks
| # | Task | Notes |
|---|---|---|
| 8.5.1 | Audit consumers of loadCurve across all nodes |
Should be zero after Phase 5 (rotatingMachine) — verify. |
| 8.5.2 | Remove loadCurve export + the underlying file |
Single PR. Test all nodes. |
| 8.5.3 | Remove menuUtils_DEPRECATED.js |
Verify zero imports first. |
| 8.5.4 | Sweep generalFunctions/src/ for _DEPRECATED.* files; remove with consumer audit |
One PR per file. |
| 8.5.5 | Update generalFunctions README to drop deprecated references |
Phase 9 — wiki cleanup (post-refactor)
Goal: each node's gitea wiki becomes visual-first, scannable, and follows one shared template. Today's wiki has lots of prose and varies per node — once the platform is uniform, the wiki should be too.
Don't start phase 9 until phase 8 is done (the wiki documents the post-refactor shape, not the in-flight transition).
Standard wiki template (one file per node, this is the spec)
1. One-paragraph "what is this node" (≤ 60 words).
2. Position in the platform — a Mermaid block showing the node and its
typical neighbours (parent + child types, with arrows for
data direction).
3. Capability matrix — small table of "what this node can do" with
✅ / ❌ / partial.
4. Topic contract — auto-generated from src/commands/index.js
(set.* / cmd.* / evt.* / data.* — payload schema and example).
5. Output payload — a Mermaid sequence-diagram of a typical tick
(parent → child → measurement → tick → port-0 emit).
6. Configuration — a Mermaid block diagram of the editor form sections
plus a table mapping each form field to the config key it lands at.
7. Examples — links to examples/01-basic, 02-integration, 03-dashboard
with one screenshot each.
8. State / mode chart — Mermaid stateDiagram for any node with
non-trivial states (rotatingMachine, pumpingStation, MGC).
9. "When you would NOT use this node" — explicit non-goals.
10. Issues / known limitations — single-line items with links to
repo issues.
Tasks
| # | Task | Notes |
|---|---|---|
| 9.1 | Author the canonical wiki template at .claude/refactor/WIKI_TEMPLATE.md (or the repo-mem rule path) |
Source of truth. |
| 9.2 | Build the auto-generator: commands/index.js → "Topic contract" markdown section |
Run via a small npm run wiki:contract script per node. |
| 9.3 | Pilot on pumpingStation wiki: replace existing pages with the new template |
Visual-first, prune prose. |
| 9.4 | Apply to other 3 core nodes (measurement, MGC, rotatingMachine) |
|
| 9.5 | Apply to remaining nodes (one per repo) | |
| 9.6 | Update parent EVOLV wiki: top-level platform overview with a Mermaid block of all 13 nodes and how they connect (S88 hierarchy + data direction) | |
| 9.7 | Add a wiki style guide (max prose per section, where Mermaid is required, screenshot conventions) | |
| 9.8 | Audit pass: every page renders, every Mermaid block compiles, every link resolves |
Visual primitives we'll lean on (Mermaid)
flowchart LR— node connections (parent ↔ child, data direction).sequenceDiagram— tick-to-port-0 lifecycle.stateDiagram-v2— rotatingMachine / pumpingStation state machines.erDiagram— only if a node has a complex internal data model worth visualising.
Skip: classDiagram (we don't expose classes to users); gantt (no schedules in a node's docs).
Hard rules
- Every page leads with the Mermaid platform-position block. No "intro paragraph then later a diagram" — diagram first.
- Each section opens with the diagram or table; prose annotates the visual, not the other way round.
- No more than 60 words of unbroken prose anywhere on a page.
- One canonical source of truth for the topic contract:
commands/index.js. The wiki page is generated from it. No hand-written drift.
Phase 10 — test-suite refactor (post-wiki)
Goal: bring every node's test layout in line with CONVENTIONS.md §Testing
now that the platform is uniform. Pre-existing test debt logged in
OPEN_QUESTIONS.md gets cleaned up here.
Tasks
| # | Task | Notes |
|---|---|---|
| 10.1 | Audit each node: basic / integration / edge split, naming, helpers | One pass; produce a per-node punch list. |
| 10.2 | Convert any Mocha-style tests (describe/it) to node:test |
Specifically dashboardAPI/test/basic/structure-module-load.basic.test.js. |
| 10.3 | Address reactor mathjs load (per OPEN_QUESTIONS): tree-shake or hoist |
If hoisted, document the pattern as a CONVENTION addition. |
| 10.4 | Promote shared test helpers to generalFunctions/test/helpers/ |
Common fakes: fake Node-RED node, fake child, fake RED. |
| 10.5 | Add missing edge tests for each refactored module flagged during P2-P5 | Edge cases discovered during refactor land here. |
| 10.6 | Make every basic-test runner exit cleanly in batch (node --test test/basic/) |
No leaked timers, no real setInterval outliving the assertions. Mirrors the BaseNodeAdapter test fix. |
| 10.7 | Standard CI shape: each node has npm run test:basic, test:integration, test:edge (consistent across nodes) |
Allows uniform CI invocation. |
| 10.8 | Audit pass: every node's test suite green in batch under one wall-clock budget (≤ 60 s for basic) | The new platform-wide gate. |
Phase 11 — unit-aware commands
Goal: every numeric setter / data topic carries an explicit unit; the user can supply any compatible unit and the commandRegistry normalises before the handler runs. Unknown units warn + list accepted alternatives.
Tasks
| # | Task | Notes |
|---|---|---|
| 11.1 | generalFunctions/src/convert/: add possibilities(measure) helper |
Returns the list of accepted unit names for a measure (volumeFlowRate, pressure, etc.). |
| 11.2 | generalFunctions/src/nodered/commandRegistry.js: handle descriptor.units |
Normalisation pipeline: extract value+unit from msg, validate against units.measure, convert to units.default, warn + fall back on bad input. Tests for all 4 paths (no-unit / valid / wrong-measure / unknown). |
| 11.3 | generalFunctions/src/nodered/BaseNodeAdapter.js: auto-wire query.units topic |
Returns { topic → { measure, default, accepted: [...] } } from the registry. No per-node wiring needed. |
| 11.4 | generalFunctions/scripts/wikiGen.js: render units column |
Topic-contract auto-gen table grows a Unit column showing measure (default <unit>). |
| 11.5 | Per-node src/commands/index.js: declare units on every numeric setter |
~10 nodes. See proposed default-units table in interview reply. |
| 11.6 | Regenerate every CONTRACT.md + wiki Home.md via npm run wiki:all |
Automated. |
| 11.7 | Tests: commandRegistry unit-handling paths | 4 scenarios per the validation table. |
Default units per topic (proposed)
| Topic | Default | Why |
|---|---|---|
pumpingStation set.inflow |
m3/h |
Operator-friendly scale |
pumpingStation set.demand |
m3/h |
same |
pumpingStation set.outflow |
m3/h |
symmetric |
pumpingStation cmd.calibrate.volume |
m3 |
basin volume |
pumpingStation cmd.calibrate.level |
m |
basin height |
MGC set.demand |
m3/h |
matches PS |
rotatingMachine set.setpoint |
% |
control% |
rotatingMachine set.flow-setpoint |
m3/h |
flow target |
rotatingMachine data.simulate-measurement |
per payload.type |
dispatch by sensor type |
valve set.position |
% |
valve open-% |
measurement data.measurement |
mode-dependent | analog → Channel scaling; digital → per-channel cfg |
monster data.flow |
m3/h |
already enforced |
reactor data.influent |
flow=m3/h, concentrations=mg/L | engine internals |
settler data.influent |
flow=m3/h, concentrations=mg/L | matches reactor |
diffuser data.flow |
m3/h |
air flow scale |