@zhuk/spego 0.21.1 → 0.22.1
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 +17 -0
- package/README.md +37 -229
- package/dist/artifacts/schemas.d.ts +1 -1
- package/dist/artifacts/schemas.js +12 -1
- package/dist/artifacts/schemas.js.map +1 -1
- package/dist/artifacts/types.d.ts +8 -0
- package/dist/artifacts/types.js +7 -0
- package/dist/artifacts/types.js.map +1 -1
- package/dist/cli/commands/board.js +38 -7
- package/dist/cli/commands/board.js.map +1 -1
- package/dist/cli/commands/record.d.ts +10 -0
- package/dist/cli/commands/record.js +80 -0
- package/dist/cli/commands/record.js.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/command-meta/registry.js +13 -0
- package/dist/command-meta/registry.js.map +1 -1
- package/dist/delivery/load.js +10 -4
- package/dist/delivery/load.js.map +1 -1
- package/dist/delivery/mirror.d.ts +34 -1
- package/dist/delivery/mirror.js +225 -1
- package/dist/delivery/mirror.js.map +1 -1
- package/dist/delivery/store.d.ts +14 -0
- package/dist/delivery/store.js +56 -0
- package/dist/delivery/store.js.map +1 -0
- package/dist/workflows/registry.js +2 -2
- package/dist/workflows/registry.js.map +1 -1
- package/dist/workspace/config.d.ts +289 -0
- package/dist/workspace/config.js +30 -0
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/paths.d.ts +10 -0
- package/dist/workspace/paths.js +25 -0
- package/dist/workspace/paths.js.map +1 -1
- package/docs/agent-targets.md +32 -0
- package/docs/cli.md +49 -0
- package/docs/delivery-board.md +63 -0
- package/docs/estimation.md +90 -0
- package/docs/openspec-adapter.md +38 -0
- package/docs/workflows.md +129 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.22.1] - 2026-08-21
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Documentation split: the README is now an overview — install, command summary, architecture, artifact types, config, the workflow-skill loop, and a documentation index — while the runbook-length material moved to `docs/`: `cli.md` (command reference, output modes, JSON contract), `delivery-board.md` (statuses, conflict tracks, drift warnings, rendering), `estimation.md` (Size Tiers, flow profiles, the pricing ladder, bias, the cross-project store, seeding), `openspec-adapter.md` (the read-only delivery view), `workflows.md` (every workflow skill, groom phases, combined OpenSpec lanes), and `agent-targets.md` (generated Claude and OpenCode files). The published package now ships `docs/*.md` alongside the README, so those links resolve from `node_modules` as well as GitHub.
|
|
7
|
+
|
|
8
|
+
## [0.22.0] - 2026-08-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Project-local OpenSpec schema `spec-driven-custom` (forked from `spec-driven` via the experimental `openspec schema fork`, selected in `openspec/config.yaml`) gains an `estimating` artifact required after `proposal` and before `tasks`: the author judges the change's Size Tier (`xs`/`s`/`m`/`l`/`xl`) while scope is fresh and persists it through `spego sync` plus `spego update` onto the change's epic, leaving a generated `estimating.md` as the human-readable trace. The step writes through spego, not into OpenSpec files — the fork carries the prompt, the epic carries the state — so changes arrive priced instead of waiting for the next groom run. Changes authored without the fork are unaffected: grooming judges their tier as before, and if the experimental schema surface changes, recorded tiers stay on their epics.
|
|
12
|
+
- Change pricing from a declared flow profile: workspace config gains an optional `flows` block (`default` Flow, per-Flow `profiles` mapping the fixed `xs`/`s`/`m`/`l`/`xl` Size Tier scale to Flow Hours, and one shared `human` table); epic metadata gains optional `tier` (Size Tier) and `flow` (profile override) fields. The delivery mirror derives a Flow Estimate, a Human Estimate, and the source rung (`config-seed`) per priced change — on render, never stored — plus each sprint's total Flow Estimate over pending changes (`flowTotal`, `unpricedPending`). A change with no `tier` or an unknown `flow` renders `?` and is excluded from its sprint's total, which then carries a `+?` suffix.
|
|
13
|
+
- `spego board`'s change table gains an `hours` column (between `group` and `signals`) and each sprint panel title reports its remaining total in a priced workspace; a workspace with no `flows` block omits the column, the totals, and every pricing field in `--json`.
|
|
14
|
+
- The groom workflow's analyze phase now judges and persists each epic's `tier` alongside deps, requires, links, gaps, and track — sizing the change itself, not the flow machinery that will build it.
|
|
15
|
+
- Estimate bias: the mirror derives a per-Flow-and-Tier bias (median ratio of recorded runs over the prices they carried, recomputed on render, never stored) and corrects seeded prices by it, clamped to [0.5, 2]; observed prices are never corrected by their own residual. `--json` carries the unclamped `bias` per priced change. A pair whose bias leaves the band around parity raises a new `stale-profile` warning naming the Flow, tier, and direction — one entry per pair in `--json`, aggregated into one human Warnings row.
|
|
16
|
+
- Flow profiles now resolve down a ladder, independently per Flow and Size Tier pair: once three or more runs are recorded for a pair, the price is the median of those runs; below the threshold it falls back to the config seed. Runs are attributed strictly per Flow. Every priced change's `rung` now reports `observed` or `config-seed` — in `--json` verbatim, and in the human board as a `*` on the hours cell plus a dim legend line. The sample threshold is fixed in code, like the Size Tier scale.
|
|
17
|
+
- Cross-project pricing rung, opt-in per workspace: `spego record` now also appends every tiered run to a cross-project store — an append-only `runs.jsonl` under the platform configuration directory, one `{flow, tier, hours}` line per run with no repo attribution — whose root resolves from `SPEGO_STORE_ROOT` before falling back to the configuration directory. The `flows` block gains `crossProject` (default `false`); when enabled, the ladder resolves per Flow and Size Tier pair from this workspace's recorded runs, then the store's median at the same three-run threshold, then the config seed, reporting the middle rung as `cross-project` in `--json`. With the flag off nothing resolves the store and boards stay reproducible from the repo alone; the test suite overrides the root for every spawned CLI run. Recording writes the epic first and the store second — a store write failure surfaces as `WRITE_FAILED` with the epic entry intact — and writes the store whether or not the workspace opts in.
|
|
18
|
+
- `spego record <change> --flow <name> --hours <n>`: records one measured run against a change's epic, appending to a new optional `actuals` meta list (`{flow, hours}` entries, validated like every other epic field) rather than overwriting. The sole-writer rule gains its first carve-out: actuals are Flow-supplied measurements written only by `spego record` — groom never touches them. The command persists through the artifact write path with optimistic concurrency (`--expected-revision`, plus the engine's write-time check), writes nothing under `openspec/`, mutates no other artifact, and fails with named errors for an unknown change, a missing epic, or negative/non-finite hours. `--json` board output now carries each change's `actuals` and `actualsTotal` alongside its estimates, so what a change cost can be compared against what it was priced at.
|
|
19
|
+
|
|
3
20
|
## [0.21.1] - 2026-08-06
|
|
4
21
|
|
|
5
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Agent-first product orchestration: markdown-first artifact engine with SQLite indexing.
|
|
4
4
|
|
|
5
|
-
## Quick
|
|
5
|
+
## Quick start
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install --global @zhuk/spego
|
|
@@ -11,58 +11,36 @@ spego init --agents claude,opencode --demo false
|
|
|
11
11
|
|
|
12
12
|
## Commands
|
|
13
13
|
|
|
14
|
-
| Command |
|
|
15
|
-
|
|
16
|
-
| `spego init` | Initialize
|
|
17
|
-
| `spego
|
|
18
|
-
| `spego
|
|
19
|
-
| `spego
|
|
20
|
-
| `spego
|
|
21
|
-
| `spego
|
|
22
|
-
| `spego list` | List artifacts |
|
|
23
|
-
| `spego view` | Overview of artifacts as a table; `--detail` exports the full markdown bundle, `--json` exports full JSON |
|
|
24
|
-
| `spego commands` | List command metadata |
|
|
25
|
-
| `spego workflows` | List workflow skills metadata |
|
|
26
|
-
| `spego skills` | Regenerate agent skill files |
|
|
27
|
-
| `spego epics [name]` | List delivery epics, or get one by change name |
|
|
28
|
-
| `spego tasks <change> [task]` | List tasks for a change, or get one task |
|
|
29
|
-
| `spego sprints` | List sprint plans in board order |
|
|
30
|
-
| `spego board` | Show the delivery board (sprints, change signals, drift); --sync reconciles first |
|
|
31
|
-
| `spego sync` | Reconcile the delivery mirror: create missing epics, close finished sprints, retire epics of archived changes |
|
|
32
|
-
| `spego index rebuild` | Rebuild SQLite index from files |
|
|
14
|
+
| Command | Purpose |
|
|
15
|
+
|---------|---------|
|
|
16
|
+
| `spego init` / `status` | Initialize a workspace, report its state |
|
|
17
|
+
| `spego create` / `read` / `update` / `delete` / `list` / `view` | Artifact CRUD with revision control |
|
|
18
|
+
| `spego epics` / `tasks` / `sprints` / `board` | Read the delivery mirror |
|
|
19
|
+
| `spego sync` / `record` | Reconcile the mirror; record a measured run |
|
|
20
|
+
| `spego commands` / `workflows` / `skills` | Inspect metadata, regenerate agent files |
|
|
21
|
+
| `spego index rebuild` | Rebuild the SQLite index from files |
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
Every command takes `--cwd <dir>` and speaks two output modes: human-readable by default, deterministic JSON under `--json`. Agents calling spego MUST pass `--json`.
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Every spego command speaks two output modes:
|
|
39
|
-
|
|
40
|
-
- **Human (default)** — pretty, scannable output for terminals. Every command renders a section: a single emoji header (`📦 📄 🛠️ 🧭 📚 ✅ ✨ 🗑 📋 🏃 🕸️ 🧩`), one blank line, then the content. Summaries render as bordered boxes (`╭─ … ─╮`), lists render as aligned tables. `view` defaults to an overview — a totals box plus one table per artifact type, with `epic` rows carrying delivery-derived status; `view --detail` renders the full markdown bundle instead, separating artifacts with `────` dividers. Errors print as `⚠️ [CODE] message` to stderr.
|
|
41
|
-
- **JSON (opt-in)** — pass the global `--json` flag to get deterministic, decoration-free JSON on stdout. Errors come back as `{ "error": { "code", "message", "details" } }` on stderr; `details` is always an object.
|
|
42
|
-
|
|
43
|
-
Agents calling spego MUST pass `--json` so they get parseable output. Humans normally do not.
|
|
44
|
-
|
|
45
|
-
`spego read --json` and `spego update --json` both return artifact records with `frontmatter`, `body`, and `path`; `update` also keeps the compatibility fields `id` and `revision`.
|
|
46
|
-
|
|
47
|
-
`spego create` and `spego update` accept body content through `--body`, `--body-file <path>`, or stdin via `--body-file -`.
|
|
25
|
+
Full reference: [docs/cli.md](docs/cli.md).
|
|
48
26
|
|
|
49
27
|
## Architecture
|
|
50
28
|
|
|
51
29
|
- Markdown files are the source of truth (`.spego/artifacts/<type>/<slug>.md`)
|
|
52
30
|
- SQLite indexes metadata and revisions for fast lookup
|
|
53
31
|
- Revisions stored as snapshots (`.spego/revisions/<type>/<slug>.r<n>.md`)
|
|
54
|
-
- Per-type metadata schemas validated on create
|
|
55
|
-
-
|
|
56
|
-
- Delivery adapters provide read-only epics
|
|
32
|
+
- Per-type metadata schemas validated on create and update
|
|
33
|
+
- The index is fully rebuildable from markdown files
|
|
34
|
+
- Delivery adapters provide read-only epics and tasks views (default: OpenSpec)
|
|
57
35
|
|
|
58
|
-
## Artifact
|
|
36
|
+
## Artifact types
|
|
59
37
|
|
|
60
|
-
Built-in: `prd`, `epic`, `okr`, `retro`, `sprint-plan`, `brainstorm`, `usecases`, `design`, `api`, `architecture`, `decision`, `risk`, `qa`, `ceremony
|
|
38
|
+
Built-in: `prd`, `epic`, `okr`, `retro`, `sprint-plan`, `brainstorm`, `usecases`, `design`, `api`, `architecture`, `decision`, `risk`, `qa`, `ceremony`.
|
|
61
39
|
|
|
62
|
-
- `epic` mirrors an OpenSpec change. Its optional meta supports `deps`, `links`, `requires`, and `
|
|
63
|
-
- `sprint-plan` may include ordered `changes`; a change
|
|
40
|
+
- `epic` mirrors an OpenSpec change. Its optional meta supports `deps`, `links`, `requires`, `gaps`, `status`, `track`, `tier`, `flow`, and `actuals`.
|
|
41
|
+
- `sprint-plan` may include ordered `changes`; a change belongs to at most one non-closed sprint-plan.
|
|
64
42
|
|
|
65
|
-
Custom types
|
|
43
|
+
Custom types register through workspace config (`extraArtifactTypes`).
|
|
66
44
|
|
|
67
45
|
## Configuration
|
|
68
46
|
|
|
@@ -80,208 +58,38 @@ deliveryAdapter:
|
|
|
80
58
|
options: {}
|
|
81
59
|
```
|
|
82
60
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
The default delivery adapter is OpenSpec. `spego epics` exposes active OpenSpec changes as delivery epics, and `spego tasks --change <name>` exposes checklist items from the change's `tasks.md`.
|
|
86
|
-
|
|
87
|
-
The adapter is read-only. It observes OpenSpec state but does not create, continue, apply, verify, sync, or archive OpenSpec changes.
|
|
88
|
-
`spego sync` reconciles the delivery mirror — it creates a missing epic for every ungroomed change, closes every finished sprint, and retires the epic of any archived change — and is unrelated to OpenSpec's own `sync` / `/opsx:sync`, which merges a change's spec deltas into the main specs tree.
|
|
89
|
-
|
|
90
|
-
| Source | spego view |
|
|
91
|
-
|--------|------------|
|
|
92
|
-
| `openspec/changes/<name>/proposal.md` | Epic title |
|
|
93
|
-
| `openspec/changes/<name>/tasks.md` | Task status and counts |
|
|
94
|
-
|
|
95
|
-
The `openspec` binary is not required on PATH — board and epics resolve status entirely from `tasks.md` on disk.
|
|
96
|
-
|
|
97
|
-
Archived changes under `openspec/changes/archive/` are excluded. A change with no `tasks.md`, or an empty one, is reported as `backlog`; a `tasks.md` with unchecked items is `in-progress`; all items checked is `done`.
|
|
98
|
-
|
|
99
|
-
## Delivery Mirror
|
|
100
|
-
|
|
101
|
-
`spego board` derives the delivery mirror on demand — it never writes artifacts or OpenSpec state. It combines active and archived OpenSpec changes with `epic` and `sprint-plan` artifacts into a sprint board: sprints in date order (undated last), each sprint's changes in dependency (execution) order, per-change status, blockers, gaps, and missing artifacts (`requires` minus resolvable `links`). A change is blocked when a dependency is not `done` or `completed`, and not scheduled in the same or an earlier sprint. Archived changes are discovered from `openspec/changes/archive/<date>-<slug>/` (the date prefix is stripped to derive the slug) and always resolve to status `completed`, so they no longer trip `dangling-dep` for dependencies that actually resolved via archive.
|
|
102
|
-
|
|
103
|
-
A change's status is one of `backlog`, `in-progress`, `done`, `completed`, `blocked`, `paused`, or `unknown`. `done` and `completed` both satisfy a dependent's blocker check, but they mean different things: `done` is all tasks checked and not yet archived; `completed` is archived — a hard filesystem fact that always wins, even over a manual override. Human output (`spego board`, `spego epics`, and `spego view`'s epic rows) prints `completed` as `archived`, since that's the fact that actually explains why the change is finished; `--json` output keeps `completed` unchanged, so agents parsing the payload see no contract change. `blocked` and `paused` have no signal in OpenSpec's plain-text files, so they're set by hand on the `epic` artifact's own `status` meta field; that override only applies to a change with a real backing OpenSpec change (a known change), never forces a change to `done`/`completed`/`in-progress`/`backlog`, and is dropped once the change is archived. An orphan epic (no backing OpenSpec change at all) has no other source of truth, so its `status` meta can be set to any of the seven values.
|
|
104
|
-
|
|
105
|
-
Every change carries a stable `id` (`c` + a 4+ hex-char slice of the slug's sha1 hash, e.g. `c4f2a`; longer only for the rare slug that collides with another on the same board) and a `group` naming its conflict track — the epic's `track` meta, set during grooming from file/subsystem overlap. Two changes sharing a group (track) are expected to conflict and must run sequentially; changes in different groups are parallel-safe (the inverse of the old wave meaning). A pending change whose epic has no track shows `?` — grooming debt to clear with `spego groom`; a `done`/`completed` change shows `—`. The `id` depends only on the slug, so adding, removing, or archiving other changes never changes an existing change's id — unlike `group`, which is a live value expected to shift as tracks are assigned and work completes. Blockers are reported by `id`, not slug.
|
|
106
|
-
|
|
107
|
-
The default output is the human board. `--graph` shows dependency edges; `--gaps` focuses on gap flags and missing artifacts; both also carry the `id` column. `--sync` applies the mechanical reconciliation plan before rendering. The global `--json` flag emits a deterministic `{ sprints, ungrouped, warnings, next }` document in all modes; `next` names the first pending, unblocked change or is `null` with a hint to groom.
|
|
108
|
-
|
|
109
|
-
Archived changes are excluded from the `Ungrouped` list by default — they still resolve dependencies and blockers correctly, just aren't shown as loose noise. Pass `--archived` to bring them back. A sprint's own change list is never filtered by this: an archived change still scheduled in a sprint keeps showing there, struck through as satisfied.
|
|
110
|
-
|
|
111
|
-
Each sprint (and the `Ungrouped` list, and the trailing `Warnings` table) renders as a bordered panel — a left rail closed on the right with `│`, corners `╮`/`╯` — with its title embedded in the top rail as `<title> · <status> · <slug>`, bolded. Every panel on a board renders at the same width: whichever is wider between the shared table grid and the longest panel title, capped at the terminal width; a title too long for that width truncates with `…` rather than widening its panel past its siblings. The default change table carries five columns — `id`, `change`, `status`, `group`, `signals` — on one shared grid across every panel; columns shrink together, widest first, to fit the terminal width (or 120 columns when not a TTY). The `change` column is protected: it never truncates, so every slug shows in full no matter how narrow the terminal gets, and the other columns absorb the deficit. The `signals` column summarizes a change's drift as nonzero counts joined by `·` — `N blk` (blockers), `N gap` (gap flags), `N mis` (missing artifacts) — or `—` when the change is clean; the full text lives in `spego board --gaps`. The board footer adds a dim `spego board --gaps` hint when any rendered change carries a signal, and a dim `N mechanical fixes — run spego sync` line when the reconciliation plan is non-empty.
|
|
112
|
-
|
|
113
|
-
Every rendering attaches drift warnings: `dangling-dep`, `dep-cycle`, `out-of-order-dep`, `ungroomed-change`, `orphan-epic`, and `closable-sprint`. `out-of-order-dep` flags a scheduled change blocked by a dependency scheduled into a later sprint. The mechanical subset — `ungroomed-change` (create an epic), `closable-sprint` (close the sprint), and `orphan-epic` whose change is archived (retire the epic) — is repaired by `spego sync`; the judgment-only warnings (`orphan-epic` whose change is missing, `dangling-dep`, `dep-cycle`, `out-of-order-dep`) belong to the groom workflow. The board only reports — pass `--sync` to apply the mechanical plan and re-render in one step.
|
|
114
|
-
|
|
115
|
-
A sprint whose changes are all `done`/`completed` — the same condition behind `closable-sprint` — is hidden from the default board and replaced by a trailing `N closed sprints hidden (--closed to show).` note; pass `--closed` to render it anyway, muted (dim panel, unbolded title). This is purely a display filter: `--json` always lists every sprint regardless of `--closed`, and hiding never writes `status: closed` to the sprint-plan artifact — that persistence still belongs to the groom workflow, after your confirmation.
|
|
116
|
-
|
|
117
|
-
## OpenCode Workflows
|
|
118
|
-
|
|
119
|
-
When `opencode` is listed in `.spego/config.yaml` agents, `spego init` and `spego skills` generate static OpenCode commands under `.opencode/commands/`:
|
|
120
|
-
|
|
121
|
-
- `/spego-apply` implements pending tasks from an OpenSpec change in the active OpenCode instance.
|
|
122
|
-
- `/spego-verify` checks completeness, correctness, coherence, and tests before archive.
|
|
123
|
-
- `/spego-explore` enters a read-only exploration stance for ideas, problems, or OpenSpec changes.
|
|
124
|
-
|
|
125
|
-
`/spego-apply` runs as a phased current-session workflow: select the change, load context, plan tasks, execute work, verify results, update task checkboxes, and summarize status. It may use OpenCode-native subagents only inside the active OpenCode instance for independent investigation, implementation review, or verification.
|
|
126
|
-
|
|
127
|
-
Spego does not start or control OpenCode through `opencode serve`, does not call the OpenCode REST API, does not create external OpenCode sessions, and does not own model selection. Configure models, variants, agents, and permissions in OpenCode itself.
|
|
128
|
-
|
|
129
|
-
There are no `spego apply`, `spego verify`, `spego explore`, or `spego orchestrate` CLI commands. Apply, verify, and explore are generated OpenCode workflows; spego remains the local artifact engine and read-only OpenSpec delivery view.
|
|
130
|
-
|
|
131
|
-
## Generated Files
|
|
132
|
-
|
|
133
|
-
For Claude agent targets, `spego init` generates:
|
|
134
|
-
|
|
135
|
-
- `.claude/skills/spego-<command>/SKILL.md` — skill definitions following Claude Code conventions
|
|
136
|
-
- `.claude/commands/spego/` — slash command wrappers
|
|
137
|
-
|
|
138
|
-
For OpenCode targets, `spego init` generates:
|
|
139
|
-
|
|
140
|
-
- `.opencode/commands/spego-apply.md`
|
|
141
|
-
- `.opencode/commands/spego-verify.md`
|
|
142
|
-
- `.opencode/commands/spego-explore.md`
|
|
143
|
-
|
|
144
|
-
Run `spego skills` to update generated files without reinitializing the workspace.
|
|
145
|
-
|
|
146
|
-
During regeneration, Spego removes stale generated OpenCode compatibility files such as old `opsx-*` commands, `openspec-*` skills, and retired orchestration assets only when they carry the `spego_generated: true` marker. User-owned OpenCode files without that marker are preserved and reported as skipped.
|
|
61
|
+
An optional `flows` block turns on hour estimates — see [docs/estimation.md](docs/estimation.md).
|
|
147
62
|
|
|
148
63
|
## Workflow Skills
|
|
149
64
|
|
|
150
|
-
Workflow skills guide an agent through a multi-persona process
|
|
151
|
-
|
|
152
|
-
### brainstorm-party
|
|
153
|
-
|
|
154
|
-
Multi-persona collaborative brainstorming. Six roles (PM, Architect, UX, Dev, QA, Skeptic) take turns framing, expanding, challenging, and converging on ideas. Outputs a `brainstorm` artifact and optionally drafts a follow-up `prd` or `design`.
|
|
155
|
-
|
|
156
|
-
Use when exploring a problem space broadly or stress-testing a concept before committing.
|
|
157
|
-
|
|
158
|
-
### review-adversarial
|
|
159
|
-
|
|
160
|
-
Adversarial critique of an existing artifact. Five roles (Skeptic, Security, Reliability, UX, Cost) attack from different angles, surface gaps, and prioritize findings. Outputs a `qa` or `risk` artifact and optionally a `decision` or `retro`.
|
|
161
|
-
|
|
162
|
-
Use when you need rigorous review before approval or implementation.
|
|
163
|
-
|
|
164
|
-
### review-edge-cases
|
|
165
|
-
|
|
166
|
-
Method-driven edge-case enumeration. Three roles (Path-tracer, Boundary-checker, Concurrency-skeptic) mechanically trace branching paths, derive edge classes (off-by-one, unguarded inputs, missing else/default, race conditions, timeout gaps), filter already-handled cases, and report only unhandled paths. Outputs a `qa` artifact with a fenced JSON array of findings.
|
|
167
|
-
|
|
168
|
-
Use when you need to find logic defects that adversarial review may miss.
|
|
169
|
-
|
|
170
|
-
### editorial-prose
|
|
171
|
-
|
|
172
|
-
Clinical copy-edit pass. A single Copy-editor persona identifies ambiguous sentences, unnecessary jargon, long sentences, passive voice, and redundancy. Confirmed edits are applied in place via `spego update` with `--expected-revision`; declined edits are recorded as recommendations. Outputs a `qa` artifact summarizing all edits.
|
|
173
|
-
|
|
174
|
-
Use when the artifact's ideas are sound but the prose needs tightening.
|
|
175
|
-
|
|
176
|
-
### editorial-structure
|
|
177
|
-
|
|
178
|
-
Structural review without mutation. A single Structural editor persona outlines the current structure, proposes cuts/merges/reorders/splits, and produces a prioritized recommendation list. Does not modify the source artifact. Outputs a `qa` artifact with restructuring recommendations.
|
|
179
|
-
|
|
180
|
-
Use when content is correct but organization needs improvement.
|
|
181
|
-
|
|
182
|
-
### help
|
|
183
|
-
|
|
184
|
-
Orientation workflow that reads workspace state and recommends next steps. A single Guide persona runs `spego status`, `spego list`, and `spego epics` to inspect the workspace, then applies a recommendation rubric to suggest the most impactful action. Produces no artifact — recommendations are delivered in chat only.
|
|
185
|
-
|
|
186
|
-
Use when starting a new session, unsure what to do next, or asking "what should I work on?".
|
|
187
|
-
|
|
188
|
-
### brainstorm-deep
|
|
189
|
-
|
|
190
|
-
Single-persona, high-volume ideation targeting 50-100 ideas on a focused topic. One Ideator persona generates breadth, then clusters, deduplicates, and ranks the output. Outputs a `brainstorm` artifact.
|
|
191
|
-
|
|
192
|
-
Use when you have a focused problem and want maximum idea throughput without persona debate.
|
|
193
|
-
|
|
194
|
-
**When to use brainstorm-deep vs brainstorm-party:**
|
|
195
|
-
|
|
196
|
-
| Scenario | Use |
|
|
197
|
-
|----------|-----|
|
|
198
|
-
| Focused problem, need volume | `brainstorm-deep` |
|
|
199
|
-
| Broad problem, need diverse perspectives | `brainstorm-party` |
|
|
200
|
-
| One voice, fast iteration | `brainstorm-deep` |
|
|
201
|
-
| Multiple viewpoints, stress-test ideas | `brainstorm-party` |
|
|
202
|
-
|
|
203
|
-
### Combined OpenSpec + spego workflows
|
|
204
|
-
|
|
205
|
-
OpenSpec prompts and spego workflow skills are separate surfaces:
|
|
206
|
-
|
|
207
|
-
- OPSX/OpenSpec prompts live under `.claude/commands/opsx/` and `.claude/skills/openspec-*/`.
|
|
208
|
-
- spego-generated commands and workflow skills live under `.claude/commands/spego/` and `.claude/skills/spego-*/`.
|
|
209
|
-
|
|
210
|
-
OpenSpec owns change execution and lifecycle state: proposal, design, specs, tasks, apply, verify, sync, and archive. spego owns durable product-thinking artifacts: `brainstorm`, `qa`, `risk`, and `retro`, plus delivery-mirror state: `epic` and `sprint-plan` written only by the groom workflow. The OpenSpec delivery adapter exposed through `spego epics` and `spego tasks` is read-only.
|
|
211
|
-
|
|
212
|
-
Use combined workflows when an OpenSpec change needs durable thinking around it:
|
|
213
|
-
|
|
214
|
-
| Lane | OpenSpec action | spego action |
|
|
215
|
-
|------|-----------------|--------------|
|
|
216
|
-
| Before implementation | Create or continue a change with OPSX/OpenSpec, then read `proposal.md`, `design.md`, specs, and `tasks.md` | `spego-groom` grooms the delivery mirror; `spego-change-brainstorm` creates a `brainstorm` artifact |
|
|
217
|
-
| During implementation | Keep task state and artifact mutations in OPSX/OpenSpec | `spego-change-review` creates `qa` or `risk` findings |
|
|
218
|
-
| Before archive | Run OPSX/OpenSpec verification | `spego-change-verify-report` creates a `qa` verification report |
|
|
219
|
-
| After completion | Archive or complete the OpenSpec change | `spego-change-retro` creates a `retro` artifact |
|
|
220
|
-
|
|
221
|
-
Example read-only context commands:
|
|
222
|
-
|
|
223
|
-
```sh
|
|
224
|
-
spego epics --json
|
|
225
|
-
spego tasks --change <change-name> --json
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Example spego persistence checkpoints:
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
spego --json create --type brainstorm --title "<change-name> brainstorm" --body "<brainstorm output>"
|
|
232
|
-
spego --json create --type qa --title "<change-name> review" --body "<quality findings>"
|
|
233
|
-
spego --json create --type risk --title "<change-name> risks" --body "<risk findings>"
|
|
234
|
-
spego --json create --type retro --title "<change-name> retro" --body "<retrospective>"
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
If a combined workflow discovers that OpenSpec artifacts need to change, use the matching OPSX/OpenSpec command. Do not mutate OpenSpec lifecycle state through spego.
|
|
238
|
-
|
|
239
|
-
### groom
|
|
240
|
-
|
|
241
|
-
Delivery-mirror workflow that reconciles active OpenSpec changes with `epic` and `sprint-plan` artifacts. One Groomer persona runs five phases:
|
|
242
|
-
|
|
243
|
-
1. `orient` — read `spego board --json` and `spego epics --json`, then classify drift warnings.
|
|
244
|
-
2. `sync` — run `spego sync` to mechanically create missing epics, close finished sprints, and retire epics of archived changes, then propose disposition for orphans resolving to no OpenSpec change.
|
|
245
|
-
3. `analyze` — update deps, requires, supporting links, gap notes, and conflict tracks on epics.
|
|
246
|
-
4. `plan` — propose releasable, testable sprint groupings and create or update `sprint-plan` artifacts after confirmation.
|
|
247
|
-
5. `summarize` — report epics, dispositions, sprint plans, and the next-change suggestion.
|
|
248
|
-
|
|
249
|
-
Sole writers: groom and `spego sync` persist mirror state only through `spego create` / `spego update` with `--expected-revision`; neither writes `openspec/` or mutates OpenSpec lifecycle. Orphan disposition (only for epics resolving to no OpenSpec change) requires explicit confirmation; default is keep. Epics of archived changes are retired, and finished sprints are closed, deterministically by `spego sync` (preview with `--dry-run`), not by hand in groom.
|
|
250
|
-
|
|
251
|
-
### elicit
|
|
252
|
-
|
|
253
|
-
Iterative refinement of an existing artifact using named methods (clarify, sharpen, generalize, specialize, stress-test, contextualize, simplify, formalize). A single Refiner persona proposes changes, confirms with the user, and applies via `spego update --expected-revision`. Maximum 5 cycles per session. Outputs a `qa` summary and in-place updates of the source artifact.
|
|
254
|
-
|
|
255
|
-
Use when you have a draft that needs systematic improvement.
|
|
256
|
-
|
|
257
|
-
### Authoring loop
|
|
258
|
-
|
|
259
|
-
The recommended authoring sequence combines all workflow skills:
|
|
260
|
-
|
|
261
|
-
1. **Orient** — Run `spego-help` to understand workspace state and get recommendations.
|
|
262
|
-
2. **Ideate** — Run `spego-brainstorm-party` (multi-perspective breadth) or `spego-brainstorm-deep` (single-voice volume) to explore the problem space.
|
|
263
|
-
3. **Capture** — Run `spego create --type prd` (or `architecture`, `design`, etc.) to persist the output.
|
|
264
|
-
4. **Refine** — Run `spego-elicit` to iteratively improve the draft with structured methods.
|
|
265
|
-
5. **Review** — Run `spego-review-adversarial` and `spego-review-edge-cases` for technical coverage, or `spego-editorial-prose` / `spego-editorial-structure` for communication quality.
|
|
65
|
+
Workflow skills guide an agent through a multi-persona process in chat, persisting results as spego artifacts: `brainstorm-party`, `brainstorm-deep`, `review-adversarial`, `review-edge-cases`, `editorial-prose`, `editorial-structure`, `elicit`, `groom`, and `help`. Each one is described in [docs/workflows.md](docs/workflows.md).
|
|
266
66
|
|
|
267
|
-
|
|
67
|
+
The recommended authoring loop:
|
|
268
68
|
|
|
269
|
-
|
|
69
|
+
1. **Orient** — `spego-help` for workspace state and recommendations.
|
|
70
|
+
2. **Ideate** — `spego-brainstorm-party` for breadth, or `spego-brainstorm-deep` for volume.
|
|
71
|
+
3. **Capture** — `spego create --type prd` (or `architecture`, `design`, …) to persist the output.
|
|
72
|
+
4. **Refine** — `spego-elicit` to improve the draft with structured methods.
|
|
73
|
+
5. **Review** — `spego-review-adversarial` and `spego-review-edge-cases` for technical coverage, `spego-editorial-prose` / `spego-editorial-structure` for communication quality.
|
|
270
74
|
|
|
271
|
-
|
|
75
|
+
Reviewers complement rather than replace each other. `spego-review-adversarial` is attitude-driven, hunting gaps and weak assumptions; `spego-review-edge-cases` is method-driven, enumerating unhandled branching paths. `spego-editorial-prose` and `spego-editorial-structure` catch the communication defects — ambiguity, confusion, poor organization — that the technical reviewers do not flag. Run at least one technical reviewer and at least one editorial reviewer before declaring an artifact ready.
|
|
272
76
|
|
|
273
|
-
|
|
77
|
+
## Documentation
|
|
274
78
|
|
|
275
|
-
-
|
|
276
|
-
-
|
|
277
|
-
-
|
|
79
|
+
- [CLI reference](docs/cli.md) — commands, output modes, JSON contract
|
|
80
|
+
- [Delivery board](docs/delivery-board.md) — statuses, conflict tracks, drift warnings, rendering
|
|
81
|
+
- [Estimation](docs/estimation.md) — Size Tiers, flow profiles, the pricing ladder, bias
|
|
82
|
+
- [OpenSpec adapter](docs/openspec-adapter.md) — the read-only delivery view
|
|
83
|
+
- [Workflow skills](docs/workflows.md) — every skill, the groom phases, combined OpenSpec lanes
|
|
84
|
+
- [Agent targets](docs/agent-targets.md) — generated Claude and OpenCode files
|
|
85
|
+
- [`CONTEXT.md`](CONTEXT.md) — project glossary
|
|
278
86
|
|
|
279
87
|
## Development
|
|
280
88
|
|
|
281
89
|
```sh
|
|
282
90
|
npm run typecheck # type checking
|
|
283
91
|
npm run lint # eslint
|
|
284
|
-
npm test # run
|
|
92
|
+
npm test # run the suite
|
|
285
93
|
npm run build # compile to dist/
|
|
286
94
|
```
|
|
287
95
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type
|
|
2
|
+
import { type BuiltinArtifactType } from './types.js';
|
|
3
3
|
export declare const ARTIFACT_META_SCHEMAS: Record<string, z.ZodTypeAny>;
|
|
4
4
|
/**
|
|
5
5
|
* One-line meta shape summary per built-in type, for agent-facing docs (generated
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { SIZE_TIERS } from './types.js';
|
|
2
3
|
export const ARTIFACT_META_SCHEMAS = {
|
|
3
4
|
prd: z.object({
|
|
4
5
|
status: z.enum(['draft', 'in-review', 'approved', 'archived']).optional(),
|
|
@@ -41,6 +42,16 @@ export const ARTIFACT_META_SCHEMAS = {
|
|
|
41
42
|
}))
|
|
42
43
|
.optional(),
|
|
43
44
|
track: z.string().min(1).optional(),
|
|
45
|
+
tier: z.enum(SIZE_TIERS).optional(),
|
|
46
|
+
flow: z.string().min(1).optional(),
|
|
47
|
+
actuals: z
|
|
48
|
+
.array(z
|
|
49
|
+
.object({
|
|
50
|
+
flow: z.string().min(1),
|
|
51
|
+
hours: z.number().finite().nonnegative(),
|
|
52
|
+
})
|
|
53
|
+
.strict())
|
|
54
|
+
.optional(),
|
|
44
55
|
tags: z.array(z.string()).optional(),
|
|
45
56
|
}),
|
|
46
57
|
brainstorm: z.object({
|
|
@@ -97,7 +108,7 @@ export const ARTIFACT_META_DOCS = {
|
|
|
97
108
|
okr: 'period, level (company|team|individual), tags[]',
|
|
98
109
|
retro: 'sprint, date, tags[]',
|
|
99
110
|
'sprint-plan': 'sprint, startDate, endDate, status (planned|active|closed), tags[], changes[] (change slugs; must be unique within the sprint)',
|
|
100
|
-
epic: 'deps[] (change slugs this depends on), links[] (linked artifact ids), requires[] (labels for required supporting artifacts), status (backlog|in-progress|done|completed|blocked|paused|unknown), gaps[] ({flag, note?}), track (conflict lane; same track = do not run in parallel), tags[]',
|
|
111
|
+
epic: 'deps[] (change slugs this depends on), links[] (linked artifact ids), requires[] (labels for required supporting artifacts), status (backlog|in-progress|done|completed|blocked|paused|unknown), gaps[] ({flag, note?}), track (conflict lane; same track = do not run in parallel), tier (size tier: xs|s|m|l|xl; prices the change from the workspace flows tables), flow (Flow profile overriding the workspace default when pricing), actuals[] ({flow, hours}; recorded runs, append-only via spego record), tags[]',
|
|
101
112
|
brainstorm: 'status (open|closed), tags[]',
|
|
102
113
|
usecases: 'status (draft|reviewed|approved), tags[]',
|
|
103
114
|
design: 'status (draft|in-review|approved), category (ux|ui|workflow|system), tags[]',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/artifacts/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/artifacts/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,14 @@ import { z } from 'zod';
|
|
|
6
6
|
export declare const BUILTIN_ARTIFACT_TYPES: readonly ["prd", "okr", "retro", "sprint-plan", "epic", "brainstorm", "usecases", "design", "api", "architecture", "decision", "risk", "qa", "ceremony"];
|
|
7
7
|
export type BuiltinArtifactType = (typeof BUILTIN_ARTIFACT_TYPES)[number];
|
|
8
8
|
export type ArtifactType = string;
|
|
9
|
+
/**
|
|
10
|
+
* Fixed Size Tier scale for change pricing — epic `tier` values and the keys
|
|
11
|
+
* of every `flows` hours table. Fixed in code so every profile table's
|
|
12
|
+
* validity is independent of config, and every project agrees on what `m`
|
|
13
|
+
* means. Adding a tier is a schema change.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SIZE_TIERS: readonly ["xs", "s", "m", "l", "xl"];
|
|
16
|
+
export type SizeTier = (typeof SIZE_TIERS)[number];
|
|
9
17
|
/** ISO-8601 datetime string. */
|
|
10
18
|
export declare const isoDateTime: z.ZodEffects<z.ZodString, string, string>;
|
|
11
19
|
/**
|
package/dist/artifacts/types.js
CHANGED
|
@@ -19,6 +19,13 @@ export const BUILTIN_ARTIFACT_TYPES = [
|
|
|
19
19
|
'qa',
|
|
20
20
|
'ceremony',
|
|
21
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* Fixed Size Tier scale for change pricing — epic `tier` values and the keys
|
|
24
|
+
* of every `flows` hours table. Fixed in code so every profile table's
|
|
25
|
+
* validity is independent of config, and every project agrees on what `m`
|
|
26
|
+
* means. Adding a tier is a schema change.
|
|
27
|
+
*/
|
|
28
|
+
export const SIZE_TIERS = ['xs', 's', 'm', 'l', 'xl'];
|
|
22
29
|
/** ISO-8601 datetime string. */
|
|
23
30
|
export const isoDateTime = z
|
|
24
31
|
.string()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/artifacts/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,KAAK;IACL,KAAK;IACL,OAAO;IACP,aAAa;IACb,MAAM;IACN,YAAY;IACZ,UAAU;IACV,QAAQ;IACR,KAAK;IACL,cAAc;IACd,UAAU;IACV,MAAM;IACN,IAAI;IACJ,UAAU;CACD,CAAC;AAKX,gCAAgC;AAChC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KAC1B,MAAM,EAAE;KACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAC;AAE3F;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC;KACxC,MAAM,CAAC;IACP,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;IAC7C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACnD,CAAC;KACD,MAAM,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/artifacts/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,KAAK;IACL,KAAK;IACL,OAAO;IACP,aAAa;IACb,MAAM;IACN,YAAY;IACZ,UAAU;IACV,QAAQ;IACR,KAAK;IACL,cAAc;IACd,UAAU;IACV,MAAM;IACN,IAAI;IACJ,UAAU;CACD,CAAC;AAKX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAU,CAAC;AAG/D,gCAAgC;AAChC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KAC1B,MAAM,EAAE;KACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAC;AAE3F;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC;KACxC,MAAM,CAAC;IACP,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;IAC7C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACnD,CAAC;KACD,MAAM,EAAE,CAAC"}
|
|
@@ -7,6 +7,7 @@ import { columnWidths, padRight, renderHeader, renderPanel, renderTable, truncat
|
|
|
7
7
|
import { deliveryStatusLabel } from '../status.js';
|
|
8
8
|
import { runEngineCommand } from '../runtime.js';
|
|
9
9
|
const BOARD_COLUMNS = ['id', 'change', 'status', 'group', 'signals'];
|
|
10
|
+
const PRICED_BOARD_COLUMNS = ['id', 'change', 'status', 'group', 'hours', 'signals'];
|
|
10
11
|
const DEFAULT_TERMINAL_WIDTH = 120;
|
|
11
12
|
const PANEL_CHROME_WIDTH = 4; // "│ " prefix + " │" suffix
|
|
12
13
|
export function registerBoard(program) {
|
|
@@ -57,12 +58,14 @@ function isFinished(sprint) {
|
|
|
57
58
|
}
|
|
58
59
|
function renderBoard(board, input, plain, showClosed, budget) {
|
|
59
60
|
const lines = [renderHeader('📋', 'Delivery board'), ''];
|
|
61
|
+
const priced = input.flows !== undefined;
|
|
62
|
+
const columns = priced ? PRICED_BOARD_COLUMNS : BOARD_COLUMNS;
|
|
60
63
|
const allSections = buildChangeSections(board);
|
|
61
64
|
const sections = showClosed ? allSections : allSections.filter((section) => !section.finished);
|
|
62
65
|
const hiddenCount = allSections.length - sections.length;
|
|
63
66
|
const totalWidth = Math.max(0, budget - PANEL_CHROME_WIDTH);
|
|
64
|
-
const rowsByColumn = sections.flatMap((section) => section.changes.map((change) => changeRow(change)));
|
|
65
|
-
const widths = columnWidths(
|
|
67
|
+
const rowsByColumn = sections.flatMap((section) => section.changes.map((change) => changeRow(change, priced)));
|
|
68
|
+
const widths = columnWidths(columns, rowsByColumn, { maxWidth: 36, totalWidth, protect: [1] });
|
|
66
69
|
const warningRows = aggregateWarningRows(board.warnings);
|
|
67
70
|
const warningWidths = columnWidths(['code', 'message'], warningRows, { maxWidth: totalWidth, totalWidth });
|
|
68
71
|
// Every panel — each sprint's table, Warnings, and the title rail — shares one width, capped
|
|
@@ -78,7 +81,7 @@ function renderBoard(board, input, plain, showClosed, budget) {
|
|
|
78
81
|
}
|
|
79
82
|
else {
|
|
80
83
|
for (const section of sections) {
|
|
81
|
-
const table = renderTable(
|
|
84
|
+
const table = renderTable(columns, section.changes.map((change) => changeRow(change, priced)), { widths });
|
|
82
85
|
lines.push(renderPanelSection(section.title, table, panelWidth, plain, section.finished, (l) => styleChangeRows(l, section.changes)));
|
|
83
86
|
lines.push('');
|
|
84
87
|
}
|
|
@@ -99,6 +102,10 @@ function renderBoard(board, input, plain, showClosed, budget) {
|
|
|
99
102
|
const note = 'Detail: spego board --gaps';
|
|
100
103
|
lines.push(plain ? note : styleText('dim', note));
|
|
101
104
|
}
|
|
105
|
+
if (renderedChanges.some((change) => change.rung === 'observed')) {
|
|
106
|
+
const note = '* observed — median of recorded runs';
|
|
107
|
+
lines.push(plain ? note : styleText('dim', note));
|
|
108
|
+
}
|
|
102
109
|
const syncActions = deriveSyncPlan(board, input).actions.length;
|
|
103
110
|
if (syncActions > 0) {
|
|
104
111
|
const noun = syncActions === 1 ? 'fix' : 'fixes';
|
|
@@ -110,7 +117,7 @@ function renderBoard(board, input, plain, showClosed, budget) {
|
|
|
110
117
|
}
|
|
111
118
|
function buildChangeSections(board) {
|
|
112
119
|
const sections = board.sprints.map((sprint) => ({
|
|
113
|
-
title:
|
|
120
|
+
title: sprintTitle(sprint),
|
|
114
121
|
changes: sprint.changes,
|
|
115
122
|
finished: isFinished(sprint),
|
|
116
123
|
}));
|
|
@@ -119,6 +126,14 @@ function buildChangeSections(board) {
|
|
|
119
126
|
}
|
|
120
127
|
return sections;
|
|
121
128
|
}
|
|
129
|
+
/** `<title> · <status> · <slug>`, plus the remaining Flow Estimate when the sprint is priced; `+?` flags pending unpriced changes. */
|
|
130
|
+
function sprintTitle(sprint) {
|
|
131
|
+
const base = `${sprint.title} · ${sprint.status} · ${sprint.slug}`;
|
|
132
|
+
if (sprint.flowTotal === undefined)
|
|
133
|
+
return base;
|
|
134
|
+
const unpriced = (sprint.unpricedPending ?? 0) > 0 ? '+?' : '';
|
|
135
|
+
return `${base} · ${formatHours(sprint.flowTotal)}${unpriced}h`;
|
|
136
|
+
}
|
|
122
137
|
/** Total rendered width of a table built from `widths`: columns plus the two-space separators between them. */
|
|
123
138
|
function tableWidth(widths) {
|
|
124
139
|
return widths.reduce((sum, w) => sum + w, 0) + 2 * Math.max(0, widths.length - 1);
|
|
@@ -213,14 +228,23 @@ function renderGaps(board) {
|
|
|
213
228
|
lines.push(nextLine(board));
|
|
214
229
|
return lines.filter((line, index, all) => !(line === '' && all[index - 1] === '')).join('\n');
|
|
215
230
|
}
|
|
216
|
-
function changeRow(change) {
|
|
217
|
-
|
|
231
|
+
function changeRow(change, priced) {
|
|
232
|
+
const row = [
|
|
218
233
|
change.id,
|
|
219
234
|
change.slug,
|
|
220
235
|
deliveryStatusLabel(change.status),
|
|
221
236
|
change.group,
|
|
222
|
-
formatSignals(change),
|
|
223
237
|
];
|
|
238
|
+
if (priced) {
|
|
239
|
+
const estimate = change.flowEstimate === undefined ? '?' : formatHours(change.flowEstimate);
|
|
240
|
+
row.push(change.rung === 'observed' ? `${estimate}*` : estimate);
|
|
241
|
+
}
|
|
242
|
+
row.push(formatSignals(change));
|
|
243
|
+
return row;
|
|
244
|
+
}
|
|
245
|
+
/** Decimal hours with trailing zeros trimmed: 0.5 → `0.5`, 2 → `2`. */
|
|
246
|
+
function formatHours(hours) {
|
|
247
|
+
return String(Math.round(hours * 100) / 100);
|
|
224
248
|
}
|
|
225
249
|
/**
|
|
226
250
|
* Collapse the board's per-fact warnings into the rows the human board prints,
|
|
@@ -290,6 +314,13 @@ function renderWarningGroup(code, members) {
|
|
|
290
314
|
const deps = uniqueStrings(members.map((m) => (m.details ?? {}).dep));
|
|
291
315
|
return [code, `Change "${first.change}" depends on ${quoteSlugs(deps)}, each scheduled in a later sprint.`];
|
|
292
316
|
}
|
|
317
|
+
case 'stale-profile': {
|
|
318
|
+
const pairs = members.map((m) => {
|
|
319
|
+
const details = m.details ?? {};
|
|
320
|
+
return `${details.flow}/${details.tier} (${details.direction} ×${details.bias})`;
|
|
321
|
+
});
|
|
322
|
+
return [code, `Profiles drifted from recorded runs: ${pairs.join(', ')} — re-groom the tier judgment or reseed.`];
|
|
323
|
+
}
|
|
293
324
|
default:
|
|
294
325
|
return [code, `Active changes ${quoteSlugs(changes)} have no epic artifacts.`];
|
|
295
326
|
}
|