generated from coulomb/repo-seed
Adds preferred workplan REST/event surfaces, legacy-meter telemetry and weekly review summaries, documentation/dashboard terminology updates, dashboard API loading fixes, and close-out sync for STATE-WP-0052 and STATE-WP-0054.
22 KiB
22 KiB
title
| title |
|---|
| Repositories |
import {API} from "./components/config.js";
// Fast data — page renders as soon as this resolves (~200ms)
let _repos = [], _domains = [], _sbom = [], _eps = [], _tds = [], _workstreams = [];
try {
[_repos, _domains, _sbom, _eps, _tds, _workstreams] = await Promise.all([
fetch(`${API}/repos/`).then(r => r.ok ? r.json() : []),
fetch(`${API}/domains/`).then(r => r.ok ? r.json() : []),
fetch(`${API}/sbom/`).then(r => r.ok ? r.json() : []),
fetch(`${API}/extension-points/`).then(r => r.ok ? r.json() : []),
fetch(`${API}/technical-debt/`).then(r => r.ok ? r.json() : []),
fetch(`${API}/workplans/`).then(r => r.ok ? r.json() : []),
]);
} catch {}
// DoI data — lazy-loaded. Starts as [] so the page renders immediately.
// When the fetch resolves (~6s), doiData updates and DoI badges appear.
import {Mutable} from "observablehq:stdlib";
const doiData = Mutable([]);
const doiLoading = Mutable(true);
fetch(`${API}/repos/doi/summary`)
.then(r => r.ok ? r.json() : [])
.catch(() => [])
.then(data => { doiData.value = data; doiLoading.value = false; });
const repos = _repos ?? [];
const domains = _domains ?? [];
const sbom = _sbom ?? [];
const eps = _eps ?? [];
const tds = _tds ?? [];
const workstreams = _workstreams ?? [];
const doi = doiData; // reactive — updates when lazy fetch completes
// DoI lookups
const doiBySlug = Object.fromEntries(doi.map(d => [d.repo_slug, d]));
const DOI_TIER_ORDER = {none: 0, core: 1, standard: 2, full: 3};
const DOI_TIER_COLOR = {none: "#ef4444", core: "#f97316", standard: "#eab308", full: "#22c55e"};
const DOI_TIER_BG = {none: "#fef2f2", core: "#fff7ed", standard: "#fefce8", full: "#f0fdf4"};
const DOI_TIER_LABEL = {none: "None", core: "Core", standard: "Standard", full: "Full"};
// Lookups
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
const integratingBySlug = Object.fromEntries(
workstreams
.filter(w => w.status === "active" && w.slug?.startsWith("repo-integration-"))
.map(w => [w.slug.replace("repo-integration-", ""), w])
);
// Per-repo SBOM stats (from sbom entries)
const sbomByRepo = {};
for (const e of sbom) {
if (!sbomByRepo[e.repo_id]) sbomByRepo[e.repo_id] = { count: 0, snapshot_at: e.snapshot_at };
sbomByRepo[e.repo_id].count++;
}
// Per-domain counts
const epByDomain = {};
const tdByDomain = {};
const contribByDomain = {};
// EPs are domain-scoped
for (const ep of eps) {
if (!ep.status || ep.status === "open" || ep.status === "in_progress") {
epByDomain[ep.domain] = (epByDomain[ep.domain] ?? 0) + 1;
}
}
for (const td of tds) {
if (!td.status || td.status === "open" || td.status === "in_progress") {
tdByDomain[td.domain] = (tdByDomain[td.domain] ?? 0) + 1;
}
}
// Contributions: try to map via workstream → 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
const repoRows = repos
.filter(r => r.status === "active")
.map(r => {
const domain = domainById[r.domain_id];
const domSlug = domain?.slug ?? "—";
const domName = domain?.name ?? "—";
const sbomData = sbomByRepo[r.id];
const hasSbom = !!sbomData || !!r.last_sbom_at;
const pkgCount = sbomData?.count ?? 0;
const lastScan = r.last_sbom_at
? new Date(r.last_sbom_at).toLocaleDateString()
: (sbomData?.snapshot_at ? new Date(sbomData.snapshot_at).toLocaleDateString() : null);
const integrating = !!integratingBySlug[r.slug];
const doiEntry = doiBySlug[r.slug] ?? null;
const doiTier = doiEntry?.tier ?? "none";
return {
_id: r.id,
_domSlug: domSlug,
_hasSbom: hasSbom,
_integrating: integrating,
_doiTier: doiTier,
repo: r.slug,
domain: domName,
status: integrating ? "⚙ integrating" : "ready",
path: r.local_path ?? "—",
sbom: hasSbom ? `✓ ${lastScan}` : "⚠ not ingested",
pkgs: pkgCount || (hasSbom ? "—" : 0),
eps: epByDomain[domSlug] ?? 0,
tds: tdByDomain[domSlug] ?? 0,
};
})
.sort((a, b) => a._domSlug.localeCompare(b._domSlug) || a.repo.localeCompare(b.repo));
const gapCount = repoRows.filter(r => !r._hasSbom).length;
const coveredCount = repoRows.filter(r => r._hasSbom).length;
const integratingCount = repoRows.filter(r => r._integrating).length;
const doiFullCount = repoRows.filter(r => r._doiTier === "full").length;
const doiNoneCount = repoRows.filter(r => r._doiTier === "none").length;
Repositories
import {withDocHelp} from "./components/doc-overlay.js";
const _h1 = document.querySelector("#observablehq-main h1");
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/repos"); }
display(html`<p style="font-size:0.85rem;color:#6b7280;margin-top:-0.5rem;display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;">
<span>DoI tiers: <strong style="color:#ef4444;">None</strong> →
<strong style="color:#f97316;">Core</strong> →
<strong style="color:#eab308;">Standard</strong> →
<strong style="color:#22c55e;">Full</strong> —
<a href="/policy/repo-doi" style="color:#1d4ed8;">Definition of Integrated policy ↗</a></span>
${doiLoading ? html`<span style="display:inline-flex;align-items:center;gap:0.35rem;color:#9ca3af;font-size:0.8rem;">
<span class="doi-spinner"></span> Loading DoI tiers…
</span>` : html`<span style="color:#16a34a;font-size:0.8rem;">✓ DoI tiers loaded</span>`}
</p>`);
// Summary KPIs
display(html`<div class="kpi-row">
<div class="card">
<h3>Registered Repos</h3>
<p class="big-num">${repoRows.length}</p>
</div>
<div class="card">
<h3>Domains</h3>
<p class="big-num">${new Set(repoRows.map(r => r._domSlug)).size}</p>
</div>
<div class="card ${integratingCount > 0 ? 'card-integrating' : ''}">
<h3>Integrating</h3>
<p class="big-num">${integratingCount}</p>
<small>${integratingCount === 0 ? "✓ All repos integrated" : `⚙ ${integratingCount} onboarding`}</small>
</div>
<div class="card ${coveredCount < repoRows.length ? '' : ''}">
<h3>SBOM Ingested</h3>
<p class="big-num">${coveredCount} / ${repoRows.length}</p>
</div>
<div class="card ${gapCount > 0 ? 'card-warn' : 'card-ok'}">
<h3>SBOM Gaps</h3>
<p class="big-num">${gapCount}</p>
<small>${gapCount === 0 ? "✓ All repos covered" : `⚠ ${gapCount} repo(s) not ingested`}</small>
</div>
<div class="card ${doiLoading ? '' : doiNoneCount > 0 ? 'card-warn' : 'card-ok'}">
<h3>DoI: Fully Integrated</h3>
<p class="big-num">${doiLoading ? html`<span class="doi-spinner" style="width:1.4rem;height:1.4rem;"></span>` : `${doiFullCount} / ${repoRows.length}`}</p>
<small>${doiLoading ? "Loading…" : doiNoneCount > 0 ? `⚠ ${doiNoneCount} at tier None` : "✓ All pass Core tier"}</small>
</div>
</div>`);
Coverage Map
// Returns a new "⚠ not ingested" span with a ? help button each time it's called.
function _sbomGap() {
const el = html`<span class="sbom-warn sbom-gap-hint">⚠ not ingested</span>`;
withDocHelp(el, "/docs/sbom");
return el;
}
function _doiBadge(tier) {
if (doiLoading) return html`<span style="color:#d1d5db;font-size:0.72rem;">…</span>`;
const color = DOI_TIER_COLOR[tier] || "#9ca3af";
const bg = DOI_TIER_BG[tier] || "#f9fafb";
const label = DOI_TIER_LABEL[tier] || tier;
return html`<span style="background:${bg}; color:${color}; border:1px solid ${color}60;
border-radius:4px; padding:1px 7px; font-size:0.72rem; font-weight:700; white-space:nowrap;">
DoI: ${label}</span>`;
}
// Group by domain
const byDomain = {};
for (const r of repoRows) {
(byDomain[r._domSlug] = byDomain[r._domSlug] ?? []).push(r);
}
const domainBlocks = Object.entries(byDomain).sort(([a], [b]) => a.localeCompare(b));
if (domainBlocks.length === 0) {
display(html`<p style="color:gray">No repos registered. Run <code>make add-repo DOMAIN=<slug> SLUG=<slug> NAME="..." PATH=/path</code>.</p>`);
} else {
display(html`<div class="domain-list">
${domainBlocks.map(([slug, rows]) => {
const dom = domainBySlug[slug];
const allCovered = rows.every(r => r._hasSbom);
const doiWorst = rows.map(r => DOI_TIER_ORDER[r._doiTier] ?? 0);
const doiMin = Math.min(...doiWorst);
const doiMinKey = Object.keys(DOI_TIER_ORDER).find(k => DOI_TIER_ORDER[k] === doiMin) ?? "none";
const hasEps = (epByDomain[slug] ?? 0) > 0;
const hasTds = (tdByDomain[slug] ?? 0) > 0;
return html`
<div class="domain-block ${allCovered ? '' : 'domain-gap'}">
<div class="domain-header">
<span class="domain-name">${dom?.name ?? slug}</span>
<span class="domain-chips">
${allCovered
? html`<span class="chip chip-ok">SBOM ✓</span>`
: html`<span class="chip chip-warn">SBOM ⚠</span>`}
${hasEps
? html`<span class="chip chip-ok">EPs: ${epByDomain[slug]}</span>`
: html`<span class="chip chip-neutral">EPs: —</span>`}
${hasTds
? html`<span class="chip chip-ok">TDs: ${tdByDomain[slug]}</span>`
: html`<span class="chip chip-neutral">TDs: —</span>`}
<span style="font-size:0.72rem; color:${DOI_TIER_COLOR[doiMinKey]}; font-weight:600;">
DoI min: ${DOI_TIER_LABEL[doiMinKey]}
</span>
</span>
</div>
<table class="repo-table">
<thead><tr>
<th>Repo</th>
<th>DoI Tier</th>
<th>Status</th>
<th>SBOM</th>
<th>Packages</th>
<th>Local path</th>
</tr></thead>
<tbody>
${rows.map(r => html`<tr class="${r._integrating ? 'row-integrating' : r._hasSbom ? '' : 'row-gap'}">
<td class="repo-cell"><code>${r.repo}</code></td>
<td>${_doiBadge(r._doiTier)}</td>
<td>${r._integrating
? html`<span class="chip chip-integrating">⚙ integrating</span>`
: html`<span class="chip chip-ok">ready</span>`}</td>
<td class="${r._hasSbom ? 'sbom-ok' : 'sbom-warn'}">${r._hasSbom ? r.sbom : _sbomGap()}</td>
<td>${r.pkgs}</td>
<td class="path-cell">${r.path}</td>
</tr>`)}
</tbody>
</table>
</div>
`;
})}
</div>`);
}
All Repos Table
const domainFilter = Inputs.select(["all", ...new Set(repoRows.map(r => r._domSlug)).values()], {label: "Domain", value: "all"});
const doiFilter = Inputs.select(["all", "none", "core", "standard", "full"], {label: "DoI tier", value: "all"});
const gapFilter = Inputs.toggle({label: "Gaps only (no SBOM)", value: false});
display(html`<div style="display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1rem">${domainFilter}${doiFilter}${gapFilter}</div>`);
const filteredRows = repoRows.filter(r =>
(domainFilter.value === "all" || r._domSlug === domainFilter.value) &&
(doiFilter.value === "all" || r._doiTier === doiFilter.value) &&
(!gapFilter.value || !r._hasSbom)
);
display(Inputs.table(filteredRows.map(r => ({
Repo: r.repo,
Domain: r.domain,
"DoI Tier": DOI_TIER_LABEL[r._doiTier] ?? r._doiTier,
Status: r.status,
SBOM: r.sbom,
Pkgs: r.pkgs,
"EPs (domain)": r.eps || "—",
"TDs (domain)": r.tds || "—",
Path: r.path,
})), {maxWidth: 1200}));
Onboard a New Repo
const _h2onboard = [...document.querySelectorAll("#observablehq-main h2")]
.find(h => h.textContent.includes("Onboard a New Repo"));
if (_h2onboard) { _h2onboard.style.position = "relative"; withDocHelp(_h2onboard, "/docs/repo-integration"); }
const onboardId = `onboard-${Math.random().toString(36).slice(2)}`;
const onboardDomainOptions = [...domains]
.sort((a, b) => a.slug.localeCompare(b.slug))
.map(d => html`<option value=${d.slug}>${d.slug}</option>`);
const onboardForm = html`<div class="onboard-action" id=${onboardId}>
<div class="onboard-action-head">
<div>
<h3>Add Repo</h3>
<p>Register an accessible git working copy and write the starter files for the selected agent profile.</p>
</div>
<button type="button" class="onboard-primary">Add Repo</button>
</div>
<div class="onboard-grid">
<label>
<span>Domain</span>
<select class="onboard-domain">${onboardDomainOptions}</select>
</label>
<label>
<span>Agent profile</span>
<select class="onboard-agent">
<option value="codex">Codex (AGENTS.md)</option>
<option value="claude-code">Claude Code (CLAUDE.md + rules)</option>
</select>
</label>
<label class="onboard-path-label">
<span>Repo path visible to State Hub</span>
<input class="onboard-path" type="text" placeholder="/home/worsch/example-repo" />
</label>
<label class="onboard-check">
<input class="onboard-additional" type="checkbox" />
<span>Additional repo for an existing domain</span>
</label>
</div>
<p class="onboard-path-note">Paste the absolute path when the checkout is on the State Hub host or exposed via ops-bridge.</p>
<pre class="onboard-status" aria-live="polite"></pre>
</div>`;
display(onboardForm);
{
const button = onboardForm.querySelector(".onboard-primary");
const status = onboardForm.querySelector(".onboard-status");
const pathInput = onboardForm.querySelector(".onboard-path");
button.addEventListener("click", async () => {
const body = {
domain_slug: onboardForm.querySelector(".onboard-domain").value,
project_path: pathInput.value.trim(),
agent_profile: onboardForm.querySelector(".onboard-agent").value,
additional: onboardForm.querySelector(".onboard-additional").checked,
};
if (!body.project_path) {
status.textContent = "Enter an absolute path that the State Hub API can access.";
pathInput.focus();
return;
}
button.disabled = true;
status.textContent = "Onboarding started...";
try {
const response = await fetch(`${API}/repos/onboard`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(body),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const detail = payload.detail ?? payload;
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail, null, 2));
}
status.textContent = [
`Onboarding complete${payload.repo_slug ? `: ${payload.repo_slug}` : ""}`,
"",
payload.stdout?.trim() ?? "",
payload.stderr?.trim() ? `\nWarnings:\n${payload.stderr.trim()}` : "",
].join("\n").trim();
} catch (error) {
status.textContent = `Onboarding failed:\n${error.message}`;
} finally {
button.disabled = false;
}
});
}
display(html`<div class="onboard-panel">
<div class="onboard-step">
<span class="onboard-num">1</span>
<div>
<strong>Make the working copy accessible</strong>
<pre>git clone <remote-url> /path/to/repo</pre>
<p class="onboard-note">The path must be visible from the State Hub API host, either as a local checkout or through an ops-bridge-exposed path.</p>
</div>
</div>
<div class="onboard-step">
<span class="onboard-num">2</span>
<div>
<strong>Choose an agent profile</strong>
<pre>Codex -> AGENTS.md + SCOPE.md
Claude Code -> CLAUDE.md + .claude/rules/</pre>
<p class="onboard-note">The API keeps this as an agent profile so future native coding agents can get their own onboarding templates without changing the repo model.</p>
</div>
</div>
<div class="onboard-step">
<span class="onboard-num">3</span>
<div>
<strong>Run onboarding</strong>
<pre>scripts/register_project.sh <domain> /path/to/repo --codex</pre>
<p class="onboard-note">Use the Add Repo form above for the automatic path. The script verifies the domain, writes agent instructions, registers the repo, records host_paths for this machine, and logs a progress event.</p>
</div>
</div>
<div class="onboard-step">
<span class="onboard-num">4</span>
<div>
<strong>Start the agent and monitor here</strong>
<p class="onboard-note">Open the repo in the chosen coding agent, complete the generated TODO stubs, then use the Repositories and DoI views to track integration gaps.</p>
</div>
</div>
</div>`);