Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dashboard/dist/
node_modules/
.oven-test-*/
.playwright-cli/
.worktrees/
output/
build/
*.zip
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,54 @@ Untracked hook configs are added to `.git/info/exclude` by default; tracked conf

Use `burnlist --help` for dashboard ports, scan roots, local state paths, and Oven data bindings.

## Review Loop (Stage 1)

The built-in `review` Loop is an optional, serial foreground workflow for an
assigned item:

```text
maker -> repository check -> fresh reviewer -> convergence gate -> CLI completion
^ |
+------------ check failure / rejection
```

Inspect and explicitly trust the repository check with `burnlist loop
capability inspect|trust`, then assign an item with `burnlist loop assign`.
Burnlist stores no agent profile, provider route, subscription, or login. Run
`burnlist loop view <item-ref>`
and paste its complete ASCII output into the work handoff; it is the frozen
graph, pin, and completion-path record for that item. Create a Run with `loop
create`; for each agent node, claim it, invoke a native subagent or external
provider CLI from the host, and submit a bound report. Burnlist automatically
advances its trusted checks and gates to the next agent or terminal node.
Inspect with `loop status|inspect`; `pause` and `stop` are idle-Run controls,
and proof-gated `reconcile` handles a demonstrably lost claim. Only a
converged Run can be applied by `loop complete`; the
command is idempotent and performs the normal shrinking-list completion.

The host owns every provider invocation. Codex and Claude can use native
subagents; any host can deliberately harness Codex CLI, AGY, Grok, or another
CLI through the installed skill's explicit recipes. Before the first Loop, the
skill inventories available native agents, CLIs, live access, and subscriptions
without reading credentials, shows the result, and asks which providers the
user wants to use or set up. Burnlist never installs, authenticates, configures,
or launches an agent provider. Host execution remains independent from
Streaming Diff hooks.

The Checklist UI is read-only and shows the active node, attempt, results,
transition history, and paused, error, or terminal state. Burnlist enforces
the graph, claim identity, trusted checks, budgets, closed outcomes, and atomic
CLI writes. Provider permissions remain host-supervised, not an OS sandbox.
Parallel execution, Docker isolation, metrics gates,
forecasting, worktrees, and background execution are deliberately unsupported
in Stage 1. Items with no Loop assignment keep the ordinary direct
`burnlist burn` workflow.

See the [Loop CLI reference](website/src/content/docs/loops.mdx) for the exact
setup and recovery commands. Installing the Burnlist skill (`burnlist install`)
does not install Streaming Diff hooks (`burnlist hooks install`), and neither
is required to use the Loop.

## Build and Verify

From a source checkout:
Expand Down
22 changes: 21 additions & 1 deletion bin/burnlist.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const knownSubcommands = new Set([
"start",
"close",
"burn",
"loop",
"agent",
"route",
"register",
"unregister",
"roots",
Expand Down Expand Up @@ -83,7 +86,7 @@ if (args[0] && !args[0].startsWith("--") && !["-h", "-v"].includes(args[0]) && !
process.exit(2);
}

if (!["oven", "hooks"].includes(args[0]) && (args.includes("--help") || args.includes("-h"))) {
if (!["oven", "hooks", "loop", "agent", "route"].includes(args[0]) && (args.includes("--help") || args.includes("-h"))) {
console.log(`Burnlist

Usage:
Expand All @@ -105,6 +108,20 @@ Usage:
burnlist start <id> [--repo <path>]
burnlist close <id> [--repo <path>]
burnlist burn <id> <item> [--check] [--repo <path>]
burnlist loop assign <ItemRef> <LoopRef> [--repo <path>]
burnlist loop unassign <ItemRef> [--repo <path>]
burnlist loop view <LoopRef|ItemRef|review> [--repo <path>]
burnlist loop create <ItemRef> [--repo <path>]
burnlist loop next|claim <RunRef> [--repo <path>]
burnlist loop report <ClaimRef> --result <file> [--repo <path>]
burnlist loop abandon <ClaimRef> --reason <host-cancelled|host-lost|expired> [--repo <path>]
burnlist loop list [--repo <path>]
burnlist loop status|inspect <RunRef> [--repo <path>]
burnlist loop pause|stop <RunRef> [--repo <path>] (idle Run only)
burnlist loop reconcile <RunRef> --recovery-proof <hex> [--repo <path>]
burnlist loop complete <RunRef> [--repo <path>]
burnlist loop capability <inspect|trust> <id> ...
burnlist loop setup status [--repo <path>]
burnlist register [path]
burnlist unregister [path]
burnlist roots [--prune]
Expand Down Expand Up @@ -144,6 +161,9 @@ if (args[0] === "oven") {
await import("../src/cli/hooks-cli.mjs");
} else if (["new", "show", "ready", "start", "close", "burn"].includes(args[0])) {
await import("../src/cli/lifecycle-cli.mjs");
} else if (args[0] === "loop") {
const { runLoopCliEntry } = await import("../src/cli/loop-cli.mjs");
await runLoopCliEntry(args.slice(1));
} else if (["register", "unregister", "roots", "init"].includes(args[0])) {
await import("../src/cli/registry-cli.mjs");
} else {
Expand Down
14 changes: 10 additions & 4 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { ListChecks } from "lucide-react";
import { AppHeader, BurnlistTable, ChecklistOvenView, CustomOvenView, DashboardError, DifferentialTestingOvenPage, EmptyState, FILTERS, Filters, LensSwitcher, ModelLabPage, NewOvenPage, OvenCatalog, OvenDefinition, OvenExplainer, PerformanceTracingOvenPage, ProjectGroup, RunBurnPage, StreamingDiff, VisualParityPage } from "@components";
import { useDashboardData } from "@hooks";
Expand All @@ -8,7 +8,13 @@ import { Button } from "@layout";

export function App() {
const section = currentSection();
const selected = useMemo(selectedBurnlist, [window.location.pathname, window.location.search]);
const [itemHash, setItemHash] = useState(() => window.location.hash);
useEffect(() => {
const changed = () => setItemHash(window.location.hash);
window.addEventListener("hashchange", changed);
return () => window.removeEventListener("hashchange", changed);
}, []);
const selected = useMemo(selectedBurnlist, [window.location.pathname, window.location.search, itemHash]);
const repoKey = ovenRepoKey();
const [filter, setFilter] = useState(() => filterFromUrl(FILTERS));
const dashboardSection = ["landing", "burnlist", "streaming-diff"].includes(section) || (section === "custom-oven" && selected) ? "burnlists" : section;
Expand All @@ -34,7 +40,7 @@ export function App() {
<main className="dashboard-main" data-layout={fullLayout ? "full" : "index"} data-section={section}>
{section === "differential-testing" ? <OvenDefinition id="differential-testing" repoKey={repoKey}>{(ir) => <DifferentialTestingOvenPage ir={ir} />}</OvenDefinition> : section === "model-lab" ? <OvenDefinition id="model-lab" repoKey={repoKey}>{(ir) => <ModelLabPage ir={ir} />}</OvenDefinition> : section === "performance-tracing" ? <OvenDefinition id="performance-tracing" repoKey={repoKey}>{(ir) => <PerformanceTracingOvenPage ir={ir} />}</OvenDefinition> : section === "streaming-diff" ? <OvenDefinition id="streaming-diff" repoKey={repoKey}>{(ir) => <StreamingDiff ir={ir} projects={projects} projectsLoading={loading} />}</OvenDefinition> : section === "visual-parity" ? <OvenDefinition id="visual-parity" repoKey={repoKey}>{(ir) => <VisualParityPage ir={ir} />}</OvenDefinition> : section === "custom-oven" ? <CustomOvenView error={error} loading={loading} progress={progress} stale={stale} /> : section === "new-oven" ? <NewOvenPage /> : section === "run-burn" ? <RunBurnPage /> : section === "ovens-catalog" ? <OvenCatalog /> : section === "oven-explainer" ? <OvenExplainer /> : selected ? (
loading && !progress ? <EmptyState title="Loading progress" detail="Reading the selected Burnlist." /> : progress ? (
<>{(error || stale) && <DashboardError message={error || "Showing the last canonical Burnlist snapshot while fresh data loads."} />}<LensSwitcher /><OvenDefinition id="checklist" repoKey={checklistRepoKey}>{(ir) => <ChecklistOvenView data={progress} ir={ir} />}</OvenDefinition></>
<>{(error || stale) && <DashboardError floating message={error || "Showing the last canonical Burnlist snapshot while fresh data loads."} />}<LensSwitcher /><OvenDefinition id="checklist" repoKey={checklistRepoKey}>{(ir) => <ChecklistOvenView data={progress} ir={ir} />}</OvenDefinition></>
) : error ? <DashboardError message={error} /> : <EmptyState title="Choose a Burnlist" detail="Select an item from the list to inspect its progress." icon={ListChecks} />
) : (
<section className="dashboard-index">
Expand All @@ -48,7 +54,7 @@ export function App() {
<Filters filter={filter} onFilterChange={updateFilter} />
</div>
</div>
{(error || stale) && <DashboardError message={error || "Showing the last canonical Burnlist index while fresh data loads."} />}
{(error || stale) && <DashboardError floating message={error || "Showing the last canonical Burnlist index while fresh data loads."} />}
{projects.length && visibleBurnlistCount ? (
<div className="dashboard-project-groups"><BurnlistTable showStatus={filter === "all"}>{projects.map((project) => <ProjectGroup filter={filter} key={project.canonicalRoot} project={project} />)}</BurnlistTable></div>
) : projects.length ? <EmptyState title="No Burnlists in this view" detail="Choose another lifecycle filter." /> : error ? null : <EmptyState title="Nothing here yet" detail="Run `burnlist init` to initialize this repository." />}
Expand Down
94 changes: 78 additions & 16 deletions dashboard/src/components/ChecklistDashboard/ChecklistDashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,25 @@ body.checklist-detail-view .dashboard-oven-title {

.checklist-detail-shell .checklist-kpi-strip .driving-parity-kpi-ratio { font-variant-numeric: tabular-nums; }
.checklist-detail-shell .checklist-kpi-strip .driving-parity-kpi-progress-donut-segment { transition: stroke-dasharray 160ms ease; }

.checklist-detail-shell #burnlist-detail {
width: min(100%, 1440px);
margin-inline: auto;
}
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace {
width: 100%;
height: 232px;
min-height: 232px;
max-height: 232px;
flex: 0 0 232px;
grid-template-columns: 30% minmax(0, 70%);
height: 260px;
min-height: 260px;
max-height: 260px;
flex: 0 0 260px;
grid-template-columns: minmax(320px, 38%) minmax(0, 62%);
}

/* The shared Differential Testing shell defaults its top workspace to 310px. */
@media (min-width: 1101px) {
body.checklist-detail-view .shell.checklist-detail-shell #burnlist-detail .checklist-overview:not([hidden]) + .checklist-progress-workspace {
height: 232px;
min-height: 232px;
max-height: 232px;
height: 260px;
min-height: 260px;
max-height: 260px;
}
}

Expand All @@ -44,9 +47,9 @@ body.checklist-detail-view .dashboard-oven-title {

.checklist-detail-shell .checklist-progress-workspace .event-ledger-panel,
.checklist-detail-shell .checklist-progress-workspace .progress-panel {
height: 232px;
min-height: 232px;
max-height: 232px;
height: 260px;
min-height: 260px;
max-height: 260px;
}

.checklist-detail-shell .checklist-progress-workspace .work-panel-body { min-height: 0; }
Expand Down Expand Up @@ -213,12 +216,71 @@ body.checklist-detail-view .dashboard-oven-title {

.event-card:hover .event-card-summary { background: #1c1c1c; }

@media (max-width: 640px) {
@media (min-width: 761px) and (max-width: 1000px) {
.shell.checklist-detail-shell #burnlist-detail .checklist-kpi-strip,
.shell.checklist-detail-shell #burnlist-detail .checklist-kpi-strip.has-burns {
grid-template-columns: repeat(5, minmax(0, 1fr));
column-gap: 0;
}
.shell.checklist-detail-shell #burnlist-detail .checklist-kpi-strip .driving-parity-kpi-section {
padding-inline: 6px;
grid-template-columns: 28px minmax(0, 1fr);
column-gap: 6px;
}
.checklist-detail-shell .checklist-kpi-strip .driving-parity-kpi-heading { font-size: 11px; }
.checklist-detail-shell .checklist-kpi-strip .driving-parity-kpi-ratio { font-size: 11px; }
.checklist-detail-shell .checklist-kpi-strip .driving-parity-kpi-scenario-icon { width: 28px; height: 28px; }
}

/* Keep Progress and Completion side by side through tablet widths. */
@media (min-width: 521px) and (max-width: 640px) {
body.checklist-detail-view .shell.checklist-detail-shell #burnlist-detail .checklist-overview:not([hidden]) + .checklist-progress-workspace {
height: 260px;
min-height: 260px;
max-height: 260px;
grid-template-columns: minmax(240px, 38%) minmax(0, 62%);
grid-template-rows: minmax(0, 1fr);
}
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace .event-ledger-panel {
height: 260px;
min-height: 260px;
max-height: 260px;
grid-column: 1;
grid-row: 1;
}
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace .progress-panel {
height: 260px;
min-height: 260px;
max-height: 260px;
grid-column: 2;
grid-row: 1;
}
}

@media (max-width: 520px) {
body.checklist-detail-view .dashboard-oven-title { max-width: calc(100% - 190px); font-size: 14px; }
.dashboard-detail-time { display: none; }
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace { height: auto; min-height: 612px; max-height: none; flex: none; grid-template-rows: 320px 282px; }
.checklist-detail-shell .checklist-progress-workspace .event-ledger-panel,
.checklist-detail-shell .checklist-progress-workspace .progress-panel { height: auto; min-height: 0; max-height: none; }
body.checklist-detail-view .shell.checklist-detail-shell #burnlist-detail .checklist-overview:not([hidden]) + .checklist-progress-workspace {
height: 532px;
min-height: 532px;
max-height: 532px;
flex: none;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: 260px 260px;
gap: 12px;
}
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace::before { display: none; }
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace .event-ledger-panel,
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace .progress-panel {
width: 100%;
height: 260px;
min-height: 260px;
max-height: 260px;
border-radius: 8px;
background: var(--card);
}
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace .event-ledger-panel { grid-column: 1; grid-row: 1; }
.checklist-detail-shell #burnlist-detail .checklist-progress-workspace .progress-panel { grid-column: 1; grid-row: 2; }
.event-card-summary { grid-template-columns: max-content minmax(0, 1fr) max-content; padding: 0 10px; gap: 8px; }
.event-card-description { padding: 0 10px 8px; }
.event-card-field { grid-template-columns: 1fr; gap: 3px; }
Expand Down
Loading