Skip to content
Merged
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
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
# so a regression in capture/redact/memory only surfaces at a later empty /distill.
name: test

# CI gates `main` and checks autonomous-agent pushes. The nightshift fleet opens one
# PR per task into `nightshift`, and its local merge gate already replicates the suite
# per branch β€” so nightshift task branches stay CI-free. `claude/**` branches (Claude
# CI gates `main` and checks autonomous-agent pushes. `claude/**` branches (Claude
# Code web/agent sessions) have no local gate, so their pushes run the suite too and
# failures wake the session that owns the branch.
on:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
*.pglite
.DS_Store
# nightshift runtime: dashboard status.json, served index.html, worker logs, pytest venv
# legacy fleet runtime dirs still present in older clones
.nightshift/
# scratch artifacts (dogfood screenshots, throwaway evidence) β€” attach to PRs, never commit
.context/
Expand Down
1 change: 0 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ changelog:
sort: asc
filters:
exclude:
- '^nightshift: merge '
- '^Merge '
- '^docs:'
- '^test:'
Expand Down
32 changes: 4 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@
Β·
<a href="#daily-use">Daily Use</a>
Β·
<a href="#nightshift">nightshift</a>
Β·
<a href="DESIGN.md">Design</a>
</p>

<p align="center">
<img src="docs/dashboard-demo.gif" alt="devbrain dashboard tour β€” the Board, the Profile, and the Nightshift cockpit" width="860">
<img src="docs/dashboard-demo.gif" alt="devbrain dashboard tour β€” the Board and the Profile" width="860">
</p>

AI coding agents forget: close the chat, switch machines, or change tools, and everything
Expand All @@ -44,8 +42,7 @@ Brain durable project memory, git-synced across every session and machine
β”œβ”€ Capture every prompt β†’ raw markdown log automatic, model-free Β· source of truth
β”œβ”€ Distill /distill β†’ brain pages + queue searchable, rebuildable
β”œβ”€ Queue what should happen next one markdown file per task
β”œβ”€ Sync many agents, one brain keyed by git remote
└─ Nightshift work the queue while you're away grounded in current context
└─ Sync many agents, one brain keyed by git remote
```

`devbrain install` wires *this machine*: a one-minute timer sweeps every new Claude Code
Expand All @@ -69,12 +66,11 @@ No Homebrew? `go install github.com/TheWeiHu/devbrain/cmd/devbrain@latest` or gr

`devbrain install` is idempotent and wires only *this machine*. In a terminal it asks
y/n per component; non-interactive runs take every default. When it finishes it opens
the browser dashboard (`devbrain dashboard` β€” Board Β· Nightshift Β· Profile). Common flags:
the browser dashboard (`devbrain dashboard` β€” Board Β· Profile). Common flags:

```bash
devbrain install --dry-run # preview every path it would touch; write nothing
devbrain install --explain # dry-run plus a one-line why per action
devbrain install --without nightshift # skip the overnight loop
devbrain install --only capture # just capture (sweep + gbrain trace)
devbrain install --no-open # don't auto-open the dashboard
DEVBRAIN_DATA=~/path devbrain install # store the brain elsewhere
Expand Down Expand Up @@ -163,9 +159,6 @@ ownership is tracked per project, so a mixed-project session can span brains.
| *(automatic)* | every prompt swept from agent transcripts each minute; committed and pushed every 15 |
| **`/distill`** | fold new log β†’ brain pages **and** queue tasks |
| **`/continue`** | resume from memory, then work the top queue task into a small PR |
| **`/work`** | work the top task into a small PR without refreshing memory first (for `/loop` + nightshift) |
| **`/loop /work`** | headless drain: one small PR per task |
| **`/loop /continue`** | interactive resume+drain: same, but folds new log into the brain each turn |
| **`/reconcile`** | mark brain facts the live repo contradicts (auto-runs ~daily) |
| **`/audit`** | spot-check recent delegated runs for protocol drift (auto-runs ~daily) |
| **`/journal`** | dated recap of the last N days across every project (cached per day) |
Expand All @@ -177,24 +170,7 @@ ownership is tracked per project, so a mixed-project session can span brains.

The queue is one markdown file per task, priority-ranked; a task isn't `done` until its
PR merges. Agents without slash commands run the same workflows as skills (`$distill`,
`$continue`, `$work`, `$reconcile`, `$audit`).

## nightshift

Runs several workers in parallel against the queue β€” `claude` by default, or a mixed
fleet with `--agents claude=2,codex=2` (codex is headless-only) β€” each in its own
worktree, grounded in current project memory, auto-merging green work onto a throwaway
`nightshift` branch β€” you wake to one `git diff main...nightshift`.

```bash
devbrain nightshift start ~/nightshift/myrepo # launch the fleet (runs until stopped)
devbrain nightshift watch # live browser dashboard
devbrain nightshift stop # stop the fleet
```

It never merges to `main`. Installing it spawns nothing β€” the fleet runs only when you
start it, and it does autonomous git ops and spends real tokens, so point the first runs
at a throwaway. You stay the only `nightshift β†’ main` gate.
`$continue`, `$reconcile`, `$audit`).

## More

Expand Down
114 changes: 0 additions & 114 deletions assets/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -271,120 +271,6 @@ small.k{color:var(--muted)}
.viewseg button:hover{color:var(--text);background:transparent}
.viewseg button.active{background:#3A3A3C;color:var(--text);box-shadow:0 1px 2px rgba(0,0,0,.4)}

/* nightshift monitor β€” regressed toward the original: token chart, agent terminals, logs, merges */
#monitor{font-family:var(--mono)}
.ns-run{margin-bottom:22px}
.ns-head{display:flex;flex-direction:column;align-items:center;gap:8px;margin:6px 0 14px;padding-bottom:14px;border-bottom:1px solid var(--line2);font-family:system-ui;text-align:center}
.ns-head-row{display:flex;align-items:baseline;justify-content:center;gap:10px;flex-wrap:wrap}
.ns-head-row.ns-controls{align-items:center}
.ns-head h2{font-size:20px;margin:0;font-weight:700;letter-spacing:-.01em}
/* Header controls are one chip family β€” same height/radius/border/bg on the
status pill, the stepper, and Stop; color shows up only as text + dot. */
.ns-stop{height:26px;box-sizing:border-box;padding:0 12px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;color:var(--danger);background:var(--panel2);border:1px solid var(--line);transition:all .12s}
.ns-stop:hover{background:color-mix(in srgb,var(--danger) 12%,var(--panel2));border-color:color-mix(in srgb,var(--danger) 45%,var(--line))}
.ns-stop:disabled{opacity:.6;cursor:default}
.ns-scale{display:inline-flex;align-items:stretch;height:26px;box-sizing:border-box;border:1px solid var(--line);border-radius:8px;background:var(--panel2);overflow:hidden}
.ns-scale-btn{width:30px;border:none;background:transparent;color:var(--muted);font-size:16px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .12s,color .12s}
.ns-scale-btn:hover:not(:disabled){background:var(--line);color:var(--text)}
.ns-scale-btn:disabled{opacity:.3;cursor:default}
.ns-scale-n{display:inline-flex;align-items:center;justify-content:center;min-width:74px;font-size:13px;font-weight:600;color:var(--text);border-left:1px solid var(--line);border-right:1px solid var(--line);font-variant-numeric:tabular-nums}
.ns-scale-u{color:var(--muted);font-size:11px;font-weight:400;margin-left:3px}
.ns-scale-fixed{align-items:center;padding:0 12px;font-size:13px;font-weight:600;color:var(--muted);cursor:default}
.ns-title{align-items:center;gap:9px}
.ns-started{color:var(--muted);font-size:11px;font-variant-numeric:tabular-nums}
.ns-caption{margin-top:-4px}
.ns-backoff{margin:0 auto 12px;padding:8px 13px;border:1px solid var(--warn);border-radius:8px;background:var(--panel2);color:var(--warn);font-size:12px;width:fit-content;max-width:100%}
.ns-backoff .when{color:var(--muted);margin-left:6px;font-variant-numeric:tabular-nums}
.ns-upd{display:inline-flex;align-items:center;gap:6px;min-height:26px;box-sizing:border-box;padding:2px 11px;border-radius:8px;line-height:1.4;font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--muted);background:var(--panel2);border:1px solid var(--line);transition:color .2s}
.ns-upd.live{color:var(--ok)}
.ns-upd.stale{color:var(--danger)}
.ns-live-dot{width:6px;height:6px;border-radius:50%;background:var(--muted);flex:0 0 auto;transition:background .2s}
.ns-live-dot.live{background:var(--ok)}
.ns-live-dot.stale{background:var(--danger)}
.ns-stats{display:flex;flex-wrap:wrap;width:fit-content;max-width:100%;margin:0 auto 12px;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--panel2)}
.ns-stat{border-right:1px solid var(--line2);padding:8px 16px;min-width:92px;text-align:center}
.ns-stat:last-child{border-right:0}
.ns-stat .n{font-size:17px;font-weight:600;font-family:var(--mono);font-variant-numeric:tabular-nums}
.ns-stat .l{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em;margin-top:2px}
.ns-panel{background:var(--panel);border:1px solid var(--line);border-radius:12px;margin-bottom:12px}
.ns-panel>h3,.ns-term>h3{margin:0;padding:9px 13px;border-bottom:1px solid var(--line);font-family:system-ui}
.ns-panel>h3{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}
.ns-chart{width:100%;display:block;height:180px;padding:8px}
.ns-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:12px;margin-bottom:12px}
.ns-term{background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
.ns-term>h3{font-size:12px;display:flex;align-items:center;gap:8px;flex-wrap:nowrap;min-width:0}
.ns-dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:0 0 auto}
.ns-dot.working{background:var(--ok);box-shadow:0 0 8px var(--ok);animation:nspulse 1.4s infinite}
@keyframes nspulse{50%{opacity:.4}}
.ns-task{color:var(--accent);font-family:var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.ns-rate{color:var(--accent);font-size:11px;margin-left:auto;white-space:nowrap;flex:0 0 auto}
.ns-state{color:var(--muted);font-size:11px;white-space:nowrap;flex:0 0 auto}
.ns-msgs{max-height:440px;overflow:auto;background:var(--bg)}
.ns-msg{padding:7px 13px;border-bottom:1px solid var(--line2);white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5}
.ns-msg:last-child{border-bottom:none}
.ns-ts{color:var(--muted);font-size:10px;margin-right:8px;user-select:none}
.ns-mtext{color:var(--text)}
.ns-sep{display:flex;align-items:center;gap:8px;padding:3px 13px;color:var(--muted);font-size:9px;letter-spacing:.6px;text-transform:uppercase;user-select:none}
.ns-sep::before,.ns-sep::after{content:"";flex:1;height:1px;background:var(--line2)}
.ns-empty{padding:14px 13px;color:var(--muted);font-style:italic}
.ns-log{margin:0;padding:11px 13px;max-height:260px;overflow:auto;white-space:pre-wrap;word-break:break-word;
color:#d2d2d7;font-size:12px;background:var(--bg);font-family:var(--mono)}
.ns-merge{color:var(--ok)}
.ns-cols{display:flex;gap:12px;flex-wrap:wrap}
.ns-cols .ns-panel{flex:1 1 360px;margin-bottom:0}

/* ── select tasks β†’ drag to the πŸŒ™ β†’ run nightshift on just those ───────────── */
.card{position:relative}
.card .chips{padding-right:28px} /* leave room for the select dot */
/* Select dot: a no-modifier toggle on every card. Faint hollow circle, brightens on hover,
fills amber with a βœ“ when picked. Click it to select; click the card body still opens edit.
22px visual, but ::before extends an invisible ~44px hit area so it's easy to hit (Fitts's law). */
.card .seldot{position:absolute;top:6px;right:6px;width:22px;height:22px;border-radius:50%;padding:0;z-index:3;
border:1.5px solid var(--line2);background:rgba(20,20,22,.6);cursor:pointer;
display:flex;align-items:center;justify-content:center;font:700 13px/1 var(--mono);color:transparent;
opacity:.45;transition:opacity .12s,background .12s,border-color .12s,transform .1s}
.card .seldot::before{content:"";position:absolute;inset:-11px} /* enlarges the clickable target to ~44px */
.card:hover .seldot,.card:focus-within .seldot{opacity:1}
.card .seldot:hover{border-color:var(--taken);background:rgba(245,158,11,.22);transform:scale(1.12)}
.card.sel{border-color:var(--taken);box-shadow:0 0 0 1px var(--taken),0 1px 3px rgba(0,0,0,.3);cursor:grab}
.card.sel:active{cursor:grabbing}
.card.sel .seldot{opacity:1;background:var(--taken);border-color:var(--taken);color:#1c1c1e}
/* Drop catch-zone: a big invisible corner region that only activates WHILE dragging a
selection, so you can release anywhere near the moon instead of hitting the 60px circle. */
#moonzone{position:fixed;right:0;bottom:0;width:260px;height:260px;z-index:49;pointer-events:none;opacity:0;
border-radius:260px 0 0 0;transition:opacity .15s;
background:radial-gradient(125% 125% at 100% 100%,rgba(245,158,11,.15),rgba(245,158,11,0) 68%)}
body.dragsel #moonzone{opacity:1;pointer-events:auto}
body.dragsel #moon{transform:scale(1.1)} /* moon grows during a drag β€” a bigger, obvious anchor */
#moonzone.over{background:radial-gradient(125% 125% at 100% 100%,rgba(245,158,11,.34),rgba(245,158,11,.05) 70%)}
/* Floating moon: a drop target + click-to-launch. Dimmed until something is selected. */
#moon{position:fixed;right:22px;bottom:22px;z-index:50;width:60px;height:60px;border-radius:50%;
display:flex;align-items:center;justify-content:center;font-size:27px;cursor:pointer;user-select:none;
background:var(--panel);border:1px solid var(--line);opacity:.4;transition:opacity .15s,transform .12s,box-shadow .15s;
box-shadow:0 4px 14px rgba(0,0,0,.4)}
@keyframes moonpulse{0%,100%{box-shadow:0 0 0 1px rgba(245,158,11,.5),0 4px 18px rgba(245,158,11,.28)}
50%{box-shadow:0 0 0 5px rgba(245,158,11,.16),0 4px 24px rgba(245,158,11,.45)}}
#moon.armed{opacity:1;animation:moonpulse 1.8s ease-in-out infinite} /* selection exists β†’ invite the drop */
#moon.over{transform:scale(1.14);animation:none;box-shadow:0 0 0 2px var(--taken),0 6px 22px rgba(245,158,11,.5)}
#moon:hover{opacity:1}
#moon .badge{position:absolute;top:-4px;right:-4px;min-width:20px;height:20px;padding:0 5px;border-radius:10px;
background:var(--taken);color:#1c1c1e;font:700 11px/20px var(--mono);text-align:center;box-shadow:0 1px 4px rgba(0,0,0,.4)}
.lx-list{max-height:230px;overflow:auto;margin:6px 0 2px;border:1px solid var(--line);border-radius:8px}
.lx-row{display:flex;gap:8px;align-items:baseline;padding:6px 10px;border-bottom:1px solid var(--line2);font-size:13px}
.lx-row:last-child{border-bottom:0}
.lx-row .lx-id{color:var(--muted);font-family:var(--mono);font-size:11px;flex:0 0 auto}
.lx-row .lx-t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lx-warn{color:var(--muted);font-size:12px;margin:8px 0 2px}
.lx-warn b{color:var(--taken)}
.lx-repo{color:var(--muted);font-size:11px;margin:6px 0 2px}
.lx-repo code{font-family:var(--mono);color:var(--text);background:var(--panel2);padding:1px 5px;border-radius:5px}
.lx-err{color:var(--danger);font-size:13px;margin:8px 0}
.ns-boot{display:flex;flex-direction:column;align-items:center;gap:10px;padding:48px 20px;color:var(--text);font-size:15px}
.ns-boot-sub{color:var(--muted);font-size:12px;text-align:center}
.ns-boot-sub code{font-family:var(--mono);background:var(--panel2);padding:1px 5px;border-radius:5px}
.ns-spin{width:22px;height:22px;border-radius:50%;border:2px solid var(--line2);border-top-color:var(--taken);animation:nsspin .8s linear infinite}
@keyframes nsspin{to{transform:rotate(360deg)}}

@media(max-width:600px){
header:has(#pf-controls[style*="inline-flex"]){padding:8px 12px;gap:6px}
#pf-controls{width:100%;gap:8px;flex-wrap:wrap}
Expand Down
18 changes: 1 addition & 17 deletions assets/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
<div class="viewseg" id="viewseg" role="tablist" aria-label="View">
<button data-view="board">πŸ“‹ Board</button>
<button data-view="profile" class="active">πŸ“Š Profile</button>
<button data-view="monitor" id="viewMonitorBtn" style="display:none">πŸŒ™ Nightshift</button>
</div>
<span class="sp"></span>
<div class="search"><input id="search" placeholder="Search tasks, PRs, authors…" aria-label="Search"><kbd>⌘K</kbd></div>
<select id="filterProject" aria-label="Project filter"><option value="">all projects</option></select>
<button class="primary" id="newBtn">οΌ‹ New</button>
<span id="pf-controls" style="display:none">
<div class="seg" id="pf-kind" title="typed = what you wrote at the keyboard (prose + your slash-commands); bot = autonomous nightshift sessions + harness injections">
<div class="seg" id="pf-kind" title="typed = what you wrote at the keyboard (prose + your slash-commands); bot = autonomous sessions + harness injections">
<button data-k="typed" class="on">β–¦ Typed</button>
<button data-k="bot">πŸ€– Bot</button>
<button data-k="all">πŸ—‚οΈ All</button>
Expand All @@ -39,7 +38,6 @@
</header>
<div id="brain-error" role="alert" hidden></div>
<div class="board" id="board"></div>
<div id="monitor" style="display:none;padding:14px 16px"></div>
<div id="profile" style="display:none">
<div class="statbar" id="pf-stats"></div>
<div class="pf-note" id="pf-kindnote"></div>
Expand Down Expand Up @@ -113,7 +111,6 @@ <h3 id="mTitle">Edit task</h3>
</div>
<div class="fld"><label>Body<textarea id="fBody"></textarea></label></div>
<div class="fld"><label>Reason / hold note<textarea id="fReason" style="min-height:54px"></textarea></label></div>
<div class="fld"><label class="chk"><input type="checkbox" id="fApproved"> Approve for unattended run (install deps + network)</label></div>
<div class="fld"><small class="k" id="fMeta"></small></div>
<div class="actions">
<button class="primary" id="saveBtn">Save</button>
Expand All @@ -123,18 +120,5 @@ <h3 id="mTitle">Edit task</h3>
</div>
</div></div>

<!-- Drag selected tasks onto the moon (or anywhere in the corner zone), or click it, to run nightshift on just those. -->
<div id="moonzone" aria-hidden="true"></div>
<div id="moon" title="Drag selected tasks here β€” or click β€” to run nightshift on just those" role="button" tabindex="0" aria-label="Start nightshift on selected tasks">πŸŒ™</div>

<div class="modal" id="launchModal"><div class="sheet">
<h3>πŸŒ™ Start nightshift</h3>
<div id="lxBody"></div>
<div class="actions">
<button class="primary" id="lxGoBtn">πŸŒ™ Launch</button>
<button class="ghost" id="lxCancelBtn">Cancel</button>
</div>
</div></div>

<script src="/dashboard.js"></script>
</body></html>
Loading
Loading