From b2264d1f06d9260bc41c506b8643a1ea455c002b Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 8 Jul 2026 16:08:32 +0200 Subject: [PATCH] feat(terminology): workplan-first dashboard and retirement backlog (STATE-WP-0069) 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. --- dashboard/observablehq.config.js | 10 +- dashboard/src/components/entity-modal.js | 16 +-- dashboard/src/components/field-help.js | 14 +-- dashboard/src/components/improvement-modal.js | 2 +- dashboard/src/components/wsjf-triage.js | 4 +- dashboard/src/data/workstreams/[id].json.py | 4 +- dashboard/src/dependencies.md | 10 +- dashboard/src/docs/capabilities.md | 2 +- dashboard/src/docs/contributions.md | 2 +- dashboard/src/docs/dashboard.md | 46 ++++---- dashboard/src/docs/dependencies.md | 22 ++-- dashboard/src/docs/extensions.md | 2 +- dashboard/src/docs/goals.md | 8 +- .../src/docs/inter-repo-communication.md | 8 +- dashboard/src/docs/interventions.md | 6 +- dashboard/src/docs/overview.md | 14 +-- dashboard/src/docs/progress-log.md | 10 +- dashboard/src/docs/ralph-workplan.md | 2 +- dashboard/src/docs/repo-integration.md | 34 +++--- dashboard/src/docs/sbom.md | 2 +- dashboard/src/docs/state-hub.md | 20 ++-- dashboard/src/docs/tasks.md | 4 +- dashboard/src/docs/todo.md | 4 +- dashboard/src/docs/workstream-health-index.md | 34 +++--- dashboard/src/docs/workstream-kpi.md | 32 +++--- dashboard/src/docs/workstream-lifecycle.md | 14 +-- dashboard/src/docs/workstreams.md | 42 +++---- dashboard/src/docs/wsjf-triage.md | 4 +- dashboard/src/index.md | 56 ++++----- dashboard/src/interventions.md | 2 +- dashboard/src/policy/workstream-dod.md | 2 +- dashboard/src/reference.md | 8 +- dashboard/src/repos.md | 8 +- dashboard/src/tasks.md | 2 +- dashboard/src/todo.md | 4 +- dashboard/src/token-cost.md | 6 +- dashboard/src/tools.md | 2 +- dashboard/src/workplan-queue.md | 2 +- dashboard/src/workstreams.md | 14 +-- dashboard/src/workstreams/[id].md | 14 +-- dashboard/src/wsjf-triage.md | 8 +- dashboard/test/terminology-nav.test.mjs | 21 ++++ ...n-terminology-legacy-retirement-backlog.md | 108 ++++++++++++++++++ ...-workplan-terminology-legacy-retirement.md | 6 +- 44 files changed, 382 insertions(+), 253 deletions(-) create mode 100644 dashboard/test/terminology-nav.test.mjs create mode 100644 docs/workplan-terminology-legacy-retirement-backlog.md diff --git a/dashboard/observablehq.config.js b/dashboard/observablehq.config.js index c675b98..e30abbe 100644 --- a/dashboard/observablehq.config.js +++ b/dashboard/observablehq.config.js @@ -54,7 +54,7 @@ export default { pages: [ { name: "Repository DoI", path: "/policy/repo-doi" }, { 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", collapsible: true, open: false, @@ -120,9 +120,9 @@ export default { { name: "TPSC — GDPR Maturity", path: "/docs/gdpr-maturity" }, { name: "Technical Debt", path: "/docs/debt" }, { name: "Todo", path: "/docs/todo" }, - { name: "Workstream Health", path: "/docs/workstream-health-index" }, - { name: "Workstream Lifecycle", path: "/docs/workstream-lifecycle" }, - { name: "Workstreams", path: "/docs/workstreams" }, + { name: "Workplan Health", path: "/docs/workstream-health-index" }, + { name: "Workplan Lifecycle", path: "/docs/workstream-lifecycle" }, + { name: "Workplans", path: "/docs/workstreams" }, { name: "Suggestions", path: "/docs/suggestions" }, { name: "WSJF Triage", path: "/docs/wsjf-triage" }, ], diff --git a/dashboard/src/components/entity-modal.js b/dashboard/src/components/entity-modal.js index ef3f26f..12da4fc 100644 --- a/dashboard/src/components/entity-modal.js +++ b/dashboard/src/components/entity-modal.js @@ -3,9 +3,9 @@ * * Usage: * 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"; @@ -216,7 +216,7 @@ function _buildBody(entity, type) { return _field(label, v); }; - if (type === "workstream") { + if (type === "workplan") { els.push( bf("Status", entity.status, _STATUS_STYLE), tf("Domain", entity.domain ?? entity.topic_title ?? "—"), @@ -261,7 +261,7 @@ function _buildBody(entity, type) { bf("Status", entity.status, _STATUS_STYLE), bf("Priority", entity.priority, _PRIORITY_STYLE), tf("Domain", entity.domain ?? "—"), - tf("Workstream", entity.workstream_title ?? "—"), + tf("Workplan", entity.workstream_title ?? "—"), tf("Assignee", entity.assignee ?? "—"), tf("Due", entity.due_date ?? "—"), ); @@ -285,7 +285,7 @@ function _buildBody(entity, type) { bf("Priority", entity.priority, _PRIORITY_STYLE), tf("Type", entity.ep_type ?? "—"), tf("Domain", entity.domain ?? "—"), - tf("Workstream", entity.workstream_title ?? "—"), + tf("Workplan", entity.workstream_title ?? "—"), tf("Location", entity.location ?? "—"), ); if (entity.description) { @@ -302,7 +302,7 @@ function _buildBody(entity, type) { bf("Status", entity.status, _STATUS_STYLE), tf("Type", entity.debt_type ?? "—"), tf("Domain", entity.domain ?? "—"), - tf("Workstream", entity.workstream_title ?? "—"), + tf("Workplan", entity.workstream_title ?? "—"), tf("Location", entity.location ?? "—"), ); if (entity.description) { @@ -319,8 +319,8 @@ function _buildBody(entity, type) { /** * Open a detail modal for the given entity. - * @param {object} entity - The entity data object (workstream, task, ep, or td) - * @param {string} type - One of: "workstream" | "task" | "ep" | "td" + * @param {object} entity - The entity data object (workplan, task, ep, or td) + * @param {string} type - One of: "workplan" | "task" | "ep" | "td" */ export function openEntityModal(entity, type) { _ensureStyles(); diff --git a/dashboard/src/components/field-help.js b/dashboard/src/components/field-help.js index 96e412e..f49c6d9 100644 --- a/dashboard/src/components/field-help.js +++ b/dashboard/src/components/field-help.js @@ -99,13 +99,13 @@ export const FIELD_HELP = { doc: "/docs/tasks", }, workstream_id: { - label: "Workstream ID", - description: "The workstream this event belongs to; auto-resolved from task if not set directly.", + label: "Workplan ID", + description: "The workplan this event belongs to; auto-resolved from task if not set directly.", doc: "/docs/workstreams", }, 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", }, session_id: { @@ -142,14 +142,14 @@ export const FIELD_HELP = { description: "Timestamp when this token event was recorded (UTC).", }, - // ── Workstream ────────────────────────────────────────────────────────────── + // ── Workplan ────────────────────────────────────────────────────────────── slug: { label: "Slug", description: "URL-safe short identifier for this entity.", }, title: { label: "Title", - description: "Human-readable name for this workstream or task.", + description: "Human-readable name for this workplan or task.", }, status: { label: "Status", @@ -158,12 +158,12 @@ export const FIELD_HELP = { }, 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", }, 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", }, diff --git a/dashboard/src/components/improvement-modal.js b/dashboard/src/components/improvement-modal.js index e37ad50..c2cdb51 100644 --- a/dashboard/src/components/improvement-modal.js +++ b/dashboard/src/components/improvement-modal.js @@ -6,7 +6,7 @@ * initImprovementModal({apiBase: "http://127.0.0.1:8000"}); * * Widget names can be declared explicitly via data attribute: - *
+ *
* * Otherwise the component walks the DOM to infer the nearest section heading. * Submissions are stored as technical-debt items with debt_type="dashboard-improvement". diff --git a/dashboard/src/components/wsjf-triage.js b/dashboard/src/components/wsjf-triage.js index 20210f2..cff6e29 100644 --- a/dashboard/src/components/wsjf-triage.js +++ b/dashboard/src/components/wsjf-triage.js @@ -80,8 +80,8 @@ export function candidateKeysForWorkplan(item = {}) { export function buildCandidateIndex(workplanIndex = {}) { const byCandidate = new Map(); - const workstreams = workplanIndex.workstreams ?? {}; - for (const [id, item] of Object.entries(workstreams)) { + const workplans = workplanIndex.workplans ?? workplanIndex.workstreams ?? {}; + for (const [id, item] of Object.entries(workplans)) { const resolved = {id, ...item}; byCandidate.set(normalizeCandidate(id), resolved); for (const key of candidateKeysForWorkplan(item)) { diff --git a/dashboard/src/data/workstreams/[id].json.py b/dashboard/src/data/workstreams/[id].json.py index 92774cb..d4a511b 100644 --- a/dashboard/src/data/workstreams/[id].json.py +++ b/dashboard/src/data/workstreams/[id].json.py @@ -1,5 +1,5 @@ #!/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 os 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 "" if not ws_id: - print(json.dumps({"error": "No workstream ID provided"})) + print(json.dumps({"error": "No workplan ID provided"})) sys.exit(1) try: diff --git a/dashboard/src/dependencies.md b/dashboard/src/dependencies.md index 4553fbb..644ecad 100644 --- a/dashboard/src/dependencies.md +++ b/dashboard/src/dependencies.md @@ -8,7 +8,7 @@ import {normalizeWorkstreamStatus} from "./components/workplan-status.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. const depState = (async function*() { let failures = 0; @@ -70,7 +70,7 @@ const _kpiBox = html`
${edges.length}
- workstreams involved + workplans involved
${_wsWithDeps.size}
`; @@ -89,7 +89,7 @@ injectTocTop("dep-kpi-box", _kpiBox); 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. ```js @@ -113,10 +113,10 @@ if (edges.length === 0) { Depends-on domain - Depends-on workstream + Depends-on workplan Blocked-by domain - Blocked-by workstream + Blocked-by workplan Status diff --git a/dashboard/src/docs/capabilities.md b/dashboard/src/docs/capabilities.md index 11f57df..e4af82a 100644 --- a/dashboard/src/docs/capabilities.md +++ b/dashboard/src/docs/capabilities.md @@ -219,7 +219,7 @@ and age in days. | Concept | Relationship | |---------|-------------| | **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 | | **Contributions** | Outbound upstream work — capabilities are *inbound* requests between internal domains | | **Human Interventions** | Flagged tasks for Bernd — capabilities are agent-to-agent coordination | diff --git a/dashboard/src/docs/contributions.md b/dashboard/src/docs/contributions.md index d773ea4..9736a74 100644 --- a/dashboard/src/docs/contributions.md +++ b/dashboard/src/docs/contributions.md @@ -56,7 +56,7 @@ communication taxonomy: | 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:]` prefix | | **Third-party** | **Contribution artifact in `contrib/` + state hub registration** | diff --git a/dashboard/src/docs/dashboard.md b/dashboard/src/docs/dashboard.md index 6441df8..656f9f6 100644 --- a/dashboard/src/docs/dashboard.md +++ b/dashboard/src/docs/dashboard.md @@ -6,7 +6,7 @@ title: Dashboard — Technical Reference The State Hub dashboard is the primary visual interface for the Custodian 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. --- @@ -63,7 +63,7 @@ Current loaders: | File | API endpoint | |---|---| | `summary.json.py` | `/state/summary` | -| `workstreams.json.py` | `/workplans/` | +| `workplans.json.py` | `/workplans/` | | `contributions.json.py` | `/contributions/` | | `decisions.json.py` | `/decisions/` | | `domains.json.py` | `/domains/` | @@ -120,7 +120,7 @@ The dashboard has 30+ pages organised in four navigation groups: | 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 | | Contributions | `/contributions` | Upstream contribution Kanban (bug reports, feature requests, upstream PRs) | | 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 | | SBOM | `/sbom` | Software bill of materials — packages, licences, copyleft risk | -### Workstreams section +### Workplans section | Page | Route | Purpose | |---|---|---| @@ -173,7 +173,7 @@ Exports shared runtime configuration used by every live-polling page: ### `entity-modal.js` A lightweight detail overlay for entities. Any table row or card can call `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). 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` Provides `injectTocTop(id, element)` — injects a DOM element into the 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. ### `doc-overlay.js` @@ -194,12 +194,12 @@ without leaving the current page. ### `help-tip.js` A custom HTML element (``) that renders an inline abbreviated label 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. ### `multiselect.js` 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. ### `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 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 (0–100%) derived from five graph metrics: | Metric | Abbrev. | Weight | Interpretation | |---|---|---|---| -| Dependency Density | DD | 30% | Average deps per open workstream; high = tightly coupled | -| Blocked Ratio | BR | 25% | Share of workstreams in a blocked state | -| Single-Point Risk | SPR | 15% | Share of workstreams that others depend on but are not yet complete | -| Parallel Execution Potential | PEP | 20% | Share of workstreams that could start/continue immediately | +| Dependency Density | DD | 30% | Average deps per open workplan; high = tightly coupled | +| Blocked Ratio | BR | 25% | Share of workplans in a blocked state | +| Single-Point Risk | SPR | 15% | Share of workplans that others depend on but are not yet complete | +| 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 | 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 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` -showing task counts (done / progress / wait / todo) per workstream. +showing task counts (done / progress / wait / todo) per workplan. A ` ${_MODE_GROUPS.map(group => html` ${group.options.map(([value, label]) => html``)} @@ -274,7 +274,7 @@ import * as Plot from "npm:@observablehq/plot"; const _chartModeValue = _modeValue(_chartModeState); 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. const chartWs = [..._chartWsFiltered].sort((a, b) => { 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); 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 _seen = new Set(); for (const w of chartWs) { @@ -321,24 +321,24 @@ function _wsTitle(d) { // ── Render ──────────────────────────────────────────────────────────────────── if (chartWs.length === 0) { const _emptyMsg = { - proposed: "No proposed workstreams.", - ready: "No ready workstreams.", - active: "No active workstreams.", - blocked: "No blocked workstreams.", - backlog: "No backlog workstreams.", - finished: "No finished workstreams.", - archived: "No archived workstreams.", - needs_review: "No ready workstreams need review.", - stalled: "No stalled workstreams — everything is moving.", - "1h": "No workstreams changed in the last hour.", - "1d": "No workstreams changed in the last 24 hours.", - "7d": "No workstreams changed in the last 7 days.", - "30d": "No workstreams changed in the last 30 days.", - today: "No workstreams changed today.", - week: "No workstreams changed this week.", - month: "No workstreams changed this month.", + proposed: "No proposed workplans.", + ready: "No ready workplans.", + active: "No active workplans.", + blocked: "No blocked workplans.", + backlog: "No backlog workplans.", + finished: "No finished workplans.", + archived: "No archived workplans.", + needs_review: "No ready workplans need review.", + stalled: "No stalled workplans — everything is moving.", + "1h": "No workplans changed in the last hour.", + "1d": "No workplans changed in the last 24 hours.", + "7d": "No workplans changed in the last 7 days.", + "30d": "No workplans changed in the last 30 days.", + today: "No workplans changed today.", + week: "No workplans changed this week.", + month: "No workplans changed this month.", }; - display(html`

${_emptyMsg[_chartModeValue] ?? "No workstreams."}

`); + display(html`

${_emptyMsg[_chartModeValue] ?? "No workplans."}

`); } else { display(Plot.plot({ y: { @@ -445,7 +445,7 @@ const decCount = (decisions.open ?? 0) + (decisions.escalated ?? 0); const statusEl = html`
-

Active Workstreams

+

Active Workplans

${ws.active ?? 0}

${ws.blocked ?? 0} blocked
@@ -510,7 +510,7 @@ const typeBadgeClass = { }; if (nextSteps.length === 0) { - display(html`

No actionable suggestions right now — all open workstreams are making progress or waiting on decisions.

`); + display(html`

No actionable suggestions right now — all open workplans are making progress or waiting on decisions.

`); } else { display(html`
${nextSteps.map(s => html`
@@ -543,7 +543,7 @@ if (regs.length === 0) { ``` ```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 registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean)); const emptyRegistered = (summary.topics ?? []).filter(t => @@ -553,9 +553,9 @@ const emptyRegistered = (summary.topics ?? []).filter(t => if (emptyRegistered.length > 0) { display(html`
💡 Getting started -

These registered projects have no workstreams yet:

+

These registered projects have no workplans yet:

    ${emptyRegistered.map(t => html`
  • - ${t.domain_slug} — open repo in Claude Code and say "Hi!" to kick off first session, or run custodian create-workstream --domain ${t.domain_slug} --title "My first workstream" manually + ${t.domain_slug} — open repo in Claude Code and say "Hi!" to kick off first session, or create a workplan file under workplans/ and run statehub fix-consistency
  • `)}
`); } diff --git a/dashboard/src/interventions.md b/dashboard/src/interventions.md index 27d7771..a688147 100644 --- a/dashboard/src/interventions.md +++ b/dashboard/src/interventions.md @@ -7,7 +7,7 @@ import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components ``` ```js -// Live poll: all tasks (filtered client-side) + workstreams + topics +// Live poll: all tasks (filtered client-side) + workplans + topics const interventionState = (async function*() { let failures = 0; while (true) { diff --git a/dashboard/src/policy/workstream-dod.md b/dashboard/src/policy/workstream-dod.md index ae6762f..ac78314 100644 --- a/dashboard/src/policy/workstream-dod.md +++ b/dashboard/src/policy/workstream-dod.md @@ -1,5 +1,5 @@ --- -title: Workstream Definition of Done +title: Workplan Definition of Done --- ```js diff --git a/dashboard/src/reference.md b/dashboard/src/reference.md index e5118d4..25ec465 100644 --- a/dashboard/src/reference.md +++ b/dashboard/src/reference.md @@ -29,15 +29,15 @@ convention used in the Custodian State Hub. | [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 | | [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 | | [Repos](/docs/repos) | Repo registry, SBOM coverage map, ingestion commands | | [SBOM](/docs/sbom) | Lockfile ingestion, licence report, copyleft detection | | [Tasks](/docs/tasks) | Task statuses, priorities, filter bar, status distribution chart | | [Technical Debt](/docs/debt) | Debt types, severities, statuses, registration | | [Todo](/docs/todo) | Internal/Ecosystem/Third-party classification, data sources | -| [Workstream Health](/docs/workstream-health-index) | WHI formula, six base metrics, per-domain breakdown | -| [Workstreams](/docs/workstreams) | Workstream statuses, dependency edges, WHI KPI card | +| [Workplan Health](/docs/workstream-health-index) | WHI formula, six base metrics, per-domain breakdown | +| [Workplans](/docs/workstreams) | Workplan statuses, dependency edges, WHI KPI card | | [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/` | `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. --- diff --git a/dashboard/src/repos.md b/dashboard/src/repos.md index 2f06d98..e9b11ef 100644 --- a/dashboard/src/repos.md +++ b/dashboard/src/repos.md @@ -39,7 +39,7 @@ const domains = _domains ?? []; const sbom = _sbom ?? []; const eps = _eps ?? []; const tds = _tds ?? []; -const workstreams = _workstreams ?? []; +const workplans = _workstreams ?? []; const doi = doiData; // reactive — updates when lazy fetch completes // 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 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( - workstreams + workplans .filter(w => w.status === "active" && w.slug?.startsWith("repo-integration-")) .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; } } -// 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 // Build enriched repo rows diff --git a/dashboard/src/tasks.md b/dashboard/src/tasks.md index ae22608..68fa6b0 100644 --- a/dashboard/src/tasks.md +++ b/dashboard/src/tasks.md @@ -224,7 +224,7 @@ display(buildEntityTable( {label: "Priority", key: "priority"}, {label: "Title", key: "title", cls: "et-title-col et-title-cell"}, {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: "Due", render: t => t.due_date ?? "—"}, ], diff --git a/dashboard/src/todo.md b/dashboard/src/todo.md index 7a5e693..0d4ffc8 100644 --- a/dashboard/src/todo.md +++ b/dashboard/src/todo.md @@ -8,7 +8,7 @@ const THIS_REPO = "the-custodian"; ``` ```js -// Live poll: tasks + workstreams + topics + contributions +// Live poll: tasks + workplans + topics + contributions const todoState = (async function*() { let failures = 0; while (true) { @@ -136,7 +136,7 @@ if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/todo"); } ## 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. ```js diff --git a/dashboard/src/token-cost.md b/dashboard/src/token-cost.md index 4d9923e..5786080 100644 --- a/dashboard/src/token-cost.md +++ b/dashboard/src/token-cost.md @@ -174,13 +174,13 @@ display(html`
No workstream data yet.

`); + display(html`

No workplan data yet.

`); } else { display(Inputs.table(rows.map((r, i) => ({...r, _ref: i})), { 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: { - _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), tokens_in: d => d.toLocaleString(), tokens_out: d => d.toLocaleString(), diff --git a/dashboard/src/tools.md b/dashboard/src/tools.md index d9d5314..5083b7d 100644 --- a/dashboard/src/tools.md +++ b/dashboard/src/tools.md @@ -65,7 +65,7 @@ Connected applications, services, and local tools used across the Custodian ecos display(html`
${appCard({ 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", })} ${appCard({ diff --git a/dashboard/src/workplan-queue.md b/dashboard/src/workplan-queue.md index ca20bd0..052fef9 100644 --- a/dashboard/src/workplan-queue.md +++ b/dashboard/src/workplan-queue.md @@ -62,7 +62,7 @@ function statusCell(row) { function blockers(row) { 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`); return parts.length ? parts.join(", ") : "—"; } diff --git a/dashboard/src/workstreams.md b/dashboard/src/workstreams.md index 20fee33..e3faa2c 100644 --- a/dashboard/src/workstreams.md +++ b/dashboard/src/workstreams.md @@ -8,7 +8,7 @@ import {WORKSTREAM_STATUSES, isClosedWorkstream, normalizeWorkstreamStatus} from ``` ```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. const wsState = (async function*() { let failures = 0; @@ -50,7 +50,7 @@ const _ts = wsState.ts; ``` ```js -// ── Workstream Health Index (WHI) ──────────────────────────────────────────── +// ── Workplan Health Index (WHI) ──────────────────────────────────────────── 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 _openCount = openWs.length; @@ -63,7 +63,7 @@ const _DD = _openCount > 0 ? _totalEdges / _openCount : 0; // Blocked Ratio 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 = {}; for (const e of _allEdges) { 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 : 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 ? openWs.filter(w => ["ready", "active"].includes(normalizeWorkstreamStatus(w.status)) && w.depends_on.every(d => _closedIds.has(d.workstream_id))).length / _openCount : 0; @@ -282,7 +282,7 @@ display(Plot.plot({ 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 _wsTable = buildEntityTable( filtered, @@ -290,12 +290,12 @@ display(_filtersForm); {label: "Title", key: "title", cls: "et-title-col et-title-cell", render: w => w.title}, {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: "Due", render: w => w.due_date ?? "—"}, {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); } diff --git a/dashboard/src/workstreams/[id].md b/dashboard/src/workstreams/[id].md index dae777b..dcc56f6 100644 --- a/dashboard/src/workstreams/[id].md +++ b/dashboard/src/workstreams/[id].md @@ -1,5 +1,5 @@ --- -title: Workstream +title: Workplan --- ```js @@ -18,8 +18,8 @@ const [raw, taskRows, workplanIndex] = await Promise.all([ .then(r => r.ok ? r.json() : []) .catch(() => []), fetch(`${API}/workplans/index`) - .then(r => r.ok ? r.json() : {workstreams: {}}) - .catch(() => ({workstreams: {}})), + .then(r => r.ok ? r.json() : {workplans: {}}) + .catch(() => ({workplans: {}})), ]); ``` @@ -27,16 +27,16 @@ const [raw, taskRows, workplanIndex] = await Promise.all([ if (raw.error) { display(html`
⚠️ ${raw.error}
`); } else { - const workplan = (workplanIndex.workstreams ?? {})[wsId] ?? {}; + const workplan = (workplanIndex.workplans ?? workplanIndex.workstreams ?? {})[wsId] ?? {}; const name = raw.title || raw.slug || wsId; const shortName = name.length > 60 ? name.slice(0, 60) + "…" : name; - display(html`

Workstream · ${shortName}

`); + display(html`

Workplan · ${shortName}

`); display(html`

← Overview  |  ← Workplans  |  ← Token Cost

`); display(html`
Status${statusControl({ entity: raw, - type: "workstream", + type: "workplan", statuses: WORKSTREAM_STATUSES, onSaved: () => setTimeout(() => location.reload(), 450), })}
@@ -53,7 +53,7 @@ if (raw.error) { display(html`

Tasks

`); if (sortedTasks.length === 0) { - display(html`

No tasks are attached to this workstream.

`); + display(html`

No tasks are attached to this workplan.

`); } else { display(html` diff --git a/dashboard/src/wsjf-triage.md b/dashboard/src/wsjf-triage.md index a1569f2..c593923 100644 --- a/dashboard/src/wsjf-triage.md +++ b/dashboard/src/wsjf-triage.md @@ -23,7 +23,7 @@ import { const triageState = (async function*() { let failures = 0; while (true) { - let events = [], workplanIndex = {workstreams: {}}, ok = false; + let events = [], workplanIndex = {workplans: {}}, ok = false; try { const [reportsResp, indexResp] = await Promise.all([ apiFetch("/progress/?event_type=daily_triage&limit=14"), @@ -31,7 +31,7 @@ const triageState = (async function*() { ]); ok = reportsResp.ok && indexResp.ok; events = reportsResp.ok ? await reportsResp.json() : []; - workplanIndex = indexResp.ok ? await indexResp.json() : {workstreams: {}}; + workplanIndex = indexResp.ok ? await indexResp.json() : {workplans: {}}; } catch {} failures = ok ? 0 : failures + 1; yield {events, workplanIndex, ok, ts: new Date()}; @@ -42,7 +42,7 @@ const triageState = (async function*() { ```js const reports = normalizeTriageReports(triageState.events ?? []); -const candidateIndex = buildCandidateIndex(triageState.workplanIndex ?? {workstreams: {}}); +const candidateIndex = buildCandidateIndex(triageState.workplanIndex ?? {workplans: {}}); const _ok = triageState.ok ?? false; const _ts = triageState.ts; const latestReport = reports[0] ?? null; @@ -140,7 +140,7 @@ function renderPatterns(reports, index) { ${rows.length === 0 ? html`

No repeated recommendations are visible in the loaded 14-day window.

` : html`
StatusPriorityTaskHuman
- + ${rows.map(row => html` diff --git a/dashboard/test/terminology-nav.test.mjs b/dashboard/test/terminology-nav.test.mjs new file mode 100644 index 0000000..c191d80 --- /dev/null +++ b/dashboard/test/terminology-nav.test.mjs @@ -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/); +}); \ No newline at end of file diff --git a/docs/workplan-terminology-legacy-retirement-backlog.md b/docs/workplan-terminology-legacy-retirement-backlog.md new file mode 100644 index 0000000..f946caa --- /dev/null +++ b/docs/workplan-terminology-legacy-retirement-backlog.md @@ -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) \ No newline at end of file diff --git a/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md b/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md index 5838bc7..b7e0de3 100644 --- a/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md +++ b/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md @@ -4,7 +4,7 @@ type: workplan title: "Workplan terminology legacy retirement (State Hub)" domain: infotech repo: state-hub -status: proposed +status: active owner: codex topic_slug: custodian planning_priority: medium @@ -72,7 +72,7 @@ excluded once documented in T01). ```task id: STATE-WP-0069-T01 -status: todo +status: done priority: high 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 id: STATE-WP-0069-T02 -status: todo +status: done priority: high state_hub_task_id: "996e484e-8cfb-4c9e-8682-c9c3da0c1d72" ```
WorkstreamTimes RecommendedMost Frequent Action
WorkplanTimes RecommendedMost Frequent Action
${candidateNode(row.candidate, index)} ${row.count} / ${Math.max(1, windowReports.length)} reports