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>
This commit is contained in:
2026-02-25 23:33:14 +01:00
parent 9965349135
commit f34b49ebde
15 changed files with 678 additions and 35 deletions

View File

@@ -108,6 +108,40 @@ display(html`<div class="grid grid-cols-4" style="gap:1rem;margin-bottom:1.5rem"
</div>`);
```
## What's next?
```js
// next_steps comes from the summary poll (derived, never persisted)
const nextSteps = summary.next_steps ?? [];
const typeLabel = {
resolved_decision: "Decision resolved",
dependency_cleared: "Dependency cleared",
unblocked_task: "Task unblocked",
};
const typeBadgeClass = {
resolved_decision: "ns-badge-decision",
dependency_cleared: "ns-badge-dep",
unblocked_task: "ns-badge-task",
};
if (nextSteps.length === 0) {
display(html`<p class="ns-empty">No actionable suggestions right now — all open workstreams are making progress or waiting on decisions.</p>`);
} else {
display(html`<div class="ns-grid">${nextSteps.map(s => html`
<div class="ns-card">
<div class="ns-card-header">
<span class="ns-badge ${typeBadgeClass[s.type] ?? ''}">${typeLabel[s.type] ?? s.type}</span>
<span class="ns-domain">${s.domain ?? "—"}</span>
</div>
<div class="ns-ws">${s.workstream_title ?? "—"}</div>
<div class="ns-task">${s.task_title ? html`→ <strong>${s.task_title}</strong>` : ""}</div>
<div class="ns-msg">${s.message}</div>
</div>
`)}</div>`);
}
```
## Registered Projects
```js
@@ -358,4 +392,17 @@ display(Inputs.table((summary.recent_progress ?? []).map(e => ({
.r-msg { font-size: 0.8rem; color: #b45309; }
.r-copy { padding: 0.15rem 0.55rem; border-radius: 3px; border: 1px solid var(--theme-foreground-faint); background: var(--theme-background); color: var(--theme-foreground-muted); cursor: pointer; font-size: 0.75rem; }
.r-copy:hover { background: var(--theme-background-alt); }
/* What's next */
.ns-empty { color: gray; font-style: italic; }
.ns-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
.ns-card { background: var(--theme-background-alt); border-radius: 8px; padding: 0.85rem 1rem; border-left: 4px solid #555; }
.ns-card-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; }
.ns-badge { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em; padding: 0.15rem 0.45rem; border-radius: 10px; font-weight: 600; }
.ns-badge-decision { background: #d4edda; color: #155724; }
.ns-badge-dep { background: #cce5ff; color: #004085; }
.ns-badge-task { background: #fff3cd; color: #856404; }
.ns-domain { font-size: 0.75rem; color: gray; }
.ns-ws { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.2rem; }
.ns-task { font-size: 0.85rem; margin-bottom: 0.35rem; }
.ns-msg { font-size: 0.78rem; color: #555; line-height: 1.4; }
</style>