generated from coulomb/repo-seed
Complete workplan state model cleanup
This commit is contained in:
@@ -131,8 +131,12 @@ function _ensureStyles() {
|
||||
|
||||
/* ── Style maps ──────────────────────────────────────────────────────────── */
|
||||
const _STATUS_STYLE = {
|
||||
proposed: "background:#fef3c7;color:#92400e",
|
||||
ready: "background:#e0f2fe;color:#075985",
|
||||
active: "background:#d4edda;color:#155724",
|
||||
blocked: "background:#f8d7da;color:#721c24",
|
||||
backlog: "background:#f1f5f9;color:#64748b",
|
||||
finished: "background:#cce5ff;color:#004085",
|
||||
completed: "background:#cce5ff;color:#004085",
|
||||
archived: "background:#e2e3e5;color:#383d41",
|
||||
open: "background:#dbeafe;color:#1e40af",
|
||||
|
||||
45
dashboard/src/components/workplan-status.js
Normal file
45
dashboard/src/components/workplan-status.js
Normal file
@@ -0,0 +1,45 @@
|
||||
export const WORKSTREAM_STATUSES = [
|
||||
"proposed",
|
||||
"ready",
|
||||
"active",
|
||||
"blocked",
|
||||
"backlog",
|
||||
"finished",
|
||||
"archived",
|
||||
];
|
||||
|
||||
export const OPEN_WORKSTREAM_STATUSES = ["ready", "active", "blocked"];
|
||||
export const CLOSED_WORKSTREAM_STATUSES = ["finished", "archived"];
|
||||
|
||||
export const LEGACY_STATUS_ALIASES = {
|
||||
todo: "ready",
|
||||
done: "finished",
|
||||
completed: "finished",
|
||||
accepted: "finished",
|
||||
};
|
||||
|
||||
export function normalizeWorkstreamStatus(status) {
|
||||
const value = String(status ?? "").trim().toLowerCase();
|
||||
return LEGACY_STATUS_ALIASES[value] ?? value;
|
||||
}
|
||||
|
||||
export function isClosedWorkstream(status) {
|
||||
return CLOSED_WORKSTREAM_STATUSES.includes(normalizeWorkstreamStatus(status));
|
||||
}
|
||||
|
||||
export function isOpenWorkstream(status) {
|
||||
return OPEN_WORKSTREAM_STATUSES.includes(normalizeWorkstreamStatus(status));
|
||||
}
|
||||
|
||||
export function isStalledWorkstream(w, staleDays = 7) {
|
||||
const staleAt = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000);
|
||||
const openTasks = (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0);
|
||||
return ["active", "blocked"].includes(normalizeWorkstreamStatus(w.status))
|
||||
&& new Date(w.updated_at) < staleAt
|
||||
&& (w.done ?? 0) > 0
|
||||
&& openTasks > 0;
|
||||
}
|
||||
|
||||
export function needsReviewWorkstream(w) {
|
||||
return Array.isArray(w.health_labels) && w.health_labels.includes("needs_review");
|
||||
}
|
||||
@@ -19,7 +19,16 @@ except urllib.error.URLError as e:
|
||||
"generated_at": None,
|
||||
"totals": {
|
||||
"topics": {"active": 0, "paused": 0, "archived": 0, "total": 0},
|
||||
"workstreams": {"active": 0, "blocked": 0, "completed": 0, "archived": 0, "total": 0},
|
||||
"workstreams": {
|
||||
"proposed": 0,
|
||||
"ready": 0,
|
||||
"active": 0,
|
||||
"blocked": 0,
|
||||
"backlog": 0,
|
||||
"finished": 0,
|
||||
"archived": 0,
|
||||
"total": 0,
|
||||
},
|
||||
"tasks": {"todo": 0, "in_progress": 0, "blocked": 0, "done": 0, "cancelled": 0, "total": 0},
|
||||
"decisions": {"open": 0, "resolved": 0, "escalated": 0, "superseded": 0, "total": 0},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ title: Dependencies
|
||||
|
||||
```js
|
||||
import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
|
||||
import {normalizeWorkstreamStatus} from "./components/workplan-status.js";
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -29,6 +30,7 @@ const depState = (async function*() {
|
||||
const repoMap = Object.fromEntries(repoList.map(r => [r.id, r]));
|
||||
wsMap = Object.fromEntries(wsList.map(w => [w.id, {
|
||||
...w,
|
||||
status: normalizeWorkstreamStatus(w.status),
|
||||
// Prefer repo→domain (GEMS primary); fall back to topic→domain
|
||||
domain: repoMap[w.repo_id]?.domain_slug ?? topicMap[w.topic_id]?.domain_slug ?? "unknown",
|
||||
}]));
|
||||
@@ -87,7 +89,7 @@ injectTocTop("dep-kpi-box", _kpiBox);
|
||||
injectTocTop("live-indicator", _liveEl);
|
||||
```
|
||||
|
||||
Directed edges between active workstreams. An edge **A → B** means A cannot
|
||||
Directed edges between open workstreams. An edge **A → B** means A cannot
|
||||
fully proceed until B reaches a satisfactory state.
|
||||
|
||||
```js
|
||||
@@ -152,9 +154,12 @@ if (edges.length === 0) {
|
||||
.dep-title { font-weight: 500; max-width: 22rem; }
|
||||
.dep-arrow { text-align: center; color: var(--theme-foreground-faint, #bbb); font-size: 1rem; }
|
||||
.dep-status { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 10px; font-size: 0.7rem; font-weight: 500; }
|
||||
.dep-status-proposed { background: #fef3c7; color: #92400e; }
|
||||
.dep-status-ready { background: #e0f2fe; color: #075985; }
|
||||
.dep-status-active { background: #dcfce7; color: #166534; }
|
||||
.dep-status-completed { background: #f1f5f9; color: #475569; }
|
||||
.dep-status-blocked { background: #fee2e2; color: #991b1b; }
|
||||
.dep-status-backlog { background: #f1f5f9; color: #64748b; }
|
||||
.dep-status-finished { background: #f1f5f9; color: #475569; }
|
||||
.dep-status-archived { background: #f1f5f9; color: #9ca3af; }
|
||||
.dim { color: gray; font-style: italic; }
|
||||
</style>
|
||||
|
||||
@@ -244,7 +244,8 @@ The Overview page renders a horizontal stacked bar chart using `@observablehq/pl
|
||||
showing task counts (done / in progress / blocked / todo) per workstream.
|
||||
A `<select>` dropdown switches between:
|
||||
|
||||
- **Status modes**: active, accepted, finished, blocked, stalled, oldies
|
||||
- **Lifecycle modes**: proposed, ready, active, blocked, backlog, finished, archived
|
||||
- **Health modes**: needs review, stalled
|
||||
- **Time modes**: last 1h, 24h, 7d, 30d, today, this week, this month
|
||||
|
||||
Domains are sorted by most recent workstream activity (most active domain at
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Dependencies — Reference
|
||||
|
||||
# Dependencies — Reference
|
||||
|
||||
The Dependencies page shows the directed dependency graph between active
|
||||
The Dependencies page shows the directed dependency graph between open
|
||||
workstreams — which workstreams are waiting on others to reach a satisfactory
|
||||
state before they can fully proceed.
|
||||
|
||||
@@ -13,7 +13,7 @@ state before they can fully proceed.
|
||||
## What is a dependency edge?
|
||||
|
||||
A dependency edge **A → B** means workstream A cannot fully proceed until
|
||||
workstream B is in a satisfactory state (typically `completed` or `archived`).
|
||||
workstream B is in a satisfactory state (typically `finished` or `archived`).
|
||||
|
||||
Edges are used to model real sequencing constraints: for example, a shared
|
||||
library must reach a stable release before downstream domains can build on it.
|
||||
@@ -36,7 +36,7 @@ Each row shows:
|
||||
| **→** | Direction arrow |
|
||||
| **Blocked-by domain** | Domain of the prerequisite workstream |
|
||||
| **Blocked-by workstream** | Title of the workstream that must complete first |
|
||||
| **Status** | Current status of the prerequisite (green = active, grey = completed) |
|
||||
| **Status** | Current status of the prerequisite (green = active, grey = finished/archived) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Four metric cards:
|
||||
|
||||
| Card | Meaning |
|
||||
|------|---------|
|
||||
| **Active Workstreams** | Count of non-completed, non-archived workstreams |
|
||||
| **Active Workstreams** | Count of active/blocked execution workstreams |
|
||||
| **Blocking Decisions** | Pending decisions with status `open` or `escalated` — orange border if > 0 |
|
||||
| **Blocked Tasks** | Click to expand the list with blocking reasons |
|
||||
| **Events Today** | Progress events created on today's date |
|
||||
|
||||
@@ -39,7 +39,7 @@ These types are used by the State Hub's built-in write operations:
|
||||
| Type | When emitted |
|
||||
|---|---|
|
||||
| `workstream_created` | A new workstream was registered |
|
||||
| `workstream_status_changed` | Workstream moved to active / blocked / completed / archived |
|
||||
| `workstream_status_changed` | Workstream moved between canonical lifecycle states |
|
||||
| `workstation_advanced` | Flow-aware movement via `advance_workstation()` succeeded |
|
||||
| `task_created` | A new task was added to a workstream |
|
||||
| `task_status_changed` | Task moved to todo / in_progress / blocked / done / cancelled |
|
||||
|
||||
@@ -92,7 +92,7 @@ cd ~/ralph-workplan && ./install.sh --uninstall
|
||||
---
|
||||
id: WP-0001
|
||||
title: "Build a thing"
|
||||
status: active
|
||||
status: ready
|
||||
---
|
||||
|
||||
Optional description.
|
||||
@@ -120,7 +120,7 @@ priority: medium
|
||||
|-------|--------|-------------|
|
||||
| `id` | string | Unique workplan identifier |
|
||||
| `title` | string | Human-readable name |
|
||||
| `status` | `active` \| `done` \| `paused` | Workplan lifecycle state |
|
||||
| `status` | `proposed` \| `ready` \| `active` \| `blocked` \| `backlog` \| `finished` \| `archived` | Workplan lifecycle state |
|
||||
|
||||
**Task block fields:**
|
||||
|
||||
@@ -144,7 +144,7 @@ status: in_progress → status: done (when verified complete)
|
||||
When every task is `done`, Claude also updates the frontmatter:
|
||||
|
||||
```
|
||||
status: active → status: done
|
||||
status: active -> status: finished
|
||||
```
|
||||
|
||||
The loop detects this on the next iteration and stops.
|
||||
|
||||
@@ -72,7 +72,7 @@ autonomously. No human interaction is needed unless the agent has a question.
|
||||
|
||||
The [Repos](/repos) page shows each repo's integration status. An **integrating**
|
||||
badge appears on repos with an active Repo Integration workstream. The badge
|
||||
clears when the workstream is marked completed.
|
||||
clears when the workstream is marked finished.
|
||||
|
||||
---
|
||||
|
||||
@@ -120,8 +120,8 @@ repo agent should:
|
||||
primary near-term work; register the workstream in the hub via MCP
|
||||
4. Execute T3 — ingest the SBOM so the repo appears green on the Repos page
|
||||
5. Execute T4 — a quick scan for obvious EPs/TDs; defer if nothing obvious
|
||||
6. Mark each task `done` in the hub as completed
|
||||
7. Mark the Repo Integration workstream `completed`
|
||||
6. Mark each task `done` in the hub
|
||||
7. Mark the Repo Integration workstream `finished`
|
||||
8. Log a progress event summarising the integration
|
||||
|
||||
The agent should resolve each task independently and in order. It does not
|
||||
|
||||
@@ -73,13 +73,13 @@ Detects concentration of blocking power. High SPR means one delay propagates wid
|
||||
### PEP — Parallel Execution Potential
|
||||
|
||||
```
|
||||
PEP = active workstreams with all deps completed / (active + blocked)
|
||||
PEP = ready or active workstreams with all deps finished / (ready + active + blocked)
|
||||
```
|
||||
|
||||
Estimates how much work can proceed right now. A workstream is eligible if its
|
||||
stored workstation label is `active` and the flow/dependency checks report no
|
||||
stored workstation label is `ready` or `active` and the flow/dependency checks report no
|
||||
unmet dependency assertion; practically, every workstream it depends on has
|
||||
reached `completed` or `archived`.
|
||||
reached `finished` or `archived`.
|
||||
|
||||
| PEP | Warning |
|
||||
|---|---|
|
||||
@@ -147,7 +147,7 @@ The domain breakdown is shown when at least two domains have active workstreams.
|
||||
| Symptom | Action |
|
||||
|---|---|
|
||||
| High DD | Decompose tightly coupled workstreams; remove unnecessary dependencies |
|
||||
| High BR | Unblock workstreams — resolve the blocking condition, or mark dependency as completed if done |
|
||||
| High BR | Unblock workstreams — resolve the blocking condition, or mark dependency as finished if done |
|
||||
| High SPR | Split the bottleneck workstream into independent deliverables |
|
||||
| Low PEP | Complete prerequisite workstreams or re-sequence work |
|
||||
| High CDDR | Refactor cross-domain dependencies into shared contracts or invert the dependency |
|
||||
|
||||
@@ -143,11 +143,11 @@ High SPR indicates fragile structure where one delay propagates widely.
|
||||
|
||||
A workstream is eligible if:
|
||||
|
||||
* Status = active
|
||||
* All dependencies are completed
|
||||
* Status = ready or active
|
||||
* All dependencies are finished or archived
|
||||
|
||||
[
|
||||
PEP = \frac{\text{Eligible active workstreams}}{\text{Active + Blocked}}
|
||||
PEP = \frac{\text{Eligible ready or active workstreams}}{\text{Ready + Active + Blocked}}
|
||||
]
|
||||
|
||||
---
|
||||
@@ -378,4 +378,3 @@ It captures both:
|
||||
* Operational flow conditions
|
||||
|
||||
By combining graph properties with status information, WHI enables proactive management of coordination complexity.
|
||||
|
||||
|
||||
@@ -1,111 +1,86 @@
|
||||
---
|
||||
title: Workstream Lifecycle — Reference
|
||||
title: Workstream Lifecycle - Reference
|
||||
---
|
||||
|
||||
# Workstream Lifecycle — Reference
|
||||
# Workstream Lifecycle - Reference
|
||||
|
||||
A workstream is an information object that occupies a named workstation. The
|
||||
stored `status` field keeps the current workstation label, while the
|
||||
task-flow engine derives which other workstations are reachable and which exit
|
||||
assertions are blocking movement. The dashboard "Workstreams by Domain" chart
|
||||
exposes stored and derived states as selectable filters so attention can be
|
||||
directed to the right workstreams at the right time.
|
||||
A workstream is an information object that occupies a named lifecycle state.
|
||||
The stored `status` field keeps that state, while the task-flow engine derives
|
||||
which other states are reachable and which exit assertions are blocking
|
||||
movement. Dashboard health filters such as `needs_review` and `stalled` are
|
||||
derived labels, not stored lifecycle values.
|
||||
|
||||
---
|
||||
|
||||
## Core workstations
|
||||
## Stored Lifecycle States
|
||||
|
||||
These are the primary workstations used by State Hub workstreams:
|
||||
|
||||
| Workstation | Source | Meaning |
|
||||
| State | Source | Meaning |
|
||||
|---|---|---|
|
||||
| **active** | DB `status = active` | Work is in progress or ready to start |
|
||||
| **finished** | Derived — no open tasks | All tasks are done, but no explicit review has taken place yet |
|
||||
| **accepted** | DB `status = completed` | Custodian and human have reviewed the workstream, quality checks passed, and it is formally signed off |
|
||||
| **proposed** | DB `status = proposed` | Plan exists, but must be reviewed against current repo state |
|
||||
| **ready** | DB `status = ready` | Plan has been reviewed and is ready to execute |
|
||||
| **active** | DB `status = active` | Work is in progress |
|
||||
| **blocked** | DB `status = blocked` | Work cannot proceed until a dependency, decision, or input clears |
|
||||
| **backlog** | DB `status = backlog` | Intentionally parked so it stays out of current work views |
|
||||
| **finished** | DB `status = finished` | Implementation is complete |
|
||||
| **archived** | DB `status = archived` | Historical record outside normal planning and execution |
|
||||
|
||||
The normal human-facing path is: **active → finished → accepted**.
|
||||
|
||||
`accepted` is the only state that requires an explicit action. It is reached by
|
||||
advancing the workstream to the `completed` workstation after deliberate
|
||||
review, not by task counts alone. This makes it a reliable anchor: anything in
|
||||
`finished` but not yet in `accepted` is work that still needs a quality pass.
|
||||
|
||||
---
|
||||
|
||||
## Attention signals
|
||||
|
||||
These signals are orthogonal to the core workstation — a workstream can be
|
||||
`active` and `stalled` at the same time. They serve as health indicators
|
||||
rather than stored lifecycle stages.
|
||||
|
||||
| Signal | Source | Meaning |
|
||||
|---|---|---|
|
||||
| **blocked** | Derived — unmet exit assertion or blocked task | Work cannot currently leave its workstation; inspect `blocked_reasons` |
|
||||
| **stalled** | Derived — `updated_at` > 7 days ago, has both done and open tasks | Work started but activity has stopped; needs a nudge |
|
||||
| **oldies** | Derived — `created_at` > 7 days ago, zero done tasks | Workstream is old and nothing has been completed yet; may need re-evaluation |
|
||||
|
||||
---
|
||||
|
||||
## The acceptance quality gate
|
||||
|
||||
When a workstream reaches **finished** (all tasks done), the custodian's role is to:
|
||||
|
||||
1. Review the deliverables against the workstream's stated purpose and scope
|
||||
2. Check for missing tests, documentation, or follow-up issues
|
||||
3. Create tasks for any gaps found — this moves the workstream back to **active**
|
||||
4. Once satisfied, advance to the `completed` workstation — this marks it as **accepted**
|
||||
|
||||
This pattern ensures that "done" and "accepted" are distinct signals.
|
||||
`finished` is a fact about task counts; `accepted` is a statement of quality.
|
||||
Normal progression:
|
||||
|
||||
```text
|
||||
backlog -> proposed -> ready -> active -> finished -> archived
|
||||
\ \
|
||||
\ -> blocked -> active
|
||||
-> backlog
|
||||
```
|
||||
# Inspect and accept a workstream via MCP
|
||||
get_flow_state(entity_type="workstream", entity_id="<uuid>")
|
||||
advance_workstation(entity_type="workstream", entity_id="<uuid>", target_workstation="completed")
|
||||
|
||||
# Or via REST
|
||||
---
|
||||
|
||||
## Health Labels
|
||||
|
||||
| Label | Source | Meaning |
|
||||
|---|---|---|
|
||||
| **needs_review** | Ready-review metadata + git diff | A `ready` workplan may be stale because relevant files changed since review |
|
||||
| **stalled** | Task counts + timestamp | Work started, but there has been no meaningful progress after the threshold |
|
||||
|
||||
`needs_review` and `stalled` can appear beside lifecycle states. They should
|
||||
not be written into workplan frontmatter or directly into the workstream
|
||||
`status` field.
|
||||
|
||||
---
|
||||
|
||||
## Ready Review Metadata
|
||||
|
||||
Ready workplans may include optional frontmatter:
|
||||
|
||||
```yaml
|
||||
reviewed_at: "YYYY-MM-DD"
|
||||
reviewed_by: "human-or-agent"
|
||||
reviewed_against_commit: "<git-sha>"
|
||||
context_paths:
|
||||
- "path/or/glob"
|
||||
```
|
||||
|
||||
If `reviewed_against_commit` differs from `HEAD`, State Hub checks
|
||||
`context_paths` when present. Relevant changes produce the derived
|
||||
`needs_review` label. Automatic demotion from `ready` to `proposed` is guarded
|
||||
behind explicit tooling, not done silently.
|
||||
|
||||
---
|
||||
|
||||
## Flow Operations
|
||||
|
||||
```text
|
||||
get_flow_state(entity_type="workstream", entity_id="<uuid>")
|
||||
advance_workstation(entity_type="workstream", entity_id="<uuid>", target_workstation="finished")
|
||||
```
|
||||
|
||||
Direct status patching still exists for bootstrap and compatibility work:
|
||||
|
||||
```bash
|
||||
curl -X PATCH http://127.0.0.1:8000/workstreams/<uuid>/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "completed"}'
|
||||
-d '{"status": "finished"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Time-based filters
|
||||
|
||||
The chart also supports time-window filters that cut across all lifecycle states:
|
||||
|
||||
| Filter | Shows workstreams where… |
|
||||
|---|---|
|
||||
| **last 1 hour** | `updated_at` or `created_at` within the last 60 minutes |
|
||||
| **last 24 hours** | … within the last 24 hours |
|
||||
| **last 7 days** | … within the last 7 days |
|
||||
| **last 30 days** | … within the last 30 days |
|
||||
| **today** | … since midnight today |
|
||||
| **this week** | … since Monday of the current week |
|
||||
| **this month** | … since the 1st of the current month |
|
||||
|
||||
In time-based views, workstream labels are **bold** for accepted and blocked
|
||||
workstreams to distinguish notable states at a glance.
|
||||
|
||||
---
|
||||
|
||||
## Stored Label Vs. Dashboard State
|
||||
|
||||
The DB stores a single `status` field on each workstream. Treat it as the
|
||||
current workstation label. The dashboard maps this alongside flow-engine
|
||||
results, dependency assertions, and task-count data to produce the richer set
|
||||
of filter states:
|
||||
|
||||
| Dashboard state | Stored label / derived source | Condition |
|
||||
|---|---|---|
|
||||
| active | `status = active` | — |
|
||||
| accepted | `status = completed` | — |
|
||||
| finished | task counts | `todo + in_progress + blocked = 0` |
|
||||
| blocked | flow result / task counts | `exit_blocked = true` or `blocked ≥ 1` |
|
||||
| stalled | task counts + timestamp | `done ≥ 1` and `open ≥ 1` and `updated_at > 7d ago` |
|
||||
| oldies | task counts + timestamp | `done = 0` and `open ≥ 1` and `created_at > 7d ago` |
|
||||
|
||||
*Workstreams are never hard-deleted — use `advance_workstation(...,
|
||||
"completed")` or advance/patch to `"archived"` to close them without losing
|
||||
history.*
|
||||
Workstreams are never hard-deleted. Use `finished` for completed
|
||||
implementation and `archived` for historical records outside normal planning.
|
||||
|
||||
@@ -20,14 +20,17 @@ as filters change.
|
||||
|
||||
| Workstation | Meaning |
|
||||
|---|---|
|
||||
| **active** | Work in progress or ready to start |
|
||||
| **proposed** | Plan exists, but needs review against current repo state |
|
||||
| **ready** | Reviewed and ready to execute |
|
||||
| **active** | Work is in progress |
|
||||
| **blocked** | Stored blocker label; the State Hub can also derive blocked state from unmet exit assertions |
|
||||
| **completed** | Formally accepted after custodian review (shown as **accepted** in the overview chart) |
|
||||
| **archived** | Closed without completion; no longer relevant |
|
||||
| **backlog** | Intentionally parked for later |
|
||||
| **finished** | Implementation is complete |
|
||||
| **archived** | Closed historical record |
|
||||
|
||||
See [Workstream Lifecycle](/docs/workstream-lifecycle) for the full task-flow
|
||||
model including derived states (finished, stalled, oldies) and assertion-based
|
||||
blocking.
|
||||
model including derived health labels (`needs_review`, `stalled`) and
|
||||
assertion-based blocking.
|
||||
|
||||
---
|
||||
|
||||
@@ -91,7 +94,7 @@ create_workstream(
|
||||
topic_id = "<uuid>",
|
||||
title = "Build user authentication",
|
||||
description = "JWT-based auth, refresh tokens, middleware",
|
||||
status = "active",
|
||||
status = "ready",
|
||||
owner = "human",
|
||||
due_date = "2026-04-01"
|
||||
)
|
||||
@@ -102,7 +105,7 @@ Via REST:
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/workstreams/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"topic_id": "<uuid>", "title": "…", "status": "active"}'
|
||||
-d '{"topic_id": "<uuid>", "title": "…", "status": "ready"}'
|
||||
```
|
||||
|
||||
---
|
||||
@@ -111,7 +114,7 @@ curl -X POST http://127.0.0.1:8000/workstreams/ \
|
||||
|
||||
```
|
||||
get_flow_state(entity_type="workstream", entity_id="<uuid>")
|
||||
advance_workstation(entity_type="workstream", entity_id="<uuid>", target_workstation="completed")
|
||||
advance_workstation(entity_type="workstream", entity_id="<uuid>", target_workstation="finished")
|
||||
```
|
||||
|
||||
Movement is flow-aware: the task-flow engine evaluates the target
|
||||
|
||||
@@ -4,6 +4,13 @@ title: Overview
|
||||
|
||||
```js
|
||||
import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
|
||||
import {
|
||||
WORKSTREAM_STATUSES,
|
||||
isClosedWorkstream,
|
||||
isStalledWorkstream,
|
||||
needsReviewWorkstream,
|
||||
normalizeWorkstreamStatus,
|
||||
} from "./components/workplan-status.js";
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -52,11 +59,13 @@ const pageState = (async function*() {
|
||||
const workplan = workplanMap[w.id] ?? {};
|
||||
return {
|
||||
...w,
|
||||
status: normalizeWorkstreamStatus(w.status),
|
||||
domain: repo?.domain_slug ?? topic?.domain_slug ?? "unknown",
|
||||
repo_label: repo?.slug ?? workplan.repo_slug ?? "unassigned",
|
||||
workplan_filename: workplan.filename ?? null,
|
||||
workplan_relative_path: workplan.relative_path ?? null,
|
||||
workplan_archived: workplan.archived ?? false,
|
||||
health_labels: workplan.health_labels ?? [],
|
||||
href: `./workstreams/${w.id}`,
|
||||
...(counts[w.id] ?? {done: 0, in_progress: 0, blocked: 0, todo: 0, total: 0}),
|
||||
};
|
||||
@@ -126,13 +135,18 @@ display(html`<div class="warning" style="display:${summary.error ? '' : 'none'}"
|
||||
// view() is the idiomatic Observable Framework reactive input:
|
||||
// it displays the element AND returns a reactive value that re-runs dependent blocks.
|
||||
const _chartMode = view(html`<select class="ws-mode-select">
|
||||
<optgroup label="By Status">
|
||||
<option value="active" selected>active</option>
|
||||
<option value="accepted">accepted</option>
|
||||
<option value="finished">finished</option>
|
||||
<optgroup label="Lifecycle">
|
||||
<option value="ready" selected>ready</option>
|
||||
<option value="active">active</option>
|
||||
<option value="blocked">blocked</option>
|
||||
<option value="proposed">proposed</option>
|
||||
<option value="backlog">backlog</option>
|
||||
<option value="finished">finished</option>
|
||||
<option value="archived">archived</option>
|
||||
</optgroup>
|
||||
<optgroup label="Health">
|
||||
<option value="needs_review">needs review</option>
|
||||
<option value="stalled">stalled</option>
|
||||
<option value="oldies">oldies</option>
|
||||
</optgroup>
|
||||
<optgroup label="Recently Changed">
|
||||
<option value="1h">last 1 hour</option>
|
||||
@@ -150,12 +164,11 @@ const _chartMode = view(html`<select class="ws-mode-select">
|
||||
import * as Plot from "npm:@observablehq/plot";
|
||||
|
||||
// ── Filter workstreams by selected mode ───────────────────────────────────────
|
||||
// "active" matches the DB status field directly.
|
||||
// "accepted" = DB status "completed" (explicitly reviewed and signed off).
|
||||
// "finished" = no open tasks remaining (derived from task counts).
|
||||
// "blocked" = has ≥1 blocked task; "stalled" / "oldies" = activity-based.
|
||||
// Lifecycle modes match stored canonical status values.
|
||||
// Health modes are derived labels; they are not stored lifecycle states.
|
||||
// Time modes filter by updated_at / created_at.
|
||||
const _STATUS_MODES = new Set(["active"]);
|
||||
const _STATUS_MODES = new Set(WORKSTREAM_STATUSES);
|
||||
const _HEALTH_MODES = new Set(["needs_review", "stalled"]);
|
||||
|
||||
function _timeCutoff(mode) {
|
||||
const now = new Date();
|
||||
@@ -175,27 +188,11 @@ function _timeCutoff(mode) {
|
||||
|
||||
const _chartWsFiltered = (
|
||||
_STATUS_MODES.has(_chartMode)
|
||||
? wsAll.filter(w => w.status === _chartMode)
|
||||
: _chartMode === "accepted"
|
||||
? wsAll.filter(w => w.status === "completed")
|
||||
: _chartMode === "finished"
|
||||
? wsAll.filter(w => (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0) === 0)
|
||||
: _chartMode === "blocked"
|
||||
? wsAll.filter(w => (w.blocked ?? 0) > 0)
|
||||
? wsAll.filter(w => normalizeWorkstreamStatus(w.status) === _chartMode)
|
||||
: _chartMode === "needs_review"
|
||||
? wsAll.filter(needsReviewWorkstream)
|
||||
: _chartMode === "stalled"
|
||||
? wsAll.filter(w => {
|
||||
const staleAt = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
||||
return new Date(w.updated_at) < staleAt
|
||||
&& (w.done ?? 0) > 0
|
||||
&& (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0) > 0;
|
||||
})
|
||||
: _chartMode === "oldies"
|
||||
? wsAll.filter(w => {
|
||||
const oldAt = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
||||
return new Date(w.created_at) < oldAt
|
||||
&& (w.done ?? 0) === 0
|
||||
&& (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0) > 0;
|
||||
})
|
||||
? wsAll.filter(isStalledWorkstream)
|
||||
: (() => {
|
||||
const since = _timeCutoff(_chartMode);
|
||||
return wsAll.filter(w =>
|
||||
@@ -215,9 +212,9 @@ const chartWs = [..._chartWsFiltered].sort((a, b) => {
|
||||
});
|
||||
|
||||
// ── Status weight: bold for notable statuses in mixed-status modes ─────────────
|
||||
// Color is NOT used for status — avoids green-on-green when completed bars fill the row.
|
||||
const _isTimeBased = !_STATUS_MODES.has(_chartMode);
|
||||
function _wsWeight(s) { return (s === "accepted" || s === "blocked" || s === "stalled") ? "bold" : "normal"; }
|
||||
// Color is NOT used for status — avoids green-on-green when finished bars fill the row.
|
||||
const _isTimeBased = !_STATUS_MODES.has(_chartMode) && !_HEALTH_MODES.has(_chartMode);
|
||||
function _wsWeight(s) { return (isClosedWorkstream(s) || normalizeWorkstreamStatus(s) === "blocked") ? "bold" : "normal"; }
|
||||
|
||||
// ── y-axis: domain/repo label for first workstream per repository only ────────
|
||||
const _yLabels = {};
|
||||
@@ -251,10 +248,15 @@ function _wsTitle(d) {
|
||||
// ── Render ────────────────────────────────────────────────────────────────────
|
||||
if (chartWs.length === 0) {
|
||||
const _emptyMsg = {
|
||||
active: "No active workstreams.", accepted: "No accepted workstreams.",
|
||||
finished: "No finished workstreams.", blocked: "No blocked workstreams.",
|
||||
proposed: "No proposed workstreams.",
|
||||
ready: "No ready workstreams.",
|
||||
active: "No active workstreams.",
|
||||
blocked: "No blocked workstreams.",
|
||||
backlog: "No backlog workstreams.",
|
||||
finished: "No finished workstreams.",
|
||||
archived: "No archived workstreams.",
|
||||
needs_review: "No ready workstreams need review.",
|
||||
stalled: "No stalled workstreams — everything is moving.",
|
||||
oldies: "No oldies — all older workstreams have at least one task done.",
|
||||
"1h": "No workstreams changed in the last hour.",
|
||||
"1d": "No workstreams changed in the last 24 hours.",
|
||||
"7d": "No workstreams changed in the last 7 days.",
|
||||
|
||||
@@ -4,6 +4,7 @@ title: Workstreams
|
||||
|
||||
```js
|
||||
import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
|
||||
import {WORKSTREAM_STATUSES, isClosedWorkstream, normalizeWorkstreamStatus} from "./components/workplan-status.js";
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -27,6 +28,7 @@ const wsState = (async function*() {
|
||||
const repoMap = Object.fromEntries(repoList.map(r => [r.id, r]));
|
||||
data = wsList.map(w => ({
|
||||
...w,
|
||||
status: normalizeWorkstreamStatus(w.status),
|
||||
domain: repoMap[w.repo_id]?.domain_slug ?? topicMap[w.topic_id]?.domain_slug ?? "unknown",
|
||||
topic_title: topicMap[w.topic_id]?.title ?? "—",
|
||||
}));
|
||||
@@ -50,7 +52,7 @@ const _ts = wsState.ts;
|
||||
```js
|
||||
// ── Workstream Health Index (WHI) ────────────────────────────────────────────
|
||||
const _idToDomain = Object.fromEntries(data.map(w => [w.id, w.domain ?? "unknown"]));
|
||||
const _completedIds = new Set(data.filter(w => w.status === "completed" || w.status === "archived").map(w => w.id));
|
||||
const _closedIds = new Set(data.filter(w => isClosedWorkstream(w.status)).map(w => w.id));
|
||||
const _openCount = openWs.length;
|
||||
const _allEdges = openWs.flatMap(w => w.depends_on.map(d => ({from: w.id, to: d.workstream_id})));
|
||||
const _totalEdges = _allEdges.length;
|
||||
@@ -64,15 +66,15 @@ const _BR = _openCount > 0 ? openWs.filter(w => w.status === "blocked").length /
|
||||
// Single-Point Risk — max inbound edges on one incomplete workstream
|
||||
const _inbound = {};
|
||||
for (const e of _allEdges) {
|
||||
if (!_completedIds.has(e.to)) _inbound[e.to] = (_inbound[e.to] ?? 0) + 1;
|
||||
if (!_closedIds.has(e.to)) _inbound[e.to] = (_inbound[e.to] ?? 0) + 1;
|
||||
}
|
||||
const _SPR = _openCount > 0
|
||||
? (Object.keys(_inbound).length > 0 ? Math.max(...Object.values(_inbound)) : 0) / _openCount
|
||||
: 0;
|
||||
|
||||
// Parallel Execution Potential — active workstreams with all deps completed
|
||||
// Parallel Execution Potential — ready/active workstreams with all deps finished
|
||||
const _PEP = _openCount > 0
|
||||
? openWs.filter(w => w.status === "active" && w.depends_on.every(d => _completedIds.has(d.workstream_id))).length / _openCount
|
||||
? openWs.filter(w => ["ready", "active"].includes(normalizeWorkstreamStatus(w.status)) && w.depends_on.every(d => _closedIds.has(d.workstream_id))).length / _openCount
|
||||
: 0;
|
||||
|
||||
// Cross-Domain Dependency Ratio
|
||||
@@ -117,9 +119,9 @@ const _domainBreakdown = [...new Set(openWs.map(w => _idToDomain[w.id] ?? "unkno
|
||||
const dd = oc > 0 ? te / oc : 0;
|
||||
const br = oc > 0 ? nodes.filter(w => w.status === "blocked").length / oc : 0;
|
||||
const pep = oc > 0 ? nodes.filter(w => {
|
||||
if (w.status !== "active") return false;
|
||||
if (!["ready", "active"].includes(normalizeWorkstreamStatus(w.status))) return false;
|
||||
const intraDeps = w.depends_on.filter(d => (_idToDomain[d.workstream_id] ?? "unknown") === domain);
|
||||
return intraDeps.every(d => _completedIds.has(d.workstream_id));
|
||||
return intraDeps.every(d => _closedIds.has(d.workstream_id));
|
||||
}).length / oc : 0;
|
||||
const inb = {};
|
||||
for (const e of edges) inb[e.to] = (inb[e.to] ?? 0) + 1;
|
||||
@@ -222,7 +224,7 @@ const _domainsResp = await fetch(`${API}/domains/?status=active`).catch(() => nu
|
||||
const DOMAINS = _domainsResp?.ok
|
||||
? (await _domainsResp.json()).map(d => d.slug)
|
||||
: ["custodian", "railiance", "markitect", "coulomb_social", "personhood", "foerster_capabilities"];
|
||||
const STATUSES = ["active", "blocked", "completed", "archived"];
|
||||
const STATUSES = WORKSTREAM_STATUSES;
|
||||
|
||||
// Create filter form without displaying — shown below the chart
|
||||
const _filtersForm = Inputs.form(
|
||||
@@ -357,7 +359,10 @@ if (wsWithDeps.length === 0) {
|
||||
.dep-status { display: inline-block; font-size: 0.7rem; padding: 1px 6px; border-radius: 10px; margin-bottom: 0.5rem; text-transform: uppercase; }
|
||||
.dep-status-active { background: #d4edda; color: #155724; }
|
||||
.dep-status-blocked { background: #f8d7da; color: #721c24; }
|
||||
.dep-status-completed { background: #cce5ff; color: #004085; }
|
||||
.dep-status-proposed { background: #fef3c7; color: #92400e; }
|
||||
.dep-status-ready { background: #e0f2fe; color: #075985; }
|
||||
.dep-status-finished { background: #cce5ff; color: #004085; }
|
||||
.dep-status-backlog { background: #f1f5f9; color: #64748b; }
|
||||
.dep-row { font-size: 0.85rem; margin: 0.2rem 0 0 0.5rem; color: #444; }
|
||||
.dep-on { color: #1a5276; }
|
||||
.dep-block { color: #6e2f00; }
|
||||
|
||||
Reference in New Issue
Block a user