generated from coulomb/repo-seed
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>
18 KiB
18 KiB
title
| title |
|---|
| Workstreams |
const API = "http://127.0.0.1:8000";
const POLL = 15_000;
// Fetch workstreams + topics + summary (for dep graph) in parallel
const wsState = (async function*() {
while (true) {
let data = [], openWs = [], ok = false;
try {
const [rw, rt, rs] = await Promise.all([
fetch(`${API}/workstreams/`),
fetch(`${API}/topics/`),
fetch(`${API}/state/summary`),
]);
ok = rw.ok && rt.ok && rs.ok;
if (ok) {
const [wsList, topicList, summary] = await Promise.all([rw.json(), rt.json(), rs.json()]);
const topicMap = Object.fromEntries(topicList.map(t => [t.id, t]));
data = wsList.map(w => ({
...w,
domain: topicMap[w.topic_id]?.domain_slug ?? "unknown",
topic_title: topicMap[w.topic_id]?.title ?? "—",
}));
// open_workstreams from summary carry depends_on / blocks lists
openWs = summary.open_workstreams ?? [];
}
} catch {}
yield {data, openWs, ok, ts: new Date()};
await new Promise(res => setTimeout(res, POLL));
}
})();
const data = wsState.data ?? [];
const openWs = wsState.openWs ?? [];
const _ok = wsState.ok ?? false;
const _ts = wsState.ts;
// ── Workstream Health Index (WHI) ────────────────────────────────────────────
const _idToDomain = Object.fromEntries(data.map(w => [w.id, w.domain ?? "unknown"]));
const _completedIds = new Set(data.filter(w => w.status === "completed" || w.status === "archived").map(w => w.id));
const _openCount = openWs.length;
const _allEdges = openWs.flatMap(w => w.depends_on.map(d => ({from: w.id, to: d.workstream_id})));
const _totalEdges = _allEdges.length;
// Dependency Density
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
const _inbound = {};
for (const e of _allEdges) {
if (!_completedIds.has(e.to)) _inbound[e.to] = (_inbound[e.to] ?? 0) + 1;
}
const _SPR = _openCount > 0
? (Object.keys(_inbound).length > 0 ? Math.max(...Object.values(_inbound)) : 0) / _openCount
: 0;
// Parallel Execution Potential — active workstreams with all deps completed
const _PEP = _openCount > 0
? openWs.filter(w => w.status === "active" && w.depends_on.every(d => _completedIds.has(d.workstream_id))).length / _openCount
: 0;
// Cross-Domain Dependency Ratio
const _crossEdges = _allEdges.filter(e => (_idToDomain[e.from] ?? "?") !== (_idToDomain[e.to] ?? "?")).length;
const _CDDR = _totalEdges > 0 ? _crossEdges / _totalEdges : 0;
// Cycle Presence Indicator — DFS with visited/inStack colouring
function _detectCycle(nodes, edges) {
const adj = Object.fromEntries(nodes.map(n => [n.id, []]));
for (const e of edges) { if (adj[e.from] !== undefined) adj[e.from].push(e.to); }
const visited = new Set(), inStack = new Set();
function dfs(id) {
if (inStack.has(id)) return true;
if (visited.has(id)) return false;
visited.add(id); inStack.add(id);
for (const nx of (adj[id] ?? [])) { if (dfs(nx)) return true; }
inStack.delete(id);
return false;
}
for (const n of nodes) { if (!visited.has(n.id) && dfs(n.id)) return 1; }
return 0;
}
const _CPI = _detectCycle(openWs, _allEdges);
// WHI aggregation — DD normalised at DD_critical = 1.0, CPI halves the score
const _DDnorm = Math.min(1, _DD / 1.0);
let _WHI = 0.30*(1 - _DDnorm) + 0.25*(1 - _BR) + 0.15*(1 - _SPR) + 0.20*_PEP + 0.10*(1 - _CDDR);
if (_CPI === 1) _WHI *= 0.5;
_WHI = Math.max(0, Math.min(1, _WHI));
// Per-domain breakdown — intra-domain edges only (measures domain autonomy)
const _domainBreakdown = [...new Set(openWs.map(w => _idToDomain[w.id] ?? "unknown"))].sort().map(domain => {
const nodes = openWs.filter(w => (_idToDomain[w.id] ?? "unknown") === domain);
const edges = nodes.flatMap(w =>
w.depends_on
.filter(d => (_idToDomain[d.workstream_id] ?? "unknown") === domain)
.map(d => ({from: w.id, to: d.workstream_id}))
);
const oc = nodes.length;
if (oc === 0) return null;
const te = edges.length;
const dd = oc > 0 ? te / oc : 0;
const br = oc > 0 ? nodes.filter(w => w.status === "blocked").length / oc : 0;
const pep = oc > 0 ? nodes.filter(w => {
if (w.status !== "active") return false;
const intraDeps = w.depends_on.filter(d => (_idToDomain[d.workstream_id] ?? "unknown") === domain);
return intraDeps.every(d => _completedIds.has(d.workstream_id));
}).length / oc : 0;
const inb = {};
for (const e of edges) inb[e.to] = (inb[e.to] ?? 0) + 1;
const spr = oc > 0 ? (Object.keys(inb).length > 0 ? Math.max(...Object.values(inb)) : 0) / oc : 0;
const cpi = _detectCycle(nodes, edges);
const ddN = Math.min(1, dd / 1.0);
let whi = 0.30*(1 - ddN) + 0.25*(1 - br) + 0.15*(1 - spr) + 0.20*pep + 0.10; // CDDR=0 within domain
if (cpi === 1) whi *= 0.5;
return {domain, whi: Math.max(0, Math.min(1, whi)), br, pep, cpi, openCount: oc};
}).filter(Boolean);
Workstreams
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
import "./components/help-tip.js";
import {openEntityModal, buildEntityTable} from "./components/entity-modal.js";
// ── Live indicator ────────────────────────────────────────────────────────────
const _liveEl = html`<div class="live-indicator">
<span style="color:${_ok ? 'var(--theme-foreground-focus)' : 'red'}">●</span>
${_ok
? `Live · updated ${_ts?.toLocaleTimeString()}`
: html`<span style="color:red">Offline — run: <code>make api</code></span>`}
</div>`;
withDocHelp(_liveEl, "/docs/live-data");
// ── WHI card ──────────────────────────────────────────────────────────────────
function _whiColor(v) { return v >= 0.75 ? "#16a34a" : v >= 0.50 ? "#d97706" : "#dc2626"; }
function _whiLabel(v) { return v >= 0.75 ? "Healthy" : v >= 0.50 ? "Optimizable" : "Critical"; }
function _warnLevel(name, val) {
if (name === "PEP") return val < 0.30 ? 2 : val < 0.60 ? 1 : 0;
if (name === "DD") return val > 1.0 ? 2 : val > 0.50 ? 1 : 0;
if (name === "BR") return val > 0.40 ? 2 : val > 0.20 ? 1 : 0;
if (name === "SPR") return val > 0.40 ? 2 : val > 0.25 ? 1 : 0;
if (name === "CDDR") return val > 0.40 ? 1 : 0;
return 0;
}
function _warnColor(lv) { return lv === 2 ? "#dc2626" : lv === 1 ? "#d97706" : "var(--theme-foreground-muted, #666)"; }
const _whiMetrics = [
{name: "DD", val: _DD, fmt: v => v.toFixed(2), label: "Dependency Density", desc: "Average number of dependencies per open workstream; high values indicate a tightly coupled graph that is hard to parallelise."},
{name: "BR", val: _BR, fmt: v => (v*100).toFixed(0)+"%", label: "Blocked Ratio", desc: "Share of open workstreams currently in a blocked state; directly reduces the work that can proceed right now."},
{name: "SPR", val: _SPR, fmt: v => (v*100).toFixed(0)+"%", label: "Single-Point Risk", desc: "Share of workstreams depended on by others but with no incoming dependencies themselves; losing one stalls everything downstream."},
{name: "PEP", val: _PEP, fmt: v => (v*100).toFixed(0)+"%", label: "Parallel Execution Potential", desc: "Share of open workstreams with zero blocking dependencies that could start or continue immediately."},
{name: "CDDR", val: _CDDR, fmt: v => (v*100).toFixed(0)+"%", label: "Cross-Domain Dependency Ratio", desc: "Share of dependency edges that cross domain boundaries; high values mean progress in one domain is gated on another team or project."},
];
const _whiBox = html`<div class="kpi-infobox whi-box">
<div class="kpi-infobox-title">Workstream Health</div>
${_openCount === 0
? html`<div class="kpi-row"><span class="kpi-muted">No active workstreams</span></div>`
: html`
<div class="whi-score-row">
<span class="whi-value" style="color:${_whiColor(_WHI)}">${(_WHI*100).toFixed(0)}<span class="whi-pct">%</span></span>
<span class="whi-label" style="color:${_whiColor(_WHI)}">${_whiLabel(_WHI)}</span>
</div>
${_CPI === 1 ? html`<div class="whi-cycle-alert">⚠ Cycle detected — deadlock</div>` : ""}
<div class="whi-metrics">
${_whiMetrics.map(m => {
const lv = _warnLevel(m.name, m.val);
return html`<div class="whi-metric-row">
<help-tip class="whi-metric-name" style="color:${_warnColor(lv)}" label="${m.label}" description="${m.desc}" doc="/docs/workstream-health-index">${m.name}</help-tip>
<span class="whi-metric-val" style="color:${_warnColor(lv)}">${m.fmt(m.val)}</span>
</div>`;
})}
</div>
${_domainBreakdown.length > 1 ? html`
<div class="whi-domains">
<div class="whi-domain-header">by domain</div>
${_domainBreakdown.map(d => html`<div class="whi-domain-row">
<span class="whi-domain-dot" style="background:${_whiColor(d.whi)}"></span>
<help-tip class="whi-domain-name"
label="${d.domain.replaceAll('_', ' ')}"
description="Domain-scoped WHI (intra-domain edges only). Open: ${d.openCount} · Blocked: ${(d.br*100).toFixed(0)}% · Runnable: ${(d.pep*100).toFixed(0)}%"
doc="/docs/workstream-health-index">${d.domain}</help-tip>
<span class="whi-domain-score" style="color:${_whiColor(d.whi)}">${(d.whi*100).toFixed(0)}%</span>
${d.cpi === 1 ? html`<help-tip style="color:#d97706;font-size:0.7rem" label="Dependency Cycle" description="A circular dependency exists within this domain — workstreams are waiting on each other and cannot all proceed." doc="/docs/workstream-health-index">⚠</help-tip>` : ""}
</div>`)}
</div>` : ""}
`}
</div>`;
withDocHelp(_whiBox, "/docs/workstream-health-index");
// ── Inject into TOC sidebar: WHI first (lower), live last (top) ───────────────
injectTocTop("whi-kpi-box", _whiBox);
injectTocTop("live-indicator", _liveEl);
const _h1 = document.querySelector("#observablehq-main h1");
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/workstreams"); }
import {MultiSelect} from "./components/multiselect.js";
// Load domain slugs from API (dynamic — works with new domains after v0.5)
const _domainsResp = await fetch(`${API}/domains/?status=active`).catch(() => null);
const DOMAINS = _domainsResp?.ok
? (await _domainsResp.json()).map(d => d.slug)
: ["custodian", "railiance", "markitect", "coulomb_social", "personhood", "foerster_capabilities"];
const STATUSES = ["active", "blocked", "completed", "archived"];
// Create filter form without displaying — shown below the chart
const _filtersForm = Inputs.form(
{
domain: MultiSelect(DOMAINS, {label: "Domain", placeholder: "All domains"}),
status: MultiSelect(STATUSES, {label: "Status", placeholder: "All statuses"}),
owner: Inputs.text({placeholder: "Owner…", style: "width:120px"}),
},
{
template: ({domain, status, owner}) => html`<div class="filter-bar">
${domain}${status}
<div class="filter-owner">${owner}</div>
</div>`,
}
);
const filters = Generators.input(_filtersForm);
// Empty array = no filter applied (show all)
const filtered = data.filter(w =>
(filters.domain.length === 0 || filters.domain.includes(w.domain)) &&
(filters.status.length === 0 || filters.status.includes(w.status)) &&
(!filters.owner || (w.owner ?? "").toLowerCase().includes(filters.owner.toLowerCase()))
);
Status Distribution
import * as Plot from "npm:@observablehq/plot";
const byStatus = Object.entries(
filtered.reduce((acc, w) => { acc[w.status] = (acc[w.status] ?? 0) + 1; return acc; }, {})
).map(([status, count]) => ({status, count}));
display(Plot.plot({
marks: [
Plot.barX(byStatus, {y: "status", x: "count", fill: "status", tip: true}),
Plot.ruleX([0]),
],
marginLeft: 80,
width: 500,
}));
All Workstreams
display(_filtersForm);
{
// Enrich each workstream with tasks/deps data from open_workstreams summary
const _openWsMap = Object.fromEntries(openWs.map(w => [w.id, w]));
const _wsTable = buildEntityTable(
filtered,
[
{label: "Title", key: "title", cls: "et-title-col et-title-cell",
render: w => w.title},
{label: "Domain", key: "domain"},
{label: "Status", key: "status"},
{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"),
);
display(_wsTable);
}
Dependencies
// Build dep cards from the enriched open_workstreams in the summary
const wsWithDeps = openWs.filter(w => {
const domain = data.find(d => d.id === w.id)?.domain ?? "unknown";
return (filters.domain.length === 0 || filters.domain.includes(domain)) &&
(filters.status.length === 0 || filters.status.includes(w.status)) &&
(w.depends_on.length > 0 || w.blocks.length > 0);
});
if (wsWithDeps.length === 0) {
display(html`<p class="dim">No dependency edges recorded for the current filter. Use <code>create_dependency()</code> via the MCP server to link workstreams.</p>`);
} else {
display(html`<div class="dep-grid">${wsWithDeps.map(w => {
const depRows = w.depends_on.map(d =>
html`<div class="dep-row dep-on">↳ depends on <strong>${d.workstream_title}</strong>${d.description ? html` <span class="dep-desc">— ${d.description}</span>` : ""}</div>`
);
const blockRows = w.blocks.map(d =>
html`<div class="dep-row dep-block">⊳ blocks <strong>${d.workstream_title}</strong>${d.description ? html` <span class="dep-desc">— ${d.description}</span>` : ""}</div>`
);
return html`<div class="dep-card">
<div class="dep-title">${w.title}</div>
<div class="dep-status dep-status-${w.status}">${w.status}</div>
${depRows}${blockRows}
</div>`;
})}</div>`);
}