feat(terminology): workplan-first dashboard and retirement backlog (STATE-WP-0069)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Add the ranked legacy-interface backlog (T01), rename dashboard navigation and
user-facing copy to workplan while preserving wire-compat API keys (T02), and
activate the retirement workplan with T01/T02 marked done.
This commit is contained in:
tegwick 2026-07-08 16:08:32 +02:00
parent 4dc69bb8f5
commit b2264d1f06
44 changed files with 382 additions and 253 deletions

View file

@ -54,7 +54,7 @@ export default {
pages: [ pages: [
{ name: "Repository DoI", path: "/policy/repo-doi" }, { name: "Repository DoI", path: "/policy/repo-doi" },
{ name: "Service DoM", path: "/policy/service-dom" }, { name: "Service DoM", path: "/policy/service-dom" },
{ name: "Workstream DoD", path: "/policy/workstream-dod" }, { name: "Workplan DoD", path: "/policy/workstream-dod" },
], ],
}, },
{ {
@ -69,7 +69,7 @@ export default {
], ],
}, },
{ {
name: "Workstreams", name: "Workplans",
path: "/workstreams", path: "/workstreams",
collapsible: true, collapsible: true,
open: false, open: false,
@ -120,9 +120,9 @@ export default {
{ name: "TPSC — GDPR Maturity", path: "/docs/gdpr-maturity" }, { name: "TPSC — GDPR Maturity", path: "/docs/gdpr-maturity" },
{ name: "Technical Debt", path: "/docs/debt" }, { name: "Technical Debt", path: "/docs/debt" },
{ name: "Todo", path: "/docs/todo" }, { name: "Todo", path: "/docs/todo" },
{ name: "Workstream Health", path: "/docs/workstream-health-index" }, { name: "Workplan Health", path: "/docs/workstream-health-index" },
{ name: "Workstream Lifecycle", path: "/docs/workstream-lifecycle" }, { name: "Workplan Lifecycle", path: "/docs/workstream-lifecycle" },
{ name: "Workstreams", path: "/docs/workstreams" }, { name: "Workplans", path: "/docs/workstreams" },
{ name: "Suggestions", path: "/docs/suggestions" }, { name: "Suggestions", path: "/docs/suggestions" },
{ name: "WSJF Triage", path: "/docs/wsjf-triage" }, { name: "WSJF Triage", path: "/docs/wsjf-triage" },
], ],

View file

@ -3,9 +3,9 @@
* *
* Usage: * Usage:
* import {openEntityModal} from "./components/entity-modal.js"; * import {openEntityModal} from "./components/entity-modal.js";
* row.addEventListener("click", () => openEntityModal(entity, "workstream")); * row.addEventListener("click", () => openEntityModal(entity, "workplan"));
* *
* Supported types: "workstream" | "task" | "ep" | "td" * Supported types: "workplan" | "task" | "ep" | "td"
*/ */
const _STYLE_ID = "entity-modal-styles"; const _STYLE_ID = "entity-modal-styles";
@ -216,7 +216,7 @@ function _buildBody(entity, type) {
return _field(label, v); return _field(label, v);
}; };
if (type === "workstream") { if (type === "workplan") {
els.push( els.push(
bf("Status", entity.status, _STATUS_STYLE), bf("Status", entity.status, _STATUS_STYLE),
tf("Domain", entity.domain ?? entity.topic_title ?? "—"), tf("Domain", entity.domain ?? entity.topic_title ?? "—"),
@ -261,7 +261,7 @@ function _buildBody(entity, type) {
bf("Status", entity.status, _STATUS_STYLE), bf("Status", entity.status, _STATUS_STYLE),
bf("Priority", entity.priority, _PRIORITY_STYLE), bf("Priority", entity.priority, _PRIORITY_STYLE),
tf("Domain", entity.domain ?? "—"), tf("Domain", entity.domain ?? "—"),
tf("Workstream", entity.workstream_title ?? "—"), tf("Workplan", entity.workstream_title ?? "—"),
tf("Assignee", entity.assignee ?? "—"), tf("Assignee", entity.assignee ?? "—"),
tf("Due", entity.due_date ?? "—"), tf("Due", entity.due_date ?? "—"),
); );
@ -285,7 +285,7 @@ function _buildBody(entity, type) {
bf("Priority", entity.priority, _PRIORITY_STYLE), bf("Priority", entity.priority, _PRIORITY_STYLE),
tf("Type", entity.ep_type ?? "—"), tf("Type", entity.ep_type ?? "—"),
tf("Domain", entity.domain ?? "—"), tf("Domain", entity.domain ?? "—"),
tf("Workstream", entity.workstream_title ?? "—"), tf("Workplan", entity.workstream_title ?? "—"),
tf("Location", entity.location ?? "—"), tf("Location", entity.location ?? "—"),
); );
if (entity.description) { if (entity.description) {
@ -302,7 +302,7 @@ function _buildBody(entity, type) {
bf("Status", entity.status, _STATUS_STYLE), bf("Status", entity.status, _STATUS_STYLE),
tf("Type", entity.debt_type ?? "—"), tf("Type", entity.debt_type ?? "—"),
tf("Domain", entity.domain ?? "—"), tf("Domain", entity.domain ?? "—"),
tf("Workstream", entity.workstream_title ?? "—"), tf("Workplan", entity.workstream_title ?? "—"),
tf("Location", entity.location ?? "—"), tf("Location", entity.location ?? "—"),
); );
if (entity.description) { if (entity.description) {
@ -319,8 +319,8 @@ function _buildBody(entity, type) {
/** /**
* Open a detail modal for the given entity. * Open a detail modal for the given entity.
* @param {object} entity - The entity data object (workstream, task, ep, or td) * @param {object} entity - The entity data object (workplan, task, ep, or td)
* @param {string} type - One of: "workstream" | "task" | "ep" | "td" * @param {string} type - One of: "workplan" | "task" | "ep" | "td"
*/ */
export function openEntityModal(entity, type) { export function openEntityModal(entity, type) {
_ensureStyles(); _ensureStyles();

View file

@ -99,13 +99,13 @@ export const FIELD_HELP = {
doc: "/docs/tasks", doc: "/docs/tasks",
}, },
workstream_id: { workstream_id: {
label: "Workstream ID", label: "Workplan ID",
description: "The workstream this event belongs to; auto-resolved from task if not set directly.", description: "The workplan this event belongs to; auto-resolved from task if not set directly.",
doc: "/docs/workstreams", doc: "/docs/workstreams",
}, },
repo_id: { repo_id: {
label: "Repo ID", label: "Repo ID",
description: "The managed repo this event is attributed to; auto-resolved from workstream.", description: "The managed repo this event is attributed to; auto-resolved from workplan.",
doc: "/docs/repos", doc: "/docs/repos",
}, },
session_id: { session_id: {
@ -142,14 +142,14 @@ export const FIELD_HELP = {
description: "Timestamp when this token event was recorded (UTC).", description: "Timestamp when this token event was recorded (UTC).",
}, },
// ── Workstream ────────────────────────────────────────────────────────────── // ── Workplan ──────────────────────────────────────────────────────────────
slug: { slug: {
label: "Slug", label: "Slug",
description: "URL-safe short identifier for this entity.", description: "URL-safe short identifier for this entity.",
}, },
title: { title: {
label: "Title", label: "Title",
description: "Human-readable name for this workstream or task.", description: "Human-readable name for this workplan or task.",
}, },
status: { status: {
label: "Status", label: "Status",
@ -158,12 +158,12 @@ export const FIELD_HELP = {
}, },
topic_id: { topic_id: {
label: "Topic ID", label: "Topic ID",
description: "The topic this workstream is grouped under.", description: "The topic this workplan is grouped under.",
doc: "/docs/reference#topics", doc: "/docs/reference#topics",
}, },
repo_goal_id: { repo_goal_id: {
label: "Repo Goal ID", label: "Repo Goal ID",
description: "Optional link to a repo-level strategic goal this workstream advances.", description: "Optional link to a repo-level strategic goal this workplan advances.",
doc: "/docs/goals", doc: "/docs/goals",
}, },

View file

@ -6,7 +6,7 @@
* initImprovementModal({apiBase: "http://127.0.0.1:8000"}); * initImprovementModal({apiBase: "http://127.0.0.1:8000"});
* *
* Widget names can be declared explicitly via data attribute: * Widget names can be declared explicitly via data attribute:
* <div data-widget-name="Workstreams by Domain"></div> * <div data-widget-name="Workplans by Domain"></div>
* *
* Otherwise the component walks the DOM to infer the nearest section heading. * Otherwise the component walks the DOM to infer the nearest section heading.
* Submissions are stored as technical-debt items with debt_type="dashboard-improvement". * Submissions are stored as technical-debt items with debt_type="dashboard-improvement".

View file

@ -80,8 +80,8 @@ export function candidateKeysForWorkplan(item = {}) {
export function buildCandidateIndex(workplanIndex = {}) { export function buildCandidateIndex(workplanIndex = {}) {
const byCandidate = new Map(); const byCandidate = new Map();
const workstreams = workplanIndex.workstreams ?? {}; const workplans = workplanIndex.workplans ?? workplanIndex.workstreams ?? {};
for (const [id, item] of Object.entries(workstreams)) { for (const [id, item] of Object.entries(workplans)) {
const resolved = {id, ...item}; const resolved = {id, ...item};
byCandidate.set(normalizeCandidate(id), resolved); byCandidate.set(normalizeCandidate(id), resolved);
for (const key of candidateKeysForWorkplan(item)) { for (const key of candidateKeysForWorkplan(item)) {

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Observable data loader: fetches a single workstream by ID.""" """Observable data loader: fetches a single workplan by ID."""
import json import json
import os import os
import sys import sys
@ -11,7 +11,7 @@ API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
ws_id = sys.argv[1] if len(sys.argv) > 1 else "" ws_id = sys.argv[1] if len(sys.argv) > 1 else ""
if not ws_id: if not ws_id:
print(json.dumps({"error": "No workstream ID provided"})) print(json.dumps({"error": "No workplan ID provided"}))
sys.exit(1) sys.exit(1)
try: try:

View file

@ -8,7 +8,7 @@ import {normalizeWorkstreamStatus} from "./components/workplan-status.js";
``` ```
```js ```js
// Fetch workstreams + topics + dep edges; /state/deps replaces the heavier // Fetch workplans + topics + dep edges; /state/deps replaces the heavier
// /state/summary which was only used here to extract dependency edges. // /state/summary which was only used here to extract dependency edges.
const depState = (async function*() { const depState = (async function*() {
let failures = 0; let failures = 0;
@ -70,7 +70,7 @@ const _kpiBox = html`<div class="kpi-infobox">
<div class="kpi-row-right"><div class="kpi-row-value">${edges.length}</div></div> <div class="kpi-row-right"><div class="kpi-row-value">${edges.length}</div></div>
</div> </div>
<div class="kpi-row"> <div class="kpi-row">
<span class="kpi-row-label">workstreams involved</span> <span class="kpi-row-label">workplans involved</span>
<div class="kpi-row-right"><div class="kpi-row-value">${_wsWithDeps.size}</div></div> <div class="kpi-row-right"><div class="kpi-row-value">${_wsWithDeps.size}</div></div>
</div> </div>
</div>`; </div>`;
@ -89,7 +89,7 @@ injectTocTop("dep-kpi-box", _kpiBox);
injectTocTop("live-indicator", _liveEl); injectTocTop("live-indicator", _liveEl);
``` ```
Directed edges between open workstreams. An edge **A → B** means A cannot Directed edges between open workplans. An edge **A → B** means A cannot
fully proceed until B reaches a satisfactory state. fully proceed until B reaches a satisfactory state.
```js ```js
@ -113,10 +113,10 @@ if (edges.length === 0) {
<thead> <thead>
<tr> <tr>
<th>Depends-on domain</th> <th>Depends-on domain</th>
<th>Depends-on workstream</th> <th>Depends-on workplan</th>
<th></th> <th></th>
<th>Blocked-by domain</th> <th>Blocked-by domain</th>
<th>Blocked-by workstream</th> <th>Blocked-by workplan</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
</thead> </thead>

View file

@ -219,7 +219,7 @@ and age in days.
| Concept | Relationship | | Concept | Relationship |
|---------|-------------| |---------|-------------|
| **SCOPE.md** | Defines what a repo *is responsible for* — the catalog registers what it *can provide* | | **SCOPE.md** | Defines what a repo *is responsible for* — the catalog registers what it *can provide* |
| **Dependencies** | Workstream-to-workstream edges — capabilities are higher-level, domain-to-domain | | **Dependencies** | Workplan-to-workplan edges — capabilities are higher-level, domain-to-domain |
| **Extension Points** | Design forks for *future* enhancement — capabilities are *operational* requests | | **Extension Points** | Design forks for *future* enhancement — capabilities are *operational* requests |
| **Contributions** | Outbound upstream work — capabilities are *inbound* requests between internal domains | | **Contributions** | Outbound upstream work — capabilities are *inbound* requests between internal domains |
| **Human Interventions** | Flagged tasks for Bernd — capabilities are agent-to-agent coordination | | **Human Interventions** | Flagged tasks for Bernd — capabilities are agent-to-agent coordination |

View file

@ -56,7 +56,7 @@ communication taxonomy:
| Todo class | Mechanism | | Todo class | Mechanism |
|------------|-----------| |------------|-----------|
| Internal | Workplan file + task in this repo's workstream | | Internal | Workplan file + task in this repo's workplan |
| Ecosystem | State hub task with `[repo:<slug>]` prefix | | Ecosystem | State hub task with `[repo:<slug>]` prefix |
| **Third-party** | **Contribution artifact in `contrib/` + state hub registration** | | **Third-party** | **Contribution artifact in `contrib/` + state hub registration** |

View file

@ -6,7 +6,7 @@ title: Dashboard — Technical Reference
The State Hub dashboard is the primary visual interface for the Custodian The State Hub dashboard is the primary visual interface for the Custodian
ecosystem. It provides live, reactive views of all tracked domains, ecosystem. It provides live, reactive views of all tracked domains,
workstreams, tasks, decisions, contributions, SBOM data, and agent activity — workplans, tasks, decisions, contributions, SBOM data, and agent activity —
all sourced from the local FastAPI state service. all sourced from the local FastAPI state service.
--- ---
@ -63,7 +63,7 @@ Current loaders:
| File | API endpoint | | File | API endpoint |
|---|---| |---|---|
| `summary.json.py` | `/state/summary` | | `summary.json.py` | `/state/summary` |
| `workstreams.json.py` | `/workplans/` | | `workplans.json.py` | `/workplans/` |
| `contributions.json.py` | `/contributions/` | | `contributions.json.py` | `/contributions/` |
| `decisions.json.py` | `/decisions/` | | `decisions.json.py` | `/decisions/` |
| `domains.json.py` | `/domains/` | | `domains.json.py` | `/domains/` |
@ -120,7 +120,7 @@ The dashboard has 30+ pages organised in four navigation groups:
| Page | Route | Purpose | | Page | Route | Purpose |
|---|---|---| |---|---|---|
| Overview | `/` | Cross-domain summary — workstream chart, status KPIs, blocking decisions, recent activity | | Overview | `/` | Cross-domain summary — workplan chart, status KPIs, blocking decisions, recent activity |
| Capabilities | `/capability-requests` | Capability request routing and fulfilment status | | Capabilities | `/capability-requests` | Capability request routing and fulfilment status |
| Contributions | `/contributions` | Upstream contribution Kanban (bug reports, feature requests, upstream PRs) | | Contributions | `/contributions` | Upstream contribution Kanban (bug reports, feature requests, upstream PRs) |
| Domains | `/domains` | Per-domain health overview and management | | Domains | `/domains` | Per-domain health overview and management |
@ -140,7 +140,7 @@ The dashboard has 30+ pages organised in four navigation groups:
| Repo Sync | `/repo-sync` | Consistency checker results and sync status | | Repo Sync | `/repo-sync` | Consistency checker results and sync status |
| SBOM | `/sbom` | Software bill of materials — packages, licences, copyleft risk | | SBOM | `/sbom` | Software bill of materials — packages, licences, copyleft risk |
### Workstreams section ### Workplans section
| Page | Route | Purpose | | Page | Route | Purpose |
|---|---|---| |---|---|---|
@ -173,7 +173,7 @@ Exports shared runtime configuration used by every live-polling page:
### `entity-modal.js` ### `entity-modal.js`
A lightweight detail overlay for entities. Any table row or card can call A lightweight detail overlay for entities. Any table row or card can call
`openEntityModal(entity, type)` to open a full-detail panel without navigating `openEntityModal(entity, type)` to open a full-detail panel without navigating
away from the page. Supports four entity types: `workstream`, `task`, `ep` away from the page. Supports four entity types: `workplan`, `task`, `ep`
(extension point), and `td` (technical debt). (extension point), and `td` (technical debt).
Also exports `buildEntityTable()` — a function that constructs a consistent, Also exports `buildEntityTable()` — a function that constructs a consistent,
@ -183,7 +183,7 @@ overflow ellipsis, and native tooltip-on-hover for truncated values.
### `toc-sidebar.js` ### `toc-sidebar.js`
Provides `injectTocTop(id, element)` — injects a DOM element into the Provides `injectTocTop(id, element)` — injects a DOM element into the
Observable Framework table-of-contents sidebar above the page's first section Observable Framework table-of-contents sidebar above the page's first section
heading. Used on the Overview and Workstreams pages to embed live KPI infoboxes heading. Used on the Overview and Workplans pages to embed live KPI infoboxes
directly in the sidebar. directly in the sidebar.
### `doc-overlay.js` ### `doc-overlay.js`
@ -194,12 +194,12 @@ without leaving the current page.
### `help-tip.js` ### `help-tip.js`
A custom HTML element (`<help-tip>`) that renders an inline abbreviated label A custom HTML element (`<help-tip>`) that renders an inline abbreviated label
with an expandable tooltip containing a longer description and a link to the with an expandable tooltip containing a longer description and a link to the
relevant reference page. Used in the Workstream Health Index card to annotate relevant reference page. Used in the Workplan Health Index card to annotate
each metric abbreviation. each metric abbreviation.
### `multiselect.js` ### `multiselect.js`
A multi-value dropdown filter input compatible with Observable's `Inputs.form()` A multi-value dropdown filter input compatible with Observable's `Inputs.form()`
reactive pattern. Used on the Workstreams and Tasks pages for domain and status reactive pattern. Used on the Workplans and Tasks pages for domain and status
filtering. filtering.
### `improvement-modal.js` ### `improvement-modal.js`
@ -223,17 +223,17 @@ shows green when the API is reachable and red with a restart command when it
is not. This allows the dashboard to be used as a persistent, always-on monitor is not. This allows the dashboard to be used as a persistent, always-on monitor
without requiring a page refresh. without requiring a page refresh.
### Workstream Health Index (WHI) ### Workplan Health Index (WHI)
The Workstreams page computes a **Workstream Health Index** — a single The Workplans page computes a **Workplan Health Index** — a single
composite score (0100%) derived from five graph metrics: composite score (0100%) derived from five graph metrics:
| Metric | Abbrev. | Weight | Interpretation | | Metric | Abbrev. | Weight | Interpretation |
|---|---|---|---| |---|---|---|---|
| Dependency Density | DD | 30% | Average deps per open workstream; high = tightly coupled | | Dependency Density | DD | 30% | Average deps per open workplan; high = tightly coupled |
| Blocked Ratio | BR | 25% | Share of workstreams in a blocked state | | Blocked Ratio | BR | 25% | Share of workplans in a blocked state |
| Single-Point Risk | SPR | 15% | Share of workstreams that others depend on but are not yet complete | | Single-Point Risk | SPR | 15% | Share of workplans that others depend on but are not yet complete |
| Parallel Execution Potential | PEP | 20% | Share of workstreams that could start/continue immediately | | Parallel Execution Potential | PEP | 20% | Share of workplans that could start/continue immediately |
| Cross-Domain Dependency Ratio | CDDR | 10% | Share of edges crossing domain boundaries | | Cross-Domain Dependency Ratio | CDDR | 10% | Share of edges crossing domain boundaries |
A **Cycle Presence Indicator** (CPI) detected via DFS halves the total score A **Cycle Presence Indicator** (CPI) detected via DFS halves the total score
@ -241,17 +241,17 @@ when a dependency cycle is found, since cyclic dependencies cause deadlock.
The index is computed per-domain as well as globally and displayed in the The index is computed per-domain as well as globally and displayed in the
TOC sidebar as a persistent KPI card. TOC sidebar as a persistent KPI card.
### Multi-mode workstream chart ### Multi-mode workplan chart
The Overview page renders a horizontal stacked bar chart using `@observablehq/plot` The Overview page renders a horizontal stacked bar chart using `@observablehq/plot`
showing task counts (done / progress / wait / todo) per workstream. showing task counts (done / progress / wait / todo) per workplan.
A `<select>` dropdown switches between: A `<select>` dropdown switches between:
- **Lifecycle modes**: proposed, ready, active, blocked, backlog, finished, archived - **Lifecycle modes**: proposed, ready, active, blocked, backlog, finished, archived
- **Health modes**: needs review, stalled - **Health modes**: needs review, stalled
- **Time modes**: last 1h, 24h, 7d, 30d, today, this week, this month - **Time modes**: last 1h, 24h, 7d, 30d, today, this week, this month
Domains are sorted by most recent workstream activity (most active domain at Domains are sorted by most recent workplan activity (most active domain at
the top). Title labels and done/total counters are overlaid directly on the bars. the top). Title labels and done/total counters are overlaid directly on the bars.
### Resolve-in-place for blocking decisions ### Resolve-in-place for blocking decisions
@ -271,14 +271,14 @@ in direct production dependencies.
### Dependency graph ### Dependency graph
The Dependencies page and the Workstreams page both surface inter-workstream The Dependencies page and the Workplans page both surface inter-workplan
dependency data. Each workstream card shows the workstreams it depends on dependency data. Each workplan card shows the workplans it depends on
(`↳ depends on`) and the workstreams it blocks (`⊳ blocks`), derived from (`↳ depends on`) and the workplans it blocks (`⊳ blocks`), derived from
the `WorkstreamDependency` table. the `WorkstreamDependency` table.
### Entity modals ### Entity modals
Any table row on any list page (workstreams, tasks, extension points, tech debt) Any table row on any list page (workplans, tasks, extension points, tech debt)
can be clicked to open a detail modal with full field data, dependency lists, can be clicked to open a detail modal with full field data, dependency lists,
task progress, and timestamps — without a page navigation or a separate detail task progress, and timestamps — without a page navigation or a separate detail
route. route.
@ -309,7 +309,7 @@ needed in the source directory). Typical mark types used across the dashboard:
| Mark | Used for | | Mark | Used for |
|---|---| |---|---|
| `Plot.barX` | Horizontal stacked task-count bars, SBOM licence distribution | | `Plot.barX` | Horizontal stacked task-count bars, SBOM licence distribution |
| `Plot.text` | Workstream title labels and done/total counters overlaid on bars | | `Plot.text` | Workplan title labels and done/total counters overlaid on bars |
| `Plot.ruleX([0])` | Zero-axis rule on all bar charts | | `Plot.ruleX([0])` | Zero-axis rule on all bar charts |
Charts are rendered as inline SVG and inherit Observable Framework's theme Charts are rendered as inline SVG and inherit Observable Framework's theme
@ -339,4 +339,4 @@ data and shows the offline error state on each page.
- [Live Data](/docs/live-data) — polling mechanism and offline behaviour in detail - [Live Data](/docs/live-data) — polling mechanism and offline behaviour in detail
- [Connecting to the Hub](/docs/connecting) — MCP server registration - [Connecting to the Hub](/docs/connecting) — MCP server registration
- [Overview](/docs/overview) — Overview page feature walkthrough - [Overview](/docs/overview) — Overview page feature walkthrough
- [Workstreams](/docs/workstreams) — Workstreams page and WHI in depth - [Workplans](/docs/workstreams) — Workplans page and WHI in depth

View file

@ -5,15 +5,15 @@ title: Dependencies — Reference
# Dependencies — Reference # Dependencies — Reference
The Dependencies page shows the directed dependency graph between open The Dependencies page shows the directed dependency graph between open
workstreams — which workstreams are waiting on others to reach a satisfactory workplans — which workplans are waiting on others to reach a satisfactory
state before they can fully proceed. state before they can fully proceed.
--- ---
## What is a dependency edge? ## What is a dependency edge?
A dependency edge **A → B** means workstream A cannot fully proceed until A dependency edge **A → B** means workplan A cannot fully proceed until
workstream B is in a satisfactory state (typically `finished` or `archived`). workplan B is in a satisfactory state (typically `finished` or `archived`).
Edges are used to model real sequencing constraints: for example, a shared 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. library must reach a stable release before downstream domains can build on it.
@ -31,18 +31,18 @@ Each row shows:
| Column | Meaning | | Column | Meaning |
|--------|---------| |--------|---------|
| **Depends-on domain** | Domain of the dependent workstream (the one waiting) | | **Depends-on domain** | Domain of the dependent workplan (the one waiting) |
| **Depends-on workstream** | Title of the workstream that has the dependency | | **Depends-on workplan** | Title of the workplan that has the dependency |
| **→** | Direction arrow | | **→** | Direction arrow |
| **Blocked-by domain** | Domain of the prerequisite workstream | | **Blocked-by domain** | Domain of the prerequisite workplan |
| **Blocked-by workstream** | Title of the workstream that must complete first | | **Blocked-by workplan** | Title of the workplan that must complete first |
| **Status** | Current status of the prerequisite (green = active, grey = finished/archived) | | **Status** | Current status of the prerequisite (green = active, grey = finished/archived) |
--- ---
## KPI sidebar card ## KPI sidebar card
Shows the total number of edges and the number of distinct workstreams involved Shows the total number of edges and the number of distinct workplans involved
in at least one dependency relationship. in at least one dependency relationship.
--- ---
@ -67,7 +67,7 @@ curl -X POST http://127.0.0.1:8000/workplans/<from_id>/dependencies/ \
-d '{"to_workstream_id": "<to_id>", "description": "..."}' -d '{"to_workstream_id": "<to_id>", "description": "..."}'
``` ```
To list dependencies for a workstream: To list dependencies for a workplan:
``` ```
list_dependencies(workstream_id="<uuid>") list_dependencies(workstream_id="<uuid>")
@ -77,10 +77,10 @@ list_dependencies(workstream_id="<uuid>")
## Cycle detection ## Cycle detection
The Workstream Health Index (WHI) includes a **Cycle Penalty Index (CPI)** The Workplan Health Index (WHI) includes a **Cycle Penalty Index (CPI)**
metric that detects circular dependencies using depth-first search. If CPI = 1, metric that detects circular dependencies using depth-first search. If CPI = 1,
a cycle exists and the WHI is penalised by 50%. The WHI KPI card on the a cycle exists and the WHI is penalised by 50%. The WHI KPI card on the
[Workstreams](/workstreams) page will display a cycle alert. [Workplans](/workstreams) page will display a cycle alert.
--- ---

View file

@ -42,7 +42,7 @@ be pursued.
| Status | Meaning | | Status | Meaning |
|--------|---------| |--------|---------|
| **open** | Identified, not yet acted on | | **open** | Identified, not yet acted on |
| **in_progress** | Being implemented as part of an active workstream | | **in_progress** | Being implemented as part of an active workplan |
| **addressed** | The capability has been built | | **addressed** | The capability has been built |
| **deferred** | Intentionally postponed | | **deferred** | Intentionally postponed |
| **wont_fix** | Decided not to pursue — kept for documentation | | **wont_fix** | Decided not to pursue — kept for documentation |

View file

@ -4,7 +4,7 @@ title: Goals — Reference
# Goals — Reference # Goals — Reference
The Goals page shows strategic intent at two levels — **domain** and **repository** — and how they relate. It provides context for why workstreams exist and what they collectively deliver. The Goals page shows strategic intent at two levels — **domain** and **repository** — and how they relate. It provides context for why workplans exist and what they collectively deliver.
--- ---
@ -123,9 +123,9 @@ The Goals page groups everything by domain:
--- ---
## Linking workstreams to repo goals ## Linking workplans to repo goals
Workstreams carry an optional `repo_goal_id` field. Setting it traces *why* a workstream exists — which specific repo goal it contributes to. This connection is currently recorded in the DB but is not yet visualised in the Workstreams page. Workplans carry an optional `repo_goal_id` field. Setting it traces *why* a workplan exists — which specific repo goal it contributes to. This connection is currently recorded in the DB but is not yet visualised in the Workplans page.
To set the link when creating a workplan through the preferred API, pass `repo_goal_id`. To update an existing one, use `PATCH /workplans/{id}/` with `{"repo_goal_id": "<uuid>"}`. Legacy `create_workstream` and `/workstreams/{id}/` callers remain compatibility-supported while they are metered. To set the link when creating a workplan through the preferred API, pass `repo_goal_id`. To update an existing one, use `PATCH /workplans/{id}/` with `{"repo_goal_id": "<uuid>"}`. Legacy `create_workstream` and `/workstreams/{id}/` callers remain compatibility-supported while they are metered.
@ -133,4 +133,4 @@ To set the link when creating a workplan through the preferred API, pass `repo_g
## Design rationale ## Design rationale
Goals are intentionally separate from workstreams. A workstream is a unit of *deliverable work*; a goal is a statement of *strategic intent*. Goals are stable and long-lived; workstreams are created, completed, and replaced as work advances. The goal hierarchy (domain → repo → workstream) provides the context needed to understand why any given piece of work exists. Goals are intentionally separate from workplans. A workplan is a unit of *deliverable work*; a goal is a statement of *strategic intent*. Goals are stable and long-lived; workplans are created, completed, and replaced as work advances. The goal hierarchy (domain → repo → workplan) provides the context needed to understand why any given piece of work exists.

View file

@ -37,7 +37,7 @@ appropriate coordination channel (see below). Do not write the files yourself.
A **task** is the state hub data entity encapsulating a suggested or required A **task** is the state hub data entity encapsulating a suggested or required
piece of work. Tasks live in the state hub database, are always scoped to a piece of work. Tasks live in the state hub database, are always scoped to a
workstream, and are the universal unit of cross-repo coordination. Tasks are workplan, and are the universal unit of cross-repo coordination. Tasks are
neutral — they describe *what* should be done, not *who* does it or *where*. neutral — they describe *what* should be done, not *who* does it or *where*.
### Todo ### Todo
@ -69,11 +69,11 @@ Todo
Use this when you identify work that belongs to another repo registered in the Use this when you identify work that belongs to another repo registered in the
Custodian State Hub. Custodian State Hub.
### Step 1 — Create a state hub task in the target domain's workstream ### Step 1 — Create a state hub task in the target domain's workplan
```python ```python
create_task( create_task(
workstream_id="<uuid of appropriate workstream in the target domain>", workstream_id="<uuid of appropriate workplan in the target domain>",
title="[repo:<target-slug>] Brief description of the required work", title="[repo:<target-slug>] Brief description of the required work",
priority="medium", # low | medium | high | critical priority="medium", # low | medium | high | critical
description="Full context: why this work is needed and what the expected outcome is" description="Full context: why this work is needed and what the expected outcome is"
@ -133,7 +133,7 @@ whose stored workstation/status label is `active`, with tasks in `wait`,
`todo`, or `progress`. `todo`, or `progress`.
**Ecosystem todos targeting this repo** (Step 1 of orientation) — **Ecosystem todos targeting this repo** (Step 1 of orientation) —
`get_state_summary()` returns all open tasks across all workstreams. The session `get_state_summary()` returns all open tasks across all workplans. The session
protocol filters for tasks with `[repo:<this-slug>]` in their title and surfaces protocol filters for tasks with `[repo:<this-slug>]` in their title and surfaces
them in the orientation brief. them in the orientation brief.

View file

@ -33,7 +33,7 @@ Tasks are sorted by priority (critical → high → medium → low), then by sta
| Priority badge | `critical` / `high` / `medium` / `low` | | Priority badge | `critical` / `high` / `medium` / `low` |
| Status chip | Current task status | | Status chip | Current task status |
| Domain | Source domain slug | | Domain | Source domain slug |
| Workstream | Parent workstream title | | Workplan | Parent workplan title |
| Action note | The `intervention_note` — what the human needs to do | | Action note | The `intervention_note` — what the human needs to do |
| Task detail | Expandable `<details>` with the task title and description (shown when different from the action note) | | Task detail | Expandable `<details>` with the task title and description (shown when different from the action note) |
@ -66,9 +66,9 @@ clear_human_flag(task_id = "<uuid>")
--- ---
## Filtering by workstream ## Filtering by workplan
`list_human_interventions(workstream_id="<uuid>")` via MCP returns only interventions for a specific workstream — useful for scoped reviews in agent sessions. `list_human_interventions(workstream_id="<uuid>")` via MCP returns only interventions for a specific workplan — useful for scoped reviews in agent sessions.
--- ---

View file

@ -12,9 +12,9 @@ blocking decisions, and system-derived next-step suggestions.
## Sections ## Sections
### Open Workstreams by Repository ### Open Workplans by Repository
A horizontal stacked bar chart showing workstreams grouped by domain and then A horizontal stacked bar chart showing workplans grouped by domain and then
by repository. Each bar is broken into four task-status segments: by repository. Each bar is broken into four task-status segments:
| Colour | Segment | | Colour | Segment |
@ -25,11 +25,11 @@ by repository. Each bar is broken into four task-status segments:
| light grey | todo | | light grey | todo |
The left axis shows the `domain / repository` label once per repository group. The left axis shows the `domain / repository` label once per repository group.
The `done/total` count is printed to the right of each bar. Workstreams with no The `done/total` count is printed to the right of each bar. Workplans with no
tasks yet show a grey "— no tasks yet" label. tasks yet show a grey "— no tasks yet" label.
Hovering a bar shows the repository, domain, and backing workplan filename when Hovering a bar shows the repository, domain, and backing workplan filename when
the workstream is file-backed. Clicking a bar or its label opens the workstream the workplan is file-backed. Clicking a bar or its label opens the workplan
drilldown page with the attached task list. drilldown page with the attached task list.
### Contribution & SBOM Health ### Contribution & SBOM Health
@ -48,7 +48,7 @@ Four metric cards:
| Card | Meaning | | Card | Meaning |
|------|---------| |------|---------|
| **Active Workstreams** | Count of active/blocked execution workstreams | | **Active Workplans** | Count of active/blocked execution workplans |
| **Blocking Decisions** | Pending decisions with status `open` or `escalated` — orange border if > 0 | | **Blocking Decisions** | Pending decisions with status `open` or `escalated` — orange border if > 0 |
| **Blocked Tasks** | Click to expand the list with blocking reasons | | **Blocked Tasks** | Click to expand the list with blocking reasons |
| **Events Today** | Progress events created on today's date | | **Events Today** | Progress events created on today's date |
@ -56,8 +56,8 @@ Four metric cards:
### What's next? ### What's next?
System-derived action suggestions from `GET /state/next_steps`. Suggestions are System-derived action suggestions from `GET /state/next_steps`. Suggestions are
generated when a decision is resolved or a workstream dependency is cleared, and generated when a decision is resolved or a workplan dependency is cleared, and
they point to the first open task in the relevant workstream. These are derived they point to the first open task in the relevant workplan. These are derived
on request and never persisted. on request and never persisted.
### Blocking Decisions ### Blocking Decisions

View file

@ -24,7 +24,7 @@ Each event carries:
| `event_type` | string | Free-form label categorising the event (see below) | | `event_type` | string | Free-form label categorising the event (see below) |
| `author` | string | Who created the event — `custodian` for agent-generated events, or a human name | | `author` | string | Who created the event — `custodian` for agent-generated events, or a human name |
| `topic_id` | UUID? | Links the event to a topic (optional) | | `topic_id` | UUID? | Links the event to a topic (optional) |
| `workstream_id` | UUID? | Links to a workstream (optional) | | `workstream_id` | UUID? | Links to a workplan (optional) |
| `task_id` | UUID? | Links to a task (optional) | | `task_id` | UUID? | Links to a task (optional) |
| `decision_id` | UUID? | Links to a decision (optional) | | `decision_id` | UUID? | Links to a decision (optional) |
| `detail` | JSON? | Arbitrary structured data — commits, counts, file paths, metrics, etc. | | `detail` | JSON? | Arbitrary structured data — commits, counts, file paths, metrics, etc. |
@ -38,10 +38,10 @@ These types are used by the State Hub's built-in write operations:
| Type | When emitted | | Type | When emitted |
|---|---| |---|---|
| `workstream_created` | A new workstream was registered | | `workstream_created` | A new workplan was registered |
| `workstream_status_changed` | Workstream moved between canonical lifecycle states | | `workstream_status_changed` | Workplan moved between canonical lifecycle states |
| `workstation_advanced` | Flow-aware movement via `advance_workstation()` succeeded | | `workstation_advanced` | Flow-aware movement via `advance_workstation()` succeeded |
| `task_created` | A new task was added to a workstream | | `task_created` | A new task was added to a workplan |
| `task_status_changed` | Task moved to wait / todo / progress / done / cancel | | `task_status_changed` | Task moved to wait / todo / progress / done / cancel |
| `decision_recorded` | A decision (pending or made) was recorded | | `decision_recorded` | A decision (pending or made) was recorded |
| `decision_resolved` | A pending decision was resolved | | `decision_resolved` | A pending decision was resolved |
@ -77,7 +77,7 @@ Via the MCP server (in a Claude Code session):
add_progress_event( add_progress_event(
summary = "What happened, in one clear sentence", summary = "What happened, in one clear sentence",
event_type = "milestone", // or note, blocker, insight, … event_type = "milestone", // or note, blocker, insight, …
workstream_id = "<uuid>", // link to relevant workstream (optional) workstream_id = "<uuid>", // link to relevant workplan (optional)
topic_id = "<uuid>", // link to relevant topic (optional) topic_id = "<uuid>", // link to relevant topic (optional)
detail = { "key": "value" } // any structured data worth preserving detail = { "key": "value" } // any structured data worth preserving
) )

View file

@ -181,7 +181,7 @@ The skill is **fully self-contained** — it reads and writes only the workplan
file. It does not call the State Hub API or require network access. file. It does not call the State Hub API or require network access.
If the project also integrates with the State Hub (via the MCP tunnel), the If the project also integrates with the State Hub (via the MCP tunnel), the
agent can additionally report progress and mark workstream tasks done through agent can additionally report progress and mark workplan tasks done through
the MCP tools during the loop — but this is optional and independent of the the MCP tools during the loop — but this is optional and independent of the
ralph-workplan lifecycle. ralph-workplan lifecycle.

View file

@ -15,12 +15,12 @@ during and after integration.
The custodian acts as a **coach**: it registers the repo, writes an The custodian acts as a **coach**: it registers the repo, writes an
integration suggestion, and generates a structured set of onboarding tasks. integration suggestion, and generates a structured set of onboarding tasks.
The repo's own Claude agent acts as the **executor**: it reads those tasks, The repo's own Claude agent acts as the **executor**: it reads those tasks,
makes all changes to the repo, and closes out the onboarding workstream. makes all changes to the repo, and closes out the onboarding workplan.
| Role | Responsibility | | Role | Responsibility |
|------|---------------| |------|---------------|
| **Custodian** | Registers the repo, generates `CLAUDE.custodian.md`, creates the onboarding workstream and tasks, monitors integration status via the dashboard | | **Custodian** | Registers the repo, generates `CLAUDE.custodian.md`, creates the onboarding workplan and tasks, monitors integration status via the dashboard |
| **Repo agent** | Integrates `CLAUDE.custodian.md``CLAUDE.md`, writes the first workplan, ingests the SBOM, catalogues EPs/TDs, closes the onboarding workstream | | **Repo agent** | Integrates `CLAUDE.custodian.md``CLAUDE.md`, writes the first workplan, ingests the SBOM, catalogues EPs/TDs, closes the onboarding workplan |
The custodian never writes files into another repo directly. All changes to The custodian never writes files into another repo directly. All changes to
the target repo are made from inside that repo by its own agent. This upholds the target repo are made from inside that repo by its own agent. This upholds
@ -52,7 +52,7 @@ What happens automatically:
2. The domain is validated; the domain's topic ID is resolved 2. The domain is validated; the domain's topic ID is resolved
3. `CLAUDE.custodian.md` is written to the repo root — the integration suggestion 3. `CLAUDE.custodian.md` is written to the repo root — the integration suggestion
4. The repo is registered in the State Hub (`POST /repos/`) 4. The repo is registered in the State Hub (`POST /repos/`)
5. A **Repo Integration** workstream is created in the domain's topic with 4 5. A **Repo Integration** workplan is created in the domain's topic with 4
onboarding tasks onboarding tasks
6. A progress event is logged 6. A progress event is logged
@ -65,14 +65,14 @@ claude
Once Claude starts, run `/init` to trigger the integration. The repo agent Once Claude starts, run `/init` to trigger the integration. The repo agent
reads `CLAUDE.custodian.md`, calls `get_domain_summary("<domain>")`, sees the reads `CLAUDE.custodian.md`, calls `get_domain_summary("<domain>")`, sees the
Repo Integration workstream, and works through the 4 onboarding tasks Repo Integration workplan, and works through the 4 onboarding tasks
autonomously. No human interaction is needed unless the agent has a question. autonomously. No human interaction is needed unless the agent has a question.
### Step 4 — Monitor on the Repos page ### Step 4 — Monitor on the Repos page
The [Repos](/repos) page shows each repo's integration status. An **integrating** The [Repos](/repos) page shows each repo's integration status. An **integrating**
badge appears on repos with an active Repo Integration workstream. The badge badge appears on repos with an active Repo Integration workplan. The badge
clears when the workstream is marked finished. clears when the workplan is marked finished.
--- ---
@ -88,13 +88,13 @@ topic ID, and slug.
The repo agent integrates this content into the existing `CLAUDE.md` (or The repo agent integrates this content into the existing `CLAUDE.md` (or
creates a new one) and deletes the suggestion file. It is not meant to persist. creates a new one) and deletes the suggestion file. It is not meant to persist.
### Repo Integration workstream ### Repo Integration workplan
A workstream titled **Repo Integration: `<repo-slug>`** is created in the A workplan titled **Repo Integration: `<repo-slug>`** is created in the
target domain's topic. It is visible via `get_domain_summary()` at the repo target domain's topic. It is visible via `get_domain_summary()` at the repo
agent's next session start. agent's next session start.
> **ADR-001 note:** This workstream is a DB-first bootstrapping exception. > **ADR-001 note:** This workplan is a DB-first bootstrapping exception.
> The file-first principle does not apply here because the repo has no > The file-first principle does not apply here because the repo has no
> `workplans/` directory yet. Writing the first workplan file is task T2. > `workplans/` directory yet. Writing the first workplan file is task T2.
@ -103,7 +103,7 @@ agent's next session start.
| # | Title | Priority | What it means | | # | Title | Priority | What it means |
|---|-------|----------|---------------| |---|-------|----------|---------------|
| T1 | Integrate `CLAUDE.custodian.md``CLAUDE.md` | high | Merge the suggestion into the existing CLAUDE.md; delete the suggestion file; commit | | T1 | Integrate `CLAUDE.custodian.md``CLAUDE.md` | high | Merge the suggestion into the existing CLAUDE.md; delete the suggestion file; commit |
| T2 | Write first workplan and initialise `workplans/` | high | Create `workplans/` and write the first workplan file per ADR-001; register the workstream in the hub | | T2 | Write first workplan and initialise `workplans/` | high | Create `workplans/` and write the first workplan file per ADR-001; register the workplan in the hub |
| T3 | Ingest SBOM | medium | Run `make ingest-sbom REPO=<slug> SCAN=1 REPO_PATH=<path>` from the state-hub dir | | T3 | Ingest SBOM | medium | Run `make ingest-sbom REPO=<slug> SCAN=1 REPO_PATH=<path>` from the state-hub dir |
| T4 | Register known EPs and TDs | low | Catalogue extension points and technical debt using the MCP tools | | T4 | Register known EPs and TDs | low | Catalogue extension points and technical debt using the MCP tools |
@ -111,17 +111,17 @@ agent's next session start.
## Repo Agent: First Session Protocol ## Repo Agent: First Session Protocol
When `get_domain_summary()` returns a **Repo Integration** workstream, the When `get_domain_summary()` returns a **Repo Integration** workplan, the
repo agent should: repo agent should:
1. Read `CLAUDE.custodian.md` alongside the existing `CLAUDE.md` 1. Read `CLAUDE.custodian.md` alongside the existing `CLAUDE.md`
2. Execute T1 first — merge and delete the suggestion file, commit 2. Execute T1 first — merge and delete the suggestion file, commit
3. Execute T2 — create `workplans/<DOMAIN>-WP-0001-<slug>.md` covering the 3. Execute T2 — create `workplans/<DOMAIN>-WP-0001-<slug>.md` covering the
primary near-term work; register the workstream in the hub via MCP primary near-term work; register the workplan in the hub via MCP
4. Execute T3 — ingest the SBOM so the repo appears green on the Repos page 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 5. Execute T4 — a quick scan for obvious EPs/TDs; defer if nothing obvious
6. Mark each task `done` in the hub 6. Mark each task `done` in the hub
7. Mark the Repo Integration workstream `finished` 7. Mark the Repo Integration workplan `finished`
8. Log a progress event summarising the integration 8. Log a progress event summarising the integration
The agent should resolve each task independently and in order. It does not The agent should resolve each task independently and in order. It does not
@ -132,10 +132,10 @@ merge conflict in CLAUDE.md.
## After Integration ## After Integration
Once the onboarding workstream is closed, the repo participates in the full Once the onboarding workplan is closed, the repo participates in the full
custodian ecosystem: custodian ecosystem:
- **Session start:** `get_domain_summary("<domain>")` shows active workstreams, - **Session start:** `get_domain_summary("<domain>")` shows active workplans,
blocking decisions, and recent progress — the standard orientation blocking decisions, and recent progress — the standard orientation
- **Ecosystem todos:** tasks with `[repo:<slug>]` in their title created by - **Ecosystem todos:** tasks with `[repo:<slug>]` in their title created by
other agents appear in the domain summary and signal cross-repo work other agents appear in the domain summary and signal cross-repo work
@ -165,4 +165,4 @@ suggestion. The repo agent should integrate and delete it.
**Repo already registered (slug conflict)** **Repo already registered (slug conflict)**
The command is idempotent for the repo row. Onboarding tasks are re-created The command is idempotent for the repo row. Onboarding tasks are re-created
only if no active Repo Integration workstream already exists. only if no active Repo Integration workplan already exists.

View file

@ -160,7 +160,7 @@ hand-rolled parsers for comprehensive coverage.
When a compliance gap is identified in a registered repo, the finding is routed When a compliance gap is identified in a registered repo, the finding is routed
as an **ecosystem todo**: a state hub task with `[repo:<slug>]` in the title, as an **ecosystem todo**: a state hub task with `[repo:<slug>]` in the title,
created in the target domain's workstream. The target repo's session protocol created in the target domain's workplan. The target repo's session protocol
surfaces it automatically at next session start. surfaces it automatically at next session start.
See the full standard: [`/docs/inter-repo-communication`](/docs/inter-repo-communication) See the full standard: [`/docs/inter-repo-communication`](/docs/inter-repo-communication)

View file

@ -13,7 +13,7 @@ seconds rather than minutes.
## Why it exists ## Why it exists
Software projects accumulate invisible state. Workstreams stall, decisions go Software projects accumulate invisible state. Workplans stall, decisions go
unresolved, dependency licences drift, integration gaps widen — and none of unresolved, dependency licences drift, integration gaps widen — and none of
this is visible without opening every file in every repository. For a single this is visible without opening every file in every repository. For a single
repo with a single engineer this is manageable. Across six domains, fifteen repo with a single engineer this is manageable. Across six domains, fifteen
@ -36,9 +36,9 @@ indexes and reflects their state.
| Role | Description | | Role | Description |
|---|---| |---|---|
| **Derived Data Store** | All hub data is computed from repo files and records. The hub holds no original information — it can be wiped and rebuilt from scratch at any time without data loss. | | **Derived Data Store** | All hub data is computed from repo files and records. The hub holds no original information — it can be wiped and rebuilt from scratch at any time without data loss. |
| **Read Model** | Provides fast, pre-computed answers to common orientation queries: active workstreams, blocking decisions, DoI compliance tiers, SBOM licence risk, GDPR warnings. | | **Read Model** | Provides fast, pre-computed answers to common orientation queries: active workplans, blocking decisions, DoI compliance tiers, SBOM licence risk, GDPR warnings. |
| **Agent Orchestration Layer** | Exposes an MCP server (Model Context Protocol) so that Claude Code sessions in any registered repository can orient themselves, record progress, resolve decisions, and coordinate with each other — all through a uniform tool interface. | | **Agent Orchestration Layer** | Exposes an MCP server (Model Context Protocol) so that Claude Code sessions in any registered repository can orient themselves, record progress, resolve decisions, and coordinate with each other — all through a uniform tool interface. |
| **Cross-Repo Observatory** | The only place where data from all repositories is visible together. Detects dependencies between workstreams, licence risks that span repos, and integration gaps that no single repo can see about itself. | | **Cross-Repo Observatory** | The only place where data from all repositories is visible together. Detects dependencies between workplans, licence risks that span repos, and integration gaps that no single repo can see about itself. |
### What it is not ### What it is not
@ -82,7 +82,7 @@ Invalidation). The practical consequence:
| `uv.lock`, `package-lock.json`, etc. | SBOM entries + licence risk | `make ingest-sbom REPO=` | | `uv.lock`, `package-lock.json`, etc. | SBOM entries + licence risk | `make ingest-sbom REPO=` |
| `tpsc.yaml` | Third-party service declarations + GDPR warnings | `make ingest-tpsc REPO=` | | `tpsc.yaml` | Third-party service declarations + GDPR warnings | `make ingest-tpsc REPO=` |
| `SCOPE.md` capability blocks | Capability catalog | `make ingest-capabilities REPO=` | | `SCOPE.md` capability blocks | Capability catalog | `make ingest-capabilities REPO=` |
| `workplans/*.md` | Workstream + task status | `statehub fix-consistency` | | `workplans/*.md` | Workplan + task status | `statehub fix-consistency` |
| Repo files + DB records | DoI compliance tier | Fingerprint cache, auto-refreshed on read | | Repo files + DB records | DoI compliance tier | Fingerprint cache, auto-refreshed on read |
--- ---
@ -96,7 +96,7 @@ ecosystem — the coordinator that no individual repo can be.
Every Claude Code session in a registered repository follows the same ritual: Every Claude Code session in a registered repository follows the same ritual:
1. **Orient** — call `get_domain_summary("<slug>")` to load active workstreams, 1. **Orient** — call `get_domain_summary("<slug>")` to load active workplans,
pending tasks, blocking decisions, and suggested next steps for this domain. pending tasks, blocking decisions, and suggested next steps for this domain.
2. **Check inbox** — call `get_messages(to_agent="<name>", unread_only=True)` 2. **Check inbox** — call `get_messages(to_agent="<name>", unread_only=True)`
to receive coordination messages from other agents or from prior sessions. to receive coordination messages from other agents or from prior sessions.
@ -119,8 +119,8 @@ communication:
- A capability request (`request_capability()`) routes to the domain that - A capability request (`request_capability()`) routes to the domain that
advertises the relevant capability in its `SCOPE.md`. The fulfilling agent advertises the relevant capability in its `SCOPE.md`. The fulfilling agent
accepts it, does the work, and marks it complete — unblocking the requesting accepts it, does the work, and marks it complete — unblocking the requesting
workstream automatically. workplan automatically.
- Workstream dependencies (`create_dependency()`) let the hub surface "what - Workplan dependencies (`create_dependency()`) let the hub surface "what
is blocking what" across repos that have never directly communicated. is blocking what" across repos that have never directly communicated.
### Kaizen agents ### Kaizen agents
@ -144,7 +144,7 @@ every repo's CLAUDE.md.
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │ │ PostgreSQL Database │
│ domains managed_repos workstreams tasks decisions │ │ domains managed_repos workplans tasks decisions │
│ sbom_entries tpsc_entries doi_cache capability_catalog │ │ sbom_entries tpsc_entries doi_cache capability_catalog │
│ progress_events agent_messages repo_goals … │ │ progress_events agent_messages repo_goals … │
└─────────┬───────────────────────────────────────────────────┘ └─────────┬───────────────────────────────────────────────────┘
@ -161,7 +161,7 @@ every repo's CLAUDE.md.
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ Observable Framework Dashboard (:3000) │ │ Observable Framework Dashboard (:3000) │
│ Overview Repositories Workstreams Decisions SBOM │ │ Overview Repositories Workplans Decisions SBOM │
│ TPSC Contributions Goals Capabilities … │ │ TPSC Contributions Goals Capabilities … │
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
@ -186,7 +186,7 @@ The hub's schema is organised in concentric layers:
`domains``managed_repos``repo_goals``domain_goals` `domains``managed_repos``repo_goals``domain_goals`
**Work tracking (active):** **Work tracking (active):**
`workstreams` → `tasks``workstream_dependencies` `workplans` → `tasks``workstream_dependencies`
**Decision log (append-only):** **Decision log (append-only):**
`decisions` · `progress_events` · `agent_messages` `decisions` · `progress_events` · `agent_messages`

View file

@ -4,7 +4,7 @@ title: Tasks — Reference
# Tasks — Reference # Tasks — Reference
The Tasks page shows all tasks across every workstream and domain, with live The Tasks page shows all tasks across every workplan and domain, with live
filtering, a workstation distribution chart, and a waiting-tasks highlight filtering, a workstation distribution chart, and a waiting-tasks highlight
section. section.
@ -70,7 +70,7 @@ Shows cards for every task currently in the `wait` workstation within the
active filter. Each card displays: active filter. Each card displays:
- Priority badge and status - Priority badge and status
- Domain and workstream context - Domain and workplan context
- Task title - Task title
- Wait reason (amber background) - Wait reason (amber background)

View file

@ -18,7 +18,7 @@ boundary rule and routing workflows.
### Internal ### Internal
Open tasks (`wait`, `todo`, `progress`) in **custodian domain workstreams** Open tasks (`wait`, `todo`, `progress`) in **custodian domain workplans**
whose title does not contain a `[repo:]` routing prefix. whose title does not contain a `[repo:]` routing prefix.
These are tasks this agent is directly responsible for and can address within These are tasks this agent is directly responsible for and can address within
@ -26,7 +26,7 @@ the current repo.
### Ecosystem (inbound) ### Ecosystem (inbound)
Tasks from **any workstream** whose title contains `[repo:the-custodian]`. Tasks from **any workplan** whose title contains `[repo:the-custodian]`.
These tasks were created by agents in other repos to route work to the custodian. These tasks were created by agents in other repos to route work to the custodian.
When a task with this prefix appears, the session protocol picks it up, the When a task with this prefix appears, the session protocol picks it up, the

View file

@ -1,10 +1,10 @@
--- ---
title: Workstream Health Index — Reference title: Workplan Health Index — Reference
--- ---
# Workstream Health Index (WHI) # Workplan Health Index (WHI)
The **Workstream Health Index** is a composite score in the range [0, 1] that measures how well the workstream network is structured for parallel execution and stable progress. It is displayed as a live KPI card in the right margin of the Workstreams page and recomputes on every poll (every 15 seconds). The **Workplan Health Index** is a composite score in the range [0, 1] that measures how well the workplan network is structured for parallel execution and stable progress. It is displayed as a live KPI card in the right margin of the Workplans page and recomputes on every poll (every 15 seconds).
**1.0 = ideal independence · 0.0 = severe systemic dysfunction** **1.0 = ideal independence · 0.0 = severe systemic dysfunction**
@ -25,14 +25,14 @@ The **Workstream Health Index** is a composite score in the range [0, 1] that me
### DD — Dependency Density ### DD — Dependency Density
``` ```
DD = total dependency edges / (active + blocked workstreams) DD = total dependency edges / (active + blocked workplans)
``` ```
Measures structural coupling. Low DD means independent, parallelizable work. Completed and archived workstreams are excluded — they no longer constrain progress. Measures structural coupling. Low DD means independent, parallelizable work. Completed and archived workplans are excluded — they no longer constrain progress.
| DD | Warning | | DD | Warning |
|---|---| |---|---|
| > 1.0 | 🔴 red — more than one dependency per workstream on average | | > 1.0 | 🔴 red — more than one dependency per workplan on average |
| 0.5 1.0 | 🟠 orange | | 0.5 1.0 | 🟠 orange |
| ≤ 0.5 | ok | | ≤ 0.5 | ok |
@ -41,7 +41,7 @@ Measures structural coupling. Low DD means independent, parallelizable work. Com
### BR — Blocked Ratio ### BR — Blocked Ratio
``` ```
BR = blocked workstreams / (active + blocked workstreams) BR = blocked workplans / (active + blocked workplans)
``` ```
Measures immediate operational impact. BR ≈ 0 means flow is unobstructed. Measures immediate operational impact. BR ≈ 0 means flow is unobstructed.
@ -57,7 +57,7 @@ Measures immediate operational impact. BR ≈ 0 means flow is unobstructed.
### SPR — Single-Point Risk ### SPR — Single-Point Risk
``` ```
SPR = max dependents on one incomplete workstream / (active + blocked) SPR = max dependents on one incomplete workplan / (active + blocked)
``` ```
Detects concentration of blocking power. High SPR means one delay propagates widely — a structural SPOF. Detects concentration of blocking power. High SPR means one delay propagates widely — a structural SPOF.
@ -73,12 +73,12 @@ Detects concentration of blocking power. High SPR means one delay propagates wid
### PEP — Parallel Execution Potential ### PEP — Parallel Execution Potential
``` ```
PEP = ready or active workstreams with all deps finished / (ready + active + blocked) PEP = ready or active workplans with all deps finished / (ready + active + blocked)
``` ```
Estimates how much work can proceed right now. A workstream is eligible if its Estimates how much work can proceed right now. A workplan is eligible if its
stored workstation label is `ready` or `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 unmet dependency assertion; practically, every workplan it depends on has
reached `finished` or `archived`. reached `finished` or `archived`.
| PEP | Warning | | PEP | Warning |
@ -134,11 +134,11 @@ Result is clamped to [0, 1].
## Domain breakdown ## Domain breakdown
The card also shows a per-domain WHI computed using **intra-domain workstreams and intra-domain edges only**. This measures each domain's internal autonomy — how well its workstreams are decomposed relative to each other, independent of cross-domain dependencies. The card also shows a per-domain WHI computed using **intra-domain workplans and intra-domain edges only**. This measures each domain's internal autonomy — how well its workplans are decomposed relative to each other, independent of cross-domain dependencies.
A domain with WHI = 100% is fully self-contained and parallelizable internally. Its global contribution to the program-level WHI may still be reduced by cross-domain dependencies (captured in CDDR). A domain with WHI = 100% is fully self-contained and parallelizable internally. Its global contribution to the program-level WHI may still be reduced by cross-domain dependencies (captured in CDDR).
The domain breakdown is shown when at least two domains have active workstreams. The domain breakdown is shown when at least two domains have active workplans.
--- ---
@ -146,10 +146,10 @@ The domain breakdown is shown when at least two domains have active workstreams.
| Symptom | Action | | Symptom | Action |
|---|---| |---|---|
| High DD | Decompose tightly coupled workstreams; remove unnecessary dependencies | | High DD | Decompose tightly coupled workplans; remove unnecessary dependencies |
| High BR | Unblock workstreams — resolve the blocking condition, or mark dependency as finished if done | | High BR | Unblock workplans — resolve the blocking condition, or mark dependency as finished if done |
| High SPR | Split the bottleneck workstream into independent deliverables | | High SPR | Split the bottleneck workplan into independent deliverables |
| Low PEP | Complete prerequisite workstreams or re-sequence work | | Low PEP | Complete prerequisite workplans or re-sequence work |
| High CDDR | Refactor cross-domain dependencies into shared contracts or invert the dependency | | High CDDR | Refactor cross-domain dependencies into shared contracts or invert the dependency |
| CPI = 1 | Find and break the cycle — identify which dependency edge is incorrect and remove it | | CPI = 1 | Find and break the cycle — identify which dependency edge is incorrect and remove it |

View file

@ -1,10 +1,10 @@
# Workstream Health Index (WHI) # Workplan Health Index (WHI)
## Introduction & Requirements Specification ## Introduction & Requirements Specification
**Status:** Draft **Status:** Draft
**Purpose:** Define a quantitative KPI for structural health, coupling, and flow efficiency of workstreams **Purpose:** Define a quantitative KPI for structural health, coupling, and flow efficiency of workplans
**Scope:** Program-level coordination across domains and within domains **Scope:** Program-level coordination across domains and within domains
**Primary Audience:** Project leads, system architects, program management, AI orchestration agents **Primary Audience:** Project leads, system architects, program management, AI orchestration agents
@ -12,7 +12,7 @@
## 1. Problem Statement ## 1. Problem Statement
Modern complex initiatives consist of multiple concurrent workstreams distributed across teams and domains. Ideally, workstreams should be: Modern complex initiatives consist of multiple concurrent workplans distributed across teams and domains. Ideally, workplans should be:
* Independently executable * Independently executable
* Minimally coupled * Minimally coupled
@ -46,9 +46,9 @@ Therefore, a dedicated metric is required to assess:
## 2. Conceptual Model ## 2. Conceptual Model
Workstreams form a **directed dependency graph**: Workplans form a **directed dependency graph**:
* Nodes = workstreams * Nodes = workplans
* Edges = prerequisite relationships * Edges = prerequisite relationships
* Status = operational state * Status = operational state
* Domains = logical grouping * Domains = logical grouping
@ -64,9 +64,9 @@ Health is determined by:
--- ---
## 3. Definition: Workstream Health Index (WHI) ## 3. Definition: Workplan Health Index (WHI)
The **Workstream Health Index (WHI)** is a composite KPI representing the overall coordination efficiency and structural soundness of the workstream network. The **Workplan Health Index (WHI)** is a composite KPI representing the overall coordination efficiency and structural soundness of the workplan network.
WHI is normalized to a value in the range: WHI is normalized to a value in the range:
@ -98,7 +98,7 @@ WHI aggregates the following primary indicators.
**Purpose:** Measure structural coupling introduced during planning. **Purpose:** Measure structural coupling introduced during planning.
[ [
DD = \frac{\text{Number of dependency edges}}{\text{Number of active + blocked workstreams}} DD = \frac{\text{Number of dependency edges}}{\text{Number of active + blocked workplans}}
] ]
Interpretation: Interpretation:
@ -115,7 +115,7 @@ Completed and archived streams are excluded because they no longer constrain pro
**Purpose:** Measure immediate operational impact of dependencies. **Purpose:** Measure immediate operational impact of dependencies.
[ [
BR = \frac{\text{Blocked workstreams}}{\text{Active + Blocked workstreams}} BR = \frac{\text{Blocked workplans}}{\text{Active + Blocked workplans}}
] ]
Interpretation: Interpretation:
@ -130,7 +130,7 @@ Interpretation:
**Purpose:** Detect concentration of blocking power. **Purpose:** Detect concentration of blocking power.
[ [
SPR = \frac{\text{Max number of dependents on one incomplete workstream}}{\text{Active + Blocked}} SPR = \frac{\text{Max number of dependents on one incomplete workplan}}{\text{Active + Blocked}}
] ]
High SPR indicates fragile structure where one delay propagates widely. High SPR indicates fragile structure where one delay propagates widely.
@ -141,13 +141,13 @@ High SPR indicates fragile structure where one delay propagates widely.
**Purpose:** Estimate how much work can proceed immediately. **Purpose:** Estimate how much work can proceed immediately.
A workstream is eligible if: A workplan is eligible if:
* Status = ready or active * Status = ready or active
* All dependencies are finished or archived * All dependencies are finished or archived
[ [
PEP = \frac{\text{Eligible ready or active workstreams}}{\text{Ready + Active + Blocked}} PEP = \frac{\text{Eligible ready or active workplans}}{\text{Ready + Active + Blocked}}
] ]
--- ---
@ -214,7 +214,7 @@ Recommended:
DD_{critical} = 1.0 DD_{critical} = 1.0
] ]
Meaning: one dependency per workstream is considered heavily coupled. Meaning: one dependency per workplan is considered heavily coupled.
--- ---
@ -224,7 +224,7 @@ WHI must be computed at three levels:
### 6.1 Intra-Domain WHI ### 6.1 Intra-Domain WHI
Using only workstreams and dependencies within the domain. Using only workplans and dependencies within the domain.
Purpose: Purpose:
@ -254,7 +254,7 @@ Computed on the full graph.
### 🟢 GREEN — Healthy Structure ### 🟢 GREEN — Healthy Structure
**Condition:** Workstreams are largely independent and flow is stable. **Condition:** Workplans are largely independent and flow is stable.
Recommended thresholds: Recommended thresholds:
@ -370,7 +370,7 @@ The metric system is designed to be:
## 11. Summary ## 11. Summary
The Workstream Health Index provides a quantitative measure of how effectively an organization structures work for parallel execution and stable progress. The Workplan Health Index provides a quantitative measure of how effectively an organization structures work for parallel execution and stable progress.
It captures both: It captures both:

View file

@ -1,10 +1,10 @@
--- ---
title: Workstream Lifecycle - Reference title: Workplan Lifecycle - Reference
--- ---
# Workstream Lifecycle - Reference # Workplan Lifecycle - Reference
A workstream is an information object that occupies a named lifecycle state. A workplan is an information object that occupies a named lifecycle state.
The stored `status` field keeps that state, while the task-flow engine derives The stored `status` field keeps that state, while the task-flow engine derives
which other states are reachable and which exit assertions are blocking which other states are reachable and which exit assertions are blocking
movement. Dashboard health filters such as `needs_review` and `stalled` are movement. Dashboard health filters such as `needs_review` and `stalled` are
@ -43,7 +43,7 @@ backlog -> proposed -> ready -> active -> finished -> archived
| **stalled** | Task counts + timestamp | Work started, but there has been no meaningful progress after the threshold | | **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 `needs_review` and `stalled` can appear beside lifecycle states. They should
not be written into workplan frontmatter or directly into the workstream not be written into workplan frontmatter or directly into the workplan
`status` field. `status` field.
--- ---
@ -70,8 +70,8 @@ behind explicit tooling, not done silently.
## Flow Operations ## Flow Operations
```text ```text
get_flow_state(entity_type="workstream", entity_id="<uuid>") get_flow_state(entity_type="workplan", entity_id="<uuid>")
advance_workstation(entity_type="workstream", entity_id="<uuid>", target_workstation="finished") advance_workstation(entity_type="workplan", entity_id="<uuid>", target_workstation="finished")
``` ```
Direct status patching still exists for bootstrap and compatibility work: Direct status patching still exists for bootstrap and compatibility work:
@ -82,5 +82,5 @@ curl -X PATCH http://127.0.0.1:8000/workplans/<uuid>/ \
-d '{"status": "finished"}' -d '{"status": "finished"}'
``` ```
Workstreams are never hard-deleted. Use `finished` for completed Workplans are never hard-deleted. Use `finished` for completed
implementation and `archived` for historical records outside normal planning. implementation and `archived` for historical records outside normal planning.

View file

@ -1,25 +1,25 @@
--- ---
title: Workstreams — Reference title: Workplans — Reference
--- ---
# Workstreams — Reference # Workplans — Reference
A workstream is a bounded unit of work within a topic. It carries a stored A workplan is a bounded unit of work within a topic. It carries a stored
workstation label in the `status` field, an optional owner and due date, and workstation label in the `status` field, an optional owner and due date, and
belongs to exactly one project domain. The Workstreams page gives you a belongs to exactly one project domain. The Workplans page gives you a
filtered, visual overview of active work, derived blocked state, and the filtered, visual overview of active work, derived blocked state, and the
dependency graph between workstreams. dependency graph between workplans.
The [Daily WSJF Triage](/wsjf-triage) page is a companion review surface for The [Daily WSJF Triage](/wsjf-triage) page is a companion review surface for
activity-core's daily recommendations. It links recommendation candidates back activity-core's daily recommendations. It links recommendation candidates back
to workstream detail pages when the candidate can be resolved through the to workplan detail pages when the candidate can be resolved through the
workplan index. workplan index.
--- ---
## Workstation Distribution chart ## Workstation Distribution chart
A horizontal bar chart showing the count of workstreams in each stored A horizontal bar chart showing the count of workplans in each stored
workstation/status label for the current filter selection. Updates immediately workstation/status label for the current filter selection. Updates immediately
as filters change. as filters change.
@ -33,7 +33,7 @@ as filters change.
| **finished** | Implementation is complete | | **finished** | Implementation is complete |
| **archived** | Closed historical record | | **archived** | Closed historical record |
See [Workstream Lifecycle](/docs/workstream-lifecycle) for the full task-flow See [Workplan Lifecycle](/docs/workstream-lifecycle) for the full task-flow
model including derived health labels (`needs_review`, `stalled`) and model including derived health labels (`needs_review`, `stalled`) and
assertion-based blocking. assertion-based blocking.
@ -43,8 +43,8 @@ assertion-based blocking.
| Filter | Effect | | Filter | Effect |
|---|---| |---|---|
| **Domain** | Multi-select — show only workstreams from selected domains | | **Domain** | Multi-select — show only workplans from selected domains |
| **Status** | Multi-select — show only workstreams with selected workstation labels | | **Status** | Multi-select — show only workplans with selected workstation labels |
| **Owner** | Text substring match on the owner field (case-insensitive) | | **Owner** | Text substring match on the owner field (case-insensitive) |
Leaving a filter empty means "show all". All three filters combine with AND logic. Filters persist across polls — selections are not lost when the page refreshes live data. Leaving a filter empty means "show all". All three filters combine with AND logic. Filters persist across polls — selections are not lost when the page refreshes live data.
@ -53,11 +53,11 @@ The six domains are: `custodian`, `railiance`, `markitect`, `coulomb_social`, `p
--- ---
## All Workstreams table ## All Workplans table
| Column | Source | | Column | Source |
|---|---| |---|---|
| Title | Workstream title | | Title | Workplan title |
| Domain | Derived from the parent topic | | Domain | Derived from the parent topic |
| Status | Current stored workstation/status label | | Status | Current stored workstation/status label |
| Owner | Assigned person (or `—` if unset) | | Owner | Assigned person (or `—` if unset) |
@ -70,11 +70,11 @@ Up to 20 rows displayed; paginate for more.
## Dependencies ## Dependencies
The Dependencies section shows workstreams that have at least one `depends_on` or `blocks` relationship. Each card displays: The Dependencies section shows workplans that have at least one `depends_on` or `blocks` relationship. Each card displays:
- **Workstream title** and current status badge - **Workplan title** and current status badge
- **↳ depends on** — workstreams that must complete before this one can proceed - **↳ depends on** — workplans that must complete before this one can proceed
- **⊳ blocks** — workstreams that are waiting on this one - **⊳ blocks** — workplans that are waiting on this one
Dependencies are created via the MCP server: Dependencies are created via the MCP server:
@ -90,7 +90,7 @@ If no dependency edges exist for the current filter, the section shows an empty-
--- ---
## Creating workstreams ## Creating workplans
Via MCP: Via MCP:
@ -115,11 +115,11 @@ curl -X POST http://127.0.0.1:8000/workplans/ \
--- ---
## Advancing a workstream ## Advancing a workplan
``` ```
get_flow_state(entity_type="workstream", entity_id="<uuid>") get_flow_state(entity_type="workplan", entity_id="<uuid>")
advance_workstation(entity_type="workstream", entity_id="<uuid>", target_workstation="finished") advance_workstation(entity_type="workplan", entity_id="<uuid>", target_workstation="finished")
``` ```
Movement is flow-aware: the task-flow engine evaluates the target Movement is flow-aware: the task-flow engine evaluates the target
@ -130,4 +130,4 @@ and compatibility work, but normal lifecycle movement should prefer
--- ---
*Workstreams are never hard-deleted — use `archived` to close them without losing history.* *Workplans are never hard-deleted — use `archived` to close them without losing history.*

View file

@ -17,7 +17,7 @@ GET /progress/?event_type=daily_triage&limit=14
Each event carries the report under `detail.report`, with a summary and a list Each event carries the report under `detail.report`, with a summary and a list
of recommendations. Candidate values are resolved through of recommendations. Candidate values are resolved through
`/workplans/index` so file-backed workplans can link to their `/workplans/index` so file-backed workplans can link to their
workstream detail pages. workplan detail pages.
## How to read recommendations ## How to read recommendations
@ -50,7 +50,7 @@ Confidence labels mean:
## Pattern view ## Pattern view
The pattern table aggregates recommendations in the loaded 14-day window. It is The pattern table aggregates recommendations in the loaded 14-day window. It is
useful for spotting recurring human gates, stale revisit signals, or workstreams useful for spotting recurring human gates, stale revisit signals, or workplans
that keep surfacing as the next best piece of work. that keep surfacing as the next best piece of work.
No write controls live on this page. It is intentionally a review page so the No write controls live on this page. It is intentionally a review page so the

View file

@ -148,10 +148,10 @@ if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/overview");
display(html`<div class="warning" style="display:${summary.error ? '' : 'none'}">⚠️ ${summary.error ?? ''}</div>`); display(html`<div class="warning" style="display:${summary.error ? '' : 'none'}">⚠️ ${summary.error ?? ''}</div>`);
``` ```
## Workstreams by Repository ## Workplans by Repository
```js ```js
// ── Filter workstreams by selected mode ─────────────────────────────────────── // ── Filter workplans by selected mode ───────────────────────────────────────
// Lifecycle modes match stored canonical status values. // Lifecycle modes match stored canonical status values.
// Health modes are derived labels; they are not stored lifecycle states. // Health modes are derived labels; they are not stored lifecycle states.
// Time modes filter by updated_at / created_at. // Time modes filter by updated_at / created_at.
@ -251,8 +251,8 @@ function _setChartMode(value) {
```js ```js
const _modeSelect = html`<select const _modeSelect = html`<select
class="ws-mode-select" class="ws-mode-select"
aria-label="Workstream chart mode with matching workstream counts" aria-label="Workplan chart mode with matching workplan counts"
title="Choose which workstreams to show; counts are matching workstreams" title="Choose which workplans to show; counts are matching workplans"
> >
${_MODE_GROUPS.map(group => html`<optgroup label=${group.label}> ${_MODE_GROUPS.map(group => html`<optgroup label=${group.label}>
${group.options.map(([value, label]) => html`<option value=${value}>${label} (${_workstreamsForMode(value, wsAll).length})</option>`)} ${group.options.map(([value, label]) => html`<option value=${value}>${label} (${_workstreamsForMode(value, wsAll).length})</option>`)}
@ -274,7 +274,7 @@ import * as Plot from "npm:@observablehq/plot";
const _chartModeValue = _modeValue(_chartModeState); const _chartModeValue = _modeValue(_chartModeState);
const _chartWsFiltered = _workstreamsForMode(_chartModeValue, wsAll); const _chartWsFiltered = _workstreamsForMode(_chartModeValue, wsAll);
// Sort by domain, then repository, then most recently updated workstream. // Sort by domain, then repository, then most recently updated workplan.
// The axis labels show each domain/repo group once. // The axis labels show each domain/repo group once.
const chartWs = [..._chartWsFiltered].sort((a, b) => { const chartWs = [..._chartWsFiltered].sort((a, b) => {
const domainCompare = (a.domain ?? "").localeCompare(b.domain ?? ""); const domainCompare = (a.domain ?? "").localeCompare(b.domain ?? "");
@ -289,7 +289,7 @@ const chartWs = [..._chartWsFiltered].sort((a, b) => {
const _isTimeBased = !_STATUS_MODES.has(_chartModeValue) && !_HEALTH_MODES.has(_chartModeValue); const _isTimeBased = !_STATUS_MODES.has(_chartModeValue) && !_HEALTH_MODES.has(_chartModeValue);
function _wsWeight(s) { return (isClosedWorkstream(s) || normalizeWorkstreamStatus(s) === "blocked") ? "bold" : "normal"; } function _wsWeight(s) { return (isClosedWorkstream(s) || normalizeWorkstreamStatus(s) === "blocked") ? "bold" : "normal"; }
// ── y-axis: domain/repo label for first workstream per repository only ──────── // ── y-axis: domain/repo label for first workplan per repository only ────────
const _yLabels = {}; const _yLabels = {};
const _seen = new Set(); const _seen = new Set();
for (const w of chartWs) { for (const w of chartWs) {
@ -321,24 +321,24 @@ function _wsTitle(d) {
// ── Render ──────────────────────────────────────────────────────────────────── // ── Render ────────────────────────────────────────────────────────────────────
if (chartWs.length === 0) { if (chartWs.length === 0) {
const _emptyMsg = { const _emptyMsg = {
proposed: "No proposed workstreams.", proposed: "No proposed workplans.",
ready: "No ready workstreams.", ready: "No ready workplans.",
active: "No active workstreams.", active: "No active workplans.",
blocked: "No blocked workstreams.", blocked: "No blocked workplans.",
backlog: "No backlog workstreams.", backlog: "No backlog workplans.",
finished: "No finished workstreams.", finished: "No finished workplans.",
archived: "No archived workstreams.", archived: "No archived workplans.",
needs_review: "No ready workstreams need review.", needs_review: "No ready workplans need review.",
stalled: "No stalled workstreams — everything is moving.", stalled: "No stalled workplans — everything is moving.",
"1h": "No workstreams changed in the last hour.", "1h": "No workplans changed in the last hour.",
"1d": "No workstreams changed in the last 24 hours.", "1d": "No workplans changed in the last 24 hours.",
"7d": "No workstreams changed in the last 7 days.", "7d": "No workplans changed in the last 7 days.",
"30d": "No workstreams changed in the last 30 days.", "30d": "No workplans changed in the last 30 days.",
today: "No workstreams changed today.", today: "No workplans changed today.",
week: "No workstreams changed this week.", week: "No workplans changed this week.",
month: "No workstreams changed this month.", month: "No workplans changed this month.",
}; };
display(html`<p style="color:gray">${_emptyMsg[_chartModeValue] ?? "No workstreams."}</p>`); display(html`<p style="color:gray">${_emptyMsg[_chartModeValue] ?? "No workplans."}</p>`);
} else { } else {
display(Plot.plot({ display(Plot.plot({
y: { y: {
@ -445,7 +445,7 @@ const decCount = (decisions.open ?? 0) + (decisions.escalated ?? 0);
const statusEl = html`<div> const statusEl = html`<div>
<div class="grid grid-cols-4" style="gap:1rem;margin-bottom:0.75rem"> <div class="grid grid-cols-4" style="gap:1rem;margin-bottom:0.75rem">
<a class="card card-link" href="./workstreams"> <a class="card card-link" href="./workstreams">
<h3>Active Workstreams</h3> <h3>Active Workplans</h3>
<p class="big-num">${ws.active ?? 0}</p> <p class="big-num">${ws.active ?? 0}</p>
<small>${ws.blocked ?? 0} blocked</small> <small>${ws.blocked ?? 0} blocked</small>
</a> </a>
@ -510,7 +510,7 @@ const typeBadgeClass = {
}; };
if (nextSteps.length === 0) { if (nextSteps.length === 0) {
display(html`<p class="ns-empty">No actionable suggestions right now — all open workstreams are making progress or waiting on decisions.</p>`); display(html`<p class="ns-empty">No actionable suggestions right now — all open workplans are making progress or waiting on decisions.</p>`);
} else { } else {
display(html`<div class="ns-grid">${nextSteps.map(s => html` display(html`<div class="ns-grid">${nextSteps.map(s => html`
<div class="ns-card"> <div class="ns-card">
@ -543,7 +543,7 @@ if (regs.length === 0) {
``` ```
```js ```js
// Registered domains with no workstreams yet — show a getting-started hint // Registered domains with no workplans yet — show a getting-started hint
const regs = pageState.milestones ?? []; const regs = pageState.milestones ?? [];
const registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean)); const registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean));
const emptyRegistered = (summary.topics ?? []).filter(t => const emptyRegistered = (summary.topics ?? []).filter(t =>
@ -553,9 +553,9 @@ const emptyRegistered = (summary.topics ?? []).filter(t =>
if (emptyRegistered.length > 0) { if (emptyRegistered.length > 0) {
display(html`<div class="hint-box"> display(html`<div class="hint-box">
<strong>💡 Getting started</strong> <strong>💡 Getting started</strong>
<p>These registered projects have no workstreams yet:</p> <p>These registered projects have no workplans yet:</p>
<ul>${emptyRegistered.map(t => html`<li> <ul>${emptyRegistered.map(t => html`<li>
<strong>${t.domain_slug}</strong> — open repo in Claude Code and say <em>"Hi!"</em> to kick off first session, or run <code>custodian create-workstream --domain ${t.domain_slug} --title "My first workstream"</code> manually <strong>${t.domain_slug}</strong> — open repo in Claude Code and say <em>"Hi!"</em> to kick off first session, or create a workplan file under <code>workplans/</code> and run <code>statehub fix-consistency</code>
</li>`)}</ul> </li>`)}</ul>
</div>`); </div>`);
} }

View file

@ -7,7 +7,7 @@ import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components
``` ```
```js ```js
// Live poll: all tasks (filtered client-side) + workstreams + topics // Live poll: all tasks (filtered client-side) + workplans + topics
const interventionState = (async function*() { const interventionState = (async function*() {
let failures = 0; let failures = 0;
while (true) { while (true) {

View file

@ -1,5 +1,5 @@
--- ---
title: Workstream Definition of Done title: Workplan Definition of Done
--- ---
```js ```js

View file

@ -29,15 +29,15 @@ convention used in the Custodian State Hub.
| [Extension Points](/docs/extensions) | EP types, statuses, priorities, registration | | [Extension Points](/docs/extensions) | EP types, statuses, priorities, registration |
| [Inter-Repo Communication](/docs/inter-repo-communication) | Boundary rule, Internal/Ecosystem/Third-party taxonomy, routing workflows | | [Inter-Repo Communication](/docs/inter-repo-communication) | Boundary rule, Internal/Ecosystem/Third-party taxonomy, routing workflows |
| [Live Data](/docs/live-data) | Poll interval, live indicator states, offline recovery | | [Live Data](/docs/live-data) | Poll interval, live indicator states, offline recovery |
| [Overview](/docs/overview) | State summary sections, workstream chart, blocking decisions, next steps | | [Overview](/docs/overview) | State summary sections, workplan chart, blocking decisions, next steps |
| [Progress Log](/docs/progress-log) | Event types, append-only policy, session protocol | | [Progress Log](/docs/progress-log) | Event types, append-only policy, session protocol |
| [Repos](/docs/repos) | Repo registry, SBOM coverage map, ingestion commands | | [Repos](/docs/repos) | Repo registry, SBOM coverage map, ingestion commands |
| [SBOM](/docs/sbom) | Lockfile ingestion, licence report, copyleft detection | | [SBOM](/docs/sbom) | Lockfile ingestion, licence report, copyleft detection |
| [Tasks](/docs/tasks) | Task statuses, priorities, filter bar, status distribution chart | | [Tasks](/docs/tasks) | Task statuses, priorities, filter bar, status distribution chart |
| [Technical Debt](/docs/debt) | Debt types, severities, statuses, registration | | [Technical Debt](/docs/debt) | Debt types, severities, statuses, registration |
| [Todo](/docs/todo) | Internal/Ecosystem/Third-party classification, data sources | | [Todo](/docs/todo) | Internal/Ecosystem/Third-party classification, data sources |
| [Workstream Health](/docs/workstream-health-index) | WHI formula, six base metrics, per-domain breakdown | | [Workplan Health](/docs/workstream-health-index) | WHI formula, six base metrics, per-domain breakdown |
| [Workstreams](/docs/workstreams) | Workstream statuses, dependency edges, WHI KPI card | | [Workplans](/docs/workstreams) | Workplan statuses, dependency edges, WHI KPI card |
| [WSJF Triage](/docs/wsjf-triage) | Daily triage reports, action vocabulary, advisory review workflow | | [WSJF Triage](/docs/wsjf-triage) | Daily triage reports, action vocabulary, advisory review workflow |
--- ---
@ -73,7 +73,7 @@ Currently implemented record types:
|-------------|-------------|-----------------| |-------------|-------------|-----------------|
| `token-events` | `/token-events/<id>` | `GET /token-events/{id}` | | `token-events` | `/token-events/<id>` | `GET /token-events/{id}` |
Further record types (repos, workstreams, tasks) will be added in subsequent workplans. Further record types (repos, workplans, tasks) will be added in subsequent workplans.
--- ---

View file

@ -39,7 +39,7 @@ const domains = _domains ?? [];
const sbom = _sbom ?? []; const sbom = _sbom ?? [];
const eps = _eps ?? []; const eps = _eps ?? [];
const tds = _tds ?? []; const tds = _tds ?? [];
const workstreams = _workstreams ?? []; const workplans = _workstreams ?? [];
const doi = doiData; // reactive — updates when lazy fetch completes const doi = doiData; // reactive — updates when lazy fetch completes
// DoI lookups // DoI lookups
@ -53,9 +53,9 @@ const DOI_TIER_LABEL = {none: "None", core: "Core", standard: "Standard", full:
const domainById = Object.fromEntries(domains.map(d => [d.id, d])); const domainById = Object.fromEntries(domains.map(d => [d.id, d]));
const domainBySlug = Object.fromEntries(domains.map(d => [d.slug, d])); const domainBySlug = Object.fromEntries(domains.map(d => [d.slug, d]));
// Active "repo-integration-{slug}" workstreams — signals onboarding in progress // Active "repo-integration-{slug}" workplans — signals onboarding in progress
const integratingBySlug = Object.fromEntries( const integratingBySlug = Object.fromEntries(
workstreams workplans
.filter(w => w.status === "active" && w.slug?.startsWith("repo-integration-")) .filter(w => w.status === "active" && w.slug?.startsWith("repo-integration-"))
.map(w => [w.slug.replace("repo-integration-", ""), w]) .map(w => [w.slug.replace("repo-integration-", ""), w])
); );
@ -83,7 +83,7 @@ for (const td of tds) {
tdByDomain[td.domain] = (tdByDomain[td.domain] ?? 0) + 1; tdByDomain[td.domain] = (tdByDomain[td.domain] ?? 0) + 1;
} }
} }
// Contributions: try to map via workstream → topic → domain (not available here; skip for now) // Contributions: try to map via workplan → topic → domain (not available here; skip for now)
// Use domain slug from contributions' related_workstream if available — fallback: count by type only // Use domain slug from contributions' related_workstream if available — fallback: count by type only
// Build enriched repo rows // Build enriched repo rows

View file

@ -224,7 +224,7 @@ display(buildEntityTable(
{label: "Priority", key: "priority"}, {label: "Priority", key: "priority"},
{label: "Title", key: "title", cls: "et-title-col et-title-cell"}, {label: "Title", key: "title", cls: "et-title-col et-title-cell"},
{label: "Domain", key: "domain"}, {label: "Domain", key: "domain"},
{label: "Workstream", key: "workstream_title", cls: "et-ws-col et-ws-cell"}, {label: "Workplan", key: "workstream_title", cls: "et-ws-col et-ws-cell"},
{label: "Assignee", render: t => t.assignee ?? "—"}, {label: "Assignee", render: t => t.assignee ?? "—"},
{label: "Due", render: t => t.due_date ?? "—"}, {label: "Due", render: t => t.due_date ?? "—"},
], ],

View file

@ -8,7 +8,7 @@ const THIS_REPO = "the-custodian";
``` ```
```js ```js
// Live poll: tasks + workstreams + topics + contributions // Live poll: tasks + workplans + topics + contributions
const todoState = (async function*() { const todoState = (async function*() {
let failures = 0; let failures = 0;
while (true) { while (true) {
@ -136,7 +136,7 @@ if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/todo"); }
## Internal ## Internal
Work fully addressable within this repo. Open tasks in custodian workstreams Work fully addressable within this repo. Open tasks in custodian workplans
without a cross-repo routing prefix. without a cross-repo routing prefix.
```js ```js

View file

@ -174,13 +174,13 @@ display(html`<div style="display:grid;grid-template-columns:repeat(auto-fit,minm
const sorted = sortRows(aggregate.by_workstream ?? [], sortOrder); const sorted = sortRows(aggregate.by_workstream ?? [], sortOrder);
const rows = sorted.slice(0, maxResults); const rows = sorted.slice(0, maxResults);
if (rows.length === 0) { if (rows.length === 0) {
display(html`<p style="color:var(--theme-foreground-muted)">No workstream data yet.</p>`); display(html`<p style="color:var(--theme-foreground-muted)">No workplan data yet.</p>`);
} else { } else {
display(Inputs.table(rows.map((r, i) => ({...r, _ref: i})), { display(Inputs.table(rows.map((r, i) => ({...r, _ref: i})), {
columns: ["_ref", "label", "tokens_in", "tokens_out", "tokens_total", "event_count"], columns: ["_ref", "label", "tokens_in", "tokens_out", "tokens_total", "event_count"],
header: {_ref: "REF", label: "Workstream", tokens_in: "Tokens In", tokens_out: "Tokens Out", tokens_total: "Total", event_count: "Events"}, header: {_ref: "REF", label: "Workplan", tokens_in: "Tokens In", tokens_out: "Tokens Out", tokens_total: "Total", event_count: "Events"},
format: { format: {
_ref: (_, i) => refCell(i + 1, "workstreams", rows[i].scope_id), _ref: (_, i) => refCell(i + 1, "workplans", rows[i].scope_id),
label: d => nameCell(d, d), label: d => nameCell(d, d),
tokens_in: d => d.toLocaleString(), tokens_in: d => d.toLocaleString(),
tokens_out: d => d.toLocaleString(), tokens_out: d => d.toLocaleString(),

View file

@ -65,7 +65,7 @@ Connected applications, services, and local tools used across the Custodian ecos
display(html`<div class="app-grid"> display(html`<div class="app-grid">
${appCard({ ${appCard({
icon: "🗄️", name: "State Hub API", status: apiUp, icon: "🗄️", name: "State Hub API", status: apiUp,
desc: "FastAPI backend — the source of truth for all workstream, task, and decision data.", desc: "FastAPI backend — the source of truth for all workplan, task, and decision data.",
url: "http://127.0.0.1:8000/docs", label: "127.0.0.1:8000 · Swagger UI", url: "http://127.0.0.1:8000/docs", label: "127.0.0.1:8000 · Swagger UI",
})} })}
${appCard({ ${appCard({

View file

@ -62,7 +62,7 @@ function statusCell(row) {
function blockers(row) { function blockers(row) {
const parts = []; const parts = [];
if (row.blocked_by_workstream_ids?.length) parts.push(`${row.blocked_by_workstream_ids.length} workstream`); if (row.blocked_by_workstream_ids?.length) parts.push(`${row.blocked_by_workstream_ids.length} workplan`);
if (row.blocked_by_task_ids?.length) parts.push(`${row.blocked_by_task_ids.length} task`); if (row.blocked_by_task_ids?.length) parts.push(`${row.blocked_by_task_ids.length} task`);
return parts.length ? parts.join(", ") : "—"; return parts.length ? parts.join(", ") : "—";
} }

View file

@ -8,7 +8,7 @@ import {WORKSTREAM_STATUSES, isClosedWorkstream, normalizeWorkstreamStatus} from
``` ```
```js ```js
// Fetch workstreams + topics + dep edges in parallel; /state/deps replaces the // Fetch workplans + topics + dep edges in parallel; /state/deps replaces the
// heavier /state/summary which was only used here to extract dependency edges. // heavier /state/summary which was only used here to extract dependency edges.
const wsState = (async function*() { const wsState = (async function*() {
let failures = 0; let failures = 0;
@ -50,7 +50,7 @@ const _ts = wsState.ts;
``` ```
```js ```js
// ── Workstream Health Index (WHI) ──────────────────────────────────────────── // ── Workplan Health Index (WHI) ────────────────────────────────────────────
const _idToDomain = Object.fromEntries(data.map(w => [w.id, w.domain ?? "unknown"])); const _idToDomain = Object.fromEntries(data.map(w => [w.id, w.domain ?? "unknown"]));
const _closedIds = new Set(data.filter(w => isClosedWorkstream(w.status)).map(w => w.id)); const _closedIds = new Set(data.filter(w => isClosedWorkstream(w.status)).map(w => w.id));
const _openCount = openWs.length; const _openCount = openWs.length;
@ -63,7 +63,7 @@ const _DD = _openCount > 0 ? _totalEdges / _openCount : 0;
// Blocked Ratio // Blocked Ratio
const _BR = _openCount > 0 ? openWs.filter(w => w.status === "blocked").length / _openCount : 0; const _BR = _openCount > 0 ? openWs.filter(w => w.status === "blocked").length / _openCount : 0;
// Single-Point Risk — max inbound edges on one incomplete workstream // Single-Point Risk — max inbound edges on one incomplete workplan
const _inbound = {}; const _inbound = {};
for (const e of _allEdges) { for (const e of _allEdges) {
if (!_closedIds.has(e.to)) _inbound[e.to] = (_inbound[e.to] ?? 0) + 1; if (!_closedIds.has(e.to)) _inbound[e.to] = (_inbound[e.to] ?? 0) + 1;
@ -72,7 +72,7 @@ const _SPR = _openCount > 0
? (Object.keys(_inbound).length > 0 ? Math.max(...Object.values(_inbound)) : 0) / _openCount ? (Object.keys(_inbound).length > 0 ? Math.max(...Object.values(_inbound)) : 0) / _openCount
: 0; : 0;
// Parallel Execution Potential — ready/active workstreams with all deps finished // Parallel Execution Potential — ready/active workplans with all deps finished
const _PEP = _openCount > 0 const _PEP = _openCount > 0
? openWs.filter(w => ["ready", "active"].includes(normalizeWorkstreamStatus(w.status)) && w.depends_on.every(d => _closedIds.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; : 0;
@ -282,7 +282,7 @@ display(Plot.plot({
display(_filtersForm); display(_filtersForm);
{ {
// Enrich each workstream with tasks/deps data from open_workstreams summary // Enrich each workplan with tasks/deps data from open_workstreams summary
const _openWsMap = Object.fromEntries(openWs.map(w => [w.id, w])); const _openWsMap = Object.fromEntries(openWs.map(w => [w.id, w]));
const _wsTable = buildEntityTable( const _wsTable = buildEntityTable(
filtered, filtered,
@ -290,12 +290,12 @@ display(_filtersForm);
{label: "Title", key: "title", cls: "et-title-col et-title-cell", {label: "Title", key: "title", cls: "et-title-col et-title-cell",
render: w => w.title}, render: w => w.title},
{label: "Domain", key: "domain"}, {label: "Domain", key: "domain"},
{label: "Status", render: w => statusControl({entity: w, type: "workstream", statuses: WORKSTREAM_STATUSES})}, {label: "Status", render: w => statusControl({entity: w, type: "workplan", statuses: WORKSTREAM_STATUSES})},
{label: "Owner", render: w => w.owner ?? "—"}, {label: "Owner", render: w => w.owner ?? "—"},
{label: "Due", render: w => w.due_date ?? "—"}, {label: "Due", render: w => w.due_date ?? "—"},
{label: "Updated", render: w => new Date(w.updated_at).toLocaleDateString()}, {label: "Updated", render: w => new Date(w.updated_at).toLocaleDateString()},
], ],
w => openEntityModal({...w, ..._openWsMap[w.id]}, "workstream"), w => openEntityModal({...w, ..._openWsMap[w.id]}, "workplan"),
); );
display(_wsTable); display(_wsTable);
} }

View file

@ -1,5 +1,5 @@
--- ---
title: Workstream title: Workplan
--- ---
```js ```js
@ -18,8 +18,8 @@ const [raw, taskRows, workplanIndex] = await Promise.all([
.then(r => r.ok ? r.json() : []) .then(r => r.ok ? r.json() : [])
.catch(() => []), .catch(() => []),
fetch(`${API}/workplans/index`) fetch(`${API}/workplans/index`)
.then(r => r.ok ? r.json() : {workstreams: {}}) .then(r => r.ok ? r.json() : {workplans: {}})
.catch(() => ({workstreams: {}})), .catch(() => ({workplans: {}})),
]); ]);
``` ```
@ -27,16 +27,16 @@ const [raw, taskRows, workplanIndex] = await Promise.all([
if (raw.error) { if (raw.error) {
display(html`<div style="color:red;padding:1rem">⚠️ ${raw.error}</div>`); display(html`<div style="color:red;padding:1rem">⚠️ ${raw.error}</div>`);
} else { } else {
const workplan = (workplanIndex.workstreams ?? {})[wsId] ?? {}; const workplan = (workplanIndex.workplans ?? workplanIndex.workstreams ?? {})[wsId] ?? {};
const name = raw.title || raw.slug || wsId; const name = raw.title || raw.slug || wsId;
const shortName = name.length > 60 ? name.slice(0, 60) + "…" : name; const shortName = name.length > 60 ? name.slice(0, 60) + "…" : name;
display(html`<h1 style="font-size:1.1rem;margin-bottom:0.25rem">Workstream · <em>${shortName}</em></h1>`); display(html`<h1 style="font-size:1.1rem;margin-bottom:0.25rem">Workplan · <em>${shortName}</em></h1>`);
display(html`<p style="margin-top:0"><a href="/">← Overview</a> &nbsp;|&nbsp; <a href="/workstreams">← Workplans</a> &nbsp;|&nbsp; <a href="/token-cost">← Token Cost</a></p>`); display(html`<p style="margin-top:0"><a href="/">← Overview</a> &nbsp;|&nbsp; <a href="/workstreams">← Workplans</a> &nbsp;|&nbsp; <a href="/token-cost">← Token Cost</a></p>`);
display(html`<div class="ws-summary"> display(html`<div class="ws-summary">
<div><span>Status</span>${statusControl({ <div><span>Status</span>${statusControl({
entity: raw, entity: raw,
type: "workstream", type: "workplan",
statuses: WORKSTREAM_STATUSES, statuses: WORKSTREAM_STATUSES,
onSaved: () => setTimeout(() => location.reload(), 450), onSaved: () => setTimeout(() => location.reload(), 450),
})}</div> })}</div>
@ -53,7 +53,7 @@ if (raw.error) {
display(html`<h2>Tasks</h2>`); display(html`<h2>Tasks</h2>`);
if (sortedTasks.length === 0) { if (sortedTasks.length === 0) {
display(html`<p style="color:gray">No tasks are attached to this workstream.</p>`); display(html`<p style="color:gray">No tasks are attached to this workplan.</p>`);
} else { } else {
display(html`<table class="task-table"> display(html`<table class="task-table">
<thead><tr><th>Status</th><th>Priority</th><th>Task</th><th>Human</th></tr></thead> <thead><tr><th>Status</th><th>Priority</th><th>Task</th><th>Human</th></tr></thead>

View file

@ -23,7 +23,7 @@ import {
const triageState = (async function*() { const triageState = (async function*() {
let failures = 0; let failures = 0;
while (true) { while (true) {
let events = [], workplanIndex = {workstreams: {}}, ok = false; let events = [], workplanIndex = {workplans: {}}, ok = false;
try { try {
const [reportsResp, indexResp] = await Promise.all([ const [reportsResp, indexResp] = await Promise.all([
apiFetch("/progress/?event_type=daily_triage&limit=14"), apiFetch("/progress/?event_type=daily_triage&limit=14"),
@ -31,7 +31,7 @@ const triageState = (async function*() {
]); ]);
ok = reportsResp.ok && indexResp.ok; ok = reportsResp.ok && indexResp.ok;
events = reportsResp.ok ? await reportsResp.json() : []; events = reportsResp.ok ? await reportsResp.json() : [];
workplanIndex = indexResp.ok ? await indexResp.json() : {workstreams: {}}; workplanIndex = indexResp.ok ? await indexResp.json() : {workplans: {}};
} catch {} } catch {}
failures = ok ? 0 : failures + 1; failures = ok ? 0 : failures + 1;
yield {events, workplanIndex, ok, ts: new Date()}; yield {events, workplanIndex, ok, ts: new Date()};
@ -42,7 +42,7 @@ const triageState = (async function*() {
```js ```js
const reports = normalizeTriageReports(triageState.events ?? []); const reports = normalizeTriageReports(triageState.events ?? []);
const candidateIndex = buildCandidateIndex(triageState.workplanIndex ?? {workstreams: {}}); const candidateIndex = buildCandidateIndex(triageState.workplanIndex ?? {workplans: {}});
const _ok = triageState.ok ?? false; const _ok = triageState.ok ?? false;
const _ts = triageState.ts; const _ts = triageState.ts;
const latestReport = reports[0] ?? null; const latestReport = reports[0] ?? null;
@ -140,7 +140,7 @@ function renderPatterns(reports, index) {
${rows.length === 0 ${rows.length === 0
? html`<p class="triage-muted">No repeated recommendations are visible in the loaded 14-day window.</p>` ? html`<p class="triage-muted">No repeated recommendations are visible in the loaded 14-day window.</p>`
: html`<table class="triage-table"> : html`<table class="triage-table">
<thead><tr><th>Workstream</th><th>Times Recommended</th><th>Most Frequent Action</th></tr></thead> <thead><tr><th>Workplan</th><th>Times Recommended</th><th>Most Frequent Action</th></tr></thead>
<tbody>${rows.map(row => html`<tr> <tbody>${rows.map(row => html`<tr>
<td>${candidateNode(row.candidate, index)}</td> <td>${candidateNode(row.candidate, index)}</td>
<td>${row.count} / ${Math.max(1, windowReports.length)} reports</td> <td>${row.count} / ${Math.max(1, windowReports.length)} reports</td>

View file

@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import {readFileSync} from "node:fs";
import test from "node:test";
import {fileURLToPath} from "node:url";
import {dirname, join} from "node:path";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const config = readFileSync(join(root, "observablehq.config.js"), "utf8");
const indexMd = readFileSync(join(root, "src/index.md"), "utf8");
test("dashboard navigation uses workplan labels", () => {
assert.match(config, /name: "Workplans"/);
assert.doesNotMatch(config, /name: "Workstreams"/);
});
test("overview page uses workplan-first user-facing copy", () => {
assert.match(indexMd, /## Workplans by Repository/);
assert.match(indexMd, /Active Workplans/);
assert.doesNotMatch(indexMd, /No active workstreams\./);
assert.doesNotMatch(indexMd, /Active Workstreams/);
});

View file

@ -0,0 +1,108 @@
# Workplan Terminology Legacy Retirement Backlog
Date: 2026-07-08
Owner: `STATE-WP-0069` (child of `CUST-WP-0055`)
Baseline: `the-custodian/docs/evidence/workstream-terminology-baseline-20260708.json`
This backlog ranks every metered legacy `workstream` interface still present in
State Hub. **Removal requires legacy-meter evidence** — zero measured calls in
the review window, replacement verified, no manual hold.
Authoritative interface matrix: `docs/workplan-terminology-transition.md`.
## Retirement rule (unchanged)
1. Registered in `legacy-meter`
2. Replacement reference verified
3. No manual hold
4. Zero measured calls in the review window
Activity-core runs the weekly review; State Hub owns usage state and removal.
## Scan allowlist (grep tooling)
Exclude these paths from non-compat prose counts (see
`the-custodian/tools/scan_workstream_allowlist.yaml`):
| Area | Paths |
| --- | --- |
| Compat REST | `api/routers/workstreams.py`, `api/routers/workstream_dependencies.py` |
| Legacy meter | `api/routers/legacy_meter.py`, `api/services/legacy_meter.py`, `api/models/legacy_meter.py`, `migrations/` |
| MCP aliases | `mcp_server/` |
| Transition docs | `docs/workplan-terminology-transition.md`, this file, `docs/nats-event-subjects.md` |
| Regression tests | `tests/test_legacy_meter.py`, `tests/test_routers_core.py` |
| CLI compat | `custodian_cli.py`, `scripts/consistency_check.py` |
Dashboard prose is **not** allowlisted — `STATE-WP-0069` T02 drives it to zero
`prose:workstream` in `dashboard/src/`.
## Ranked backlog
Risk order: REST > MCP > events > dashboard prose > internal identifiers.
| Phase | Legacy-meter key | Replacement | Risk | Owner task |
| ---: | --- | --- | --- | --- |
| 1 | `rest_api:GET /workstreams/` | `GET /workplans/` | REST | T04 |
| 1 | `rest_api:POST /workstreams/` | `POST /workplans/` | REST | T04 |
| 1 | `rest_api:GET /workstreams/{workstream_id}` | `GET /workplans/{workplan_id}` | REST | T04 |
| 1 | `rest_api:PATCH /workstreams/{workstream_id}` | `PATCH /workplans/{workplan_id}` | REST | T04 |
| 1 | `rest_api:DELETE /workstreams/{workstream_id}` | `DELETE /workplans/{workplan_id}` | REST | T04 |
| 1 | `rest_api:GET /workstreams/workplan-index` | `GET /workplans/index` | REST | T04 |
| 2 | `rest_api:GET /workstreams/{workstream_id}/dependencies/` | `GET /workplans/{workplan_id}/dependencies/` | REST | T04 |
| 2 | `rest_api:POST /workstreams/{workstream_id}/dependencies/` | `POST /workplans/{workplan_id}/dependencies/` | REST | T04 |
| 2 | `rest_api:DELETE /workstreams/{workstream_id}/dependencies/{dep_id}` | `DELETE /workplans/{workplan_id}/dependencies/{dep_id}` | REST | T04 |
| 2 | `rest_api:PATCH /execution/workstreams/{workstream_id}/intent` | `PATCH /execution/workplans/{workplan_id}/intent` | REST | T04 |
| 3 | `mcp:create_workstream` | `create_workplan` | MCP | T03 |
| 3 | `mcp:update_workstream` | `update_workplan` | MCP | T03 |
| 3 | `mcp:update_workstream_status` | `update_workplan_status` | MCP | T03 |
| 3 | `mcp:list_workstreams` | `list_workplans` | MCP | T03 |
| 3 | `state://workstreams/{topic_slug}` | `state://workplans/{topic_slug}` (proposed) | MCP resource | T03 |
| 4 | `event_subject:org.statehub.workstream.completed` | `org.statehub.workplan.completed` | Event | T05 |
| 5 | Dashboard nav label `Workstreams` | `Workplans` (URL compat retained) | Prose | T02 |
| 5 | `dashboard/src/index.md` user-facing copy | workplan-first strings | Prose | T02 |
| 6 | `open_workstreams` summary cache key | `open_workplans` | Internal | T06 |
| 6 | `flows/workstream.yaml` entity id | workplan successor flow | Internal | T06 |
### Query-param aliases (not separately metered today)
These accept `workstream_id` alongside `workplan_id` on preferred routes:
- `GET /tasks/``api/routers/tasks.py`
- `GET /decisions/``api/routers/decisions.py`
- `GET /token-events/``api/routers/token_events.py`
Retire param aliases in T04 after route retirement; document callers via
weekly review component headers (`X-StateHub-Component`).
## Grep budget by phase
Measured with:
```bash
python ~/the-custodian/tools/scan_workstream_terminology.py --repo state-hub --apply-allowlist --json
python ~/the-custodian/tools/scan_workstream_terminology.py --repo state-hub --check-prose-gate
```
| Phase | Target |
| --- | --- |
| After T02 | Zero `prose:workstream` in `dashboard/src/` |
| After T03 | MCP tool docstrings and error messages workplan-first |
| After T04 | OpenAPI lists `/workplans` only; `/workstreams` returns 410 or unmounted |
| After T07 | Total repo hits reduced ≥50% from 2026-07-08 baseline |
## Sequencing
```
T01 backlog (this document) ──► T02 dashboard prose
├─► T03 MCP deprecation warnings → alias removal
├─► T04 REST retirement (per-key zero usage)
├─► T05 stop dual-publish (after CUST-WP-0055 T03)
├─► T06 internal renames
└─► T07 closeout
```
## Related workplans
- `STATE-WP-0054` — compatibility layer and legacy-meter (finished)
- `STATE-WP-0069` — this retirement plan
- `CUST-WP-0055` — fleet coordination; activity-core catalog alignment (T03)

View file

@ -4,7 +4,7 @@ type: workplan
title: "Workplan terminology legacy retirement (State Hub)" title: "Workplan terminology legacy retirement (State Hub)"
domain: infotech domain: infotech
repo: state-hub repo: state-hub
status: proposed status: active
owner: codex owner: codex
topic_slug: custodian topic_slug: custodian
planning_priority: medium planning_priority: medium
@ -72,7 +72,7 @@ excluded once documented in T01).
```task ```task
id: STATE-WP-0069-T01 id: STATE-WP-0069-T01
status: todo status: done
priority: high priority: high
state_hub_task_id: "ffc186e0-807d-4ee4-b11f-3f769af2ab2d" state_hub_task_id: "ffc186e0-807d-4ee4-b11f-3f769af2ab2d"
``` ```
@ -96,7 +96,7 @@ Done when the backlog is reviewed and each interface has a phase assignment (T02
```task ```task
id: STATE-WP-0069-T02 id: STATE-WP-0069-T02
status: todo status: done
priority: high priority: high
state_hub_task_id: "996e484e-8cfb-4c9e-8682-c9c3da0c1d72" state_hub_task_id: "996e484e-8cfb-4c9e-8682-c9c3da0c1d72"
``` ```