Commit Graph

64 Commits

Author SHA1 Message Date
70c8e3cd51 feat(mcp): add get_domain_summary() for low-token domain session orientation
get_state_summary() returns ~10k tokens — too expensive for routine domain
repo sessions that only need their own workstreams and decisions.

New get_domain_summary(domain_slug):
- 5 targeted API calls: topics (filter), workstreams (topic+status), decisions
  (topic+pending), progress (topic, limit 5), repos (domain, slug+SBOM only)
- Returns: topic, active workstreams, blocking decisions, 5 recent events,
  repo SBOM status — all scoped to one domain
- Estimated ~80-90% token reduction vs get_state_summary()

get_state_summary() preserved unchanged for cross-domain / custodian sessions.
Updated its docstring to note the large response and point to get_domain_summary.

Template updated: Step 1 now calls get_domain_summary("{DOMAIN}") instead of
get_state_summary() + get_next_steps(). TOOLS.md updated with usage guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 22:05:31 +01:00
a3338c3a23 chore(dashboard): sort Reference nav pages alphabetically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 21:09:32 +01:00
6d97a992ae feat(dashboard): collapse Reference nav section by default
Observable Framework 1.13.3 supports collapsible: true on nav sections,
rendering them as <details> elements. Collapsed by default; auto-expands
when any page within the section is active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 21:07:56 +01:00
ba89ebfa67 feat(canon): add inter-repo communication standard with todo taxonomy
Establishes the repo boundary rule and a formal vocabulary for classifying
work items by scope:

- Task: neutral state hub data entity
- Todo: a task scoped to the current session's repo/domain
  - Internal todo: addressed within this repo by this agent
  - Ecosystem todo: work for another registered repo → state hub task [repo:<slug>]
  - Third-party todo: work for an upstream repo → contribution artifact (BR/FR/EP/UPR)

New dashboard doc: /docs/inter-repo-communication — defines the boundary rule,
the full terminology, ecosystem and third-party todo workflows, and a decision
table for classifying any piece of work found during a session.

Also:
- sbom.md: replace verbose inter-repo section with a 3-line summary + link
- observablehq.config.js: add "Inter-Repo Communication" to Reference nav
- project_claude_md.template: add "### Repo Boundary Rule" section; fix
  Workplan Convention section (removing incorrect claim that the custodian
  writes workplan files in other repos — that is the target repo's job)

Cross-repo: created state hub task [repo:railiance-bootstrap] for that repo's
agent to apply the boundary rule and workplan convention fix to its own CLAUDE.md
(task 78d43cb0, workstream 59155efb).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 20:52:07 +01:00
98e991b49f fix(template): use reliable workplan discovery in step 2
Glob with pattern 'workplans/*.md' from repo root fails silently.
Changed instruction to Glob(pattern="**/*.md", path="workplans/")
with Bash ls as fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 20:13:31 +01:00
00272842ca fix(template): rewrite session protocol to produce concrete orientation output
The previous template only defined a First Session Protocol (triggered when no
workstreams existed). When workstreams did exist, get_state_summary() was called
but no output was defined, causing registered-repo Claude sessions to produce
nothing useful.

New 3-step normal session protocol:
- Step 1: get_state_summary() + get_next_steps()
- Step 2: scan workplans/*.md for active tasks (todo/in_progress)
- Step 3: output orientation brief covering active workstreams, pending tasks
  for this repo (from workplans/ + [repo:<slug>] state hub tasks), suggested
  next action, and SBOM status

Also strengthens First Session Protocol, ADR-001 workplan convention section,
and SBOM ingest section (adds SCAN=1 REPO_PATH= flags).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 20:05:16 +01:00
7caaec25a2 docs(sbom): add SBOM reference page + withDocHelp on SBOM dashboard
- docs/sbom.md: what SBOM is, lockfile semantics, 5-level maturity standard,
  gap types A–E, per-ecosystem guidance, Syft OSS tooling, inter-repo task
  communication convention, ingest commands, compliance check commands
- sbom.md: wire withDocHelp(h1, "/docs/sbom") — ? button on page title
- observablehq.config.js: add SBOM entry to Reference nav section

EP-CUST-002 registered: Syft-based comprehensive SBOM generation
Task 5f8cade5 created: [repo:railiance-bootstrap] Add Ansible lockfile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 19:29:20 +01:00
9bfb0c130a feat(dashboard): Repos page with coverage map; expose last_sbom_at on RepoRead
- RepoRead schema: add last_sbom_at + sbom_source fields (already in model,
  now surfaced in API response)
- repos.md: new dashboard page — KPI row (total/domains/ingested/gaps),
  domain-grouped coverage map with SBOM/EP/TD chips, per-repo table with
  gap highlighting, domain filter + gap-only toggle, ingest how-to section
- observablehq.config.js: add Repos after Domains in nav

Coverage state: 3 repos registered (custodian×1, railiance×2);
2 ingested (the-custodian + railiance-hosts), 1 gap (railiance-bootstrap
— infra-only, no lockfile, expected)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 18:53:25 +01:00
fae9151144 feat(sbom): add Terraform .terraform.lock.hcl parser; ingest railiance repos
- ingest_sbom.py: parse .terraform.lock.hcl provider blocks (name, version);
  ecosystem stored as 'other' until terraform added to DB ENUM
- Registered railiance-bootstrap + railiance-hosts under railiance domain
- railiance-hosts ingested: 2 Terraform providers (hashicorp/template 2.2.0,
  hetznercloud/hcloud 1.52.0)
- railiance-bootstrap: no lockfile (pure Ansible/shell — noted in convention)
- sbom-convention_v0.1.md: add Terraform + Ansible rows to lockfile table;
  update registered repos status table

Total SBOM: 422 packages across 2 repos (custodian + railiance-hosts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 18:07:56 +01:00
4c157d43a8 feat(sbom): scan mode, domain grouping dashboard, SBOM convention doc
- ingest_sbom.py: add --scan flag (recursive lockfile discovery) +
  --lockfile repeatable for explicit multi-file ingestion; skip
  .venv/node_modules/.git/dist/etc; Makefile gains SCAN= and REPO_PATH= vars
- sbom.md: add /domains/ fetch; domain-level summary table; per-repo
  accordion with details/summary; domain filter on package table; dual-
  licence false-positive note; +1 KPI card (Domains Covered)
- canon/standards/sbom-convention_v0.1.md: authoritative lockfile table,
  ingest workflow (single/scan/explicit), snapshot semantics, direct-vs-
  transitive caveats, licence governance + copyleft escalation, update
  cadence, multi-repo domain pattern, planned enhancements

First ingest: the-custodian — 420 pkgs (88 python + 332 node), 13 licence
groups, 1 copyleft flag (jszip dual-licensed MIT OR GPL-3.0-or-later)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 16:15:40 +01:00
7d3487d4fe feat(state-hub): v0.3 registration workflow + ingest-sbom + CLAUDE.md template update
- scripts/ingest_sbom.py: lockfile parser + API poster for uv.lock, requirements.txt,
  package-lock.json, yarn.lock, Cargo.lock; auto-detects from repo root
- Makefile: make ingest-sbom REPO=<slug> [LOCKFILE=<path>] target
- scripts/register_project.sh: adds {REPO_SLUG} template substitution + optional
  SBOM ingest prompt at end of registration (non-fatal if venv not ready)
- scripts/project_claude_md.template: adds Contribution Tracking + SBOM sections
  documenting register_contribution(), update_contribution_status(), ingest-sbom,
  and the contrib/ directory layout
- workplans/CUST-WP-0002: all 15 tasks → done, status → completed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:28:49 +01:00
afac54ec09 feat(state-hub): v0.3 MCP tools + dashboard pages for contributions and SBOM
MCP server additions (5 tools + 3 resources):
- register_contribution(), update_contribution_status(), get_contributions()
- ingest_sbom_tool(repo_slug, lockfile_path) — shells out to ingest_sbom.py
- get_licence_report()
- state://contributions, state://sbom/aggregated, state://sbom/{repo_slug}

Dashboard pages:
- contributions.md — live-polled Kanban by status (draft→merged), filter bar
  (type/status/repo), KPI grid (total + per type), follow-up banner, full table
- sbom.md — licence distribution bar chart (Plot), copyleft risk section,
  package table with ecosystem/direct/dev filters, repo-slug resolution
- data/contributions.json.py, data/sbom.json.py — Observable data loaders
- index.md — added Contribution & SBOM Health KPI row (total, follow-up count,
  copyleft risk indicator; sourced from state summary fields)
- observablehq.config.js — added Contributions + SBOM to nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:28:41 +01:00
8d38110275 feat(state-hub): v0.3 schema — contributions + sbom_entries migrations, models, schemas, routers
Migrations (chain: b1c2d3e4f5a6 → c2d3e4f5a6b7 → d3e4f5a6b7c8):
- c2d3e4f5a6b7: contributions table (contributiontype BR/FR/EP/UPR enum,
  contributionstatus 7-state lifecycle, FKs to topics/workstreams)
- d3e4f5a6b7c8: sbom_entries table (ecosystem enum, snapshot-based replacement),
  + sbom_source + last_sbom_at columns on managed_repos

New models: Contribution (ContributionType, ContributionStatus), SBOMEntry (Ecosystem)
Modified: ManagedRepo (sbom_source, last_sbom_at columns)

New routers:
- /contributions/ — CRUD + lifecycle-guarded PATCH /status + soft-delete (withdrawn)
- /sbom/ — ingest (replace snapshot), list, per-repo view, licence report

Modified:
- /state/summary now includes contribution_counts and licence_risk_count
- main.py: registers contributions + sbom routers; bumps version to 0.6.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:28:27 +01:00
6edd39f4b8 fix(cli): auto-create topic when registering a brand-new domain
register-project now creates a topic automatically if the domain has
no active topic yet, instead of exiting with an error. This makes the
"create domain → register project" flow self-contained.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:40:32 +01:00
d734c50289 fix(cli): replace hardcoded VALID_DOMAINS with live /domains/ API lookup
The custodian CLI had a static VALID_DOMAINS list used as argparse
choices= and for in-process domain validation, preventing any domain
added after v0.5 from being used. Now fetches active domains from the
API at runtime. Also fixes t.get("domain") → t.get("domain_slug")
in two topic lookup sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:39:34 +01:00
fccb7b2375 fix(dashboard): replace stale t.domain with t.domain_slug across all pages
After the v0.5 migration TopicRead.domain was renamed to domain_slug.
index.md, decisions.md and tasks.md still referenced the old field,
causing every workstream domain to fall back to "unknown". Also
updated tasks.md to load the domain filter list dynamically from
/domains/ instead of the hardcoded 6-slug array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:31:28 +01:00
eaad6b591c fix(state-hub): repair await-in-generator in _derive_next_steps
str.join() is synchronous and cannot consume a generator that uses await.
Build the blocker slugs list with an explicit async for loop instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:27:26 +01:00
fcd0f06536 feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.

P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
  ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
  rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
  backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup

P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/

P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
  archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
  DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
  repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section

P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
  dynamic domain list loaded from /domains/ API (no longer hardcoded)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
c3efb099f1 feat(custodian): add ADR-001 compliance validator
Scripts, Makefile target, and MCP tool for checking a repository
against ADR-001 (workplans as repo artefacts, state-hub as cache).

Checks performed:
  File-side: workplans/ dir exists, valid YAML frontmatter (required
  fields, type, status, id format), filename matches id, embedded
  task blocks have id/status/priority.

  State-hub cross-reference: state_hub_workstream_id references
  resolve to real DB records; orphan detection flags active DB
  workstreams with no backing workplan file.

Usage:
  make validate-adr REPO=<path> [DOMAIN=<slug>]
  validate_repo_adr(repo_path, domain_slug?)  # MCP tool

Running against the-custodian itself correctly surfaces the 4
pre-ADR-001 workstreams that still need workplan files written.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:00:09 +01:00
0546a1bb2a feat(dashboard): add entity detail modal and fixed-layout tables
Replace Inputs.table() with buildEntityTable() across workstreams and
tasks pages. Add click-to-detail modal (openEntityModal) on all entity
list views: workstreams, tasks, extension points, and technical debt.

- New component: src/components/entity-modal.js
  - openEntityModal(entity, type) — full-detail overlay (Esc/click-outside to close)
  - buildEntityTable(rows, cols, onRowClick) — table-layout:fixed, overflow-safe wrapper
  - CSS injected lazily; no separate stylesheet required

- Tables: table-layout:fixed keeps content within the content column;
  title col 32%, workstream col 14%, all cells ellipsis + title tooltip
- Cards (EP, TD): onclick → modal; workstream name span gets title tooltip
- Blocked task cards also wired to modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 18:28:44 +01:00
bd6e16394a chore: mark llm-shared-library workstream completed
All 9 tasks done (S1.1–S3.2). llm-connect is now a standalone
installable package at /home/worsch/llm-connect, integrated into
state-hub as its first consumer (S3.1, commit 444b35d).

Also tracks workstream-kpi.md spec (previously untracked).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:53:43 +01:00
e94d7d445b feat(state-hub): integrate llm-connect as dependency (S3.1)
Add llm-connect as an editable local dependency via [tool.uv.sources].
Creates tests/test_llm_connect_integration.py: 7 offline smoke tests
covering public symbol imports, MockLLMAdapter execute/reset, RunConfig
and LLMResponse fields, and ErrorLLMAdapter error propagation.

All 7 tests pass. Satisfies workstream llm-shared-library S3.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:32:17 +01:00
f8e76deeaa feat(dashboard): replace title tooltips with <help-tip> web component
New custom element (src/components/help-tip.js):
- Floating card appears on hover/focus, appended to document.body
  (position:fixed) so it escapes overflow:hidden in the TOC sidebar
- Attributes: label (bold), description (body), doc (optional
  "Learn more →" link)
- Mouse-over-card grace period so the link stays reachable
- Correct viewport clamping (horizontal + prefer-above/fallback-below)

workstreams.md:
- WHI metric abbreviations (DD/BR/SPR/PEP/CDDR) now use <help-tip>
  with full name, one-sentence description, and doc link
- Domain breakdown labels show domain-scoped stats (open count,
  blocked%, runnable%) and a doc link
- Cycle ⚠ icon upgraded to <help-tip> with explanation
- Removed dotted underline; cursor:help comes from the element CSS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:11:09 +01:00
29a0368e6d feat(dashboard): add mouseover tooltips to WHI metric abbreviations
DD, BR, SPR, PEP, CDDR now show full name + one-sentence explanation
on hover via title attributes. Metric labels get a dotted underline
and cursor:help to signal they are hoverable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:03:19 +01:00
090a206f3d feat(state-hub): add Extension Points and Technical Debt tracking
New entity types (DB tables, API routers, Pydantic schemas, Alembic
migration a3f1c2d4e5b6):
- extension_points: ep_id, domain, title, ep_type, status, priority,
  location, description, topic_id, workstream_id
- technical_debt: td_id, domain, title, debt_type, severity, status,
  location, description, topic_id, workstream_id

MCP server: 6 new tools — register_extension_point, list_extension_points,
update_ep_status, register_technical_debt, list_technical_debt,
update_td_status (each write emits a progress_event)

Dashboard: two new pages (extensions.md, techdept.md) with KPI sidebar,
charts, urgent-items section, and filterable card lists. Both added to
nav in observablehq.config.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:29:51 +01:00
c0f6f01bff Add Tasks dashboard page
New page with:
- Data fetch: /tasks/ + /workstreams/ + /topics/ in parallel, enriched
  with domain and workstream_title per task
- Task Overview KPI card in TOC sidebar: open / blocked (red if >0) /
  in progress / done with % of total
- Status Distribution chart (horizontal bar, colour-coded by status)
- Blocked Tasks section: cards with priority badge, domain, workstream,
  blocking_reason highlighted in amber
- All Tasks: filterable table (status, priority, domain, assignee
  multiselect + text), sorted blocked→in_progress→todo→done, 25 rows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 01:04:19 +01:00
8decb6a4df Implement Workstream Health Index (WHI) KPI card
Add a live WHI card to the Workstreams page TOC sidebar. All six base
metrics from the spec (workstream-kpi.md) computed client-side from
existing data — no API or schema changes required.

Computation (workstreams.md):
- DD: dependency edges / open workstreams (normalised at DD_crit=1.0)
- BR: blocked workstreams / open workstreams
- SPR: max inbound deps on one incomplete workstream / open count
- PEP: active workstreams with all deps completed / open count
- CDDR: cross-domain edges / total edges
- CPI: DFS cycle detection (back-edge = 1, halves WHI as hard penalty)
- WHI = 0.30(1-DDnorm) + 0.25(1-BR) + 0.15(1-SPR) + 0.20·PEP + 0.10(1-CDDR)
- Per-domain breakdown using intra-domain edges only

Card UI: global WHI % with green/orange/red health label, sub-metric
rows with per-spec warning thresholds, cycle alert panel, per-domain
breakdown rows with coloured dots.

Also add src/docs/workstream-health-index.md reference page (formula,
thresholds, improvement guidance) and wire ? button on the card.
Add "Workstream Health" to Reference nav.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 00:03:27 +01:00
a3d989bfc8 Add Decisions and Workstreams reference docs with heading help wiring
- Remove residual constitution footnote from progress page header
- Create src/docs/decisions.md: types, statuses, resolution history chart,
  filter bar, card anatomy, Decision Health KPI, escalation protocol
- Create src/docs/workstreams.md: status distribution chart, filter bar,
  table columns, dependency graph, create/update patterns
- Wire withDocHelp(h1) on Decisions and Workstreams pages pointing to new docs
- Add both pages to Reference nav section in observablehq.config.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 18:12:12 +01:00
f829bed6b2 dashboard: add progress log documentation and ? button on page heading
- src/docs/progress-log.md: covers append-only constitution §5 guarantee,
  event structure (all fields), standard + convention event types, session
  protocol, MCP and curl usage, filters, and the 30-day volume chart
- progress.md: withDocHelp applied to #observablehq-main h1 → ? button
  appears on hover over the 'Progress Log' page heading
- observablehq.config.js: Progress Log added to Reference nav section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 18:03:05 +01:00
c780255eaf dashboard: move Open Workstreams by Domain chart to top of overview page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:49:12 +01:00
a8d2382e64 dashboard: add 'Event Log' subtitle above filtered table on progress page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:06:28 +01:00
a0b65ca803 dashboard: add 'All Workstreams' subtitle above filtered table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:50:44 +01:00
c986957fad dashboard: move charts to top of main content on workstreams and progress pages
workstreams: decouple filter form from display (Generators.input pattern);
Status Distribution chart now renders before the filter bar and table.
Dependencies section follows after.

progress: Event Volume chart moved above the filter controls and table;
no reactive changes needed (chart uses raw data, not filtered).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:49:33 +01:00
129a6cc919 dashboard: add card padding to live indicator; fix ? button vertical position
Replaces padding-right-only with full padding (0.55rem top/bottom, 0.7rem
left, 1.8rem right). The top padding gives the absolute-positioned ? button
a proper anchor and stops it sitting too low against the text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:42:00 +01:00
298a5184cd dashboard: add Reference nav section with Live Data and Decision Health docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:37:10 +01:00
816f1e25f1 dashboard: move live indicator to TOC sidebar on all pages; add live-data docs
- All four pages (index, workstreams, decisions, progress) now inject the
  live indicator into #observablehq-toc via injectTocTop("live-indicator", el)
  Left-aligned (no text-align: right), position:relative + padding-right for
  the ? button affordance

- decisions.md: splits the former combined "decisions-sidebar" widget into two
  separate injectTocTop calls — KPI box first (ends lower), live indicator
  second (ends at top); both now have their own stable ids

- withDocHelp(_liveEl, "/docs/live-data") wires the ? button on every page

- src/docs/live-data.md: new documentation page explaining poll interval (15s),
  indicator colour semantics, offline recovery, and which endpoints each page hits

- Removes the .live-bar CSS class from all pages; replaces with .live-indicator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 16:18:09 +01:00
902aafcfb1 dashboard: add toc-sidebar utility; move live indicator into TOC column
Extracts the TOC-injection pattern into a reusable component:

  src/components/toc-sidebar.js
    injectTocTop(id, element) — prepends an element to #observablehq-toc,
    removing any previous instance with the same id first so reactive cells
    can re-inject on each poll without accumulating duplicates.

decisions.md now uses injectTocTop to place a single widget (live
indicator + Decision Health KPI box) into the right-column sidebar,
removing the standalone live-indicator cell and the ad-hoc id/remove
pattern that was previously inlined.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 14:42:38 +01:00
5b743196db dashboard: remove stale KPI box before re-inserting on each poll
Assign a stable id to the KPI element so the previous instance can be
found and removed before the fresh one is prepended to the TOC sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 13:33:23 +01:00
e56e63b1f5 dashboard: move Decision Health card into TOC sidebar column
Inject the KPI box into #observablehq-toc (the framework's right-column
aside) instead of position:fixed. The TOC column already doesn't scroll,
so no CSS tricks are needed. Falls back to a float:right inline block if
the TOC element is absent.

- Remove .kpi-sidebar-outer and its fixed positioning
- Remove min-width/max-width from .kpi-infobox; add margin-bottom to
  separate it from the TOC links below

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 13:29:34 +01:00
aab8bb1bbb dashboard: fix KPI sidebar to fixed top-right position
- `.kpi-sidebar-outer` is now `position: fixed; top: 3.75rem; right: 1.5rem;`
  so the Decision Health box stays visible while scrolling
- Re-adds the live indicator as a standalone cell (was accidentally dropped
  when the combined `decisions-header` flex layout was removed)
- CSS: replaces `.decisions-header` block with `.kpi-sidebar-outer`;
  `.live-indicator` is now standalone (text-align right, margin-bottom)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 12:07:49 +01:00
3212a5be93 dashboard: prominent KPI infobox, doc-overlay component, decisions reference page
KPI infobox
- Replace slim kpi-bar with a boxed card (border, shadow, 195–240px) floating
  right in a flex header alongside the live indicator
- Rows: avg resolve time (last ≤5 resolved) + avg open age (all open)
- avg open age colored via CSS var --oc: red/orange/black per threshold
- "no open decisions" shown as muted italic when queue is empty

doc-overlay component (src/components/doc-overlay.js)
- withDocHelp(element, docPath) — adds absolute-positioned ? button
  that is invisible until the parent is hovered; click opens overlay
- Overlay: fixed backdrop + animated box with iframe; closes on Esc,
  backdrop click, or the close button
- CSS injected once via style tag (STYLE_ID guard, same pattern as MultiSelect)

? buttons wired up in decisions.md
- KPI infobox → /docs/decisions-kpi
- Cumulative chart (wrapped in position:relative div) → /docs/decisions-kpi
- Filter & List section header → /docs/decisions-kpi

Reference page (src/docs/decisions-kpi.md)
- Standalone Observable Framework page at /docs/decisions-kpi
- Documents: KPI card (avg resolve, avg open age, color thresholds),
  Resolution History chart (cumulative, period→resolution mapping, filter
  interaction, timestamp logic), Filter & List (type/status/search, card
  age badge, escalation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 11:48:47 +01:00
61c43af3a4 dashboard: decision age, KPI bar, and open-age health indicator
- KPI bar top-right: avg resolution time (last ≤5 resolved decisions)
  and avg open age with count; replaces old live-bar with kpi-bar row
- Color logic for avg-open-age KPI:
    red    — mean open age > avg resolve time
    orange — any single open decision exceeds avg resolve time
    black  — all open decisions younger than avg resolve time
- Decision cards: age badge in header showing "open Xd/h/w" for
  open/escalated or "took X" for resolved/superseded; orange when an
  open decision has aged past the avg resolve time baseline
- fmtDuration() helper: compact duration formatting (m/h/d/w/mo)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 07:50:54 +01:00
02b2542a2a dashboard: cumulative decisions chart with flexible period selector
- Replace static per-month bar chart with cumulative step-area chart
- Period selector: day / week / month / quarter / YTD / year / all
- Time resolution adapts to period:
    day → hours, week → days, month → weeks,
    quarter/YTD/year/all → months
- Chart respects the type/status/search filter (uses filtered, not data)
- Chart and period selector appear before the filter form and list
- Use Generators.input() to decouple filter form creation from its
  display position; display(_filtersForm) renders it below the chart
- Dots on chart mark buckets where decisions occurred; tip shows delta
- "all" period derives start from earliest decision in filtered set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 07:22:25 +01:00
a74bb9f732 Dashboard decisions: list view with MultiSelect filters
- Replace Pending/Made tab + Inputs.table with MultiSelect filters and
  a proper list view
- Filters: Type (pending/made), Status (open/escalated/resolved/superseded),
  title text search — all stable across polls (no data dependency)
- Each decision renders as a compact card: left border coloured by status
  (blue=open, amber=escalated, green=resolved, gray=superseded), type and
  status badges, domain context, deadline (red+overdue warning if past),
  full title, description/rationale snippet, resolved-by attribution
- Decisions sorted: escalated → open → resolved → superseded, then by
  deadline ascending within each group
- Fetch now includes topics alongside decisions for domain name join
- Escalation warning box and velocity chart retained

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 00:39:44 +01:00
154ec47046 Dashboard: reusable MultiSelect dropdown component for workstreams filters
Adds src/components/multiselect.js — a compact dropdown multi-select that is
Observable-compatible (exposes .value, dispatches bubbling input events) so it
works with view(), Inputs.form, and Generators.input without modification.

Component behaviour:
- Closed state: pill button showing "Label: All" (muted) or active selection
  (1-2 items shown by name, 3+ shown as "N of M"); blue border when active
- Open state: dropdown with per-item checkboxes + "Clear selection" link
  (only visible when something is selected); closes on outside click / Escape
- Styles injected once into document.head (STYLE_ID guard prevents duplicates)
- Uses CSS custom properties for light/dark mode compatibility

Workstreams page update:
- Domain and Status filters now use MultiSelect instead of Inputs.checkbox
- Filter bar layout reduced to a tight inline row (0.5rem gap)
- Owner text filter restyled to match trigger button height
- No changes to filter logic or downstream cells (filters.domain / .status
  are still string[] with empty = show all)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 00:19:58 +01:00
de936acd6d Dashboard workstreams: multi-select filters that survive data polls
- Replace single-select Domain/Status dropdowns with checkbox multi-selects
- Use Inputs.form() with a custom template to lay the three filters out
  side by side in a card-style filter bar
- Filter options are now static constants (DOMAINS, STATUSES) — no
  dependency on the polled data, so selections are never reset on refresh
- Empty selection = no filter applied (show all); any checked item = include
- Updated filtered computation and wsWithDeps to use filters.domain /
  filters.status array semantics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 00:05:58 +01:00
da71a1bfac Dashboard: make status cards interactive links
- Active Workstreams → navigates to ./workstreams page
- Blocking Decisions → anchor-scrolls to #blocking-decisions section
- Blocked Tasks → click toggles inline panel showing each blocked task
  with workstream name and blocking reason; label toggles expand/collapse
- Events Today → anchor-scrolls to #recent-activity section
- All cards get hover lift effect (box-shadow + 1px translateY)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 23:43:44 +01:00
f34b49ebde Implement State Hub v0.2: dependency graph, next-steps suggestions, design boundary
S0 — Design boundary formalised across all integration surfaces:
- TOOLS.md restructured with Design Boundary section, Sanctioned Write Tools,
  and Bootstrap-Only Tools (create_workstream, create_task) with explicit note
- project_claude_md.template and railiance CLAUDE.md updated with boundary note
  and get_next_steps() in session start protocol
- Global ~/.claude/CLAUDE.md updated accordingly

S1 — Workstream dependency graph:
- WorkstreamDependency model (directed edge, CASCADE on delete, unique pair constraint)
- Alembic migration 0b547c153153; script.py.mako added (was missing)
- REST API: POST/GET /workstreams/{id}/dependencies/, DELETE …/{dep_id} (hard delete)
- StateSummary open_workstreams enriched with depends_on/blocks lists
- MCP tools: create_dependency(), list_dependencies()
- Dashboard workstreams page: Dependencies section with relationship cards
- Seeded: custodian-agent-runtime → llm-shared-library + phase-0-operational-baseline

S2 — Suggesting Next Steps (sanctioned write use case #2):
- GET /state/next_steps derives suggestions from recently resolved decisions
  (→ first open task in same workstream) and cleared dependencies
  (→ first todo task in now-unblocked workstream)
- StateSummary.next_steps included on every summary call
- MCP tool: get_next_steps()
- Dashboard: "What's next?" card grid above Registered Projects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 23:33:14 +01:00
9965349135 Dashboard decisions: stable form inputs + copy to clipboard
Polling fix:
- Blocking decisions now use a Mutable (blockingDecisions) + refreshDecisions()
  instead of deriving from summary.blocking_decisions
- Cards only re-render on initial page load or after a successful resolve,
  not on every 15 s summary poll — so typing in the form is never interrupted
- On successful resolve, refreshDecisions() re-fetches the list; the resolved
  decision no longer matches the open/escalated filter so it disappears cleanly

Copy to clipboard:
- Small "Copy" button in the card header (next to deadline/escalation badges)
- Formats full decision as markdown: title, description, context, status, date
- Shows "✓ Copied" for 1.5 s, reverts to "Copy"; shows "⚠ Failed" on error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 09:47:52 +01:00
533fecd6e1 Add in-dashboard decision resolution with project log write
API:
- DecisionResolve schema (rationale, decided_by, write_log flag)
- POST /decisions/{id}/resolve — marks resolved, emits progress event,
  appends entry to DECISIONS.md in the project's registered directory
  (found via the topic's registration milestone event)

Dashboard:
- Replace Inputs.table for blocking decisions with full-text cards
- Each card shows title, full description (pre-wrap), rationale/context,
  escalation warning if present
- Expandable "Resolve →" section with rationale textarea, decided-by
  input, submit button that calls the resolve endpoint
- On success: collapses form, dims card, confirms log was written

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 09:34:35 +01:00