ex-workflow 0.4.0-stage → 1.0.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 +58 -2
- package/README.md +68 -32
- package/dist/adapters/index.cjs +1 -1
- package/dist/adapters/index.mjs +1 -1
- package/dist/{roles-BLBRKhUZ.d.ts → budget-Cx63FX2K.d.ts} +9 -2
- package/dist/{roles-aneKYJ3Z.d.cts → budget-JyNKxP5N.d.cts} +9 -2
- package/dist/chunk-3KFQPSRX.mjs +2 -0
- package/dist/chunk-3KFQPSRX.mjs.map +1 -0
- package/dist/chunk-5PHK72VM.cjs +2 -0
- package/dist/chunk-5PHK72VM.cjs.map +1 -0
- package/dist/chunk-5TXYMLBF.mjs +3 -0
- package/dist/chunk-5TXYMLBF.mjs.map +1 -0
- package/dist/chunk-7IG6EQI4.mjs +6 -0
- package/dist/chunk-7IG6EQI4.mjs.map +1 -0
- package/dist/chunk-CYXVGHQM.cjs +2 -0
- package/dist/chunk-CYXVGHQM.cjs.map +1 -0
- package/dist/chunk-FKTCGQYB.cjs +2 -0
- package/dist/chunk-FKTCGQYB.cjs.map +1 -0
- package/dist/chunk-HIHMBMB5.cjs +2 -0
- package/dist/chunk-HIHMBMB5.cjs.map +1 -0
- package/dist/chunk-MQWMQ4PD.mjs +2 -0
- package/dist/chunk-MQWMQ4PD.mjs.map +1 -0
- package/dist/chunk-QDMEM62U.mjs +2 -0
- package/dist/chunk-QDMEM62U.mjs.map +1 -0
- package/dist/chunk-U3OXAO67.mjs +2 -0
- package/dist/chunk-U3OXAO67.mjs.map +1 -0
- package/dist/chunk-UZQV73X7.mjs +2 -0
- package/dist/chunk-UZQV73X7.mjs.map +1 -0
- package/dist/chunk-VXH2FAKV.cjs +2 -0
- package/dist/chunk-VXH2FAKV.cjs.map +1 -0
- package/dist/chunk-WOGK54HB.cjs +3 -0
- package/dist/chunk-WOGK54HB.cjs.map +1 -0
- package/dist/chunk-XMO2RCXB.cjs +2 -0
- package/dist/chunk-XMO2RCXB.cjs.map +1 -0
- package/dist/chunk-YBHHICTZ.mjs +2 -0
- package/dist/chunk-YBHHICTZ.mjs.map +1 -0
- package/dist/chunk-YJN4JEJU.cjs +2 -0
- package/dist/chunk-YJN4JEJU.cjs.map +1 -0
- package/dist/chunk-YRATJT7P.cjs +6 -0
- package/dist/chunk-YRATJT7P.cjs.map +1 -0
- package/dist/chunk-Z2G7DGDF.mjs +2 -0
- package/dist/chunk-Z2G7DGDF.mjs.map +1 -0
- package/dist/cli/bin.cjs +1 -1
- package/dist/cli/bin.mjs +1 -1
- package/dist/cli/index.cjs +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -12
- package/dist/index.d.ts +35 -12
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/kanban/index.cjs +1 -1
- package/dist/kanban/index.mjs +1 -1
- package/dist/schema/index.cjs +1 -1
- package/dist/schema/index.mjs +1 -1
- package/dist/scrum/index.cjs +1 -1
- package/dist/scrum/index.d.cts +18 -3
- package/dist/scrum/index.d.ts +18 -3
- package/dist/scrum/index.mjs +1 -1
- package/dist/types.d.cts +23 -1
- package/dist/types.d.ts +23 -1
- package/dist/waterfall/index.cjs +1 -1
- package/dist/waterfall/index.d.cts +4 -1
- package/dist/waterfall/index.d.ts +4 -1
- package/dist/waterfall/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-36M6OVUJ.mjs +0 -2
- package/dist/chunk-36M6OVUJ.mjs.map +0 -1
- package/dist/chunk-7KF5K3L7.cjs +0 -2
- package/dist/chunk-7KF5K3L7.cjs.map +0 -1
- package/dist/chunk-CWBH7H52.cjs +0 -2
- package/dist/chunk-CWBH7H52.cjs.map +0 -1
- package/dist/chunk-DXC2RSN5.cjs +0 -3
- package/dist/chunk-DXC2RSN5.cjs.map +0 -1
- package/dist/chunk-GATJZYTB.mjs +0 -2
- package/dist/chunk-GATJZYTB.mjs.map +0 -1
- package/dist/chunk-GFH2FEZH.mjs +0 -2
- package/dist/chunk-GFH2FEZH.mjs.map +0 -1
- package/dist/chunk-HHC5EZ54.cjs +0 -2
- package/dist/chunk-HHC5EZ54.cjs.map +0 -1
- package/dist/chunk-KSKEFYY7.cjs +0 -2
- package/dist/chunk-KSKEFYY7.cjs.map +0 -1
- package/dist/chunk-KUUIAKEJ.cjs +0 -2
- package/dist/chunk-KUUIAKEJ.cjs.map +0 -1
- package/dist/chunk-L2ZN6HEL.mjs +0 -2
- package/dist/chunk-L2ZN6HEL.mjs.map +0 -1
- package/dist/chunk-NDORREWR.mjs +0 -2
- package/dist/chunk-NDORREWR.mjs.map +0 -1
- package/dist/chunk-NZ2DIDOD.mjs +0 -2
- package/dist/chunk-NZ2DIDOD.mjs.map +0 -1
- package/dist/chunk-OHSQ52DW.mjs +0 -6
- package/dist/chunk-OHSQ52DW.mjs.map +0 -1
- package/dist/chunk-RSKZVOYC.cjs +0 -6
- package/dist/chunk-RSKZVOYC.cjs.map +0 -1
- package/dist/chunk-S22CPYXJ.mjs +0 -3
- package/dist/chunk-S22CPYXJ.mjs.map +0 -1
- package/dist/chunk-SHAPFMVP.mjs +0 -2
- package/dist/chunk-SHAPFMVP.mjs.map +0 -1
- package/dist/chunk-SJRRQZ3R.cjs +0 -2
- package/dist/chunk-SJRRQZ3R.cjs.map +0 -1
- package/dist/chunk-UM5X2JHB.cjs +0 -2
- package/dist/chunk-UM5X2JHB.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 1.0.0 (2026-10-24)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
First stable release, published to `latest`. Phase 7 fixes the five engine gaps the workflowAI S2 dogfood raised (#23-#27) and closes the 1.0 definition; the decisions are in the design spec, section 23.
|
|
6
|
+
|
|
7
|
+
- Trace and reopen ids (#23): a Waterfall `item.add` or `item.edit` is refused (`rule-violation`) when a `traces` id does not name an existing, non-dropped item, or when a change-request's `impactAnalysis.reopen` names a phase that is not configured; the fix lists the ids or phases that are valid.
|
|
8
|
+
- Impact analysis at add (#24): with `changeControl.requiresImpactAnalysis: true`, a change-request without an impact analysis is refused at `item.add` and `item.edit`, and a present one is shape-checked (`summary`, `reopen`, `weight`).
|
|
9
|
+
- Coverage recount (#25): the traceability matrix no longer counts change-requests; `waterfallMetrics.requirementsTraced`, `requirementsTotal` and `requirementCoverage` are computed from the same matrix as `status.report.coverage`, so the two agree.
|
|
10
|
+
- `readyForGate` (#26): `readyForGate` is true as soon as the gate would pass; `PhaseReportRow` gains `missingTraces`, and the generated `traceability-matrix` counts as present for the last traced phase when nothing is missing.
|
|
11
|
+
- Kanban lessons (#27): `service-delivery-review` takes `lessons`, `adopt`, `dismiss` and `retire` with the Scrum retro semantics for agreements; the `cadence.service-delivery-review` payload gains five arrays (`lessons`, `adopted`, `dismissed`, `agreements`, `retired`).
|
|
12
|
+
- Shared lesson helper: the Scrum `retro`, the Waterfall `phaseRetrospective` and the Kanban review share one core helper (`src/lessons.ts`), so a malformed `lessons[]` or `adopt[]` entry on the Scrum retro is refused `invalid-input` (`retro.lessons entries need a text and a scope`) where it was silently accepted before; valid inputs, payloads and messages are unchanged.
|
|
13
|
+
- Compatibility test: `test/compat.test.ts` loads `state.json` fixtures written by the published 0.1.0, 0.2.0, 0.3.0, 0.4.0 and 0.6.0 stage releases (`test/fixtures/compat`, regenerated by `scripts/gen-compat-fixtures.mjs`) and runs `status`, `metrics` and `next` on each; 0.5.0-stage was never published.
|
|
14
|
+
- API-surface freeze: `test/api-surface.test.ts` compares the runtime exports of the five entry points, the CLI `USAGE` lines (10), the schema documents (8) and the event kinds (52) with `test/api-surface.json`; a removal is now a major version, an addition a minor one.
|
|
15
|
+
|
|
16
|
+
Migration: from any 0.N.0-stage a `.workflow/state.json` loads unchanged; the compatibility test proves it for every stage release. Coming from an earlier stage, the behaviour changes of 0.5.0 and 0.6.0 listed below apply. New in 1.0.0: a Waterfall `add` or `edit` is refused for an unknown `traces` or `reopen` id and for a change-request without an impact analysis when the config requires one (#23, #24); `status.report.coverage` and `metrics.requirements*` no longer count change-requests and agree with each other (#25); `readyForGate` is true as soon as the gate would pass (#26); Kanban `service-delivery-review` takes lessons (#27); a malformed `lessons[]`, `adopt[]` or `dismiss[]` entry on the Scrum `retro` is refused `invalid-input` (shared helper); `PhaseReportRow.missingTraces` is added and the `cadence.service-delivery-review` payload gains five arrays. No schema document, event kind or `state.json` change. Versioning from here: semver, `latest`.
|
|
17
|
+
|
|
18
|
+
## 0.6.0-stage (2026-10-24)
|
|
19
|
+
|
|
20
|
+
Pre-release under the `stage` dist-tag. Phase 6 (backlog zero) fixes or explicitly accepts all 24 items the 1.0 definition named; the decisions are in the design spec, section 22, and the accepted limitations are in the README.
|
|
21
|
+
|
|
22
|
+
- Stale-lock takeover (B01): the lock is re-stat'd (inode and mtime) immediately before `unlink`; a lock changed between the staleness check and the unlink is not taken over; the takeover is retried and the lock-held error follows after the retry cap. The residual window between the stat and the unlink is accepted.
|
|
23
|
+
- Completion rules (B02): `complete()` runs the methodology rules through `applyAction` with action type `item.complete` after validation, so rule hooks see completions; `checkGate` still answers `gate-unmet` for a conflict of interest.
|
|
24
|
+
- Gate approval (B05): `decide(approve)` honours a `needs-review` verdict from the item's last `item.completed` event and advances to the review stage.
|
|
25
|
+
- Scrum status (B04): `exwf status` reports a `report` for Scrum (`{ closing, sprint, reason, inFlight }`), so a sprint left in `closing` is flagged.
|
|
26
|
+
- Scrum closing (B07): the `sprint-closing` rule refuses `ceremony commit` into a closing sprint; `uncommit` stays allowed.
|
|
27
|
+
- Scrum stage ids (B06): `init` refuses a Scrum config whose stages lack `backlog`, `sprint` or `review` (`config-invalid`).
|
|
28
|
+
- CLI write order (B08): `next` and `complete` write briefs and handoff files before the state.
|
|
29
|
+
- Evidence freshness (B10, semantic change, own commit): policies and gates count only evidence recorded after the item's last `item.rework` event; revert that commit alone to restore append-only evidence.
|
|
30
|
+
- Test gaps (B15): `writeAtomic` failure path, `decide` reject and unknown-pending cases, and a usage exit-2 test per command group.
|
|
31
|
+
- Planner budgets (B20): `plan auto` applies the `tokens` and `turns` limits together.
|
|
32
|
+
- Carryover (B22): a carryover blocked or awaiting human in the review stage resumes at review, as `review` already did.
|
|
33
|
+
- Waterfall fallback (B18): `WaterfallConfig.fallbackTokensPerWeight` (optional) feeds the planner like Scrum's `fallbackTokensPerWeight`.
|
|
34
|
+
- Waterfall lessons (B21): phase retrospective lessons carry `from` and `item`, as in Scrum (`from` defaults to the retro's `by`).
|
|
35
|
+
- Valueless flags (B24): a value-taking flag given without a value is `invalid-input` naming the flag; `--estimate` is the only boolean flag.
|
|
36
|
+
|
|
37
|
+
Compatibility: no `state.json` schema change and no new event kind; every `.workflow/state.json` written by an earlier stage loads unchanged. The generated `WaterfallConfig` schema document gains one optional key, `fallbackTokensPerWeight`. Behaviour changes, all deliberate: rules run on `item.complete` (B02); custom rules on `item.complete` can refuse a completion (`rule-violation`) and their warnings are returned; `decide(approve)` honours `needs-review` (B05); `ceremony commit` is refused into a closing sprint (B07); a freshly reworked item needs fresh evidence before it passes (B10); a blocked or awaiting-human carryover in review resumes at review (B22); `plan auto` applies tokens and turns together (B20); a value-taking flag without a value is refused (B24); Scrum's `status.report` is no longer `null`.
|
|
38
|
+
|
|
39
|
+
## 0.5.0-stage (unreleased; never published, shipped inside 0.6.0-stage)
|
|
40
|
+
|
|
41
|
+
Pre-release under the `stage` dist-tag. Phase 5 closes the engine gaps the workflowAI scrum-master dogfood raised (issues #8–#21); the decisions are in the design spec, section 21.
|
|
42
|
+
|
|
43
|
+
- Planner budget gates and `closing` (#16): the planner gates on `turns` and `wallclockMs` as well as `tokens`; in-flight items are counted by the iteration's committed list as well as `item.iteration`; a spent dimension skips each ready item with reason `budget` and answers `budget-exhausted`; an iteration in `closing` answers `budget-exhausted`, not `no-candidates`. `ScrumConfig.autoClose: false` keeps an exhausted sprint in `closing` until `review` or `cancel` closes it.
|
|
44
|
+
- Estimate mode, fallback and CLI flag (#21): `ScrumConfig.estimateMode` (`per-weight` or `per-call`) and `ScrumConfig.fallbackTokensPerWeight`; `exwf next --fallback-tokens-per-weight n`.
|
|
45
|
+
- Zero accounting and `exwf account` (#9): `complete()` refuses 0 tokens unless the handoff is declared an estimate (`exwf complete --estimate`); `correctAccounting` and `exwf account` correct one line of an item's accounting, adjust the owning sprint or phase, and record `accounting.corrected`; Scrum metrics fold corrections made after a sprint closed into its frozen `spent`.
|
|
46
|
+
- `metrics.current` (#15): mid-sprint figures for the open sprint (spent, committed and done weight, cost per point, budget utilisation), all zero when no sprint is open.
|
|
47
|
+
- `changeConfig` / `exwf config set` (#8): change the methodology config between iterations, re-validated and recorded as `config.changed`; keys a methodology locks (Kanban `columns` and `classesOfService`, Waterfall `phases`, `traceability` and `humanGates`) are refused.
|
|
48
|
+
- `exwf unblock` (#17): clears a named blocker (`--blocker`) or every open one, with an optional `--note`.
|
|
49
|
+
- Open-questions warning and `status.openQuestions` (#14): a `done` or `needs-review` completion that carries open questions warns `open-questions-ignored`; `exwf status` lists the questions still open.
|
|
50
|
+
- Question brief `parent` (#19): a brief for a question item carries its parent's last completion (handoff, produced, open questions, do-not).
|
|
51
|
+
- Review carryover (#10): an item carried over from the review stage re-enters at `review` on the next `plan` or `commit` and is not re-estimated.
|
|
52
|
+
- `refine.reestimate` (#11): confirm a backlog item's weight without changing it; satisfies the carryover re-estimate rule.
|
|
53
|
+
- `plan auto` skipped (#18): `plan auto` skips a candidate that fails the backlog exit policy or does not fit the remaining budget, keeps walking, and reports each skip in `sprint.opened`.
|
|
54
|
+
- `dod-not-weakened` at retro (#20): a retro between sprints that drops a Definition of Done policy is refused unless `allowWeaken: true`; `dodChanges` replaces the whole list, `suggestedChange { kind: "dod" }` appends one.
|
|
55
|
+
- Agreements replace and retire (#12): a retro lesson's agreement may `replaces` an earlier one, and `retire` lists agreements to remove, matched by exact text; Waterfall's phase retrospective shares the helper.
|
|
56
|
+
- Lesson `from` and `item` (#13): a retro lesson is attributed to a role (or `human`) and may name an item; `from` defaults to the retro's `by`.
|
|
57
|
+
- Events and hooks: `Methodology.lockedConfigKeys` and `afterConfigChange`; `estimateTokens(state, weight, fallback, mode)`; new event kinds `agreement.retired` and `item.reestimated`; payload additions `item.completed.open_questions`, `sprint.opened.skipped` / `resumed`, `sprint.committed.resumed`, `blocker.cleared.note` and retro `retired`.
|
|
58
|
+
|
|
59
|
+
Compatibility: every `.workflow/state.json` written by an earlier stage loads unchanged (every new field is optional). Behaviour changes, all deliberate: a 0-token completion now needs `--estimate`; `plan auto` no longer aborts on an incomplete candidate and keeps walking past one that does not fit; a review-stage carryover re-enters at `review`; a retro that drops a DoD policy between sprints needs `allowWeaken: true`; `next` reports `budget-exhausted` for a spent turns budget and for a sprint in `closing`.
|
|
60
|
+
|
|
61
|
+
## 0.4.0-stage (2026-10-23)
|
|
6
62
|
|
|
7
63
|
- Schema entry (`ex-workflow/schema`): JSON Schema (draft-07) documents generated from the TypeScript types for `WorkflowState`, `Role` (manifest), `NewItem`, `Handoff`, `ScrumConfig`, `KanbanConfig`, `WaterfallConfig`, plus a dependency-free `validate` / `assertValid` for the subset the generator emits. `pnpm schema:check` fails the build when the types and the committed schema drift.
|
|
8
64
|
- Adapters: `loadState` validates `.workflow/state.json` and every `roles/*.json` manifest against the schema after the version check; a state that fails is never returned.
|
package/README.md
CHANGED
|
@@ -7,10 +7,10 @@ State is plain JSON, every operation is `(state, action) -> state`, and every er
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
|
|
10
|
+
npm install ex-workflow
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`npm install ex-workflow` resolves to 1.0.0 (`pnpm add ex-workflow` likewise). Stage pre-releases may still appear under the `stage` dist-tag.
|
|
14
14
|
|
|
15
15
|
Node >= 18. Runs on macOS, Linux and Windows.
|
|
16
16
|
|
|
@@ -61,10 +61,12 @@ board(s2, new Date().toISOString()); // columns with active/queued counts and ca
|
|
|
61
61
|
kanbanMetrics(s2); // lead time, cycle time per column, throughput, WIP, flow efficiency, aging, CFD, SLE compliance
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Cadences are ceremonies: `replenishment`, `delivery-planning`, `service-delivery-review` (WIP, policies, SLEs), `operations-review` (blockers, aging WIP), `risk-review` (fixed-date risk, stale blockers), `strategy-review` (classes of service). `exwf status` reports which cadences are due (`config.cadences.<name>.everyTurns`). `test/kanban.e2e.test.ts` is a full week with an expedite, an escalation and a blocker.
|
|
64
|
+
Cadences are ceremonies: `replenishment`, `delivery-planning`, `service-delivery-review` (WIP, policies, SLEs), `operations-review` (blockers, aging WIP), `risk-review` (fixed-date risk, stale blockers), `strategy-review` (classes of service). `exwf status` reports which cadences are due (`config.cadences.<name>.everyTurns`). `delivery-planning` needs a non-empty `items` list, and the `operations-review` assign entries need an existing blocker id. `test/kanban.e2e.test.ts` is a full week with an expedite, an escalation and a blocker.
|
|
65
65
|
|
|
66
66
|
`next()` also returns `skipped`: each ready card it did not pull, with `reason` (`entry-policy`, `wip`, `resource-cap` or `budget`) and a `detail`; the commitment column's policies gate the pull out of it (escalation `question` cards are exempt, as from exit policies and gates). Ordering is by class band (question, expedite, fixed-date, standard, intangible) and, with `fairness.aging` on (the default), priority stays primary inside a window of `fairness.maxDeferralRounds` passes (default 5); each full window a card is passed over promotes it one step above any priority difference, and an intangible card reaching the first window is ranked as standard, so nothing is starved.
|
|
67
67
|
|
|
68
|
+
`service-delivery-review` also takes `lessons`, `adopt`, `dismiss` and `retire`, with the Scrum retro semantics for agreements.
|
|
69
|
+
|
|
68
70
|
### Waterfall
|
|
69
71
|
|
|
70
72
|
```ts
|
|
@@ -90,6 +92,8 @@ generates the `traceability-matrix` deliverable. Edits to baselined items are re
|
|
|
90
92
|
they go through `applyAction` or the CLI. `test/waterfall.e2e.test.ts` is a full project with a
|
|
91
93
|
change-request that reopens two baselined phases.
|
|
92
94
|
|
|
95
|
+
`add` and `edit` validate `traces` ids and the impact analysis of a change-request, and `status` phase rows carry `missingTraces`.
|
|
96
|
+
|
|
93
97
|
## CLI
|
|
94
98
|
|
|
95
99
|
State lives in `.workflow/`. Every command prints JSON; errors exit 1, bad usage exits 2.
|
|
@@ -99,19 +103,44 @@ State lives in `.workflow/`. Every command prints JSON; errors exit 1, bad usage
|
|
|
99
103
|
| `init <scrum\|kanban\|waterfall> --config <file> [--roles <file>]` | `{ ok, state: { methodology, stages, roles } }` |
|
|
100
104
|
| `add <item.json>` | `{ ok, warnings, item }` (checked by the methodology rules) |
|
|
101
105
|
| `edit <id> <patch.json>` | `{ ok, warnings, item }` (checked by the methodology rules) |
|
|
102
|
-
| `next [--max n]` | `{ ok, reason, skipped, briefs }`; writes `.workflow/briefs/<id>.json` |
|
|
103
|
-
| `complete <id> <handoff.json> --tokens-in n --tokens-out n --duration-ms n --model m [--role r]` | `{ ok, item, warnings }` |
|
|
106
|
+
| `next [--max n] [--fallback-tokens-per-weight n]` | `{ ok, reason, skipped, briefs }`; writes `.workflow/briefs/<id>.json`. `--fallback-tokens-per-weight` prices a candidate when there is no accounting history (default 20000 per weight unit, or `ScrumConfig.fallbackTokensPerWeight`) |
|
|
107
|
+
| `complete <id> <handoff.json> --tokens-in n --tokens-out n --duration-ms n --model m [--role r] [--estimate]` | `{ ok, item, warnings }`. Zero tokens is refused unless `--estimate` marks the line as a placeholder; correct it later with `account` |
|
|
108
|
+
| `account <item> [--line n] [--tokens-in n] [--tokens-out n] [--duration-ms n] [--model m] [--by who]` | `{ ok, line, event }`: rewrites one accounting line of an item (the last by default), adjusts the owning sprint or phase, clears the placeholder flag and records `accounting.corrected` |
|
|
104
109
|
| `ceremony <name> [input.json] [--by who]` | `{ ok, warnings, lastEvent }` |
|
|
105
110
|
| `decide <pendingId> approve\|reject --by who [--note text]` | `{ ok, item }` (runs the methodology's `afterDecide`, e.g. a Waterfall phase baseline) |
|
|
106
|
-
| `
|
|
111
|
+
| `config set <patch.json> [--by who]` | `{ ok, warnings, changed, config }`: merges a patch into the methodology config between iterations (refused while an iteration is open, and for locked keys); the merge is shallow: top-level keys replace wholesale, so `{ "sprintBudget": { "tokens": N } }` drops `turns`/`wallclockMs` |
|
|
112
|
+
| `unblock <item> [--blocker id] [--note text] [--by who]` | `{ ok, warnings, item }`: clears the named open blocker, or every open one |
|
|
113
|
+
| `status` | `{ ok, methodology, openIteration, counts, pending, openQuestions, lastEvent, report }` (`openQuestions` lists the questions a `done` completion left open; `report` is the methodology's own status: Scrum's closing sprint, Kanban's cadences due, Waterfall's phases, pending gates and traceability coverage) |
|
|
107
114
|
| `board` | `{ ok, columns }` |
|
|
108
|
-
| `metrics` | `{ ok, metrics }` |
|
|
115
|
+
| `metrics` | `{ ok, metrics }` (Scrum adds `current`, the open sprint's spent, committed and done weight, cost per point and budget utilisation; zeros when no sprint is open) |
|
|
109
116
|
| `pending` / `lessons` | the arrays |
|
|
110
117
|
| `validate <action.json>` | `{ ok: true, violations }` |
|
|
111
118
|
| `role add <manifest.json>` / `role list` | `{ ok, roles }` |
|
|
112
119
|
|
|
113
120
|
`--by` defaults to `human`.
|
|
114
121
|
|
|
122
|
+
### Scrum configuration
|
|
123
|
+
|
|
124
|
+
`init scrum --config <file>` takes a `ScrumConfig`. Three keys were added in 0.5.0-stage, all optional:
|
|
125
|
+
|
|
126
|
+
- `autoClose` (default `true`): close the sprint automatically when its budget is spent and nothing is in flight. `false` leaves an exhausted sprint in `closing` until `ceremony review` or `ceremony cancel` closes it, so the human gate is never skipped.
|
|
127
|
+
- `estimateMode` (`"per-weight"` by default, or `"per-call"`): how the planner and `plan auto` price a candidate. `per-weight` uses the median tokens per weight unit from history. `per-call` uses the median tokens per completion regardless of weight, which fits a host that bills one all-inclusive total per worker call.
|
|
128
|
+
- `fallbackTokensPerWeight` (default 20000): tokens per weight unit when there is no history.
|
|
129
|
+
|
|
130
|
+
The planner gates on every budget dimension. `sprintBudget.turns` counts one per completion, so a host with a calls budget sets `turns` and `estimateMode: "per-call"` rather than a `calls` key.
|
|
131
|
+
|
|
132
|
+
### Scrum retrospective
|
|
133
|
+
|
|
134
|
+
`ceremony retro <input.json>` runs between sprints. Its input:
|
|
135
|
+
|
|
136
|
+
- `lessons[]`: `{ text, scope, from?, item?, suggestedChange? }`. `from` is `"human"` or a role id (default: the `--by` who); `item` must name an existing item.
|
|
137
|
+
- `adopt[]` / `dismiss[]`: raised lessons, by id, each once.
|
|
138
|
+
- `retire: string[]`: agreement texts to remove, matched by exact text (never by index). An agreement that is not current fails `invalid-input` and lists the current ones.
|
|
139
|
+
- `suggestedChange` (on a lesson or an adopt entry): `{ kind: "agreement", text, replaces? }` adds a working agreement, and `replaces` retires the earlier agreement with that exact text; `{ kind: "dod", policy }` appends one Definition of Done policy; `{ kind: "backlog", item }` queues work; `{ kind: "skill", note }` is for the host.
|
|
140
|
+
- `dodChanges: Policy[]` replaces the whole Definition of Done. Dropping a current policy is refused by `dod-not-weakened` unless the retro input also sets `allowWeaken: true`.
|
|
141
|
+
|
|
142
|
+
`ceremony refine` takes `reestimate: string[]`: confirm the weight of backlog items (each gets `item.reestimated`) without changing them. An item carried over from `review` re-enters at review and is not re-estimated.
|
|
143
|
+
|
|
115
144
|
### Concurrency
|
|
116
145
|
|
|
117
146
|
One writer at a time per `.workflow/`: `exwf` serialises writers with `.workflow/state.lock` (created exclusively, retried for about two seconds, taken over when older than 60 s). If a crashed process left the lock behind and a command reports that another `exwf` holds it, delete the file. Read-only commands do not lock. Use `withStateLock` from `ex-workflow/adapters` when you write state yourself.
|
|
@@ -142,28 +171,33 @@ This package is L0. All layers read and write the same `.workflow/` files.
|
|
|
142
171
|
|
|
143
172
|
## Status
|
|
144
173
|
|
|
145
|
-
0.4.0-stage
|
|
146
|
-
|
|
147
|
-
##
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- Stale-lock takeover
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
- `
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
- Kanban
|
|
160
|
-
- Kanban
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
- Waterfall `
|
|
165
|
-
-
|
|
166
|
-
-
|
|
174
|
+
1.0.0 (2026-10-24) closes the Phase 7 engine gaps (#23-#27) and freezes the public API (exports, CLI commands, schema documents, event kinds — test/api-surface.json) with a compatibility test over state files from every stage release; from here versioning is semver and `latest` is the supported line. 0.6.0-stage closed the Phase 6 backlog: every item the 1.0 definition named is either fixed or an accepted limitation (see [Accepted limitations](#accepted-limitations)). 0.5.0-stage closes the engine gaps from the workflowAI dogfood (Scrum budget gates, configuration between iterations, accounting corrections, agreements that can be replaced or retired, question briefs with their parent); see [CHANGELOG.md](CHANGELOG.md). 0.4.0-stage added `ex-workflow/schema` (JSON Schema documents and a validator, checked by `exwf` on load and on input). 0.3.0-stage added the Waterfall module; all three methodologies ship. Design: [docs/superpowers/specs/2026-10-19-ex-workflow-design.md](docs/superpowers/specs/2026-10-19-ex-workflow-design.md).
|
|
175
|
+
|
|
176
|
+
## Accepted limitations
|
|
177
|
+
|
|
178
|
+
The 1.0 definition requires every known gap to be fixed or accepted as a documented limitation. These are the accepted ones (design spec, section 22). Each has one line of reasoning.
|
|
179
|
+
|
|
180
|
+
- Stale-lock takeover (B01): the re-stat narrows the race to the gap between the stat and the `unlink`, which no portable call closes; `ino` may be 0 (FAT/exFAT, some shares), in which case only the mtime is compared.
|
|
181
|
+
- Conflict of interest (B02): `"conflict-of-interest"` stays a declared, reserved `ErrorCode` that is never thrown, and the core rule matching action type `complete` stays dormant; `checkGate` already refuses the case as `gate-unmet`, and removing the code would narrow a public type.
|
|
182
|
+
- Blockers (B03): a blocker is owned by a human, and clearing it would need a link from the follow-up that the schema does not carry; `exwf unblock` is the path.
|
|
183
|
+
- Scrum stage ids (B06): `backlog`, `sprint` and `review` are owned by the module (about twenty uses); init refuses a config that lacks them.
|
|
184
|
+
- Uncommit while closing (B07): `ceremony uncommit` stays allowed during `closing` because it only shrinks the sprint.
|
|
185
|
+
- Roles on load (B09): state wins on an id conflict, `exwf role add` is the audited path, and re-merging is idempotent.
|
|
186
|
+
- Scrum size row (B11): budgets sit at measured size by the owner's standing decision; the row stays as a tripwire.
|
|
187
|
+
- Kanban WIP (B12): the engine must not refuse a card it advanced itself; operations-review surfaces queued cards.
|
|
188
|
+
- Kanban `wipBypass` (B13): it is a column total; re-scoping the key after 1.0 would break configs.
|
|
189
|
+
- Kanban cadences (B14): the key is `everyTurns` because the engine has no clock; wall-clock schedules are the host's job.
|
|
190
|
+
- Waterfall traceability (B16): it is keyed by phase ids because the core item union has no requirement, design or test types; phase membership classifies items.
|
|
191
|
+
- Direct primitives (B17): `editItem` and `addItem` bypass `baseline-immutable` by design; `applyAction` and the CLI enforce it.
|
|
192
|
+
- Phase budgets (B18): a phase budget reuses `Iteration` and `status` shows `openIteration`; the naming is accepted, and `fallbackTokensPerWeight` covers the no-history case.
|
|
193
|
+
- Phase retrospective (B19): baselines and config are locked in Waterfall, so only `agreement` lessons apply; `dod` and `backlog` suggestions are stored for the host.
|
|
194
|
+
- Accounting outside sprints (B23): a line whose `endedAt` lies outside every sprint window has no sprint to charge.
|
|
195
|
+
- Waterfall deliverables (B10): `deliverables-present` aggregates raw item evidence while stage gates use fresh evidence; the two diverge only when one config uses both.
|
|
196
|
+
- Rework in flight (B10): an item reworked mid-flight in a state written before 0.6.0-stage needs fresh evidence before it can pass.
|
|
197
|
+
- Flag values (B24): CLI flags take `--flag value` only; a value beginning with `--` must come via a JSON input file; `--flag=value` is not supported.
|
|
198
|
+
- Stale lock unlink: `withStateLock` retries without reaching the retry cap when a stale lock's unlink keeps failing (EPERM, read-only directory); delete the file by hand.
|
|
199
|
+
- Scrum `status.report.reason`: it is always `budget`; the engine has no other closing cause yet.
|
|
200
|
+
- Carryover in an unflagged stage (B22): a carryover that was `blocked` or `awaiting_human` in a stage without `review: true` restarts at the sprint start and needs a reestimate; only a stage flagged `review`, or an item still at status `review`, resumes where it was. Flag the stage `review: true` to resume there.
|
|
167
201
|
|
|
168
202
|
## Development gate
|
|
169
203
|
|
|
@@ -172,16 +206,18 @@ There is no hosted CI. A husky pre-commit hook runs the local gate on every comm
|
|
|
172
206
|
minified build, the dist identity check and the size ratchet, and `prepublishOnly` runs it
|
|
173
207
|
before any publish. oxfmt and oxlint need Node ^20.19 or >=22.12 on the developer machine; the
|
|
174
208
|
package itself supports Node >=18 (verified on ubuntu, macOS and Windows x Node 18/20/22
|
|
175
|
-
before the hosted workflow was removed). Schema drift is guarded by `pnpm schema:check` alone, not by a test, because the generator needs Node >=22 and the test suite must run on >=18.
|
|
209
|
+
before the hosted workflow was removed). `pnpm test` includes the compatibility fixtures (`test/fixtures/compat`, `state.json` files written by the published 0.1.0, 0.2.0, 0.3.0, 0.4.0 and 0.6.0 stage releases; 0.5.0-stage was never published); `node scripts/gen-compat-fixtures.mjs` regenerates them (network needed). Schema drift is guarded by `pnpm schema:check` alone, not by a test, because the generator needs Node >=22 and the test suite must run on >=18.
|
|
176
210
|
|
|
177
211
|
## Publishing
|
|
178
212
|
|
|
179
|
-
For the owner.
|
|
213
|
+
For the owner. A release moves `latest`:
|
|
180
214
|
|
|
181
215
|
```sh
|
|
182
|
-
pnpm run ci && npm publish
|
|
216
|
+
pnpm run ci && npm publish
|
|
183
217
|
```
|
|
184
218
|
|
|
219
|
+
Use `npm publish --tag stage` only for a pre-release.
|
|
220
|
+
|
|
185
221
|
## Design principles
|
|
186
222
|
|
|
187
223
|
1. One engine, three methodologies, modelled as completely as the canonical texts describe them (Scrum Guide 2020; the Kanban Method's six practices, cadences, classes of service and flow metrics; classic phase-gate Waterfall with change control and traceability).
|
package/dist/adapters/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkYRATJT7P_cjs=require('../chunk-YRATJT7P.cjs');require('../chunk-WOGK54HB.cjs'),require('../chunk-S3BXVHO7.cjs');Object.defineProperty(exports,"WORKFLOW_DIR",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.a}});Object.defineProperty(exports,"loadState",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.e}});Object.defineProperty(exports,"saveBrief",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.f}});Object.defineProperty(exports,"saveHandoff",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.g}});Object.defineProperty(exports,"saveState",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.d}});Object.defineProperty(exports,"statePath",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.b}});Object.defineProperty(exports,"withStateLock",{enumerable:true,get:function(){return chunkYRATJT7P_cjs.c}});//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/adapters/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{a as WORKFLOW_DIR,e as loadState,f as saveBrief,g as saveHandoff,d as saveState,b as statePath,c as withStateLock}from'../chunk-
|
|
1
|
+
export{a as WORKFLOW_DIR,e as loadState,f as saveBrief,g as saveHandoff,d as saveState,b as statePath,c as withStateLock}from'../chunk-7IG6EQI4.mjs';import'../chunk-5TXYMLBF.mjs';import'../chunk-2FRRD6A3.mjs';//# sourceMappingURL=index.mjs.map
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowState, Role, Capability } from './types.js';
|
|
1
|
+
import { WorkflowState, Role, Capability, Budget, Accounting } from './types.js';
|
|
2
2
|
|
|
3
3
|
declare function registerRole(state: WorkflowState, role: Role, by: string, now?: string): WorkflowState;
|
|
4
4
|
declare function retireRole(state: WorkflowState, roleId: string, by: string, now?: string): WorkflowState;
|
|
@@ -11,4 +11,11 @@ declare function separationLost(state: WorkflowState, required: Capability[]): R
|
|
|
11
11
|
declare function canDo(state: WorkflowState, by: string, ...caps: Capability[]): boolean;
|
|
12
12
|
declare function requireCap(state: WorkflowState, by: string, ...caps: Capability[]): void;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
declare const ZERO_SPENT: Required<Budget>;
|
|
15
|
+
declare function addSpent(spent: Required<Budget>, acc: Accounting): Required<Budget>;
|
|
16
|
+
declare function remaining(budget: Budget, spent: Required<Budget>): Budget;
|
|
17
|
+
declare function isExhausted(budget: Budget, spent: Required<Budget>): boolean;
|
|
18
|
+
type EstimateMode = "per-weight" | "per-call";
|
|
19
|
+
declare function estimateTokens(state: WorkflowState, weight: number | undefined, fallbackPerWeight?: number, mode?: EstimateMode): number;
|
|
20
|
+
|
|
21
|
+
export { type EstimateMode as E, ZERO_SPENT as Z, addSpent as a, approversFor as b, canDo as c, remaining as d, estimateTokens as e, requireCap as f, requireCapabilities as g, hasCapability as h, isExhausted as i, retireRole as j, rolesWith as k, registerRole as r, separationLost as s };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowState, Role, Capability } from './types.cjs';
|
|
1
|
+
import { WorkflowState, Role, Capability, Budget, Accounting } from './types.cjs';
|
|
2
2
|
|
|
3
3
|
declare function registerRole(state: WorkflowState, role: Role, by: string, now?: string): WorkflowState;
|
|
4
4
|
declare function retireRole(state: WorkflowState, roleId: string, by: string, now?: string): WorkflowState;
|
|
@@ -11,4 +11,11 @@ declare function separationLost(state: WorkflowState, required: Capability[]): R
|
|
|
11
11
|
declare function canDo(state: WorkflowState, by: string, ...caps: Capability[]): boolean;
|
|
12
12
|
declare function requireCap(state: WorkflowState, by: string, ...caps: Capability[]): void;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
declare const ZERO_SPENT: Required<Budget>;
|
|
15
|
+
declare function addSpent(spent: Required<Budget>, acc: Accounting): Required<Budget>;
|
|
16
|
+
declare function remaining(budget: Budget, spent: Required<Budget>): Budget;
|
|
17
|
+
declare function isExhausted(budget: Budget, spent: Required<Budget>): boolean;
|
|
18
|
+
type EstimateMode = "per-weight" | "per-call";
|
|
19
|
+
declare function estimateTokens(state: WorkflowState, weight: number | undefined, fallbackPerWeight?: number, mode?: EstimateMode): number;
|
|
20
|
+
|
|
21
|
+
export { type EstimateMode as E, ZERO_SPENT as Z, addSpent as a, approversFor as b, canDo as c, remaining as d, estimateTokens as e, requireCap as f, requireCapabilities as g, hasCapability as h, isExhausted as i, retireRole as j, rolesWith as k, registerRole as r, separationLost as s };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {a as a$1,b as b$2}from'./chunk-U3OXAO67.mjs';import {c,u,x,o,n,i,j as j$1,I,R as R$1,N,Y as Y$1,a,K as K$1,M as M$1}from'./chunk-YBHHICTZ.mjs';import {b as b$1,c as c$1}from'./chunk-2FRRD6A3.mjs';import {timSort}from'exsorted';var De=["requirements","design","implementation","verification","deployment","maintenance"],pe={requirements:["srs"],design:["design-doc","interface-spec"],implementation:["code-baseline"],verification:["test-report","traceability-matrix"],deployment:["release-notes","deployment-record"],maintenance:["change-log"]},fe={requirements:"business-analyst",design:"architect",deployment:"release-manager"},ue=e=>`${e}-gate`,me="change-control",je=["order-backlog","define-acceptance","facilitate","deliver","review","verify","approve-gate","manage-flow","control-change","escalate"],Be=e=>e.charAt(0).toUpperCase()+e.slice(1);function Me(){return De.map(e=>{let t={id:e,name:Be(e),deliverables:[...pe[e]??[]],gate:{id:ue(e),requires:[{capability:"approve-gate"}],evidence:[],onFail:{action:"block"}}},n=fe[e];return n!==void 0&&(t.deliverBy=n),t})}function w(e){return e.config}var G=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),re=e=>Array.isArray(e)&&e.every(t=>typeof t=="string"&&t.trim()!=="");function Ye(e,t){if(e===void 0)return;G(e)||b$1("config-invalid",`${t}.budget must be an object`,'e.g. { "tokens": 400000 }');let n={};for(let a of ["tokens","turns","wallclockMs"]){let p=e[a];p!==void 0&&((typeof p!="number"||!Number.isFinite(p)||p<0)&&b$1("config-invalid",`${t}.budget.${a} must be a non-negative number`,"omit it for no limit"),n[a]=p);}return n}function Ge(e,t){return (!Array.isArray(e)||e.length===0)&&b$1("config-invalid",`${t} must be a non-empty array of "human" | { capability }`,'e.g. ["human", { "capability": "approve-gate" }]'),e.map(n=>n==="human"?"human":G(n)&&typeof n.capability=="string"&&je.includes(n.capability)?{capability:n.capability}:b$1("config-invalid",`${t} entry ${JSON.stringify(n)} is not "human" or a known capability`,`use "human" or { "capability": one of ${je.join(", ")} }`))}function Ee(e,t,n){let a=`waterfall config.phases[${t}]`;G(e)||b$1("config-invalid",`${a} must be an object`,'e.g. { "id": "design" }'),c$1(e.id,`${a}.id`);let p=e.id,c=e.deliverables??pe[p]??[];re(c)||b$1("config-invalid",`${a}.deliverables must be a string[] of evidence kinds`,'e.g. ["srs"]');let o;if(e.gate===void 0)o={id:ue(p),requires:[{capability:"approve-gate"}],evidence:[],onFail:{action:"block"}};else {let l=e.gate;(!G(l)||typeof l.id!="string"||l.id.trim()==="")&&b$1("config-invalid",`${a}.gate must be a Gate with an id`,'e.g. { "id": "design-gate", "requires": [{ "capability": "approve-gate" }], "evidence": [] }');let f=l.evidence??[];re(f)||b$1("config-invalid",`${a}.gate.evidence must be a string[]`,"list evidence kinds or omit it"),o={id:l.id,requires:Ge(l.requires??[{capability:"approve-gate"}],`${a}.gate.requires`),evidence:[...f],onFail:{action:"block"}};}n(p)&&!o.requires.includes("human")&&(o.requires=["human",...o.requires]);let d={id:p,name:typeof e.name=="string"&&e.name.trim()!==""?e.name:Be(p),deliverables:[...c],gate:o},i=Ye(e.budget,a);i!==void 0&&(d.budget=i);let s=e.deliverBy??fe[p];return s!==void 0&&((typeof s!="string"||s.trim()==="")&&b$1("config-invalid",`${a}.deliverBy must be a role id`,"name the role that delivers this phase or omit it"),d.deliverBy=s),d}function ge(e){let t=e??{};t.phases!==void 0&&!Array.isArray(t.phases)&&b$1("config-invalid","waterfall config.phases must be an array of phases","omit phases for the six classic phases");let n=t.humanGates??"all";n!=="all"&&!re(n)&&b$1("config-invalid",'waterfall config.humanGates must be "all" or a string[] of phase ids','omit it for "all"');let a=n==="all"?()=>true:r=>n.includes(r),p=t.phases===void 0?Me().map((r,m)=>Ee(r,m,a)):t.phases.map((r,m)=>Ee(r,m,a));p.length===0&&b$1("config-invalid","waterfall config.phases needs at least one phase",'e.g. [{ "id": "requirements" }]');let c=p.map(r=>r.id),o=c.find((r,m)=>c.indexOf(r)!==m);if(o!==void 0&&b$1("config-invalid",`duplicate phase id "${o}"`,"give every phase a unique id"),n!=="all")for(let r of n)c.includes(r)||b$1("config-invalid",`waterfall config.humanGates names unknown phase "${r}"`,`phases are: ${c.join(", ")}`);let d=t.changeControl??{};G(d)||b$1("config-invalid","waterfall config.changeControl must be an object",'e.g. { "requiresImpactAnalysis": true, "approvers": ["human"] }'),d.requiresImpactAnalysis!==void 0&&typeof d.requiresImpactAnalysis!="boolean"&&b$1("config-invalid","waterfall config.changeControl.requiresImpactAnalysis must be a boolean","omit it for true");let i={requiresImpactAnalysis:d.requiresImpactAnalysis===void 0?true:d.requiresImpactAnalysis,approvers:Ge(d.approvers??["human",{capability:"control-change"}],"waterfall config.changeControl.approvers")},s=t.traceability,l;if(s===void 0)l=c.includes("requirements")&&c.includes("design")&&c.includes("verification")?{from:"requirements",to:["design","verification"]}:{from:c[0],to:[]};else {(!G(s)||typeof s.from!="string"||!c.includes(s.from))&&b$1("config-invalid","waterfall config.traceability.from must be a phase id",`phases are: ${c.join(", ")}`);let r=s.to??[];re(r)||b$1("config-invalid","waterfall config.traceability.to must be a string[] of phase ids",`phases are: ${c.join(", ")}`);for(let m of r)c.includes(m)||b$1("config-invalid",`waterfall config.traceability.to names unknown phase "${m}"`,`phases are: ${c.join(", ")}`),c.indexOf(m)<=c.indexOf(s.from)&&b$1("config-invalid",`waterfall config.traceability.to phase "${m}" must come after "${s.from}"`,"trace forward only: from an earlier phase to later ones");l={from:s.from,to:[...r]};}let f=t.fallbackTokensPerWeight;f!==void 0&&(typeof f!="number"||!Number.isFinite(f)||f<=0)&&b$1("config-invalid","waterfall config.fallbackTokensPerWeight must be a positive finite number","omit it for the default 20000");let u={phases:p,changeControl:i,traceability:l,humanGates:n==="all"?"all":[...n]};return f!==void 0&&(u.fallbackTokensPerWeight=f),u}var L="work",V="review",H="done";function b(e){return e.phases?e.phases:w(e).phases.map(t=>{let n={id:t.id,name:t.name,deliverables:[...t.deliverables],gate:t.gate,status:"pending"};return t.budget&&(n.budget=t.budget),n})}function j(e,t){return {...e,phases:t}}function E(e,t){let n=b(e).findIndex(a=>a.id===t);return n<0&&b$1("config-invalid",`unknown phase "${t}"`,`phases are: ${b(e).map(a=>a.id).join(" \u2192 ")}`),n}function _(e,t){return b(e)[E(e,t)]}function q(e,t){let n=new Set,a=t;for(;a&&!n.has(a.id);){if(a.phase!==void 0)return a.phase;n.add(a.id),a=a.parent!==void 0&&Object.hasOwn(e.items,a.parent)?e.items[a.parent]:void 0;}}function P(e,t){return Object.values(e.items).filter(n=>n.status!=="dropped"&&q(e,n)===t)}function D(e){return b(e).find(t=>t.status==="open")}function he(e){return b(e).find(t=>t.status!=="baselined")}function Je(e,t){let n=q(e,t);return n!==void 0&&b(e).find(a=>a.id===n)?.status==="baselined"}function _e(e,t){let n=new Set((t.produced??[]).map(a=>a.kind));for(let a of P(e,t.id))for(let p of a.evidence)n.add(p.kind);return n}function U(e,t,n=[]){let a=_e(e,t);for(let c of n)a.add(c);let p=[];for(let c of [...t.deliverables,...t.gate.evidence])p.includes(c)||p.push(c);return p.filter(c=>!a.has(c))}function B(e){let t=b(e).map(a=>a.id),n=w(e).traceability.to.filter(a=>t.includes(a));if(n.length!==0)return n.reduce((a,p)=>t.indexOf(a)>=t.indexOf(p)?a:p)}function Fe(e,t,n,a){let p=R$1(e);return !p||!p.id.startsWith(`${t}#`)?e:{...e,iterations:e.iterations.map(c=>c.id===p.id?{...c,endedAt:a,outcome:n}:c)}}function ae(e,t,n,a){let p=_(e,t);p.status!=="open"&&b$1("invalid-transition",`phase "${t}" is ${p.status}, not open`,p.status==="baselined"?"it is already baselined; a change-request reopens it":"kickoff the phase first");let c$1=P(e,t),o={};for(let l of c$1)o[l.id]=l;let d=(e.baselines??[]).filter(l=>l.phase===t).length,i={phase:t,version:d+1,at:a,approvedBy:n,itemSnapshot:o},s=j(e,b(e).map(l=>l.id===t?{...l,status:"baselined"}:l));return s={...s,baseline:i,baselines:[...s.baselines??[],i]},s=Fe(s,t,"completed",a),c(s,"phase.baselined",n[n.length-1]??"system",{phase:t,version:i.version,approvedBy:n,items:c$1.map(l=>l.id)},a)}function ye(e,t,n){let a=[];for(let d of t){let i=_(e,d);i.status==="pending"&&b$1("invalid-input",`phase "${d}" was never baselined; there is nothing to reopen`,"remove it from impactAnalysis.reopen"),i.status==="baselined"&&!a.includes(d)&&a.push(d);}let p=b(e).map(d=>a.includes(d.id)?{...d,status:"reopened"}:d),c=null,o=e;if(a.length){let d=Math.min(...a.map(s=>E(e,s))),i=p.find((s,l)=>s.status==="open"&&l>d);i&&(c=i.id,p=p.map(s=>s.id===i.id?{...s,status:"reopened"}:s),o=Fe(o,i.id,"cancelled",n));}return {state:j(o,p),reopened:a,suspended:c}}function z(e,t,n,a,p){let c$1=u(e,t),o=c$1.impactAnalysis,d=ye(e,o?.reopen??[],p),i=x(d.state,t,{stage:H,status:"done"},n,p);for(let s of d.reopened)i=c(i,"phase.reopened",n,{phase:s,change:t},p);return d.suspended!==null&&(i=c(i,"phase.suspended",n,{phase:d.suspended,change:t},p)),c(i,"change.decided",n,{change:t,decision:"approve",by:n,note:a,reopened:d.reopened,suspended:d.suspended,weight:o?.weight??0,affects:c$1.affects??[]},p)}function K(e,t,n,a,p){let c$1=u(e,t),o=x(e,t,{status:"dropped"},n,p);return c(o,"change.decided",n,{change:t,decision:"reject",by:n,note:a,reopened:[],suspended:null,weight:0,affects:c$1.affects??[]},p)}function Ne(e){return (t,n)=>{o(t,n.by,"facilitate");let a$2=n.phase===void 0?void 0:Y(t,n,"phaseRetrospective"),p=a$1(t,n,"phaseRetrospective");return Y$1(t,e(),{type:"ceremony.phaseRetrospective",by:n.by,payload:n},c$1=>{let o=c$1,d=a(n.now),i=b$2(o,p,n.by,d);return o=i.state,c(o,"phase.retrospective",n.by,{phase:a$2?.id??null,lessons:i.created,adopted:i.adopted.slice(i.created.length),dismissed:i.dismissed,agreements:i.agreements,retired:i.retired},d)})}}var Ze=(e,t)=>e.id<t.id?-1:e.id>t.id?1:0,Le=(e,t)=>timSort(P(e,t).filter(n=>n.type!=="question"&&n.type!=="change-request"),Ze),Xe=(e,t)=>(e.traces??[]).includes(t.id)||(t.traces??[]).includes(e.id);function O(e){let t=w(e).traceability,n=b(e).map(o=>o.id),a=t.to.filter(o=>n.includes(o)),p=Le(e,t.from).map(o=>{let d={};for(let i of a)d[i]=Le(e,i).filter(s=>Xe(o,s)).map(s=>s.id);return {item:o.id,links:d}}),c=p.map(o=>({item:o.item,phases:a.filter(d=>o.links[d].length===0)})).filter(o=>o.phases.length>0);return {from:t.from,to:a,rows:p,missing:c,coverage:{traced:p.length-c.length,total:p.length}}}function J(e,t){return O(e).rows.filter(n=>(n.links[t]??[]).length===0).map(n=>n.item)}var et=e=>typeof e=="object"&&e!==null&&!Array.isArray(e);function Y(e,t,n){let a=t.phase;return (typeof a!="string"||a.trim()==="")&&b$1("invalid-input",`${n} needs phase: string`,`pass one of: ${b(e).map(p=>p.id).join(", ")}`),_(e,a)}function Ve(e,t,n,a,p,c$1){let o=j(e,b(e).map(d=>d.id===t?{...d,produced:[...d.produced??[],{kind:n,ref:a,by:p,at:c$1}]}:d));return c(o,"phase.deliverable",p,{phase:t,kind:n,ref:a},c$1)}function be(e){let t=(c,o,d,i)=>Y$1(c,e(),{type:o,by:d.by,payload:d},s=>i(s,a(d.now)));return {kickoff:(c$1,o$1)=>{o(c$1,o$1.by,"facilitate");let d=Y(c$1,o$1,"kickoff");return t(c$1,"ceremony.kickoff",o$1,(i,s)=>{let l=i.events.filter(r=>r.type==="phase.opened"&&r.payload.phase===d.id).length+1,f=P(i,d.id).filter(r=>r.status!=="done").map(r=>r.id);i=j(i,b(i).map(r=>r.id===d.id?{...r,status:"open"}:r));let u=d.budget??{};return i={...i,iterations:[...i.iterations,{id:`${d.id}#${l}`,goal:d.name,budget:u,spent:M$1,startedAt:s,committed:f,carryover:[]}]},c(i,"phase.opened",o$1.by,{phase:d.id,version:l,items:f,budget:u},s)})},gateReview:(c$1,o)=>{let d=Y(c$1,o,"gateReview");d.status!=="open"&&b$1("invalid-transition",`phase "${d.id}" is ${d.status}, not open`,d.status==="baselined"?"it is already baselined; a change-request reopens it":`kickoff "${d.id}" first`);let i=d.gate,s=j$1(c$1,i.id,"approve-gate");o.by!=="human"&&!s.includes(o.by)&&b$1("gate-unmet",`"${o.by}" may not review gate "${i.id}"`,`review as one of: human${s.map(r=>`, ${r}`).join("")}`,{rule:"gate"});let l=c$1.pending.find(r=>r.kind==="gate"&&r.item===void 0&&r.gate===i.id);l&&b$1("no-op",`gate "${i.id}" is already awaiting a decision (${l.id})`,`decide ${l.id} approve|reject`);let f=o.deliverables??[];(!Array.isArray(f)||!f.every(r=>et(r)&&typeof r.kind=="string"&&r.kind.trim()!==""&&typeof r.ref=="string"))&&b$1("invalid-input","gateReview.deliverables must be [{ kind, ref }]",'e.g. { "deliverables": [{ "kind": "srs", "ref": "docs/srs.md" }] }');let u=f;return t(c$1,"ceremony.gateReview",o,(r,m)=>{for(let h of u)r=Ve(r,d.id,h.kind,h.ref,o.by,m);if(w(r).traceability.to.includes(d.id)){let h=O(r);r=c(r,"traceability.computed",o.by,{phase:d.id,from:h.from,to:h.to,coverage:h.coverage,missing:h.missing,rows:h.rows},m),d.id===B(r)&&h.missing.length===0&&(r=Ve(r,d.id,"traceability-matrix",`traceability.computed#${r.events[r.events.length-1].seq}`,o.by,m));}if(i.requires.includes("human")&&o.by!=="human"){r=K$1(r,{kind:"gate",gate:i.id,question:`Approve gate "${i.id}" and baseline phase "${d.name}"?`},m);let h=r.pending[r.pending.length-1].id;return c(r,"phase.gate-pending",o.by,{phase:d.id,gate:i.id,by:o.by,pending:h},m)}return ae(r,d.id,[o.by],m)})},changeControlBoard:(c$1,o$1)=>{o(c$1,o$1.by,"control-change");let d=o$1.change;(typeof d!="string"||d.trim()==="")&&b$1("invalid-input","changeControlBoard needs change: string (a change-request id)",'e.g. { "change": "CR-1", "decision": "approve", "note": "why" }');let i$1=u(c$1,d);if(i$1.type!=="change-request"&&b$1("invalid-input",`item "${d}" is a ${i$1.type}, not a change-request`,"only change-request items go through the board",{items:[d]}),(i$1.status==="done"||i$1.status==="dropped")&&b$1("invalid-transition",`change "${d}" is already ${i$1.status==="done"?"approved":"rejected"}`,"raise a new change-request",{items:[d]}),i$1.status==="awaiting_human"){let r=c$1.pending.find(m=>m.item===d);b$1("no-op",`change "${d}" is awaiting the human decision${r?` (${r.id})`:""}`,`decide ${r?.id??"<pending id>"} approve|reject`,{items:[d]});}let s=o$1.decision;s!=="approve"&&s!=="reject"&&b$1("invalid-input",'changeControlBoard.decision must be "approve" or "reject"',"state the board's decision");let l=typeof o$1.note=="string"?o$1.note:"",f=w(c$1).changeControl.approvers;for(let r of f)r==="human"||o$1.by==="human"||n(c$1,o$1.by,r.capability)||b$1("gate-unmet",`"${o$1.by}" lacks capability "${r.capability}" required by change control`,`decide as one of: human${i(c$1,r.capability).map(m=>`, ${m.id}`).join("")}`,{rule:"change-control",items:[d]});let u$1=f.includes("human")&&o$1.by!=="human";return t(c$1,"ceremony.changeControlBoard",o$1,(r,m)=>{if(u$1){r=K$1(r,{kind:"decision",item:d,question:`Change "${d}" (${i$1.title}): the board (${o$1.by}) recommends ${s}${l?`: ${l}`:""}. Approve or reject the change?`},m);let h=r.pending[r.pending.length-1].id;return c(r,"change.recommended",o$1.by,{change:d,decision:s,note:l,pending:h},m)}return s==="approve"?z(r,d,o$1.by,l,m):K(r,d,o$1.by,l,m)})},phaseRetrospective:Ne(e)}}function ve(e,t,n,a,p,c$1){if(t.kind==="gate"&&t.item===void 0&&t.gate!==void 0){let o=b(e).find(s=>s.gate.id===t.gate);if(!o)return e;a!=="human"&&b$1("gate-unmet",`"${a}" may not decide phase gate "${t.gate}" (${t.id}); it awaits a human`,`decide ${t.id} as human`,{rule:"gate"});let d;for(let s=e.events.length-1;s>=0;s--){let l=e.events[s];if(l.type==="phase.gate-pending"&&l.payload.pending===t.id){d=l.by;break}}let i=d!==void 0&&d!==a?[d,a]:[a];return n==="approve"?ae(e,o.id,i,c$1):c(e,"phase.gate-failed",a,{phase:o.id,gate:t.gate,note:p},c$1)}if(t.kind==="decision"&&t.item!==void 0&&Object.hasOwn(e.items,t.item)){let o=e.items[t.item];return o.type!=="change-request"?e:(a!=="human"&&b$1("gate-unmet",`"${a}" may not decide change "${o.id}" (${t.id}); change control awaits a human`,`decide ${t.id} as human`,{rule:"change-control",items:[o.id]}),n==="approve"?z(e,o.id,a,p,c$1):K(e,o.id,a,p,c$1))}return e}function ke(e){let t=w(e).phases.map($=>$.id),n=()=>Object.fromEntries(t.map($=>[$,0])),a=n(),p=n(),c=n(),o=n(),d=n(),i=new Map,s=new Map,l=new Map,f=0,u=0,r=0,m=0,h=0,v=$=>{let W=s.get($),y=i.get($);return W!=="question"&&W!=="change-request"&&y!==void 0&&y in a};for(let $ of e.events){let W=$.payload??{};switch($.type){case "item.added":{let y=W,T=y.phase??(y.parent!==null?i.get(y.parent):void 0);i.set(y.id,T),s.set(y.id,y.type),l.set(y.id,"todo"),y.type==="change-request"&&(f+=1),v(y.id)&&(a[T]+=1,y.type==="bug"&&(d[T]+=1));break}case "item.transition":{let y=W.id,T=W.to.status,Pe=l.get(y);if(l.set(y,T),!v(y))break;let de=i.get(y);T==="done"&&Pe!=="done"&&(p[de]+=1),T==="dropped"&&(a[de]-=1,Pe==="done"&&(p[de]-=1));break}case "item.completed":{let y=i.get(W.id);y!==void 0&&y in o&&(o[y]+=W.tokens??0);break}case "phase.opened":{let y=W.phase;y in c&&(c[y]=(W.budget??{}).tokens??0);break}case "change.decided":{W.decision==="approve"?(u+=1,m+=W.weight??0,h+=(W.reopened??[]).length):r+=1;break}}}let Z=n(),I=n();for(let $ of t){let W=a[$]??0;Z[$]=W===0?0:(p[$]??0)/W*100,I[$]=o[$]-c[$];}let{traced:x,total:se}=O(e).coverage,Re=u+r;return {phaseCompletion:Z,budgetPlanned:c,budgetSpent:o,scheduleVariance:I,requirementCoverage:se===0?0:x/se,requirementsTraced:x,requirementsTotal:se,defectsPerPhase:d,changeRequests:f,changesApproved:u,changesRejected:r,changeApprovalRate:Re===0?0:u/Re,reworkWeight:m,phasesReopened:h}}function we(e){let t=e.phases.map(n=>n.gate.id);return [{id:"project-manager",name:"Project Manager",modelTier:"large",capabilities:["facilitate","control-change","approve-gate","escalate"],canApprove:[...t,me],inputs:["phase-plan","deliverables","change-requests"],outputs:["gate-decisions","baselines","change-decisions"],do_not:["do not implement items yourself","do not reopen a phase without an approved change-request"]},{id:"developer",name:"Developer",modelTier:"small",capabilities:["deliver"],canApprove:[],inputs:["design-doc","interface-spec","brief"],outputs:["code","code-baseline","test-output"],do_not:["do not change requirements or designs; raise a change-request","do not approve your own work"]},{id:"business-analyst",name:"Business Analyst",modelTier:"medium",capabilities:["define-acceptance","deliver"],canApprove:[],inputs:["stakeholder-needs"],outputs:["srs","acceptance-criteria"],do_not:["do not design or implement","do not edit a baselined requirement; raise a change-request"]},{id:"architect",name:"Architect",modelTier:"medium",capabilities:["deliver"],canApprove:[],inputs:["srs"],outputs:["design-doc","interface-spec"],do_not:["do not change requirements; raise a change-request","do not implement"]},{id:"qa",name:"QA",modelTier:"small",capabilities:["verify","review"],canApprove:[],inputs:["srs","design-doc","code-baseline"],outputs:["test-report","review-notes"],do_not:["do not fix defects yourself; raise bugs","do not approve phase gates"]},{id:"release-manager",name:"Release Manager",modelTier:"small",capabilities:["deliver"],canApprove:[],inputs:["code-baseline","test-report"],outputs:["release-notes","deployment-record"],do_not:["do not deploy an unbaselined verification phase"]}]}var k=(e,t,n,a)=>a?{rule:e,severity:"error",item:a,message:t,fix:n}:{rule:e,severity:"error",message:t,fix:n},R=e=>e.payload??{},C=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Q=e=>{let t=R(e).phase;return typeof t=="string"?t:void 0},S=(e,t)=>b(e).find(n=>n.id===t),oe=e=>b(e).map(t=>t.id).join(", "),M=(e,t)=>typeof t=="string"&&Object.hasOwn(e.items,t)?e.items[t]:void 0;function He(e,t){if(t.phase!==void 0)return t.phase;let n=M(e,t.parent);return n?q(e,n):void 0}var Ue=e=>e.type!=="change-request"&&e.type!=="question",ze=(e,t)=>`raise a change-request: addItem({ type: "change-request", affects: ["${e}"], impactAnalysis: { summary, reopen: ["${t}"], weight } }) and decide it in changeControlBoard`;function Ke(e,t){let n=M(e,R(t).change);return n&&n.type==="change-request"?n:void 0}function $e(e,t,n){let a="change-needs-impact-analysis",p=`editItem("${t}", { impactAnalysis: { summary: "what changes and why", reopen: ["<phase>"], weight: <points> } })`;if(n===void 0)return w(e).changeControl.requiresImpactAnalysis?[k(a,`change "${t}" has no impact analysis`,p,t)]:[];let c=[],o=C(n)?n:{};return (typeof o.summary!="string"||o.summary.trim()==="")&&c.push(k(a,`change "${t}" impactAnalysis.summary is empty`,p,t)),(!Array.isArray(o.reopen)||!o.reopen.every(d=>typeof d=="string"))&&c.push(k(a,`change "${t}" impactAnalysis.reopen must be a string[] of phase ids`,p,t)),(typeof o.weight!="number"||!Number.isFinite(o.weight)||o.weight<0)&&c.push(k(a,`change "${t}" impactAnalysis.weight must be a non-negative number`,p,t)),c}function We(){return [{id:"phase-sequential",severity:"error",check(i,s){let l=[];if(s.type==="ceremony.kickoff"){let f=Q(s),u=f===void 0?void 0:S(i,f);if(!u)return [];u.status==="open"&&l.push(k("phase-sequential",`phase "${u.id}" is already open`,`gateReview "${u.id}" when its work is done`)),u.status==="baselined"&&l.push(k("phase-sequential",`phase "${u.id}" is baselined`,"only an approved change-request reopens a baselined phase"));let r=D(i);r&&r.id!==u.id&&l.push(k("phase-sequential",`phase "${r.id}" is still open`,`pass its gate (gateReview "${r.id}") before opening "${u.id}"`));}else if(s.type==="item.add"){let f=R(s).item;if(!C(f)||!Ue(f))return [];let u=He(i,f);u===void 0?l.push(k("phase-sequential",`item "${f.id}" belongs to no phase`,`set phase to one of: ${oe(i)}`,f.id)):S(i,u)||l.push(k("phase-sequential",`item "${f.id}" names unknown phase "${u}"`,`phases are: ${oe(i)}`,f.id));}else if(s.type==="item.edit"){let f=R(s).id,u=R(s).patch,r=M(i,f);if(!r||!C(u)||u.phase===void 0||u.phase===r.phase)return [];l.push(k("phase-sequential",`item "${r.id}" cannot move from phase "${r.phase??"(none)"}" to "${String(u.phase)}"`,`drop "${r.id}" and add a new item in phase "${String(u.phase)}"`,r.id));}return l}},{id:"gate-before-next-phase",severity:"error",check(i,s){if(s.type!=="ceremony.kickoff")return [];let l=Q(s),f=b(i),u=l===void 0?-1:f.findIndex(m=>m.id===l);if(u<0)return [];let r=f.slice(0,u).filter(m=>m.status!=="baselined").map(m=>m.id);return r.length===0?[]:[k("gate-before-next-phase",`phase(s) ${r.join(", ")} before "${l}" are not baselined`,`kickoff and gateReview them in order, starting with "${r[0]}"`)]}},{id:"baseline-immutable",severity:"error",check(i,s){let l=f=>(i.baselines??[]).filter(u=>u.phase===f).length||1;if(s.type==="item.edit"){let f=M(i,R(s).id);if(!f)return [];let u=q(i,f);return u===void 0||S(i,u)?.status!=="baselined"?[]:[k("baseline-immutable",`item "${f.id}" is frozen: phase "${u}" is baselined (v${l(u)})`,ze(f.id,u),f.id)]}if(s.type==="item.add"){let f=R(s).item;if(!C(f)||!Ue(f))return [];let u=He(i,f);return u===void 0||S(i,u)?.status!=="baselined"?[]:[k("baseline-immutable",`cannot add "${f.id}" to baselined phase "${u}" (v${l(u)})`,ze(f.id,u),f.id)]}return []}},{id:"change-needs-impact-analysis",severity:"error",check(i,s){if(s.type==="item.add"){let r=R(s).item;return !C(r)||r.type!=="change-request"?[]:$e(i,String(r.id),r.impactAnalysis)}if(s.type==="item.edit"){let r=R(s).patch,m=M(i,R(s).id);return !m||m.type!=="change-request"||!C(r)||!("impactAnalysis"in r)?[]:$e(i,m.id,r.impactAnalysis)}if(s.type!=="ceremony.changeControlBoard")return [];let l=Ke(i,s);if(!l)return [];let f=$e(i,l.id,l.impactAnalysis);if(f.length||l.impactAnalysis===void 0)return f;let u=R(s).decision==="approve";for(let r of l.impactAnalysis.reopen){let m=S(i,r);m?u&&m.status==="pending"&&f.push(k("change-needs-impact-analysis",`phase "${r}" was never baselined; it cannot be reopened`,`remove "${r}" from impactAnalysis.reopen`,l.id)):f.push(k("change-needs-impact-analysis",`change "${l.id}" impactAnalysis.reopen names unknown phase "${r}"`,`phases are: ${oe(i)}`,l.id));}return f}},{id:"requirement-traced",severity:"error",check(i,s){if(s.type!=="ceremony.gateReview")return [];let l=Q(s),f=w(i).traceability;return l===void 0||!f.to.includes(l)||!S(i,l)?[]:J(i,l).map(u=>k("requirement-traced",`item "${u}" of phase "${f.from}" has no trace into "${l}"`,`editItem a "${l}" item with traces: ["${u}"] (or editItem("${u}", { traces: [...] }) while "${f.from}" is not baselined)`,u))}},{id:"deliverables-present",severity:"error",check(i,s){if(s.type!=="ceremony.gateReview")return [];let l=Q(s),f=l===void 0?void 0:S(i,l);if(!f)return [];let u=[],r=P(i,f.id).filter(v=>v.status!=="done").map(v=>v.id);r.length&&u.push(k("deliverables-present",`${r.length} item(s) in phase "${f.id}" are not done: ${r.join(", ")}`,"finish them (next/complete) or drop them before the gate"));let m=R(s).deliverables,h=Array.isArray(m)?m.filter(v=>C(v)&&typeof v.kind=="string").map(v=>v.kind):[];f.id===B(i)&&h.push("traceability-matrix");for(let v of U(i,f,h))u.push(k("deliverables-present",`phase "${f.id}" has no deliverable "${v}"`,`pass deliverables: [{ "kind": "${v}", "ref": "<where it lives>" }] to gateReview, or attach evidence of kind "${v}" on an item of the phase`));return u}},{id:"approver-not-author",severity:"error",check(i$1,s){if(s.by==="human")return [];if(s.type==="ceremony.gateReview"){let l=Q(s),f=l===void 0?void 0:S(i$1,l);if(!f)return [];let u=P(i$1,f.id).filter(m=>I(i$1,m)===s.by).map(m=>m.id);if(u.length===0)return [];let r=j$1(i$1,f.gate.id,"approve-gate",s.by);return [k("approver-not-author",`"${s.by}" authored ${u.join(", ")} in phase "${f.id}" and may not approve its gate`,`review the gate as one of: human${r.map(m=>`, ${m}`).join("")}`)]}if(s.type==="ceremony.changeControlBoard"){let l=Ke(i$1,s);if(!l)return [];let f=i$1.events.find(r=>r.type==="item.added"&&r.payload.id===l.id);if(!f||f.by!==s.by)return [];let u=i(i$1,"control-change").map(r=>r.id).filter(r=>r!==s.by);return [k("approver-not-author",`"${s.by}" raised change "${l.id}" and may not decide it`,`decide as human${u.map(r=>`, ${r}`).join("")}`,l.id)]}return []}},{id:"trace-ids-valid",severity:"error",check(i,s){let l,f,u,r;if(s.type==="item.add"){let h=R(s).item;if(!C(h))return [];l=String(h.id),f=h.traces,u=h.impactAnalysis,r=h.type==="change-request";}else if(s.type==="item.edit"){let h=M(i,R(s).id),v=R(s).patch;if(!h||!C(v))return [];l=h.id,f=v.traces,u=v.impactAnalysis,r=h.type==="change-request";}else return [];let m=[];if(Array.isArray(f)){let h=w(i).traceability,v=[h.from,...h.to].flatMap(I=>P(i,I).filter(x=>x.type!=="question"&&x.type!=="change-request").map(x=>x.id)),Z=`traceable items: ${v.length?v.join(", "):"(none yet)"}`;for(let I of f){if(typeof I!="string")continue;let x=M(i,I);(!x||x.status==="dropped")&&m.push(k("trace-ids-valid",`item "${l}" traces unknown item "${I}"`,Z,l));}}if(r&&C(u)&&Array.isArray(u.reopen))for(let h of u.reopen)typeof h=="string"&&!S(i,h)&&m.push(k("trace-ids-valid",`change "${l}" impactAnalysis.reopen names unknown phase "${h}"`,`phases are: ${oe(i)}`,l));return m}}]}function Ae(e){return [{id:L,name:"Work",entryPolicy:[{kind:"dependencies-done"}],exitPolicy:[]},{id:V,name:"Review",entryPolicy:[],exitPolicy:[{kind:"no-open-blockers"}],review:true},{id:H,name:"Done",entryPolicy:[],exitPolicy:[]}]}var tt={backlog:"Requirements Baseline",item:"Work Package",stage:"Step",iteration:"Phase",increment:"Deliverable",ceremony:"Review",gate:"Phase Gate"};function nt(e,t){let n=q(e,t);if(n!==void 0)return w(e).phases.find(a=>a.id===n)?.deliverBy}function it(e){return {phases:b(e).map(n=>{let a=P(e,n.id),p=a.filter(s=>s.status==="done").length,o=w(e).traceability.to.includes(n.id)?J(e,n.id):[],d=n.id===B(e)&&o.length===0?["traceability-matrix"]:[],i=U(e,n,d);return {id:n.id,name:n.name,status:n.status,items:a.length,done:p,missingDeliverables:i,missingTraces:o,readyForGate:n.status==="open"&&p===a.length&&i.length===0&&o.length===0}}),current:he(e)?.id??null,open:D(e)?.id??null,pendingGates:e.pending.filter(n=>n.kind==="gate"&&n.item===void 0&&n.gate!==void 0).map(n=>({pending:n.id,gate:n.gate}))}}function rt(){return {name:"waterfall",vocabulary:tt,validateConfig:ge,defaultStages:Ae,defaultRoles:we,requiredCapabilities:["facilitate","control-change","approve-gate","deliver","verify"],exclusiveCapabilities:[],plannerOptions(e){let t={schedulerMode:"level",roleFor:a=>a.status==="todo"&&a.type!=="question"?nt(e,a):void 0},n=w(e).fallbackTokensPerWeight;return n!==void 0&&(t.fallbackTokensPerWeight=n),t},candidates(e){let t=D(e);return t?Object.values(e.items).filter(n=>(n.status==="todo"||n.status==="review")&&(n.stage===L||n.stage===V)&&n.type!=="change-request"&&q(e,n)===t.id):[]},invariants(e,t){let n=w(e),a=b(e),p=q(e,t),c=p===void 0?void 0:a[E(e,p)],o=a.filter(i=>i.status==="baselined").map(i=>i.id),d=c?.gate.requires.includes("human");return [c?`Phase "${c.name}" (${E(e,c.id)+1}/${a.length}); deliverables: ${c.deliverables.join(", ")||"(none)"}; gate "${c.gate.id}"${d?" needs human approval":""}`:"This item belongs to no phase; it is dispatched only through its parent's phase",`Baselined phases are frozen: ${o.join(", ")||"(none yet)"}; to change their items, raise a change-request with an impact analysis`,n.traceability.to.length?`Traceability: every item of phase "${n.traceability.from}" must trace to an item in ${n.traceability.to.join(", ")} (set traces on either side)`:"Traceability: none configured",`Do not change the scope of "${t.id}" or its acceptance criteria; raise open_questions instead`]},ceremonies:be(rt),rules:We(),metrics:e=>({...ke(e)}),humanDefaults:{gates:"all"},lockedConfigKeys:["phases","traceability","humanGates"],report:e=>({...it(e),coverage:O(e).coverage}),afterDecide:ve,afterComplete(e,t,n){let a=R$1(e),p=D(e);if(!a||!p||a.committed.includes(t))return e;let c=e.items[t];if(!c||q(e,c)!==p.id)return e;let o=e.accounting[e.accounting.length-1];return !o||o.item!==t?e:{...e,iterations:e.iterations.map(d=>d.id===a.id?{...d,committed:[...d.committed,t],spent:N(d.spent,o)}:d)}}}}export{K as A,O as B,J as C,Y as D,be as E,ve as F,ke as G,we as H,We as I,Ae as J,tt as K,nt as L,it as M,rt as N,De as a,pe as b,fe as c,ue as d,me as e,Me as f,w as g,ge as h,L as i,V as j,H as k,b as l,j as m,E as n,_ as o,q as p,P as q,D as r,he as s,Je as t,_e as u,U as v,B as w,ae as x,ye as y,z};//# sourceMappingURL=chunk-3KFQPSRX.mjs.map
|
|
2
|
+
//# sourceMappingURL=chunk-3KFQPSRX.mjs.map
|