cohorte 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -1
- package/README.md +20 -12
- package/bin/cli.js +8 -0
- package/core/agents/review.md +23 -0
- package/core/commands/audit.md +9 -1
- package/core/commands/brainstorm.md +6 -0
- package/core/commands/build.md +90 -5
- package/core/commands/doctor.md +8 -3
- package/core/commands/{loop.md → drive.md} +25 -6
- package/core/commands/fix.md +5 -0
- package/core/commands/review.md +58 -9
- package/core/commands/spec.md +20 -0
- package/core/commands/update-pipeline.md +6 -1
- package/core/templates/decisions.template.md +42 -0
- package/core/templates/spec.template.md +3 -1
- package/core/templates/steps/init-pipeline/04-write-render.md +2 -2
- package/core/workflows/review.js +44 -2
- package/dashboard/dist/assets/{index-dkO8UUVl.css → index-BZ_LQlEj.css} +1 -1
- package/dashboard/dist/assets/{index-8owBnqyv.js → index-DYyn4p93.js} +11 -11
- package/dashboard/dist/index.html +2 -2
- package/dashboard/server/doctor.js +8 -1
- package/install.ps1 +8 -0
- package/install.sh +7 -0
- package/package.json +1 -1
- package/profile/SCHEMA.md +140 -1
- package/scripts/loop.sh +141 -12
- package/scripts/test-loop.mjs +227 -0
- package/scripts/test-workflows.mjs +28 -0
- package/scripts/validate-core.mjs +16 -1
package/core/commands/spec.md
CHANGED
|
@@ -13,6 +13,10 @@ You run the **spec** step in the main thread — interactive, with the human. Pa
|
|
|
13
13
|
> Template paths below (`.claude/templates/…`) resolve to `~/.claude/templates/…` when the core is
|
|
14
14
|
> installed globally — read whichever exists.
|
|
15
15
|
>
|
|
16
|
+
> **Decision journal** (SCHEMA.md §Decisions): read `specs/_decisions.md` §Live if it exists — one
|
|
17
|
+
> line per standing decision, so it is cheap. It is the ONLY place the project's transverse rules
|
|
18
|
+
> live; a spec that contradicts one silently un-decides it. Absent ⇒ nothing to honour yet.
|
|
19
|
+
>
|
|
16
20
|
> **Kanban** (SCHEMA.md §Kanban): when the spec opens, move card `#<feature_id>` → **Spec**; on freeze
|
|
17
21
|
> (`status: frozen`, Mode A) → **Ready to build**. No-op silently if no board is configured.
|
|
18
22
|
|
|
@@ -53,6 +57,22 @@ Detect the mode from the pasted content:
|
|
|
53
57
|
Ping only on a **landed** freeze, so the funnel counts specs that exist, not attempts. Mode B does
|
|
54
58
|
not ping — it re-enters an already-counted spec, and `/fix` covers that loop. Silent no-op without
|
|
55
59
|
consent; never ask about consent here.
|
|
60
|
+
5b. **Record the transverse decisions — the journal, not a summary.** Walk what the interview settled
|
|
61
|
+
and keep ONLY the decisions that **outlive this feature**: a rule the next spec would otherwise
|
|
62
|
+
re-litigate or contradict (auth model, id/naming scheme, where a kind of state lives, an error
|
|
63
|
+
convention, a deliberate non-goal that binds future features). Typical yield: **0–3 lines**; zero
|
|
64
|
+
is a normal, healthy outcome for a feature that decided nothing new — never invent lines to fill
|
|
65
|
+
the section. Append them to `specs/_decisions.md` §Live (create the file from
|
|
66
|
+
`.claude/templates/decisions.template.md` on first use), each exactly:
|
|
67
|
+
`- <YYYY-MM-DD> · <area> · <decision> — because <reason> · <feature_id>`
|
|
68
|
+
- **Never** duplicate what §5, `PIPELINE.md` §Conventions or the code already states — the journal
|
|
69
|
+
carries the *non-obvious rule*, not the feature's content. A line that restates a spec section is
|
|
70
|
+
a line every future `/spec` pays for and learns nothing from.
|
|
71
|
+
- **Contradicting an existing line** is allowed but never silent: tell the human which line this
|
|
72
|
+
feature overrides, get their go-ahead, then append the new line with
|
|
73
|
+
`· supersedes <YYYY-MM-DD> <area>` and move the old one to `## Superseded`.
|
|
74
|
+
- Append with one `>>` Bash call, not a full-file rewrite (the file is append-only, and reading it
|
|
75
|
+
back to re-write it is the one way to make a bounded file expensive).
|
|
56
76
|
6. Author the **design brief** — `specs/design/<id>.md`, rendered via
|
|
57
77
|
`.claude/templates/design-brief.md` (resolves to `~/.claude/templates/…` on a global install).
|
|
58
78
|
_Only if `design.enabled` / the feature has UI; skip entirely for a backend-only feature._
|
|
@@ -84,7 +84,12 @@ actually connected) and repair whatever fails — wiring that worked at
|
|
|
84
84
|
init can rot (PATH changes, uninstalls, hand-edits). Report what was reconciled; if nothing was
|
|
85
85
|
missing, say so. This is why `/init-pipeline` never needs re-running for a core upgrade.
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Three of the §Reconcile steps matter specifically here:
|
|
88
|
+
|
|
89
|
+
- **Spec-template top-up** (§Reconcile step 7): `specs/_template.md` was seeded at install and never
|
|
90
|
+
refreshed since, so add the front-matter fields the current `templates/spec.template.md` has and the
|
|
91
|
+
repo's copy lacks (1.6 added `loop_pass`/`loop_phase` and two states to the `status:` comment) —
|
|
92
|
+
front-matter only, never the body.
|
|
88
93
|
|
|
89
94
|
- **Global config seed** (§Reconcile step 5): if `~/.claude/cohorte.config.yaml` is absent, seed it
|
|
90
95
|
from the template so the kanban + shared-vault config has a home. Never clobber an existing filled
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Decisions
|
|
2
|
+
|
|
3
|
+
> The project's **transverse decision journal** — the non-obvious rules a stateless agent would
|
|
4
|
+
> otherwise re-discover or contradict, one feature at a time. `PIPELINE.md` says *how this repo is
|
|
5
|
+
> built* (surfaces, commands, conventions); this file says *what was decided and why*.
|
|
6
|
+
>
|
|
7
|
+
> **Append-only. One line per decision. No prose blocks, no code.** The bound is the point: this file
|
|
8
|
+
> is read by `/spec`, `/brainstorm` and `/audit` — the deciding stages — and by **nothing else**.
|
|
9
|
+
> Implementers and reviewers never read it: they work from the frozen contract, which already tells
|
|
10
|
+
> them what to do; handing them the rationale too would cost `surfaces × dispatches` tokens per
|
|
11
|
+
> feature for a fact they cannot act on.
|
|
12
|
+
>
|
|
13
|
+
> Line shape (≤ ~160 chars, `·`-separated, no wrapping):
|
|
14
|
+
>
|
|
15
|
+
> ```
|
|
16
|
+
> - <YYYY-MM-DD> · <area> · <the decision, imperative> — because <the reason> · <feature_id>
|
|
17
|
+
> ```
|
|
18
|
+
>
|
|
19
|
+
> `<area>` is a short tag, not a path: `auth`, `data`, `api`, `ui`, `deploy`, `naming`, `surfaces`…
|
|
20
|
+
>
|
|
21
|
+
> **Reversing a decision** never edits or deletes a line — append a superseding one and move the old
|
|
22
|
+
> one to `## Superseded`:
|
|
23
|
+
>
|
|
24
|
+
> ```
|
|
25
|
+
> - <YYYY-MM-DD> · <area> · <the new decision> — because <reason> · supersedes <YYYY-MM-DD> <area> · <feature_id>
|
|
26
|
+
> ```
|
|
27
|
+
>
|
|
28
|
+
> **Keep it bounded:** when `## Live` passes ~100 lines, move every superseded line into
|
|
29
|
+
> `## Superseded` (the audit trail survives; the section the deciding stages actually read stays
|
|
30
|
+
> short). Never summarize or merge live lines — a decision either holds or has been superseded.
|
|
31
|
+
>
|
|
32
|
+
> **What does NOT belong here:** anything the code, the tests, `PIPELINE.md` §Conventions or a spec
|
|
33
|
+
> already states; a feature-local choice (that lives in its spec); a task, a TODO or a finding (those
|
|
34
|
+
> are `## Remediation` and `specs/refactor-backlog.md`).
|
|
35
|
+
|
|
36
|
+
## Live
|
|
37
|
+
|
|
38
|
+
<!-- newest last -->
|
|
39
|
+
|
|
40
|
+
## Superseded
|
|
41
|
+
|
|
42
|
+
<!-- moved here when a line above supersedes them; never deleted -->
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
feature_id: <slug>
|
|
3
3
|
title: <Feature title>
|
|
4
|
-
status: draft # draft → frozen → in-review → shipped
|
|
4
|
+
status: draft # draft → frozen → in-progress → in-review → shipped · blocked (see SCHEMA.md §Spec status)
|
|
5
5
|
branch: <feature_branch_prefix><slug>
|
|
6
6
|
created: <YYYY-MM-DD>
|
|
7
|
+
loop_pass: 0 # /drive bookkeeping — the review pass it is on; 0 = no loop running. Written by the driver, not by hand
|
|
8
|
+
loop_phase: # /drive bookkeeping — build | review | fix | done. With loop_pass, this is what `--resume` reads back
|
|
7
9
|
reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /review)
|
|
8
10
|
reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /ship re-checks
|
|
9
11
|
design_files: [] # design page links — full URLs of the form https://claude.ai/design/p/<projectId>?file=<file> (each carries its own project + page); blank until designed; omit if no UI
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
`Bash(git status:*)`, `Bash(git diff:*)`, `Bash(git log:*)`, `Bash(git rev-parse:*)` — plus the
|
|
29
29
|
shipped pipeline scripts for BOTH cores (`Bash(.claude/pipeline/scripts/:*)` and
|
|
30
30
|
`Bash(~/.claude/pipeline/scripts/:*)` — preflight, kanban-move, telemetry-send) **plus the
|
|
31
|
-
`bash`-prefixed form the `/
|
|
31
|
+
`bash`-prefixed form the `/drive` driver uses** (`Bash(bash .claude/pipeline/scripts/loop.sh:*)`
|
|
32
32
|
and `Bash(bash ~/.claude/pipeline/scripts/loop.sh:*)`) — those prefix rules match a command
|
|
33
|
-
*starting* with the path, so `bash <path>` needs its own entry or `/
|
|
33
|
+
*starting* with the path, so `bash <path>` needs its own entry or `/drive` stalls on a permission
|
|
34
34
|
prompt at every launch, and the
|
|
35
35
|
retrieval provider's MCP tools when wired (e.g. `mcp__serena`). Never allowlist anything matching
|
|
36
36
|
a `gate.ask`/`gate.deny` pattern. Mention the human can widen it later with
|
package/core/workflows/review.js
CHANGED
|
@@ -131,11 +131,29 @@ const FINDING = {
|
|
|
131
131
|
},
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// A DEFERRED finding is real but out of this feature's scope (pre-existing code the
|
|
135
|
+
// diff never touched). It carries its own out-of-scope reason, counts in no severity
|
|
136
|
+
// row, is never cross-checked, and can never move the verdict — it is routed to
|
|
137
|
+
// specs/refactor-backlog.md so /refactor owns it. See core/agents/review.md §Deferred.
|
|
138
|
+
const DEFERRED = {
|
|
139
|
+
type: 'object', required: ['severity', 'file', 'line', 'kind', 'problem', 'fix', 'outOfScope'],
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
properties: {
|
|
142
|
+
severity: { enum: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] },
|
|
143
|
+
file: { type: 'string' }, line: { type: 'integer' },
|
|
144
|
+
kind: { enum: ['quality', 'security', 'rule'] },
|
|
145
|
+
problem: { type: 'string', description: 'one line, no code excerpts' },
|
|
146
|
+
fix: { type: 'string', description: 'one concrete change, one line' },
|
|
147
|
+
outOfScope: { type: 'string', description: 'one line: why this feature does not own it' },
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
|
|
134
151
|
const REPORT = {
|
|
135
152
|
type: 'object', required: ['verdict', 'findings'], additionalProperties: false,
|
|
136
153
|
properties: {
|
|
137
154
|
verdict: { enum: ['SHIP', 'REVISE', 'BLOCK'] },
|
|
138
155
|
findings: { type: 'array', maxItems: 20, items: FINDING },
|
|
156
|
+
deferred: { type: 'array', maxItems: 10, items: DEFERRED },
|
|
139
157
|
overflow: { type: 'integer', description: 'findings beyond the 20-item cap, if any' },
|
|
140
158
|
notes: { type: 'string', description: 'RBAC / mobile-first assessment only, when the profile enables them' },
|
|
141
159
|
},
|
|
@@ -222,7 +240,8 @@ const reviewed = await pipeline(
|
|
|
222
240
|
s => agent(
|
|
223
241
|
'Review one feature surface against its frozen spec, per your agent instructions (read the staged ' +
|
|
224
242
|
'diff FIRST; open a full source file only when a finding demands it; capped shape — max 20 findings, ' +
|
|
225
|
-
'one line each, no code excerpts).
|
|
243
|
+
'one line each, no code excerpts). Put anything real but OUT of this feature\'s scope in `deferred` ' +
|
|
244
|
+
'(max 10, each with its out-of-scope reason) per your §Deferred rules — never in `findings`. — Variable slots: ' +
|
|
226
245
|
`feature ${feature} · scope: the ${s.key} surface only · spec: specs/${feature}.md · ` +
|
|
227
246
|
`staged diff: ${s.diff} · changed files: ${s.files.join(', ')}`,
|
|
228
247
|
{ agentType: 'review', label: `review:${s.key}`, phase: 'Review', schema: REPORT },
|
|
@@ -231,7 +250,10 @@ const reviewed = await pipeline(
|
|
|
231
250
|
if (!report) return null
|
|
232
251
|
const hard = report.findings.filter(f => f.severity === 'CRITICAL' || f.kind === 'security')
|
|
233
252
|
const rest = report.findings.filter(f => !hard.includes(f))
|
|
234
|
-
|
|
253
|
+
// Deferred items skip the cross-check entirely: refuting one would spend an agent
|
|
254
|
+
// to argue about something that cannot affect the verdict either way.
|
|
255
|
+
const deferred = report.deferred || []
|
|
256
|
+
if (!hard.length) return { key: s.key, report, kept: rest, refuted: [], deferred }
|
|
235
257
|
const votes = await parallel(hard.map(f => () => agent(
|
|
236
258
|
'Adversarially verify ONE review finding — your job is to REFUTE it if you can. Read the staged ' +
|
|
237
259
|
'diff and the exact file:line; refuted=true when the code, a guard, a test, or the spec shows the ' +
|
|
@@ -246,6 +268,7 @@ const reviewed = await pipeline(
|
|
|
246
268
|
report,
|
|
247
269
|
kept: rest.concat(checkedVotes.filter(v => !v.refuted).map(v => v.f)),
|
|
248
270
|
refuted: checkedVotes.filter(v => v.refuted).map(v => ({ ...v.f, reason: v.reason })),
|
|
271
|
+
deferred,
|
|
249
272
|
}
|
|
250
273
|
},
|
|
251
274
|
)
|
|
@@ -259,6 +282,9 @@ const unreviewed = touched.filter(s => !results.some(r => r.key === s.key)).map(
|
|
|
259
282
|
if (unreviewed.length) log(`Reviewer died on: ${unreviewed.join(', ')} — those surfaces are NOT reviewed`)
|
|
260
283
|
const kept = results.flatMap(r => r.kept.map(f => ({ ...f, surface: r.key })))
|
|
261
284
|
const refuted = results.flatMap(r => r.refuted.map(f => ({ ...f, surface: r.key })))
|
|
285
|
+
// Deferred findings are deliberately kept OUT of `counts`, out of `verdict` and out
|
|
286
|
+
// of `clean`: they belong to /refactor, not to this feature's fix loop.
|
|
287
|
+
const deferredAll = results.flatMap(r => (r.deferred || []).map(f => ({ ...f, surface: r.key })))
|
|
262
288
|
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }
|
|
263
289
|
for (const f of kept) counts[f.severity] = (counts[f.severity] || 0) + 1
|
|
264
290
|
// Verdict from the findings that SURVIVED the cross-check (a refuted CRITICAL
|
|
@@ -283,6 +309,10 @@ const reportBody = [
|
|
|
283
309
|
...['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].map(s => `| ${s} | ${counts[s] || 0} |`), '',
|
|
284
310
|
`Verdict: ${verdict}`, '', '## Findings', '',
|
|
285
311
|
kept.length ? kept.map(findingLine).join('\n') : 'None.',
|
|
312
|
+
'', '## Deferred', '',
|
|
313
|
+
deferredAll.length
|
|
314
|
+
? deferredAll.map(f => `${findingLine(f)} · out of scope: ${f.outOfScope} · deferred:${feature}`).join('\n')
|
|
315
|
+
: 'None.',
|
|
286
316
|
...(unreviewed.length ? ['', '## NOT reviewed (reviewer died — no verdict on these)', '',
|
|
287
317
|
unreviewed.map(k => `- \`${k}\` — re-run /review ${feature} (or the review workflow)`).join('\n')] : []),
|
|
288
318
|
...(refuted.length ? ['', '## Refuted by cross-check (no action needed)', '',
|
|
@@ -295,6 +325,17 @@ const staging = await agent(
|
|
|
295
325
|
`{"ts":"<ISO now>","feature":"${feature}","phase":"review","seconds":0,"surfaces":{${results.map(r => `"${r.key}":"${verdict}:${r.kept.length}"`).join(',')}}}\n` +
|
|
296
326
|
`3. Chain the opt-in usage ping: <core>/pipeline/scripts/telemetry-send.sh review "${feature}" 0 "${verdict}:${kept.length}" || true ` +
|
|
297
327
|
'(<core> = .claude if .claude/pipeline/scripts/telemetry-send.sh exists, else ~/.claude; script on neither ⇒ skip the ping).\n' +
|
|
328
|
+
// Deferred findings must land in the backlog on EVERY verdict — parked only on a
|
|
329
|
+
// SHIP is parked nowhere the rest of the time, which is the leak this closes.
|
|
330
|
+
(deferredAll.length
|
|
331
|
+
? `3b. Route the deferred findings to specs/refactor-backlog.md (create it if absent): for each line below, ` +
|
|
332
|
+
`append it under the \`## <domain>\` heading named in its prefix (create that heading if absent) — with \`>>\`, ` +
|
|
333
|
+
`never by rewriting the file, and skip any whose file path + first words already appear there (grep -F first, ` +
|
|
334
|
+
`they may be left from a prior round or an /audit):\n` +
|
|
335
|
+
deferredAll.map(f =>
|
|
336
|
+
`${f.surface}||- [ ] ${f.severity} · ${f.file}:${f.line} · ${f.kind} · ${f.fix} · deferred:${feature}`).join('\n') +
|
|
337
|
+
'\n'
|
|
338
|
+
: '') +
|
|
298
339
|
// Stamp + tick only when nothing above LOW survived: the conversational /review
|
|
299
340
|
// keeps the stamp only for LOW findings, and a SHIP verdict here can still carry
|
|
300
341
|
// HIGH/MEDIUM ones — certifying those for /ship would ship known defects. A dead
|
|
@@ -318,6 +359,7 @@ const staged_ok = staging != null && /done/i.test(String(staging))
|
|
|
318
359
|
return {
|
|
319
360
|
verdict,
|
|
320
361
|
counts,
|
|
362
|
+
deferred: deferredAll.length, // parked in the backlog for /refactor — never blocking
|
|
321
363
|
refutedByCrossCheck: refuted.length,
|
|
322
364
|
reportStaged: staged_ok,
|
|
323
365
|
unreviewedSurfaces: unreviewed, // reviewers that died — these carry NO verdict
|
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--bg: #0e0f13;--panel: #16181f;--panel-2: #1c1f28;--border: #262a35;--text: #e6e8ee;--muted: #8b90a0;--accent: #6ea8fe;--ok: #3fb950;--warn: #d9a441;--bad: #f85149;--dev: #a371f7;--mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-family:var(--mono);font-size:14px}.app{min-height:100vh}.topbar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:20;background:#0e0f13e6;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}.brand{font-weight:600;letter-spacing:.2px;display:flex;align-items:center;gap:8px}.dot{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 10px var(--accent)}.muted{color:var(--muted)}.small{font-size:12px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;gap:16px;padding:20px;max-width:1100px;margin:0 auto}@media(max-width:720px){.grid{grid-template-columns:1fr}}.panel{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 18px}.panel h2{margin:0 0 12px;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--muted)}.panel.placeholder{opacity:.7}.panel.error{grid-column:1 / -1;border-color:var(--bad);color:var(--bad)}.panel-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.panel-head h2{margin:0}.badge{font-size:11px;padding:3px 9px;border-radius:999px;border:1px solid var(--border);white-space:nowrap}.badge.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 40%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}.badge.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 40%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)}.badge.bad{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,transparent);background:color-mix(in srgb,var(--bad) 12%,transparent)}.badge.dev{color:var(--dev);border-color:color-mix(in srgb,var(--dev) 40%,transparent);background:color-mix(in srgb,var(--dev) 12%,transparent)}.badge.neutral{color:var(--muted)}.rows{display:flex;flex-direction:column;gap:2px}.row{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px dashed var(--border)}.row:last-child{border-bottom:none}.row-label{color:var(--muted)}.row-value{text-align:right}.row-value.strong{font-weight:600}.row-value.mono{font-family:var(--mono);font-size:12px}.actions{display:flex;align-items:center;gap:10px;margin-top:16px}.fresh-hint{margin:14px 0 0;padding-top:12px;border-top:1px dashed var(--border)}.fresh-hint strong{color:var(--accent)}button{font-family:var(--mono);font-size:13px;padding:7px 12px;border-radius:8px;border:1px solid var(--border);background:var(--panel-2);color:var(--text);cursor:pointer}button:disabled{opacity:.4;cursor:not-allowed}button.primary{background:color-mix(in srgb,var(--accent) 22%,var(--panel-2));border-color:color-mix(in srgb,var(--accent) 40%,transparent)}button.ghost{background:transparent}button:not(:disabled):hover{border-color:var(--accent)}.span2{grid-column:span 2}@media(max-width:720px){.span2{grid-column:1 / -1}}.summary{display:flex;gap:6px;flex-wrap:wrap}.checks{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}.check{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--border)}.check:last-child{border-bottom:none}.check.skip{opacity:.55}.check-icon{flex:none;width:20px;height:20px;border-radius:6px;display:grid;place-items:center;font-size:12px;font-weight:700;margin-top:1px}.check-icon.ok{color:var(--ok);background:color-mix(in srgb,var(--ok) 15%,transparent)}.check-icon.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 15%,transparent)}.check-icon.bad{color:var(--bad);background:color-mix(in srgb,var(--bad) 15%,transparent)}.check-icon.skip{color:var(--muted);background:var(--panel-2)}.check-body{flex:1;min-width:0}.check-line{display:flex;gap:10px;justify-content:space-between;flex-wrap:wrap}.check-label{font-weight:600}.check-detail{color:var(--muted);text-align:right}.check-fix{margin-top:4px;font-size:12px;color:var(--muted)}.check-fix code{color:var(--accent);background:var(--panel-2);padding:1px 6px;border-radius:5px}.surfaces{display:flex;flex-direction:column;gap:10px}.surface{border:1px solid var(--border);border-radius:9px;padding:10px 12px;background:var(--panel-2)}.surface-top{display:flex;align-items:center;gap:8px}.surface-key{font-weight:600}.surface-meta{display:flex;justify-content:space-between;gap:10px;margin-top:4px;font-size:12px}.surface-tools{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.tool{font-size:11px;color:var(--muted);border:1px solid var(--border);border-radius:5px;padding:1px 6px}.chip{font-size:11px;padding:1px 8px;border-radius:999px;border:1px solid var(--border)}.chip.design{color:var(--dev);border-color:color-mix(in srgb,var(--dev) 40%,transparent)}.chip.model-sonnet{color:var(--accent)}.chip.model-haiku{color:var(--ok)}.chip.model-inherit{color:var(--warn)}.fleet{max-width:1100px;margin:0 auto;padding:20px}.core-banner{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;background:linear-gradient(180deg,var(--panel-2),var(--panel));border:1px solid var(--border);border-radius:12px;padding:14px 18px;margin-bottom:20px}.cb-left{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.cb-title{text-transform:uppercase;letter-spacing:1px;font-size:12px;color:var(--muted)}.cb-version{font-weight:600;font-size:15px}.cb-actions{display:flex;gap:8px}.fleet-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px;flex-wrap:wrap}.fleet-head h2{margin:0;font-size:14px;display:flex;align-items:center;gap:8px}.add-wrap{flex:1;max-width:560px}.add-form{display:flex;gap:8px}.path-input{flex:1;font-family:var(--mono);font-size:13px;padding:7px 12px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:8px}.path-input:focus{outline:none;border-color:var(--accent)}.path-input.invalid{border-color:var(--bad)}.add-error{margin-top:6px;font-size:12px;color:var(--bad);word-break:break-word}.fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}.project-card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px 16px;cursor:pointer;transition:border-color .12s,transform .12s}.project-card:hover{border-color:var(--accent);transform:translateY(-1px)}.project-card.gone{opacity:.6;cursor:default}.pc-head{display:flex;align-items:center;justify-content:space-between}.pc-name{font-weight:600}.pc-path{margin:2px 0 10px}.pc-badges{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}.pc-health{display:flex;align-items:center;gap:5px}.pc-counts{margin-left:auto}.icon-btn{border:none;background:transparent;color:var(--muted);padding:2px 6px;border-radius:6px;font-size:13px}.icon-btn:hover{color:var(--bad);background:var(--panel-2)}.pill{font-size:11px;min-width:20px;text-align:center;padding:1px 7px;border-radius:999px;font-weight:600}.pill.ok{color:var(--ok);background:color-mix(in srgb,var(--ok) 14%,transparent)}.pill.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}.pill.bad{color:var(--bad);background:color-mix(in srgb,var(--bad) 14%,transparent)}.pill.neutral{color:var(--muted);background:var(--panel-2)}.detail-crumb{grid-column:1 / -1;margin-bottom:-4px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.detail-tools{display:flex;gap:8px;flex-wrap:wrap}.tool-btn{font-size:12px;padding:5px 10px;border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));color:var(--accent);background:transparent}.tool-btn:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 10%,transparent)}.confirm-text p{margin:0 0 8px;font-size:13px;line-height:1.6}.confirm-text code{background:var(--panel-2);padding:1px 5px;border-radius:4px;color:var(--accent)}.warn-line{color:var(--warn)!important;background:color-mix(in srgb,var(--warn) 10%,transparent);border-radius:8px;padding:8px 10px}.warn-line code{color:var(--text)!important}.danger-ghost{background:transparent;border:1px solid color-mix(in srgb,var(--bad) 40%,var(--border));color:var(--bad);font-size:12px;padding:5px 10px}.danger-ghost:hover{background:color-mix(in srgb,var(--bad) 12%,transparent);border-color:var(--bad)}button.danger{background:color-mix(in srgb,var(--bad) 20%,var(--panel-2));border-color:color-mix(in srgb,var(--bad) 45%,transparent);color:#ffd7d3}button.danger:hover{border-color:var(--bad)}.reset-list{margin:8px 0;padding-left:18px;font-size:13px;line-height:1.7}.reset-list code{color:var(--accent)}.reset-note{font-size:12.5px;color:var(--muted);background:var(--panel-2);border-radius:8px;padding:10px 12px}.reset-note code{color:var(--text)}.reset-check{display:flex;align-items:center;gap:8px;margin:12px 0 0;font-size:13px}.reset-check code{color:var(--accent)}.board-scroll{max-height:500px;overflow:auto}.board{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}.col{background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:8px;min-width:140px}.col-head{display:flex;justify-content:space-between;font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);padding:2px 4px 8px;position:sticky;top:0;background:var(--bg);z-index:1}.col-shipped{opacity:.85}.col-other{border-color:color-mix(in srgb,var(--warn) 40%,var(--border))}.spec-card{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;padding:8px 9px;margin-bottom:7px}.spec-card.bad{border-color:color-mix(in srgb,var(--warn) 45%,transparent)}.spec-title{font-size:13px;font-weight:600}.spec-meta,.spec-branch{margin-top:3px}@media(max-width:640px){.board{grid-template-columns:repeat(2,1fr)}}.kanban-board{display:flex;gap:10px}.kanban-col{flex:0 0 210px;background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:8px}.kanban-col.empty{opacity:.5}.kanban-card{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;padding:8px 9px;margin-bottom:7px}.kanban-card.done{opacity:.6}.kanban-card.done .kc-text{text-decoration:line-through}.kc-text{font-size:12.5px;line-height:1.4}.kc-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}.kc-tag{font-size:10px;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border-radius:4px;padding:1px 5px}.kc-pr{font-size:10px;border-radius:4px;padding:1px 5px;text-decoration:none;border:1px solid var(--border);color:var(--muted)}a.kc-pr:hover{filter:brightness(1.25)}.kc-pr.state-open{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 40%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}.kc-pr.state-merged{color:var(--dev);border-color:color-mix(in srgb,var(--dev) 40%,transparent);background:color-mix(in srgb,var(--dev) 12%,transparent)}.kc-pr.state-closed{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,transparent);background:color-mix(in srgb,var(--bad) 12%,transparent)}.kc-pr.draft{color:var(--muted);border-color:var(--border);background:var(--panel-2)}.kc-pr.flat{border-style:dashed;cursor:default}.kc-status{font-size:10px;margin-top:5px;color:var(--muted);text-transform:capitalize}.kc-status.state-open{color:var(--ok)}.kc-status.state-merged{color:var(--dev)}.kc-status.state-closed{color:var(--bad)}.metrics-list{display:flex;flex-direction:column;gap:12px}.metric-feature{background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:10px 12px}.mf-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:8px}.mf-name{font-weight:600}.mf-badges{display:flex;gap:6px;flex-wrap:wrap}.phase-bars{display:flex;flex-direction:column;gap:4px}.phase-row{display:grid;grid-template-columns:56px 1fr 90px;align-items:center;gap:10px}.phase-label{color:var(--muted);font-size:12px}.phase-track{height:12px;border-radius:4px;background:var(--panel-2);overflow:hidden}.phase-fill{display:block;height:100%;min-width:2px;border-radius:4px;background:color-mix(in srgb,var(--accent) 65%,var(--panel-2))}.phase-value{text-align:right;white-space:nowrap}.surface-table{width:100%;border-collapse:collapse;margin-top:10px;font-size:12px}.surface-table th{text-align:left;color:var(--muted);font-weight:400;text-transform:uppercase;letter-spacing:.6px;font-size:10px;padding:4px 8px 6px 0;border-bottom:1px dashed var(--border)}.surface-table td{padding:5px 8px 5px 0;border-bottom:1px dashed var(--border)}.surface-table tr:last-child td{border-bottom:none}.st-key{font-weight:600}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;display:grid;place-items:center;z-index:50;padding:20px}.modal{background:var(--panel);border:1px solid var(--border);border-radius:12px;width:min(720px,100%);max-height:80vh;display:flex;flex-direction:column;padding:18px}.modal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.modal-head h3{margin:0;font-size:15px}.modal-actions{display:flex;gap:8px;margin-top:14px}.cmd{background:#000;color:var(--accent);padding:10px 12px;border-radius:8px;font-size:13px;overflow-x:auto}.run-log{background:#000;color:#d6d9e0;padding:12px;border-radius:8px;font-size:12.5px;line-height:1.5;overflow:auto;white-space:pre-wrap;word-break:break-word;flex:1;min-height:200px;max-height:55vh;margin:0}.picker{max-height:74vh}.picker-path{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;padding:7px 10px;margin-bottom:10px;word-break:break-all}.picker-list{flex:1;overflow:auto;border:1px solid var(--border);border-radius:8px;padding:6px;min-height:220px;max-height:48vh}.picker-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:transparent;border:none;border-radius:6px;padding:7px 9px;color:var(--text);font-size:13px}.picker-row:hover{background:var(--panel-2);border-color:transparent}.picker-row.up{color:var(--muted)}.picker-icon{color:var(--muted);width:14px;flex:none}.picker-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker-row .badge.small{font-size:10px;padding:0 6px;margin-left:auto}.picker-empty{padding:12px}.add-form .ghost[type=button]{white-space:nowrap}.usage-list{display:flex;flex-direction:column;gap:2px}.usage-row{display:grid;grid-template-columns:minmax(96px,1.4fr) 44px 62px minmax(90px,1.6fr) 66px 52px 52px 52px;align-items:center;gap:8px;padding:3px 0;font-size:12px}.usage-head{color:var(--muted);font-size:11px;border-bottom:1px solid var(--border);padding-bottom:5px;margin-bottom:3px}.usage-cmd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.usage-num,.usage-bar-head{text-align:right;font-variant-numeric:tabular-nums}.usage-bar{position:relative;height:16px;border-radius:4px;background:var(--panel-2);overflow:hidden}.usage-fill{position:absolute;inset:0 auto 0 0;border-radius:4px;background:color-mix(in srgb,var(--accent) 55%,var(--panel-2))}.usage-bar-label{position:relative;display:block;padding-right:6px;text-align:right;line-height:16px;font-variant-numeric:tabular-nums}.usage-chat{color:var(--muted)}.usage-chat .usage-fill{background:var(--panel-2);border:1px solid var(--border)}@media(max-width:720px){.usage-row{grid-template-columns:1fr 40px 58px minmax(70px,1fr)}.usage-row>:nth-child(n+5){display:none}}
|
|
1
|
+
:root{--bg: #0e0f13;--panel: #16181f;--panel-2: #1c1f28;--border: #262a35;--text: #e6e8ee;--muted: #8b90a0;--accent: #6ea8fe;--ok: #3fb950;--warn: #d9a441;--bad: #f85149;--dev: #a371f7;--mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-family:var(--mono);font-size:14px}.app{min-height:100vh}.topbar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:20;background:#0e0f13e6;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}.brand{font-weight:600;letter-spacing:.2px;display:flex;align-items:center;gap:8px}.dot{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 10px var(--accent)}.muted{color:var(--muted)}.small{font-size:12px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;gap:16px;padding:20px;max-width:1100px;margin:0 auto}@media(max-width:720px){.grid{grid-template-columns:1fr}}.panel{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 18px}.panel h2{margin:0 0 12px;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--muted)}.panel.placeholder{opacity:.7}.panel.error{grid-column:1 / -1;border-color:var(--bad);color:var(--bad)}.panel-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.panel-head h2{margin:0}.badge{font-size:11px;padding:3px 9px;border-radius:999px;border:1px solid var(--border);white-space:nowrap}.badge.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 40%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}.badge.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 40%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)}.badge.bad{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,transparent);background:color-mix(in srgb,var(--bad) 12%,transparent)}.badge.dev{color:var(--dev);border-color:color-mix(in srgb,var(--dev) 40%,transparent);background:color-mix(in srgb,var(--dev) 12%,transparent)}.badge.neutral{color:var(--muted)}.rows{display:flex;flex-direction:column;gap:2px}.row{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px dashed var(--border)}.row:last-child{border-bottom:none}.row-label{color:var(--muted)}.row-value{text-align:right}.row-value.strong{font-weight:600}.row-value.mono{font-family:var(--mono);font-size:12px}.actions{display:flex;align-items:center;gap:10px;margin-top:16px}.fresh-hint{margin:14px 0 0;padding-top:12px;border-top:1px dashed var(--border)}.fresh-hint strong{color:var(--accent)}button{font-family:var(--mono);font-size:13px;padding:7px 12px;border-radius:8px;border:1px solid var(--border);background:var(--panel-2);color:var(--text);cursor:pointer}button:disabled{opacity:.4;cursor:not-allowed}button.primary{background:color-mix(in srgb,var(--accent) 22%,var(--panel-2));border-color:color-mix(in srgb,var(--accent) 40%,transparent)}button.ghost{background:transparent}button:not(:disabled):hover{border-color:var(--accent)}.span2{grid-column:span 2}@media(max-width:720px){.span2{grid-column:1 / -1}}.summary{display:flex;gap:6px;flex-wrap:wrap}.checks{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}.check{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--border)}.check:last-child{border-bottom:none}.check.skip{opacity:.55}.check-icon{flex:none;width:20px;height:20px;border-radius:6px;display:grid;place-items:center;font-size:12px;font-weight:700;margin-top:1px}.check-icon.ok{color:var(--ok);background:color-mix(in srgb,var(--ok) 15%,transparent)}.check-icon.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 15%,transparent)}.check-icon.bad{color:var(--bad);background:color-mix(in srgb,var(--bad) 15%,transparent)}.check-icon.skip{color:var(--muted);background:var(--panel-2)}.check-body{flex:1;min-width:0}.check-line{display:flex;gap:10px;justify-content:space-between;flex-wrap:wrap}.check-label{font-weight:600}.check-detail{color:var(--muted);text-align:right}.check-fix{margin-top:4px;font-size:12px;color:var(--muted)}.check-fix code{color:var(--accent);background:var(--panel-2);padding:1px 6px;border-radius:5px}.surfaces{display:flex;flex-direction:column;gap:10px}.surface{border:1px solid var(--border);border-radius:9px;padding:10px 12px;background:var(--panel-2)}.surface-top{display:flex;align-items:center;gap:8px}.surface-key{font-weight:600}.surface-meta{display:flex;justify-content:space-between;gap:10px;margin-top:4px;font-size:12px}.surface-tools{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.tool{font-size:11px;color:var(--muted);border:1px solid var(--border);border-radius:5px;padding:1px 6px}.chip{font-size:11px;padding:1px 8px;border-radius:999px;border:1px solid var(--border)}.chip.design{color:var(--dev);border-color:color-mix(in srgb,var(--dev) 40%,transparent)}.chip.model-sonnet{color:var(--accent)}.chip.model-haiku{color:var(--ok)}.chip.model-inherit{color:var(--warn)}.fleet{max-width:1100px;margin:0 auto;padding:20px}.core-banner{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;background:linear-gradient(180deg,var(--panel-2),var(--panel));border:1px solid var(--border);border-radius:12px;padding:14px 18px;margin-bottom:20px}.cb-left{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.cb-title{text-transform:uppercase;letter-spacing:1px;font-size:12px;color:var(--muted)}.cb-version{font-weight:600;font-size:15px}.cb-actions{display:flex;gap:8px}.fleet-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px;flex-wrap:wrap}.fleet-head h2{margin:0;font-size:14px;display:flex;align-items:center;gap:8px}.add-wrap{flex:1;max-width:560px}.add-form{display:flex;gap:8px}.path-input{flex:1;font-family:var(--mono);font-size:13px;padding:7px 12px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:8px}.path-input:focus{outline:none;border-color:var(--accent)}.path-input.invalid{border-color:var(--bad)}.add-error{margin-top:6px;font-size:12px;color:var(--bad);word-break:break-word}.fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}.project-card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px 16px;cursor:pointer;transition:border-color .12s,transform .12s}.project-card:hover{border-color:var(--accent);transform:translateY(-1px)}.project-card.gone{opacity:.6;cursor:default}.pc-head{display:flex;align-items:center;justify-content:space-between}.pc-name{font-weight:600}.pc-path{margin:2px 0 10px}.pc-badges{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}.pc-health{display:flex;align-items:center;gap:5px}.pc-counts{margin-left:auto}.icon-btn{border:none;background:transparent;color:var(--muted);padding:2px 6px;border-radius:6px;font-size:13px}.icon-btn:hover{color:var(--bad);background:var(--panel-2)}.pill{font-size:11px;min-width:20px;text-align:center;padding:1px 7px;border-radius:999px;font-weight:600}.pill.ok{color:var(--ok);background:color-mix(in srgb,var(--ok) 14%,transparent)}.pill.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}.pill.bad{color:var(--bad);background:color-mix(in srgb,var(--bad) 14%,transparent)}.pill.neutral{color:var(--muted);background:var(--panel-2)}.detail-crumb{grid-column:1 / -1;margin-bottom:-4px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.detail-tools{display:flex;gap:8px;flex-wrap:wrap}.tool-btn{font-size:12px;padding:5px 10px;border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));color:var(--accent);background:transparent}.tool-btn:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 10%,transparent)}.confirm-text p{margin:0 0 8px;font-size:13px;line-height:1.6}.confirm-text code{background:var(--panel-2);padding:1px 5px;border-radius:4px;color:var(--accent)}.warn-line{color:var(--warn)!important;background:color-mix(in srgb,var(--warn) 10%,transparent);border-radius:8px;padding:8px 10px}.warn-line code{color:var(--text)!important}.danger-ghost{background:transparent;border:1px solid color-mix(in srgb,var(--bad) 40%,var(--border));color:var(--bad);font-size:12px;padding:5px 10px}.danger-ghost:hover{background:color-mix(in srgb,var(--bad) 12%,transparent);border-color:var(--bad)}button.danger{background:color-mix(in srgb,var(--bad) 20%,var(--panel-2));border-color:color-mix(in srgb,var(--bad) 45%,transparent);color:#ffd7d3}button.danger:hover{border-color:var(--bad)}.reset-list{margin:8px 0;padding-left:18px;font-size:13px;line-height:1.7}.reset-list code{color:var(--accent)}.reset-note{font-size:12.5px;color:var(--muted);background:var(--panel-2);border-radius:8px;padding:10px 12px}.reset-note code{color:var(--text)}.reset-check{display:flex;align-items:center;gap:8px;margin:12px 0 0;font-size:13px}.reset-check code{color:var(--accent)}.board-scroll{max-height:500px;overflow:auto}.board{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(140px,1fr);gap:10px}.col{background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:8px;min-width:140px}.col-head{display:flex;justify-content:space-between;font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);padding:2px 4px 8px;position:sticky;top:0;background:var(--bg);z-index:1}.col-shipped{opacity:.85}.col-blocked{border-color:color-mix(in srgb,var(--bad) 40%,var(--border))}.col-other{border-color:color-mix(in srgb,var(--warn) 40%,var(--border))}.spec-card{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;padding:8px 9px;margin-bottom:7px}.spec-card.bad{border-color:color-mix(in srgb,var(--warn) 45%,transparent)}.spec-title{font-size:13px;font-weight:600}.spec-meta,.spec-branch,.spec-loop{margin-top:3px}@media(max-width:640px){.board{grid-auto-flow:row;grid-template-columns:repeat(2,1fr)}}.kanban-board{display:flex;gap:10px}.kanban-col{flex:0 0 210px;background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:8px}.kanban-col.empty{opacity:.5}.kanban-card{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;padding:8px 9px;margin-bottom:7px}.kanban-card.done{opacity:.6}.kanban-card.done .kc-text{text-decoration:line-through}.kc-text{font-size:12.5px;line-height:1.4}.kc-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}.kc-tag{font-size:10px;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border-radius:4px;padding:1px 5px}.kc-pr{font-size:10px;border-radius:4px;padding:1px 5px;text-decoration:none;border:1px solid var(--border);color:var(--muted)}a.kc-pr:hover{filter:brightness(1.25)}.kc-pr.state-open{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 40%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}.kc-pr.state-merged{color:var(--dev);border-color:color-mix(in srgb,var(--dev) 40%,transparent);background:color-mix(in srgb,var(--dev) 12%,transparent)}.kc-pr.state-closed{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,transparent);background:color-mix(in srgb,var(--bad) 12%,transparent)}.kc-pr.draft{color:var(--muted);border-color:var(--border);background:var(--panel-2)}.kc-pr.flat{border-style:dashed;cursor:default}.kc-status{font-size:10px;margin-top:5px;color:var(--muted);text-transform:capitalize}.kc-status.state-open{color:var(--ok)}.kc-status.state-merged{color:var(--dev)}.kc-status.state-closed{color:var(--bad)}.metrics-list{display:flex;flex-direction:column;gap:12px}.metric-feature{background:var(--bg);border:1px solid var(--border);border-radius:9px;padding:10px 12px}.mf-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:8px}.mf-name{font-weight:600}.mf-badges{display:flex;gap:6px;flex-wrap:wrap}.phase-bars{display:flex;flex-direction:column;gap:4px}.phase-row{display:grid;grid-template-columns:56px 1fr 90px;align-items:center;gap:10px}.phase-label{color:var(--muted);font-size:12px}.phase-track{height:12px;border-radius:4px;background:var(--panel-2);overflow:hidden}.phase-fill{display:block;height:100%;min-width:2px;border-radius:4px;background:color-mix(in srgb,var(--accent) 65%,var(--panel-2))}.phase-value{text-align:right;white-space:nowrap}.surface-table{width:100%;border-collapse:collapse;margin-top:10px;font-size:12px}.surface-table th{text-align:left;color:var(--muted);font-weight:400;text-transform:uppercase;letter-spacing:.6px;font-size:10px;padding:4px 8px 6px 0;border-bottom:1px dashed var(--border)}.surface-table td{padding:5px 8px 5px 0;border-bottom:1px dashed var(--border)}.surface-table tr:last-child td{border-bottom:none}.st-key{font-weight:600}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;display:grid;place-items:center;z-index:50;padding:20px}.modal{background:var(--panel);border:1px solid var(--border);border-radius:12px;width:min(720px,100%);max-height:80vh;display:flex;flex-direction:column;padding:18px}.modal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.modal-head h3{margin:0;font-size:15px}.modal-actions{display:flex;gap:8px;margin-top:14px}.cmd{background:#000;color:var(--accent);padding:10px 12px;border-radius:8px;font-size:13px;overflow-x:auto}.run-log{background:#000;color:#d6d9e0;padding:12px;border-radius:8px;font-size:12.5px;line-height:1.5;overflow:auto;white-space:pre-wrap;word-break:break-word;flex:1;min-height:200px;max-height:55vh;margin:0}.picker{max-height:74vh}.picker-path{background:var(--panel-2);border:1px solid var(--border);border-radius:7px;padding:7px 10px;margin-bottom:10px;word-break:break-all}.picker-list{flex:1;overflow:auto;border:1px solid var(--border);border-radius:8px;padding:6px;min-height:220px;max-height:48vh}.picker-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:transparent;border:none;border-radius:6px;padding:7px 9px;color:var(--text);font-size:13px}.picker-row:hover{background:var(--panel-2);border-color:transparent}.picker-row.up{color:var(--muted)}.picker-icon{color:var(--muted);width:14px;flex:none}.picker-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker-row .badge.small{font-size:10px;padding:0 6px;margin-left:auto}.picker-empty{padding:12px}.add-form .ghost[type=button]{white-space:nowrap}.usage-list{display:flex;flex-direction:column;gap:2px}.usage-row{display:grid;grid-template-columns:minmax(96px,1.4fr) 44px 62px minmax(90px,1.6fr) 66px 52px 52px 52px;align-items:center;gap:8px;padding:3px 0;font-size:12px}.usage-head{color:var(--muted);font-size:11px;border-bottom:1px solid var(--border);padding-bottom:5px;margin-bottom:3px}.usage-cmd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.usage-num,.usage-bar-head{text-align:right;font-variant-numeric:tabular-nums}.usage-bar{position:relative;height:16px;border-radius:4px;background:var(--panel-2);overflow:hidden}.usage-fill{position:absolute;inset:0 auto 0 0;border-radius:4px;background:color-mix(in srgb,var(--accent) 55%,var(--panel-2))}.usage-bar-label{position:relative;display:block;padding-right:6px;text-align:right;line-height:16px;font-variant-numeric:tabular-nums}.usage-chat{color:var(--muted)}.usage-chat .usage-fill{background:var(--panel-2);border:1px solid var(--border)}@media(max-width:720px){.usage-row{grid-template-columns:1fr 40px 58px minmax(70px,1fr)}.usage-row>:nth-child(n+5){display:none}}
|