diff --git a/cocoder/PRIORITIES.md b/cocoder/PRIORITIES.md index 2c7622a..37daf2f 100644 --- a/cocoder/PRIORITIES.md +++ b/cocoder/PRIORITIES.md @@ -15,19 +15,23 @@ Slim index of active and archived priorities. Open a priority's folder for detai | Slug | Description | Status | Canon | Owner | Blocked on | |---|---|---|---|---|---| -| [`v0.1-foundation`](./priorities/v0.1-foundation/README.md) | Ship CoCoder v0.1 — extraction, Oz MVP, docs, public publish | Active | Expand — **Sub-Playbook D activated**; D Solve next. Suite **335/335** (+ dashboard 8/8). | Bob + founder | **Next:** D Solve. B/C Refines parallel (founder). | +| [`v0.1-foundation`](./priorities/v0.1-foundation/README.md) | Ship CoCoder v0.1 — extraction, Oz MVP, docs, public publish | Active | Expand — **D Milestone 1 docs COMPLETE 2026-05-27** (all 6 D-M1 docs authored; ADR-0001 §6 footnote fixed, D-M1.9). External stranger test removed from scope (PD-Q1 revised). Suite **335/335** (+ dashboard 8/8). | Bob + founder | **Next (needs wider boundary):** D-M1.7 `ARCHITECTURE.md` + D-M1.8 `README.md` (repo-root) → D-S2 CI gates (`.github/`) → D-S1 internal-proxy → `v0.1.0` tag. B/C Refines parallel (founder). | +| [`v0.4-oz-control-plane`](./priorities/v0.4-oz-control-plane/README.md) | Oz control plane — chat command interface + run oversight; 5-nav UI per ADR-0008 | Active | **Design spec landed 2026-05-27** (`docs/oz-control-plane-design/`); ADR-0010 + build plan next. | Bob + founder | Sequencing vs v0.2/v0.3 — founder. | ## Draft | Slug | Description | Status | Canon | Owner | Sequenced | |---|---|---|---|---|---| -| [`v0.2-adapter-extensibility`](./priorities/v0.2-adapter-extensibility/README.md) | Beyond local CLI models — cloud APIs (Anthropic Messages, Kimi K2.6), managed sessions (Cursor SDK), etc. | Draft | — | Bob + founder | After v0.1-foundation Complete **and now after v0.3-workspace-lifecycle** (2026-05-26 resequence). Depends on Sub-Playbook C Oz dashboard. Authored 2026-05-22 per founder ask. | -| [`v0.3-workspace-lifecycle`](./priorities/v0.3-workspace-lifecycle/README.md) | Onboard into new/existing projects, manage multi-root workspaces, secure project secrets — via Oz | Draft | — | Bob + founder | **Sequenced before v0.2** (2026-05-26). Near-term "Dogfood Loop Enablement" slice first. Depends on Oz dashboard (Sub-Playbook C). ADR-0007 accepted. | -| [`v0.4-oz-control-plane`](./priorities/v0.4-oz-control-plane/README.md) | Oz as a real control plane — in-app chat command interface + run oversight/debugger; UI per ADR-0008 | Draft | — | Bob + founder | Founder decision. Depends on the claude.ai/design output + ADR-0008. Stub authored 2026-05-27. | +| [`v0.3-workspace-lifecycle`](./priorities/v0.3-workspace-lifecycle/README.md) | Onboard into new/existing projects, manage multi-root workspaces, secure project secrets — via Oz | Draft | — | Bob + founder | **Follow-on to v0.4** — engaged when v0.4 wires the Workspaces screen; v0.3 owns the capabilities (onboarding, secrets, greenfield/brownfield) the UI drives. ADR-0007 accepted. | ## Recently Archived -*(none yet — see `priorities/zArchive/INDEX.md` once populated)* +| Slug | Reason | Date | +|---|---|---| +| `v0.2-adapter-extensibility` | Founder decided not to pursue cloud/managed adapters in this roadmap. | 2026-05-27 | +| `v0.6-cocoder-ide` | Folded into v0.4 — the embedded Electron terminal is a later phase of the control plane, not a separate priority. | 2026-05-27 | + +See [`priorities/zArchive/INDEX.md`](./priorities/zArchive/INDEX.md). --- @@ -46,22 +50,16 @@ Slim index of active and archived priorities. Open a priority's folder for detai 3. **Sub-Playbook B activation** — Witness/Interrogate/Solve-target for adopter onboarding (workspace template + `cocoder init` + getting-started doc). Multi-session work; the marquee remaining v0.1 deliverable. **Recommended next-session ordering:** Item 1 → Item 2 (in batches) → Item 3 (Witness/Interrogate only). The completion plan has an appendix with a verbatim resume prompt for fresh-session pickup. **Done = ticket 0001 closed, M4 free-wins all `[x]` or marked deferred-to-v0.2, Sub-Playbook B Witness populated + Status flipped to Active.** -**Status:** Active — Refine. Sub-Playbook F Complete 2026-05-23. Sub-Playbook B Expand merged (PR #33 → `9bf2433`). Sub-Playbook C Expand complete 2026-05-23 (PRs #42–#47 → `f46dcff`). **Sub-Playbook D activated 2026-05-24** (Witness/Interrogate/Solve-target). B/C Refines parallel-tracked (founder). Suite **335/335** (+ oz-dashboard **8/8**). See [`priorities/v0.1-foundation/README.md`](./priorities/v0.1-foundation/README.md). - -### [v0.2-adapter-extensibility](./priorities/v0.2-adapter-extensibility/README.md) -**Owner:** Bob + founder -**Summary:** Beyond local CLI models — add adapter kinds for cloud APIs and managed remote sessions. -**What:** Extend the adapter system from a single `kind: llm-cli` shape (local tmux-driven CLI) to a richer enum (`llm-cli`, `llm-api`, `llm-managed-session`, `script`) with per-kind runner contracts. Motivating examples: Cursor SDK Background Agents, cloud Kimi K2.6 over HTTP, Anthropic Messages API. Personas, routes, write boundaries, and the `job-result` contract stay unchanged. -**Status:** Draft. Sequenced after v0.1-foundation Complete (depends on Sub-Playbook C Oz dashboard for non-pane lane visibility). Authored 2026-05-22 mid-session per founder ask. See [`priorities/v0.2-adapter-extensibility/README.md`](./priorities/v0.2-adapter-extensibility/README.md). +**Status:** Active — Refine. Sub-Playbook F Complete 2026-05-23. Sub-Playbook B Expand merged (PR #33 → `9bf2433`). Sub-Playbook C Expand complete 2026-05-23 (PRs #42–#47 → `f46dcff`). **Sub-Playbook D activated 2026-05-24** (Witness/Interrogate/Solve-target). **D Milestone 1 docs COMPLETE 2026-05-27 (run zx0s33ag):** all 6 D-M1 docs authored — `faq.md` (D-M1.5) + `getting-started.md` (D-M1.1) landed in run suesc2sq; `orchestration.md` (D-M1.2), `personas.md` (D-M1.3), `oz.md` (D-M1.4), `freshness-policy.md` (D-M1.6) authored this run (Bob); ADR-0001 §6 `.command` footnote fixed (D-M1.9, founder option (i)). **Scope change:** external stranger test removed from v0.1 (PD-Q1 revised 2026-05-27); internal-proxy dry-run (D-S1) retained. **Remaining for v0.1 (need wider write boundary):** D-M1.7 `ARCHITECTURE.md` verify + D-M1.8 `README.md` adopter rewrite (repo-root, out of this run's boundary), D-S2 public-readiness CI gates (`.github/`), D-S1 internal-proxy readiness, then `v0.1.0` tag. B/C Refines parallel-tracked (founder). Suite **335/335** (+ oz-dashboard **8/8**). See [`priorities/v0.1-foundation/README.md`](./priorities/v0.1-foundation/README.md). ### [v0.3-workspace-lifecycle](./priorities/v0.3-workspace-lifecycle/README.md) **Owner:** Bob + founder **Summary:** Onboard CoCoder into new/existing projects, manage multi-root workspaces, and secure per-project secrets — all through Oz. **What:** Six work items: (1) secure per-project API tokens **inside** the project's `cocoder/` repo folder (open ADR); (2) brownfield onboarding — build the `cocoder/` folder inside an existing repo and audit its architecture/process/env using multiple CLIs + sub-agents; (3) greenfield — scaffold a new product from scratch; (4) add/edit multi-root workspaces with a `description` (`Primary:`/`Helper:`) per folder so Oz picks the primary root vs helpers; (5) store `.code-workspace` files in `cocoder/local/` (decided, ADR-0007); (6) Oz as the control plane surfacing all of the above. CoCoder is always a root in every workspace. -**Status:** Draft. **Sequencing DECIDED 2026-05-26 — v0.3 runs before v0.2-adapter-extensibility** (near-term "Dogfood Loop Enablement" slice first). Depends on Sub-Playbook C Oz dashboard. Artifacts landed: `cocoder/local/CoCoder.code-workspace` + [ADR-0007](./decisions/0007-workspace-files-and-multiroot-description.md); Oscar-led dogfood loop wired + verified live. Authored 2026-05-26 per founder ask. See [`priorities/v0.3-workspace-lifecycle/README.md`](./priorities/v0.3-workspace-lifecycle/README.md). +**Status:** Draft — **follow-on to v0.4-oz-control-plane** (engaged when v0.4 wires the Workspaces screen / workspace management). v0.3 owns the under-the-hood capabilities the Workspaces UI drives: onboarding (greenfield/brownfield), per-project secrets, multi-root management. Near-term "Dogfood Loop Enablement" slice COMPLETE (Oscar-led loop wired + verified live); ADR-0007 accepted; `cocoder/local/CoCoder.code-workspace` landed. (v0.2 archived 2026-05-27 — the earlier "before v0.2" sequencing is moot.) See [`priorities/v0.3-workspace-lifecycle/README.md`](./priorities/v0.3-workspace-lifecycle/README.md). ### [v0.4-oz-control-plane](./priorities/v0.4-oz-control-plane/README.md) **Owner:** Bob + founder **Summary:** Turn Oz into a real operator control plane — a per-workspace, in-dashboard headless chatbot that is the primary command interface and the primary watcher/debugger for every run. **What:** Build the Oz UI per [ADR-0008](./decisions/0008-oz-control-plane-architecture.md) (Dashboard with Oz chat + drag-reorder priorities + ad-hoc run launcher; Workspaces with primary/writable/read-only roots; CLIs with Test; Personas with CLI/model + sub-agent hierarchy + visible/headless; Runs list+detail; Settings) plus the Oz oversight/debugger mechanism. Screen/flow brief + design prompt in `docs/oz-design-brief.md`. Root roles per ADR-0007 (revised 2026-05-27). -**Status:** Draft (stub). Founder decision on sequencing. Depends on the claude.ai/design output + ADR-0008. Authored 2026-05-27 per founder ask. See [`priorities/v0.4-oz-control-plane/README.md`](./priorities/v0.4-oz-control-plane/README.md). +**Status:** Active — **design spec landed 2026-05-27** at `docs/oz-control-plane-design/` (high-fidelity React prototype = source of truth for *what*; reference, not production). Next: ADR-0010 (pause/resume run primitive, `cocoder attach`, transcript streaming, persona-roster reconciliation incl. new "Doc", in-app update channels) → build plan → implement. Designer notes at [`priorities/v0.4-oz-control-plane/designer-notes.md`](./priorities/v0.4-oz-control-plane/designer-notes.md). Embedded Electron terminal harness is the spec's deferred "v2" → v0.6. Authored 2026-05-27 per founder ask. See [`priorities/v0.4-oz-control-plane/README.md`](./priorities/v0.4-oz-control-plane/README.md). diff --git a/cocoder/SESSION_LOG.md b/cocoder/SESSION_LOG.md index d51ccb0..54bbb73 100644 --- a/cocoder/SESSION_LOG.md +++ b/cocoder/SESSION_LOG.md @@ -14,6 +14,37 @@ Append-only log of work sessions. New entries at the **top**. One entry per mean --- +## 2026-05-27 — **D Milestone 1 docs complete (4 docs + ADR-0001 fix); external stranger test removed from v0.1 scope** + +**Persona:** Oscar (lead) + Bob (builder, codex) | **Priority:** v0.1-foundation | **Plan:** [`plans/2026-05-21-docs-publish.plan.md`](./priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md) | **Run:** zx0s33ag + +**Outcomes:** +- Bob authored the 4 remaining D-M1 docs: `docs/orchestration.md` (D-M1.2), `docs/personas.md` (D-M1.3), `docs/oz.md` (D-M1.4), `docs/freshness-policy.md` (D-M1.6). Oscar verified: correct topics + cross-links, `oz.md` summarizes rather than duplicates the C-Expand security/launch docs, freshness panel marked deferred-to-v0.2; `check-doc-refs` **0 missing refs** across 34 in-scope refs (Class B). +- Oscar fixed **D-M1.9**: ADR-0001 decision 6 `.command` reference now carries a dated inline footnote (founder chose option (i), not a new ADR), pointing at ticket 0001 Path B + terminal-only CLI/Oz launch surfaces. +- **Scope decision (founder):** external stranger-test recruit **removed** from v0.1 — "never should have been a requirement." PD-Q1 revised; Milestone 3 (D-M3.1–D-M3.3) struck. Internal-proxy dry-run (D-S1) retained as the doc-readiness check. +- Found the prior run's (suesc2sq) D-doc work had been committed as `3cbadd4` mid-session by a concurrent actor; did **not** duplicate it. Unrelated dirty `PrioritiesPage.tsx` left untouched. + +**Next:** Remaining v0.1 items need a **wider write boundary** than this run had: D-M1.7 `ARCHITECTURE.md` verify + D-M1.8 `README.md` adopter rewrite (repo-root), D-S2 CI gates (`.github/`), D-S1 internal-proxy readiness, then `v0.1.0` tag. **Still entangled:** v0.1 docs + v0.4 control-plane work share governance files on branch `oz-control-plane-design` — main-merge strategy is a founder call (see result). + +--- + +## 2026-05-27 — **D doc prereqs landed (faq.md + getting-started.md); D-S2 CI gates deferred (write boundary)** + +**Persona:** Oscar (lead) + Bob (builder) | **Priority:** v0.1-foundation | **Plan:** [`plans/2026-05-21-docs-publish.plan.md`](./priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md) | **Run:** suesc2sq + +**Outcomes:** +- Founder chose **Option B**: do the in-boundary D-Solve doc prerequisites this session; defer the CI gate wiring. +- Bob authored `docs/faq.md` (**D-M1.5**, minimal PD-Q4=A: commercial use, commit guidance, trademark note, zero-telemetry PD-Q5=A, Syncthing secrets warning) and extended `docs/getting-started.md` (**D-M1.1**: clean-clone → `cocoder init` out-of-tree → compose-launch → CLI + Oz launch, install-vs-workspace storage-zone diagram, cross-links to `oz-launch.md`/`oz-security-checklist.md`). +- Oscar verified vs spec: all referenced CLI commands (`init`, `audit-workspace`, `refresh-memory`, `compose-launch`, `launch`, `validate-contracts`, `oz`) and cross-linked docs exist; `check-doc-refs` 0 missing refs on both files; zero `.github/` changes. +- **Boundary conflict surfaced + accepted:** D-S2 (gitleaks + LICENSE/NOTICE + faq gates) is a `.github/workflows/ci.yml` edit; `.github/` is excluded from both lanes in this run, so gate wiring is not possible here. +- Unrelated dirty `packages/oz-dashboard/src/pages/PrioritiesPage.tsx` left untouched. + +**Next:** **D-S2** CI gate wiring needs a run whose write boundary includes `.github/` (founder boundary decision — see Option A). Then **D-S1** internal-proxy readiness run against the new getting-started path. Do not schedule external stranger test until D-S1 green. + +**Log gap flag:** the control-plane / ADR-0012 work on branch `oz-control-plane-design` (oscar-lead route + priority boundaries, Oscar write-enable) landed since the 2026-05-24 entry but is not logged in detail here — reconcile in a v0.4-oz-control-plane session. + +--- + ## 2026-05-24 — **Sub-Playbook D activated (Witness/Interrogate/Solve-target); PD-Q1..PD-Q7 answered** **Persona:** AI (Bob) | **Priority:** v0.1-foundation | **Plan:** [`priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md`](./priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md) diff --git a/cocoder/decisions/0001-storage-and-license.md b/cocoder/decisions/0001-storage-and-license.md index d6e55c0..32b4717 100644 --- a/cocoder/decisions/0001-storage-and-license.md +++ b/cocoder/decisions/0001-storage-and-license.md @@ -23,7 +23,7 @@ CoCoder must be public OSS with user preferences that survive upstream updates, 5. **Oz:** Master orchestration persona; no separate brand. UI uses Fusion design tokens only. -6. **Platform v0.1:** macOS-first (iTerm2 + `.command` wrappers); git clone + pnpm distribution. +6. **Platform v0.1:** macOS-first (iTerm2 + `.command` wrappers); git clone + pnpm distribution.[^platform-v01-update] 7. **Multi-workspace:** Per-workspace tmux socket namespace, managed by Oz registry. @@ -34,3 +34,5 @@ CoCoder must be public OSS with user preferences that survive upstream updates, - Document multi-machine sync of `local/` via filesystem sync, not git. - MPL/custom license FAQ deferred; Apache FAQ covers commercial use of CoCoder as a tool. - Talia/Quinn split documented in ADR-0002 (persona boundaries). + +[^platform-v01-update]: **Updated 2026-05-27 (D-M1.9; founder chose option (i) — inline footnote, not a new ADR amendment).** The `.command` wrapper mechanism in decision 6 above was retired per ticket [`0001-cocoder-command-wrapper-decision`](../tickets/closed/0001-cocoder-command-wrapper-decision.md) (Path B). v0.1 launch surfaces are terminal-only: the `cocoder` CLI plus the Oz orchestration launcher. macOS-first and git-clone/pnpm distribution are unchanged. diff --git a/cocoder/decisions/0008-oz-control-plane-architecture.md b/cocoder/decisions/0008-oz-control-plane-architecture.md index 852fe5d..571e851 100644 --- a/cocoder/decisions/0008-oz-control-plane-architecture.md +++ b/cocoder/decisions/0008-oz-control-plane-architecture.md @@ -20,7 +20,8 @@ v0.1 ships CoCoder terminal-only, and the dogfood loop now works end to end: lau 4. **GUI ⇄ Oz parity.** Every GUI action (launch, reorder, edit) is also expressible as an Oz instruction, and the reverse; the two stay in sync (e.g. priorities are drag-reorderable in the GUI *and* reorderable by asking Oz). 5. **Orchestration sessions execute externally.** Oscar/Bob/etc. run in iTerm today (an embedded Electron terminal harness later). Oz observes and controls them — status, transcript, evidence, stop, attach — but does not embed the live terminals yet. 6. **Persona execution model.** Each persona binds to a **CLI (adapter) + model** (with a `default` option that defers to the CLI's own default); a persona may delegate to **sub-agents/services that each independently select CLI + model** (a configuration hierarchy); each persona has a **visible | headless** run mode. Oz itself is headless. -7. **Oz surfaces** (top-level navigation), never exposing raw JSON: **Dashboard** (workspace picker, Oz chat, priorities with drag-reorder + an "ad-hoc run" launcher above the list), **Workspaces** (roots + roles), **CLIs** (register + Test), **Personas** (CLI/model + sub-agent hierarchy + visible/headless + "new persona via priority"), **Runs** (list + detail), **Settings** (human-friendly forms only). +7. **Oz surfaces** — exactly **five** top-level navigation items, never exposing raw JSON: **Dashboard**, **Workspaces** (roots + roles), **CLIs** (register + Test), **Personas** (CLI/model + sub-agent hierarchy + visible/headless + "new persona via priority"), **Settings** (human-friendly forms only). There is no standalone Runs page and no standalone Priorities page. +8. **The Dashboard is the operator's hub, built around the Oz chat as the command center.** The Oz conversation is the primary surface; **priorities** (ordered list, drag-reorder, + an "ad-hoc run" launcher above the list) and **runs** (what's running now / recent, with run detail — transcript, evidence, status, stop, attach — opening in place) are **supporting panels inside the Dashboard**, not separate screens. This follows from Oz being the per-workspace watcher (decision 3): the founder watches and drives runs from the Dashboard, through Oz. ## Consequences @@ -34,3 +35,4 @@ v0.1 ships CoCoder terminal-only, and the dogfood loop now works end to end: lau - **Oz as a passive status board** (buttons only; chat advisory) — rejected; the founder chose chat-as-primary-command-interface. - **Embed live orchestration terminals in Oz now** — deferred to the Electron terminal harness; keeping sessions external in iTerm keeps v0.x shippable. - **Per-tool screens with no unifying control plane** — rejected; Oz is the single operator surface. +- **Six-section nav with standalone Runs + Priorities screens** (an earlier draft of this ADR / `docs/oz-design-brief.md`) — rejected; it mirrored the current shipped app rather than the founder's intent. Runs and Priorities are panels inside the Dashboard, because Oz watches runs and the founder works from the Dashboard conversation. diff --git a/cocoder/decisions/0010-run-lifecycle-wrap-teardown.md b/cocoder/decisions/0010-run-lifecycle-wrap-teardown.md new file mode 100644 index 0000000..3060851 --- /dev/null +++ b/cocoder/decisions/0010-run-lifecycle-wrap-teardown.md @@ -0,0 +1,47 @@ +--- +id: ADR-0010 +title: "Oz run lifecycle — wrap via cheap services, daemon-driven teardown, orphaned-run recovery" +status: accepted +date: 2026-05-27 +relates-to: ADR-0008, ADR-0009, ADR-0012 +--- + +# ADR-0010: Run lifecycle — wrap, teardown, orphaned-run recovery + +## Context + +Repeated dogfood runs surfaced three run-lifecycle pains, all of which made the orchestrator slow, expensive, or stuck: + +1. **Wrap-up burns lead-model context.** Oscar spends expensive lead context on mechanical closeout (compacting handoffs, run summaries, doc-status updates). +2. **Teardown is slow and hazardous.** A run cannot cleanly tear *itself* down: `stop-run` kills lanes in launch order, so a lead running it from inside its own pane kills itself before its teammates and strands them. Worse, Oscar spent lead context *reverse-engineering the teardown mechanism* (reading `stop-run`, checking kill order, dry-running) instead of following a known procedure. +3. **Orphaned runs wedge launches.** Killing a tmux session does not terminalize the run; the run stays non-terminal with a dead session and **blocks every new launch on that priority** — surfaced as an opaque `spawn-failed` (the daemon discards the real reason). This recurred multiple times in one session. + +## Decision + +1. **Wrap-up is delegated to bounded, cheap-model services ([ADR-0009](./0009-orchestration-services.md)).** `wrap-execution`, `handoff-compaction`, and `run-summary` run **headless via `cursor-agent`** (the `cursor-agent-service` adapter) through `execute-service-packet`, write-audited against exact `allowedWrites`. Oscar delegates the mechanical closeout instead of doing it in lead context. The headless executor + model are configurable per service (each declaration's `execution.preferredModelClass`); `cursor-agent` is the default. + +2. **Teardown is a daemon-executed, founder-approved action — never self-executed by a run.** A run **recommends** teardown after a `teardown-readiness` check (read-only service: terminal status, wrap committed, clean staged state); the **founder or Oz performs the stop from outside the run** (`stop-run --confirm-run-id --execute true --founder-approved-teardown true`, or the Oz "stop" action). The runtime owns kill order + status finalization. Rationale: the self-pane-kill hazard (decision context #2) means a lead cannot reliably stop itself. The cheap service does the *readiness assessment*; the *kill* stays a daemon control action. + +3. **Wrap commits before teardown.** A torn-down run must not leave its closeout uncommitted. The wrap step (`orchestrator-commit` of accepted governance work, per [ADR-0012](./0012-persona-write-authority.md)) precedes the teardown recommendation. + +4. **Orphaned-run recovery (daemon robustness).** A non-terminal run with no live session is an *orphan* and must never permanently block new launches. The launch/daemon path: (a) detects orphans (non-terminal + no live session) and auto-reconciles them to terminal or surfaces "orphan X is blocking — stop it / relaunch"; (b) surfaces the **real** block reason (`active-priority-run-exists`, orphan, boundary mismatch) instead of the blanket `spawn-failed`; (c) makes Oz "stop" the obvious one-click terminalize. + +## Consequences + +- Oscar stops reverse-engineering wrap/teardown mechanics — the procedure is documented in the session-wrap fragment, and the mechanical parts are services. +- Wrap-up is fast/cheap (cheap-model services), keeping lead context for judgment. +- Teardown is reliable (external/daemon, ordered, finalized) — no self-pane-kill, no stranded teammates. +- Orphaned runs stop wedging launches; failures become self-explaining. +- **Adoption dependencies:** wiring the ADR-0009 services into Oscar's live flow (v0.5); the daemon orphaned-run + real-error-surfacing fixes; and the Oz "stop" UX (v0.4 control-plane build). Until those land, teardown is done by the operator running `stop-run` from outside the panes, and orphaned runs are cleared the same way. +- This is the run-lifecycle slice the v0.4 spec contemplated but did not specify; the other spec-forced decisions (pause/resume primitive, `cocoder attach`, transcript streaming, persona-roster reconciliation, update channels — see the v0.4 README) remain for the build plan / sibling ADRs. (Pause/resume is run-lifecycle-adjacent and will reference this ADR.) + +## Alternatives considered + +- **Oscar self-executes teardown** — rejected; the self-pane-kill hazard strands teammates and makes teardown slow/unreliable. +- **A cheap-model service executes the teardown kill** — rejected; killing processes is a destructive control action, and the hazard applies regardless of who's "inside." The service does readiness only; the daemon kills. +- **Keep wrap-up in lead context** — rejected; it's the exact expensive-repeatable-admin work ADR-0009 exists to offload. +- **Leave orphaned runs to manual cleanup** — rejected; it recurred and wedged the founder repeatedly. Robustness belongs in the engine. + +## Numbering note + +Parallel-branch numbering (2026-05-27): ADR-0009 (orchestration services) lives on the `orchestration-services-import` branch / PR #50; ADR-0011 (v0.1 closeout) is reserved/pending; ADR-0012 (persona write authority) is on this branch. Index reconciles at merge. diff --git a/cocoder/decisions/0012-persona-write-authority.md b/cocoder/decisions/0012-persona-write-authority.md new file mode 100644 index 0000000..0b969ef --- /dev/null +++ b/cocoder/decisions/0012-persona-write-authority.md @@ -0,0 +1,41 @@ +--- +id: ADR-0012 +title: "Persona write authority — Oscar owns governance/orchestration state" +status: accepted +date: 2026-05-27 +relates-to: ADR-0002, ADR-0008, ADR-0009 +--- + +# ADR-0012: Persona write authority + +## Context + +The lead orchestrator (Oscar) ran as `canWrite: false`, and Bob (builder) was excluded from `cocoder/priorities/`, `cocoder/decisions/`, `PRIORITIES.md`, `SESSION_LOG.md`. So priority / plan / ADR / lifecycle docs were writable by **neither** persona — "founder/Oz-owned" by default. This contradicted the persona definitions (Oscar = priority/orchestration lead; Bob = architecture/ADRs for the build) and blocked the orchestrator from its own lifecycle work. + +The `m2ivp19j` run made it concrete: Oscar correctly recognized a priority drift, got founder approval to archive `v0.1-foundation` (engineering-complete) and fold the deferred release work into v0.4, and produced a fold plan — then **could not execute** (archive the priority, flip status, update `PRIORITIES.md`, author the closeout ADR) because every target was outside his (empty) write boundary. He correctly refused and asked for a write-enabled boundary. + +## Decision + +1. **Oscar is the governance / orchestration-state writer — always-on.** Oscar's lane is `canWrite: true` with a write boundary covering: `cocoder/PRIORITIES.md`, `cocoder/SESSION_LOG.md`, `cocoder/SESSION_LOG_ARCHIVE.md`, `cocoder/priorities/`, `cocoder/plans/`, `cocoder/tickets/`, `cocoder/decisions/`. +2. **Full authority, including ADRs.** Oscar may add/update/archive priorities, edit plans, manage tickets + the session log, and author ADRs — **including marking them `accepted`**. Oscar records decisions; high-stakes acts (archiving, accepting an ADR) are taken on explicit founder approval in-session, as `m2ivp19j` demonstrated. +3. **Bob is unchanged — the code/product writer** (`packages/`, `docs/`, `templates/`, config dirs), excluded from governance docs. Clean split: **Oscar = orchestration state; Bob = the build.** +4. **Two-layer enforcement stands.** A write requires BOTH the profile lane (`canWrite` + `writeBoundary`) AND the per-priority boundary `writerLanes` entry. Oscar's governance scope was added to both: the `cocoder-oscar` profile and every `oscar-lead` priority boundary gained an `oscar` writer lane. +5. **Run-lifecycle authority — wrap autonomously, teardown on founder approval.** Oscar **wraps a live run he leads autonomously**: `finalize-run-status`, `closeout`, and `orchestrator-commit` of accepted governance work are part of the lead role. This is already enabled by decisions 1–4 — `orchestrator-commit` audits against the lane's resolved write boundary (now Oscar's governance scope), `finalize-run-status`/`closeout` are run bookkeeping, and both are allowed for the lead by default; the only gate is `terminal-run-locked` (no mutations on an already-terminal run). **Teardown** (`stop-run`, killing sessions / closing panes) **remains founder-approved** per the session-wrap discipline — Oscar may recommend it and execute only on an explicit founder go. **Clearing an *orphaned* run** (session killed, status still non-terminal) is the **daemon's** responsibility, not Oscar's — his pane is dead in that case — and is tracked as a run-lifecycle robustness fix (orphaned runs must not permanently block new launches; "stop" must be the obvious terminalize action; the daemon must surface the real block reason instead of `spawn-failed`). + +## Consequences + +- Future Oscar runs execute orchestration lifecycle work directly — the v0.1 closeout Oscar teed up in `m2ivp19j` can now run write-enabled. +- **Increased autonomous-lead blast radius:** an Oscar run can now edit the roadmap and ADRs. Mitigations: writes are bounded to governance docs (never code); every change is in git history for founder review; the "forbidden decisions" guardrails still apply (Oscar records decisions, does not invent priority/architecture direction); and `write-boundaries.md` makes Oscar follow the resolved boundary and stop-and-report on overreach. +- The orchestration services ([ADR-0009](./0009-orchestration-services.md): `wrap-execution`, `handoff-compaction`) remain the future optimization — Oscar can delegate the *mechanical* governance edits to a cheap bounded model instead of spending lead context, once they are wired into Oscar's live flow (v0.5). +- `write-boundaries.md` and `session-wrap.md` needed no change — both already reference the runtime-resolved boundary. + +## Alternatives considered + +- **Opt-in write-enabled Oscar profile** (default read-only; a separate write-enabled profile for closeout) — rejected; founder chose always-on for simplicity (solo dogfood; founder reviews all writes). +- **Read-only Oscar + delegate all governance writes to services** — deferred; services aren't wired into Oscar's live flow yet (v0.5). +- **ADRs gated proposed→accepted** (founder flips to accepted) — rejected; founder chose full authority including accept. +- **Widen Bob to governance/ADRs too** — rejected; keep the Oscar = governance / Bob = code split crisp. + +## Numbering note + +Authored on the `oz-control-plane-design` branch. ADR-0009 (orchestration services) lives on the `orchestration-services-import` branch (PR #50); ADR-0010 (Oz control-plane build) and ADR-0011 (v0.1 closeout) are reserved/pending (see the v0.4 priority README + the `m2ivp19j` run). Numbering reconciles at merge. diff --git a/cocoder/decisions/README.md b/cocoder/decisions/README.md index 872b979..b907cbf 100644 --- a/cocoder/decisions/README.md +++ b/cocoder/decisions/README.md @@ -21,10 +21,12 @@ Numbered, dated, single-purpose decisions. Each ADR captures **context → decis | [ADR-0006](./0006-no-nested-workspaces-inside-install.md) | No workspaces nested inside the CoCoder install repository | accepted | 2026-05-22 | | [ADR-0007](./0007-workspace-files-and-multiroot-description.md) | Workspace files — storage location and the multi-root description convention | accepted (revised 2026-05-27) | 2026-05-26 | | [ADR-0008](./0008-oz-control-plane-architecture.md) | Oz control-plane architecture | accepted | 2026-05-27 | +| [ADR-0010](./0010-run-lifecycle-wrap-teardown.md) | Run lifecycle — wrap via cheap services, daemon-driven teardown, orphaned-run recovery | accepted | 2026-05-27 | +| [ADR-0012](./0012-persona-write-authority.md) | Persona write authority — Oscar owns governance/orchestration state | accepted | 2026-05-27 | ## Pending / proposed -(None — next ADR number is 0009.) +Numbering note (parallel branches, 2026-05-27): **ADR-0009** (orchestration services) lives on the `orchestration-services-import` branch / PR #50; **ADR-0010** (Oz control-plane build) and **ADR-0011** (v0.1 closeout) are reserved/pending (see the v0.4 priority README + the `m2ivp19j` run). ADR-0012 took the next free number here to avoid colliding with those; the index reconciles at merge. ## Authoring guide diff --git a/cocoder/personas/prompts/shared/session-wrap.md b/cocoder/personas/prompts/shared/session-wrap.md index 12f43d8..fa92692 100644 --- a/cocoder/personas/prompts/shared/session-wrap.md +++ b/cocoder/personas/prompts/shared/session-wrap.md @@ -6,7 +6,23 @@ - During Wrap Up, commit, or autonomous continuation decisions, classify dirty worktree state against the selected priority boundary and require a commit-boundary audit. This is not a pre-`add-lanes` smoke-test blocker; unrelated unstaged dirt should be preserved and carried as background context unless it overlaps the current command's read/write set or files Oscar is about to commit. - Keep `SESSION_LOG.md` as a short newest-first handoff: at most 10 live entries, 10-20 lines per entry, no file inventories, commit-SHA lists, LOC counts, or test-count dumps. Rotate older entries to `SESSION_LOG_ARCHIVE.md` in the same route-owned commit when needed. - A terminal run is closed for new atom work, but its panes must remain open for the founder handoff. After `finalize-run-status` returns a terminal status, do not dispatch, send helper messages, commit, continue implementation, close panes, request pane teardown, or launch another Oscar run from the old pane unless the founder explicitly approves the next launch path. +- **Teardown is an external action you *recommend*, not one you run from your own pane.** A run cannot cleanly stop itself: `stop-run` kills lanes in launch order, so a lead executing it from inside its own pane kills itself before its teammates and strands them. Do not reverse-engineer the teardown mechanism or kill tmux manually. When teardown is warranted: (1) confirm readiness — terminal status, wrap committed, clean staged state (a `teardown-readiness` check); (2) **recommend** it to the founder with the exact run id. The founder or Oz performs the stop from *outside* the run (`stop-run --confirm-run-id --execute true --founder-approved-teardown true`, or the Oz "stop" action); the runtime handles kill order and status finalization. **Commit your wrap before recommending teardown** — a torn-down run must not leave its closeout uncommitted. - If the founder asks to commit, update priorities, revise status docs, or keep going after terminalization, treat that as a new post-terminal execution request. Do not run git commands or edit files from the closed pane; state that the old pane is report-only and route the work to a fresh visible run or the Orchestrator Debugger. A broad approval such as "follow your recommendation" is acknowledgement of the next run/route, not permission to mutate state from a terminalized pane. The runtime enforces this: commit, lead-support-commit, send-message, add-lanes, and continuation paths refuse a terminal run (`terminal-run-locked`); read-only debugger, stop, and evidence commands stay available. - Implementation surfaces require the configured Bob lane result artifact. Oscar may research, critique, wrap, and update allowed status docs, but Oscar or hosted subagents cannot satisfy Bob build ownership. - Before asking the founder to leave the orchestrator running, confirm the next atom is named, the priority boundary is resolved, stop conditions are listed, required tests are named, founder decisions are explicit, and no wrap or commit-audit blockers remain. +- **Write the next-start brief into the priority before you wrap — every run, not deferred.** Update the active priority's README and its `PRIORITIES.md` entry (table row + parser block) with exactly one of: (a) **resumable** — the parseable `**Recommended next atom:** -- ` in the plan's "Next Session Start Here", plus route/topology, required personas, stop conditions, required tests, and explicit founder decisions; or (b) **archive-candidate** — if the priority appears complete, state that in the priority and ask the founder to confirm archival (never self-archive). A wrapped run always leaves the priority either resumable or marked archive-candidate. +- **Before reporting to the founder, confirm internally — every answer must be Yes (fix it if not):** (1) all lane result files present and reconciled with `status.json`; (2) stale/mismatched result text handled; (3) worktree classified against the priority boundary (commit-boundary audit done); (4) the next atom is named in both the plan and `PRIORITIES.md`, or the priority is marked archive-candidate; (5) stop conditions, required personas, route/topology, required tests, and founder decisions are explicit; (6) the route-owned commit (`orchestrator-commit`) succeeded before `finalize-run-status`. +- **Emit the founder handoff in this exact shape** (a short run-navigation surface — keep technical evidence in `jobs/oscar/result.md`, the plan, `PRIORITIES.md`, and `SESSION_LOG.md`, not here): + ``` + Run Handoff + Priority worked: + Disposition: Continue | Archive candidate | Blocked + Session fully wrapped: Yes | No + Priority briefed for next start: Yes | No + Next priority to launch: | none + Founder action: + ``` + - **Continue** — the priority has more work; `Session fully wrapped` and `Priority briefed for next start` must both be `Yes` before recommending a fresh launch, and `Next priority to launch` names the same priority. + - **Archive candidate** — the priority appears complete; ask for archive confirmation instead of naming another atom. `Next priority to launch` may be `none` or the next active priority. + - **Blocked** — the priority cannot proceed; `Next priority to launch` names the blocking priority/workstream and `Founder action` states the return condition. - When another priority blocks the active priority, recommend a separate Oscar/Bob session pair for the blocking priority and record or request a blocking note instead of switching the active priority mid-session. diff --git a/cocoder/priorities/v0.1-foundation/README.md b/cocoder/priorities/v0.1-foundation/README.md index 15be5f2..05da055 100644 --- a/cocoder/priorities/v0.1-foundation/README.md +++ b/cocoder/priorities/v0.1-foundation/README.md @@ -317,9 +317,9 @@ Tracked in [`pending-decisions.md`](./pending-decisions.md). **All resolved 2026 ## Progress -**Last worked:** 2026-05-24 (Sub-Playbook D activated — Witness/Interrogate/Solve-target authored) -**Current Canon:** v0.1 completion phase. Sub-Playbook F Complete. B/C Expand merged — Refines parallel-tracked (founder). **Sub-Playbook D Active — Solve pending.** Test count: **335 / 335 / 0 fail / 0 skipped** (+ oz-dashboard **8/8**). -**Next action:** D Solve (D-S2 CI gates, then Expand doc batches, then D-S1 internal proxy). B/C Refines remain founder-only parallel tracks. +**Last worked:** 2026-05-27 (run zx0s33ag — Sub-Playbook D Milestone 1 docs completed; external stranger test removed from scope) +**Current Canon:** v0.1 completion phase. Sub-Playbook F + E Complete. B/C Expand merged — Refines parallel-tracked (founder). **Sub-Playbook D Active — Milestone 1 docs COMPLETE; remaining = repo-root README/ARCHITECTURE + CI gates + internal proxy + tag.** Test count: **335 / 335 / 0 fail / 0 skipped** (+ oz-dashboard **8/8**). +**Next action (needs a wider write boundary):** D-M1.7 ARCHITECTURE verify + D-M1.8 README adopter rewrite (repo-root), then D-M2.1 dogfood-evidence, D-S2 CI gates (`.github/`), D-S1 internal proxy, then `v0.1.0` tag. External stranger test removed (PD-Q1 revised). B/C Refines remain founder-only parallel tracks. ### Sub-Playbook status @@ -330,7 +330,7 @@ Tracked in [`pending-decisions.md`](./pending-decisions.md). **All resolved 2026 | B. Personas + workspace template | **Active — Expand merged (`9bf2433`); Refine pending (founder)** | PR #33 merged; PB-Q1..PB-Q4 answered; B-S1..B-M3 green; suite 265/265 | B Refine (founder) | [`2026-05-21-personas-template.plan.md`](./plans/2026-05-21-personas-template.plan.md) | | **F. Structural cleanup** | **Complete (2026-05-23)** | Final Check closed; PR #28 merged `58e1fe2`; suite 249/249; compose-launch diff clean | — | [`2026-05-23-structural-cleanup.plan.md`](./plans/2026-05-23-structural-cleanup.plan.md) | | **C. Oz MVP** | **Active — Expand complete (2026-05-23); Refine pending (founder)** | C-M1..C-M3 green (PRs #42–#47 → `f46dcff`); suite 335/335 + dashboard 8/8 | C Refine (founder) | [`2026-05-21-oz-mvp.plan.md`](./plans/2026-05-21-oz-mvp.plan.md) | -| **D. Docs + dogfood + publish** | **Active — Witness/Interrogate/Solve-target authored (2026-05-24); Solve pending** | PD-Q1..PD-Q7 answered; suite 335/335 + dashboard 8/8 | D Solve (D-S2 gates → Expand docs → D-S1 proxy) | [`2026-05-21-docs-publish.plan.md`](./plans/2026-05-21-docs-publish.plan.md) | +| **D. Docs + dogfood + publish** | **Active — Milestone 1 docs COMPLETE (2026-05-27, run zx0s33ag); repo-root surfaces + gates + tag remain** | All 6 D-M1 docs authored + ADR-0001 §6 fixed (D-M1.9); external stranger test removed (PD-Q1 revised); suite 335/335 + dashboard 8/8 | D-M1.7/1.8 (repo-root, needs wider boundary) → D-M2.1 → D-S2 (`.github/`) → D-S1 → `v0.1.0` tag | [`2026-05-21-docs-publish.plan.md`](./plans/2026-05-21-docs-publish.plan.md) | | **v0.1 Completion Plan** (cross-cuts A, B, ticket 0001) | **Active** | Items 1 + 2 CLOSED; Item 2.5 F Complete; Item 3 W/I/S authored | PB-Q1..PB-Q4 + B Solve | [`2026-05-23-v0.1-completion.plan.md`](./plans/2026-05-23-v0.1-completion.plan.md) | ### Canon roll-up (Master only) @@ -349,7 +349,7 @@ Tracked in [`pending-decisions.md`](./pending-decisions.md). **All resolved 2026 ## Success Criteria - [ ] All four sub-Playbooks reach Status: Complete -- [ ] Stranger test (P-R2) passes: external dev clones, inits, launches in ≤30 minutes without founder help +- [ ] Stranger test (P-R2) passes: **internal proxy** (PD-Q1 revised 2026-05-27 — external recruit removed from v0.1) clones, inits, launches in ≤30 minutes without doc-clarifying questions - [ ] Two-workspace concurrency test (P-R1) passes without tmux collision - [ ] Recovery test (P-R3) passes after `local/` deletion and Syncthing restore - [ ] All public-readiness gates green on the commit tagged `v0.1.0` diff --git a/cocoder/priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md b/cocoder/priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md index 34dee55..952143b 100644 --- a/cocoder/priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md +++ b/cocoder/priorities/v0.1-foundation/plans/2026-05-21-docs-publish.plan.md @@ -126,7 +126,7 @@ A public CoCoder release at which: | ID | Question | Blocks | Recommended default | Answer (2026-05-24) | ADR / HOLD FOR GO | |---|---|---|---|---|---| -| **PD-Q1** | Stranger test (P-R2) execution model: **(A)** recruited external dev only; **(B)** internal proxy dry-run in **Solve** proves doc readiness; external recruit mandatory in **Refine/Final Check**; **(C)** founder-as-stranger after time gap counts as P-R2. | D-S1, D Refine M3 | **B — Internal proxy in Solve; external recruit in Refine.** | **B (founder-approved 2026-05-24)** | No | +| **PD-Q1** | Stranger test (P-R2) execution model: **(A)** recruited external dev only; **(B)** internal proxy dry-run in **Solve** proves doc readiness; external recruit mandatory in **Refine/Final Check**; **(C)** founder-as-stranger after time gap counts as P-R2. | D-S1, D Refine M3 | **B — Internal proxy in Solve; external recruit in Refine.** | ~~B (2026-05-24)~~ **REVISED 2026-05-27 (run zx0s33ag): external recruit is NOT a v0.1 requirement (founder: "no external stranger test — never should have been a requirement"). Internal-proxy dry-run (D-S1) retained as the readiness check; Milestone 3 external recruit (D-M3.1–D-M3.3) removed from v0.1 scope.** | No | | **PD-Q2** | `README.md` depth at `v0.1.0`: **(A)** full pitch + quick-start (remove "not yet usable" banner); **(B)** minimal pointer to `docs/getting-started.md` only. | D-M1.8, tag | **A — Full pitch + quick-start.** | **A (founder-approved 2026-05-24)** | No | | **PD-Q3** | `NOTICE` maintenance: **(A)** hand-authored (current); **(B)** auto-generated from dependency license scan each release. | D-M4.3 | **A — Hand-authored for v0.1.** | **A (founder-approved 2026-05-24)** | No | | **PD-Q4** | `faq.md` commercial-use scope: **(A)** minimal — Apache-2.0 commercial use, tool vs name/trademark note, commit guidance; **(B)** extended legal FAQ (CoBuilder relationship, redistribution essay). | D-M1.5 | **A — Minimal FAQ.** | **A (founder-approved 2026-05-24)** | **ADR-graduating if B** | @@ -198,25 +198,27 @@ A public CoCoder release at which: ### Milestone 1 — Documentation -- [ ] **D-M1.1** Extend `docs/getting-started.md` to full ≤30 min stranger-test path: install → init out-of-tree workspace → first launch (CLI or Oz). Include labeled diagram: install-level `/local/` vs workspace-level `/cocoder/local/`. -- [ ] **D-M1.2** `docs/orchestration.md` — tmux model, runs, evidence, session wrap (cross-link configuration + custom-personas). -- [ ] **D-M1.3** `docs/personas.md` — who does what, dispatch rules, custom persona ergonomics (cross-link `docs/custom-personas.md`). -- [ ] **D-M1.4** `docs/oz.md` — Oz overview, security model summary, troubleshooting; **cross-link** `docs/oz-launch.md` and `docs/oz-security-checklist.md` (do not duplicate C Expand content). -- [ ] **D-M1.5** `docs/faq.md` — minimal commercial use (PD-Q4=A), what to commit vs not, trademark/name note, zero telemetry (PD-Q5=A), Syncthing secrets warning. -- [ ] **D-M1.6** `docs/freshness-policy.md` — ADR/ARCHITECTURE verification stamps + doc audit cadence; Oz freshness panel deferred v0.2. +- [x] **D-M1.1** Extend `docs/getting-started.md` to full ≤30 min stranger-test path: install → init out-of-tree workspace → first launch (CLI or Oz). Include labeled diagram: install-level `/local/` vs workspace-level `/cocoder/local/`. **(Authored 2026-05-27, run suesc2sq — clean-clone→init→compose-launch→CLI launch + Oz launch path, storage-zone diagram, cross-links to `oz-launch.md`/`oz-security-checklist.md`. The ≤30-min readiness *proof* is D-S1 internal-proxy, still deferred — authoring done, not yet stranger-validated.)** +- [x] **D-M1.2** `docs/orchestration.md` — tmux model, runs, evidence, session wrap (cross-link configuration + custom-personas). **(Authored 2026-05-27, run zx0s33ag — Bob; `check-doc-refs` 0 missing refs.)** +- [x] **D-M1.3** `docs/personas.md` — who does what, dispatch rules, custom persona ergonomics (cross-link `docs/custom-personas.md`). **(Authored 2026-05-27, run zx0s33ag — Bob; `check-doc-refs` 0 missing refs.)** +- [x] **D-M1.4** `docs/oz.md` — Oz overview, security model summary, troubleshooting; **cross-link** `docs/oz-launch.md` and `docs/oz-security-checklist.md` (do not duplicate C Expand content). **(Authored 2026-05-27, run zx0s33ag — Bob; summary-plus-pointers, no C-Expand duplication; `check-doc-refs` 0 missing refs.)** +- [x] **D-M1.5** `docs/faq.md` — minimal commercial use (PD-Q4=A), what to commit vs not, trademark/name note, zero telemetry (PD-Q5=A), Syncthing secrets warning. **(Authored 2026-05-27, run suesc2sq — minimal PD-Q4=A scope; all five required topics present; link-checked via `check-doc-refs` 0 missing refs.)** +- [x] **D-M1.6** `docs/freshness-policy.md` — ADR/ARCHITECTURE verification stamps + doc audit cadence; Oz freshness panel deferred v0.2. **(Authored 2026-05-27, run zx0s33ag — Bob; Oz freshness panel explicitly marked deferred-to-v0.2; `check-doc-refs` 0 missing refs.)** - [ ] **D-M1.7** Mermaid diagram(s) in `ARCHITECTURE.md` verified for clarity (update stamps if edited). - [ ] **D-M1.8** **`README.md` adopter-ready rewrite (PD-Q2=A).** Remove "not yet usable by adopters" banner; replace stale Sub-Playbook A progress text with v0.1 pitch + quick-start pointer to `docs/getting-started.md`. -- [ ] **D-M1.9** **ADR-0001 §6 stale `.command` reference — founder process gate.** Line 26 still says "iTerm2 + `.command` wrappers"; retired per ticket 0001 Path B. **Blocked until founder picks:** **(i)** inline "Updated 2026-05-24" footnote in ADR-0001 noting terminal-only + Oz launch surface, or **(ii)** graduate new ADR amending §6. Do not silently edit accepted decision text without recording which option was chosen. +- [x] **D-M1.9** **ADR-0001 §6 stale `.command` reference — founder process gate.** ~~Line 26 still says "iTerm2 + `.command` wrappers"; retired per ticket 0001 Path B.~~ **Resolved 2026-05-27, run zx0s33ag (Oscar): founder chose option (i) — inline footnote.** ADR-0001 decision 6 now carries a dated `[^platform-v01-update]` footnote noting the `.command` retirement (ticket 0001 Path B) and terminal-only CLI + Oz launch surfaces; accepted decision text preserved, no new ADR graduated. ### Milestone 2 — Dogfood evidence (PD-Q7=A) - [ ] **D-M2.1** Author `docs/dogfood-evidence.md` summarizing Sub-Playbook E orchestrated runs + Oz observability (C Expand). No mandatory new Oscar polish session. -### Milestone 3 — Refine: stranger test (external recruit) +### Milestone 3 — Refine: stranger test (external recruit) — ❌ REMOVED FROM v0.1 SCOPE (2026-05-27, run zx0s33ag) -- [ ] **D-M3.1** Recruit external developer (not familiar with CoCoder or CoBuilder; not the internal proxy from D-S1). -- [ ] **D-M3.2** Time the run from `git clone` to first `cocoder launch` (target: ≤30 min; hard cap: 60 min before pause for diagnosis). -- [ ] **D-M3.3** Capture every clarifying question as a doc task; iterate until run completes cleanly (binary green/red for Final Check). +> **Founder decision 2026-05-27:** the external stranger-test recruit is not a v0.1 requirement and never should have been (PD-Q1 revised). v0.1 doc-readiness is proven by the **internal-proxy dry-run (D-S1)** only. The items below are retained struck-through for history; they do not gate v0.1. + +- [~] ~~**D-M3.1** Recruit external developer (not familiar with CoCoder or CoBuilder; not the internal proxy from D-S1).~~ **Removed from v0.1 scope.** +- [~] ~~**D-M3.2** Time the run from `git clone` to first `cocoder launch` (target: ≤30 min; hard cap: 60 min before pause for diagnosis).~~ **Removed from v0.1 scope.** +- [~] ~~**D-M3.3** Capture every clarifying question as a doc task; iterate until run completes cleanly (binary green/red for Final Check).~~ **Removed from v0.1 scope.** ### Milestone 4 — Publish @@ -281,26 +283,43 @@ A public CoCoder release at which: ## Resume Instructions -1. Confirm PD-Q1..PD-Q7 answers recorded above (all approved 2026-05-24). -2. Read Master Playbook Final Check §public-readiness gates + this Sub-Playbook Witness inventory. -3. Execute **D Solve** (D-S2 gates first if doc Expand not ready; D-S1 after getting-started extension lands). -4. Do not schedule external stranger test until D-S1 internal proxy is green. -5. Do not tag `v0.1.0` until D Refine + Final Check complete. +### Next Session Start Here + +**Recommended next atom:** D-M1.7+D-M1.8 -- ARCHITECTURE.md verification + README.md adopter-ready rewrite (the repo-root publish surfaces that gate the `v0.1.0` tag). + +- **Route / topology:** `oscar-lead` (Oscar lead + Bob builder), same as run zx0s33ag. +- **Required personas:** Oscar (orchestrator), Bob (builder). Strict substitution. +- **Required write boundary (MUST widen vs zx0s33ag):** repo-root `README.md` + `ARCHITECTURE.md` (for D-M1.7/D-M1.8), `.github/workflows/ci.yml` (for D-S2 gates), and `docs/` (for D-M2.1). The zx0s33ag run could not touch any of these — that is the only reason these items are still open. +- **Stop conditions:** do NOT tag `v0.1.0` until D-S1 internal-proxy is green AND README/ARCHITECTURE landed AND D-S2 CI gates exit 0. Do NOT self-archive — archival is a founder confirmation. +- **Required tests/checks:** full suite stays **335/335** + oz-dashboard **8/8**; `check-doc-refs` 0 missing on any new/edited doc; D-S2 CI gate steps exit 0 on `main`; D-S1 internal-proxy completes clean-clone → `cocoder init` → first launch without doc-clarifying questions. +- **Explicit founder decisions:** external stranger test is REMOVED from v0.1 (PD-Q1 revised 2026-05-27). The `v0.1.0` tag + release notes (PD-Q6=A, semver) remain a founder release action. + +### Remaining v0.1 work items +1. **D-M1.7** ARCHITECTURE.md Mermaid verification (repo-root). +2. **D-M1.8** README.md adopter rewrite — remove "not yet usable by adopters" banner + stale Sub-Playbook A text (PD-Q2=A; repo-root). +3. **D-M2.1** `docs/dogfood-evidence.md` (Bob, `docs/`). +4. **D-S1** internal-proxy stranger readiness (the retained doc-readiness proof). +5. **D-S2** public-readiness CI gates — gitleaks + LICENSE/NOTICE + faq (`.github/workflows/ci.yml`). +6. **`v0.1.0` tag** + release notes (founder action). + +### History +- D-M1.1–D-M1.6 + D-M1.9 complete (getting-started, faq in run suesc2sq; orchestration, personas, oz, freshness-policy + ADR-0001 footnote in run zx0s33ag). +- Do not tag `v0.1.0` until the stop conditions above are met. --- ## Progress -**Last worked:** 2026-05-24 (Activated — Witness/Interrogate/Solve-target authored) -**Current Canon:** Active — Witness/Interrogate complete; Solve not started -**Next action:** D Solve — wire `gitleaks` + FAQ/LICENSE gates in CI (D-S2); then Expand doc batches; then D-S1 internal proxy. +**Last worked:** 2026-05-27 (run zx0s33ag — D-M1.2/1.3/1.4/1.6 docs + D-M1.9 ADR fix; external stranger test removed from scope) +**Current Canon:** Active — Expand. D Milestone 1 docs COMPLETE; remaining = repo-root publish surfaces + CI gates + internal proxy + tag. +**Next action:** D-M1.7 ARCHITECTURE verify + D-M1.8 README rewrite (repo-root — needs wider write boundary), then D-M2.1 dogfood-evidence, D-S2 CI gates (`.github/`), D-S1 internal proxy, then `v0.1.0` tag. See "Next Session Start Here" above. | Canon | Items | Done | Status | |---|---|---|---| | Witness | 1 audit table + objective + scope + current state | 4 | **Complete (2026-05-24)** | -| Interrogate | 7 PD-Q + 9 risks + reuse check | 7 + 9 + 5 | **Complete (2026-05-24)** — PD-Q1..PD-Q7 all-A/B per table | -| Solve | 2 (D-S1, D-S2) | 0 | Not started | -| Expand | M1: 9 · M2: 1 · M3: 3 · M4: 3 | 0 | Not started (gated on Solve checkpoint for sequencing; doc batches may overlap) | +| Interrogate | 7 PD-Q + 9 risks + reuse check | 7 + 9 + 5 | **Complete (2026-05-24)** — PD-Q1 revised 2026-05-27 (external stranger test removed) | +| Solve | 2 (D-S1, D-S2) | 0 | Not started (D-S2 needs `.github/` boundary; D-S1 needs proxy actor) | +| Expand | M1: 9 (incl. D-M1.9) · M2: 1 · ~~M3: 3~~ removed · M4: 3 | M1: 7 of 9 | **M1 docs done** (D-M1.1–1.6, 1.9); open: D-M1.7/1.8 (repo-root). M3 external recruit removed from scope. | | Refine | 4 | 0 | Not started | | Final Check | 8 | 0 | Not started | diff --git a/cocoder/priorities/v0.3-workspace-lifecycle/README.md b/cocoder/priorities/v0.3-workspace-lifecycle/README.md index 1588865..2b62667 100644 --- a/cocoder/priorities/v0.3-workspace-lifecycle/README.md +++ b/cocoder/priorities/v0.3-workspace-lifecycle/README.md @@ -2,7 +2,7 @@ **Status:** Draft **Owner:** Bob + founder -**Sequencing:** DECIDED (2026-05-26) — **v0.3 runs before `v0.2-adapter-extensibility`.** Rationale: the dogfood loop (Oz actually driving an Oscar/Bob session) is the proof-of-life the whole product rests on; cloud/managed adapters are additive and non-blocking, and the near-term adapter want (cursor-agent) already shipped. Depends on the Sub-Playbook C Oz dashboard as the surface for this work. +**Sequencing:** **Follow-on to `v0.4-oz-control-plane`** (updated 2026-05-27) — v0.3 engages when v0.4 begins wiring the **Workspaces** screen / workspace management. The Workspaces *UI* is designed under v0.4 (the Oz control-plane spec); v0.3 owns the capabilities behind it (onboarding, per-project secrets, multi-root management). (`v0.2-adapter-extensibility` was archived 2026-05-27, so the earlier "before v0.2" ordering no longer applies.) Depends on the Oz dashboard as the surface for this work. ## Near-term: Dogfood Loop Enablement — ✅ COMPLETE (2026-05-26) diff --git a/cocoder/priorities/v0.4-oz-control-plane/README.md b/cocoder/priorities/v0.4-oz-control-plane/README.md index 0d8b6e9..ff87927 100644 --- a/cocoder/priorities/v0.4-oz-control-plane/README.md +++ b/cocoder/priorities/v0.4-oz-control-plane/README.md @@ -1,6 +1,6 @@ # v0.4 — Oz Control Plane -**Status:** Draft (stub) +**Status:** Active — **design spec landed 2026-05-27** (`docs/oz-control-plane-design/`); ADR-0010 + build plan next. **Owner:** Bob + founder **Sequencing:** Founder decision. Depends on the claude.ai/design output for the Oz UI and on [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md). Builds on v0.3 work item #6 ("Oz as control plane") and the founder-flagged "improve Oz dashboard / Oz oversight + debugger" theme. @@ -10,19 +10,53 @@ Turn Oz into a real **operator control plane**: a per-workspace, in-dashboard he ## Architecture (decided) -- [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) — Oz control-plane architecture (Oz persona model, command + watch, GUI⇄Oz parity, external orchestration sessions, persona exec model incl. visible/headless, the six surfaces). +- [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) — Oz control-plane architecture (Oz persona model, command + watch, GUI⇄Oz parity, external orchestration sessions, persona exec model incl. visible/headless, the five surfaces with runs + priorities folded into the Dashboard). - [ADR-0007](../../decisions/0007-workspace-files-and-multiroot-description.md) (revised 2026-05-27) — root-role taxonomy primary / writable / read-only. -- Screen/flow brief + design prompt: [`docs/oz-design-brief.md`](../../../docs/oz-design-brief.md) (intent; refined by the claude.ai/design output). +- Screen/flow brief + design prompt: [`docs/oz-design-brief.md`](../../../docs/oz-design-brief.md) (intent; **now realized by the spec below**). + +## Design spec — landed 2026-05-27 + +The claude.ai/design output landed as a high-fidelity, working React prototype (the "Oz — Control Plane Prototype"). It is the **source of truth for *what* to build**; it is **reference, not production code** (production is a fresh build informed by it). + +- **Spec + prototype:** [`docs/oz-control-plane-design/`](../../../docs/oz-control-plane-design/) — brief (`README.md`), `app.jsx` (shell + Oz intent bot), `dashboard.jsx`/`screens.jsx`/`components.jsx`, `data.js` (informal data model), `dev-notes.js` (18 numbered component specs — the binding implementation spec), Fusion `design-system/` tokens (Josefin Sans + gold kanji marks). Open `Oz.html` to run it. +- **Designer implementation notes:** [`designer-notes.md`](./designer-notes.md) — simulated-vs-real wiring, what NOT to inherit, architecture invariants, a11y gaps, scope. +- **Confirms our ADRs:** five-section nav + Runs/Priorities as Dashboard panels (ADR-0008); `Root.role: primary|writable|readonly` (ADR-0007); Fusion tokens (ADR-0001). + +### New decisions this spec forces (→ ADR-0010) + +> **[ADR-0010](../../decisions/0010-run-lifecycle-wrap-teardown.md) written 2026-05-27** specifies the **run-lifecycle slice**: wrap-up delegated to cheap `cursor-agent` services (ADR-0009), **daemon-driven teardown** (a run never stops itself — self-pane-kill hazard; the operator/Oz executes `stop-run` from outside), and **orphaned-run recovery** (a dead-session run must not wedge launches; surface the real reason, not `spawn-failed`). The remaining items below stay for the build plan / sibling ADRs (pause/resume is run-lifecycle-adjacent and references ADR-0010). + +1. **Pause/resume decision primitive** (largest core touch) — a run reaches a decision point → **pauses** (Oscar blocks for a founder answer) → Oz surfaces a callout → founder answers → run **resumes**, routed through Oscar. Real run-lifecycle state in `packages/core`. +2. **`cocoder attach `** — new CLI; connects to the cmux/tmux session owning that run's iTerm. +3. **Per-run transcript streaming** (websocket/SSE) + **disk persistence** + replay-on-reconnect (`packages/oz-daemon`); real **`run.progress`** from Oscar's step counter. +4. **Oz as a tool-using agent** (intents → actions), replacing the prototype's regex `buildOzReply`. +5. **Persona roster reconciliation** — the spec's roster is Oz · Oscar · Bob · Talia · Quinn · **Doc** (new); **Ian and Phil are absent**. Reconcile against [ADR-0002](../../decisions/0002-talia-quinn-boundary.md) and the shipped persona set before building the Personas screen. +6. **In-app update channels** — founder-confirmed in scope (packaged app self-update). Auth/identity, billing, telemetry/crash reporting are **deferred**. +7. Build-target: a fresh **`packages/cocoder-ide`** (or extend `packages/oz-dashboard`) using dnd-kit (not native HTML5 DnD), Electron native folder picker + min-window clamp; **strip** the Tweaks panel + dev annotations (keep the registry as design docs). + +> **Scope notes (2026-05-27):** +> - **Embedded Electron terminal harness = a later phase of v0.4, not a separate priority.** The brief's "v2" (replacing external iTerm sessions with an in-app terminal) is folded into v0.4 as future work; the standalone `v0.6-cocoder-ide` stub was **archived** ([`zArchive`](../zArchive/v0.6-cocoder-ide/README.md)). For now v0.4 keeps orchestration sessions external (iTerm) per ADR-0008 decision 5 — the Run Detail transcript is Oz's read-only window into them. +> - **`v0.3-workspace-lifecycle` is v0.4's follow-on priority.** It engages **when v0.4 starts wiring the Workspaces screen / workspace management**: v0.4 builds the screens; v0.3 provides the under-the-hood capabilities those screens drive (greenfield/brownfield onboarding, per-project secrets, multi-root management). Don't rebuild that scope inside v0.4 — surface it, and route the capability work to v0.3. ## Work items (provisional — refine after design output lands) -1. **Dashboard** — workspace picker, in-app Oz chat (primary command interface), priorities list with **drag-and-drop reorder** + an **ad-hoc "run without a priority"** launcher. +1. **Dashboard** — the operator's hub, built around the **Oz chat as the command center** (primary command interface). Supporting panels *inside* the Dashboard: **priorities** list with **drag-and-drop reorder** + an **ad-hoc "run without a priority"** launcher; **runs** (what's running now / recent) with run detail (live transcript / evidence / status / stop / attach) opening in place as Oz's window into externally-running iTerm sessions. No standalone Runs or Priorities pages. 2. **Workspaces** — add/edit workspaces (name + description) and roots with the three roles (primary / writable / read-only); plumb `role` as a first-class registry field (the registry entry schema already `.passthrough()`es — extends WS-DESC work). 3. **CLIs** — register adapters + a **Test** button returning success or the exact error. 4. **Personas** — per-persona **CLI + model** (with `default`), **sub-agent/service CLI+model hierarchy**, **visible/headless** run mode; list defaults incl. Oz; "create a new persona via a priority." -5. **Runs** — list + run detail (live transcript / evidence / status / stop / attach) as Oz's window into externally-running iTerm sessions. -6. **Oz oversight / debugger** — Oz as live watcher of all workspace runs; the Orchestrator Debugger surface (reference: CoBuilder `ORCH DEBUGGER`); terminal-state awareness (the daemon already refuses mutations on terminal runs — surface it). -7. **Settings** — human-friendly only, never JSON; extensible. +5. **Oz oversight / debugger** — Oz as live watcher of all workspace runs (surfaced in the Dashboard runs panel); the Orchestrator Debugger surface (reference: CoBuilder `ORCH DEBUGGER`); terminal-state awareness (the daemon already refuses mutations on terminal runs — surface it). +6. **Settings** — human-friendly only, never JSON; extensible. + +Note: the left nav is **five sections** — Dashboard, Workspaces, CLIs, Personas, Settings. Runs and Priorities are Dashboard panels, not top-level pages (corrects the earlier six-section draft). + +## Priorities panel — display + persisted reorder (design) + +Today `cocoder/PRIORITIES.md` is the single source of truth: two sections (`## Active`, `## Draft`), each a markdown table parsed by `packages/oz-daemon/src/priorities.ts`. A priority has `slug`, `description`, `status`, `section`, `readmePath` (`packages/schemas/src/oz/priorities-http.ts`) — **no `order`/`rank` field**; order is implicitly the table row order. The `v0.x-` prefix in slugs is a roadmap-version label baked into the slug, not a separate field, and "Draft" is the **section** the row sits under, not a glitch. + +- **Human-readable display (shipped in the dashboard):** the Priorities panel shows `description` as the title and demotes the `slug` (incl. its `v0.x` version) to a small tag — slugs stay stable IDs; the readable name leads. +- **Section vs status:** keep both but label them distinctly — `section` (Active/Draft = which table / backlog vs promoted) is organizational; `status` (Active/Draft/Paused/Complete/Cancelled) is lifecycle. Promoting a priority = moving its row to `## Active`. +- **Persisted reorder (no new ID scheme):** drag-reorder reorders rows **within a section**; order = row order in `PRIORITIES.md`. The daemon gains `PATCH /workspaces/:id/priorities` that **rewrites the markdown row order** in place. Keep the version prefix as a label only — it is *not* the ordering mechanism, so reordering never renumbers versions. One git-tracked source of truth, no DB. +- **Ownership:** `PRIORITIES.md` is founder/Oz-owned — Bob is boundary-blocked from it (`cocoder/profiles/cocoder-oscar.profile.json` `bob.excludedWriteBoundary`), so the **daemon** performs the rewrite on a founder/Oz drag action, not an orchestration lane. ## Open questions diff --git a/cocoder/priorities/v0.4-oz-control-plane/designer-notes.md b/cocoder/priorities/v0.4-oz-control-plane/designer-notes.md new file mode 100644 index 0000000..5452887 --- /dev/null +++ b/cocoder/priorities/v0.4-oz-control-plane/designer-notes.md @@ -0,0 +1,53 @@ +# CoCoder IDE — Designer Notes + +Implementation guidance from the designer, captured 2026-05-27 (before the full spec/prototype is placed in `docs/cocoder-ide-design/`). These notes are authoritative input for **ADR-0010** and the build plan. The spec ships as a React prototype (`app.jsx` + design tokens + a dev/annotation registry); the prototype is **reference**, not production code. + +## What's simulated in the prototype → real wiring it implies + +| Prototype (fake) | Real wiring to build | +|---|---| +| Oz chat is a regex bot (`app.jsx::buildOzReply`); seed prompts (status, launch next, promote #N, full/partial) | Oz must be a **tool-using agent**: recognize **intents → actions**, call the orchestrator (Claude/chosen CLI), stream replies back. The seed prompts are the intent set, not a protocol. | +| Run transcripts are static arrays | **Stream per run** (websocket or SSE), indexed by `run.id`, with **replay-on-reconnect** and **disk persistence** (close laptop → return to same transcript). | +| `cocoder attach ` (Attach tab just copies a string) | Decide what the **CLI actually does** — most likely connect to the cmux/tmux session that owns that run's iTerm. **New CLI command.** | +| "Decision needed" flow suspends nothing | Wire a real **pause/resume primitive**: run hits a decision point → **pauses** (Oscar blocks waiting for human input) → Oz surfaces the callout → founder answers → run **resumes** with the answer routed through Oscar. | +| CLI Test = 1.1s timeout | Real Test **invokes the CLI** with a no-op command and parses the result. | +| Re-check dependency (same fake shape) | Wire to `which iterm2` / `which cmux` (or platform equivalent). | +| Root folder picker = text input w/ folder icon | Use the platform's **real picker** (Electron `dialog.showOpenDialog`). | + +## Do NOT inherit from the prototype + +- **Drag-and-drop priorities** uses native HTML5 drag → use **dnd-kit** or **react-dnd** (keyboard, touch, a11y, animation included). +- **Tweaks panel** is design-review-only → **strip for production**. The first-run state IS real (drive off "no workspaces configured yet"); theme/density move into **Settings → Appearance**. +- **Dev annotations** are spec, not feature → strip them, but **keep the registry as design docs**. +- **Fixed-width artboard** isn't responsive below ~1280px. Desktop-class is fine per brief, but **Electron should clamp min window size**. + +## Architecture (real, easy to miss) + +- **Oz is workspace-scoped and plural.** Three open tabs = **three live Oz processes**, each with its own conversation, watcher list, retention window. **Don't share state.** **Suspend watchers for backgrounded (non-active) tabs** to save tokens; wake on tab focus. +- **Persona color identity is stable across the app:** Bob = sage, Quinn = coral, Oz = gold, system = muted. They double as recognition cues — keep consistent (esp. transcript view). +- **"Craft new persona" priority** carries the full spec in `priority.spec`. When it runs, Oscar reads the spec and builds the persona (prompt, sub-agents, tests). The persona appears in the Personas list **only after Quinn green-lights** the build. +- **`run.progress`** is currently hardcoded. Real progress is probably **Oscar's step counter** (step 2 of 5) projected to a 0–1 float. **Decide where the truth lives.** +- **Light theme** is wired but lightly tested; some colors are inline hex/rgba. Tokens flip, but verify a full pass. + +## Accessibility (needs work — not there yet) + +No keyboard nav on workspace tabs · drag-reorder has no keyboard path · modal focus-trap not bulletproof · ARIA labels sparse. + +## Out of scope for the control plane (FOUNDER-CONFIRMED 2026-05-27) + +Designer flagged auth, identity, billing, telemetry, crash reporting, update channels as mute-in-brief. Founder decision: + +- **In scope for v0.6:** **Update channels** — the packaged Electron app gets an in-app update mechanism (vs. today's git-clone + pnpm). ADR-0010 must cover update channel/signing/distribution. +- **Deferred (out of scope for v0.6):** Auth & identity, billing, telemetry & crash reporting. (CoCoder stays a local single-operator tool — localhost Oz daemon + per-install token. Revisit telemetry only as an explicit opt-in design if ever needed.) + +## Implications for the orchestration core / oz-daemon (flag, don't assume) + +Several "real wiring" items reach beyond the IDE shell into existing packages — to be validated against the code during spec review, not assumed: + +- **`cocoder attach `** → new `packages/core` CLI command + session-ownership lookup. +- **Pause/resume decision primitive** → run lifecycle state in `packages/core` (ledger/launch) where a run can block awaiting a founder decision and resume with the answer routed through Oscar. This is the most significant core touch. +- **Real `run.progress`** → Oscar emits a step counter the daemon projects to 0–1. +- **Per-run transcript streaming (SSE/WS) + persistence** → `packages/oz-daemon`. +- **Oz-as-tool-using-agent (intents → actions)** → oz-daemon/dashboard + orchestration. + +> ADR-0008 discipline: keep core changes minimal and **flag** required ones rather than assume. Where the IDE genuinely needs a core/daemon primitive (pause/resume especially), that becomes an explicit companion work item in the build plan. diff --git a/cocoder/priorities/zArchive/INDEX.md b/cocoder/priorities/zArchive/INDEX.md new file mode 100644 index 0000000..c2fc4a3 --- /dev/null +++ b/cocoder/priorities/zArchive/INDEX.md @@ -0,0 +1,8 @@ +# Archived Priorities — Index + +Priorities removed from the active roadmap. Folders are kept here for history; their rows no longer appear in [`../../PRIORITIES.md`](../../PRIORITIES.md). + +| Slug | Archived | Reason | +|---|---|---| +| [`v0.2-adapter-extensibility`](./v0.2-adapter-extensibility/README.md) | 2026-05-27 | Founder decided not to pursue cloud/managed adapter kinds (cloud APIs, managed remote sessions) in this roadmap. The single local `llm-cli` adapter shape stays; revisit only if a concrete need arises. | +| [`v0.6-cocoder-ide`](./v0.6-cocoder-ide/README.md) | 2026-05-27 | Folded into [`v0.4-oz-control-plane`](../v0.4-oz-control-plane/README.md). The embedded Electron terminal harness (the design brief's "v2") is a later phase of the control-plane app, not a separate priority — tracked as future work inside v0.4. | diff --git a/cocoder/priorities/v0.2-adapter-extensibility/README.md b/cocoder/priorities/zArchive/v0.2-adapter-extensibility/README.md similarity index 100% rename from cocoder/priorities/v0.2-adapter-extensibility/README.md rename to cocoder/priorities/zArchive/v0.2-adapter-extensibility/README.md diff --git a/cocoder/priorities/zArchive/v0.6-cocoder-ide/README.md b/cocoder/priorities/zArchive/v0.6-cocoder-ide/README.md new file mode 100644 index 0000000..e3dfcdb --- /dev/null +++ b/cocoder/priorities/zArchive/v0.6-cocoder-ide/README.md @@ -0,0 +1,20 @@ +# v0.6 — CoCoder IDE (embedded Electron terminal harness, "v2") + +**Status:** Draft — reserved; depends on `v0.4-oz-control-plane` shipping first. **Owner:** Bob + founder. +**Relates to:** [`v0.4-oz-control-plane`](../v0.4-oz-control-plane/README.md) (the control-plane app this extends), [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) (deferred the embedded Electron terminal harness). + +## Why + +The Oz control-plane design spec (realized under **v0.4**) explicitly defers one thing to **"v2"**: replacing the **external iTerm orchestration sessions** with an **embedded Electron terminal harness** inside the app — so the founder watches *and runs* sessions in-app rather than Oz being a read-only window onto iTerm. + +This priority is that "v2": the CoCoder IDE proper — the Electron app shell hosting the v0.4 control-plane surfaces **plus** embedded live terminals and (eventually) editor surfaces. + +## Scope (provisional — refine after v0.4 ships) + +- Embedded terminal harness that **drives and observes** orchestration sessions in-app (today external in iTerm — ADR-0008 decision 5). Keep the v0.4 **Run Detail contract stable** so the transcript/evidence/stop/attach surface works whether the session is external (v0.4) or embedded (v0.6). +- Electron app shell hosting the five v0.4 surfaces; editor surfaces as the IDE grows. +- Reuses the v0.4 build (`packages/cocoder-ide` or whatever v0.4 establishes) — this is a phase *within* the same app, not a separate product. + +## Not yet + +This stays a reserved stub until v0.4 (the control plane) is built and shipped. The `cocoder attach ` CLI + pause/resume primitive landed in v0.4 are prerequisites the embedded harness builds on. Slug/sequencing provisional (founder). diff --git a/cocoder/priority-boundaries/v0.1-foundation-oscar-lead.boundary.json b/cocoder/priority-boundaries/v0.1-foundation-oscar-lead.boundary.json new file mode 100644 index 0000000..32cf37f --- /dev/null +++ b/cocoder/priority-boundaries/v0.1-foundation-oscar-lead.boundary.json @@ -0,0 +1,53 @@ +{ + "id": "v0.1-foundation-oscar-lead", + "prioritySlug": "v0.1-foundation", + "label": "v0.1-foundation oscar-lead boundary (Bob builds implementation surfaces; governance + root deps excluded)", + "routeIds": [ + "oscar-lead" + ], + "writerLanes": { + "oscar": { + "allowed": [ + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/priorities/", + "cocoder/plans/", + "cocoder/tickets/", + "cocoder/decisions/" + ], + "excluded": [] + }, + "bob": { + "allowed": [ + "packages/", + "docs/", + "templates/", + "examples/", + ".github/", + "cocoder/", + "README.md", + "ARCHITECTURE.md", + "LICENSE", + "NOTICE", + "CONTRIBUTING.md", + "package.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + ".nvmrc", + ".gitignore" + ], + "excluded": [ + "cocoder/priorities/", + "cocoder/decisions/", + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/tickets/", + "cocoder/local/", + "local/", + "secrets/" + ] + } + } +} diff --git a/cocoder/priority-boundaries/v0.3-workspace-lifecycle.boundary.json b/cocoder/priority-boundaries/v0.3-workspace-lifecycle.boundary.json index c977697..49add64 100644 --- a/cocoder/priority-boundaries/v0.3-workspace-lifecycle.boundary.json +++ b/cocoder/priority-boundaries/v0.3-workspace-lifecycle.boundary.json @@ -2,29 +2,51 @@ "id": "v0.3-workspace-lifecycle-oscar-lead", "prioritySlug": "v0.3-workspace-lifecycle", "label": "v0.3-workspace-lifecycle oscar-lead boundary (Bob builds implementation surfaces; governance + root deps excluded)", - "routeIds": ["oscar-lead"], + "routeIds": [ + "oscar-lead" + ], "writerLanes": { + "oscar": { + "allowed": [ + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/priorities/", + "cocoder/plans/", + "cocoder/tickets/", + "cocoder/decisions/" + ], + "excluded": [] + }, "bob": { "allowed": [ "packages/", "docs/", "templates/", - "cocoder/routes/", - "cocoder/profiles/", - "cocoder/priority-boundaries/", - "cocoder/personas/", - "cocoder/local/scratch/" + "examples/", + ".github/", + "cocoder/", + "README.md", + "ARCHITECTURE.md", + "LICENSE", + "NOTICE", + "CONTRIBUTING.md", + "package.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + ".nvmrc", + ".gitignore" ], "excluded": [ "cocoder/priorities/", "cocoder/decisions/", "cocoder/PRIORITIES.md", "cocoder/SESSION_LOG.md", - "cocoder/memory/", - "package.json", - "pnpm-lock.yaml", - "pnpm-workspace.yaml", - ".github/" + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/tickets/", + "cocoder/local/", + "local/", + "secrets/" ] } } diff --git a/cocoder/priority-boundaries/v0.4-oz-control-plane-oscar-lead.boundary.json b/cocoder/priority-boundaries/v0.4-oz-control-plane-oscar-lead.boundary.json new file mode 100644 index 0000000..79dce0e --- /dev/null +++ b/cocoder/priority-boundaries/v0.4-oz-control-plane-oscar-lead.boundary.json @@ -0,0 +1,53 @@ +{ + "id": "v0.4-oz-control-plane-oscar-lead", + "prioritySlug": "v0.4-oz-control-plane", + "label": "v0.4-oz-control-plane oscar-lead boundary (default — tighten as needed; Bob builds implementation surfaces, governance + root deps excluded)", + "routeIds": [ + "oscar-lead" + ], + "writerLanes": { + "oscar": { + "allowed": [ + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/priorities/", + "cocoder/plans/", + "cocoder/tickets/", + "cocoder/decisions/" + ], + "excluded": [] + }, + "bob": { + "allowed": [ + "packages/", + "docs/", + "templates/", + "examples/", + ".github/", + "cocoder/", + "README.md", + "ARCHITECTURE.md", + "LICENSE", + "NOTICE", + "CONTRIBUTING.md", + "package.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + ".nvmrc", + ".gitignore" + ], + "excluded": [ + "cocoder/priorities/", + "cocoder/decisions/", + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/tickets/", + "cocoder/local/", + "local/", + "secrets/" + ] + } + } +} diff --git a/cocoder/priority-boundaries/v0.5-orchestration-services-oscar-lead.boundary.json b/cocoder/priority-boundaries/v0.5-orchestration-services-oscar-lead.boundary.json new file mode 100644 index 0000000..176c316 --- /dev/null +++ b/cocoder/priority-boundaries/v0.5-orchestration-services-oscar-lead.boundary.json @@ -0,0 +1,53 @@ +{ + "id": "v0.5-orchestration-services-oscar-lead", + "prioritySlug": "v0.5-orchestration-services", + "label": "v0.5-orchestration-services oscar-lead boundary (default — tighten as needed; Bob builds implementation surfaces, governance + root deps excluded)", + "routeIds": [ + "oscar-lead" + ], + "writerLanes": { + "oscar": { + "allowed": [ + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/priorities/", + "cocoder/plans/", + "cocoder/tickets/", + "cocoder/decisions/" + ], + "excluded": [] + }, + "bob": { + "allowed": [ + "packages/", + "docs/", + "templates/", + "examples/", + ".github/", + "cocoder/", + "README.md", + "ARCHITECTURE.md", + "LICENSE", + "NOTICE", + "CONTRIBUTING.md", + "package.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + ".nvmrc", + ".gitignore" + ], + "excluded": [ + "cocoder/priorities/", + "cocoder/decisions/", + "cocoder/PRIORITIES.md", + "cocoder/SESSION_LOG.md", + "cocoder/SESSION_LOG_ARCHIVE.md", + "cocoder/tickets/", + "cocoder/local/", + "local/", + "secrets/" + ] + } + } +} diff --git a/cocoder/profiles/cocoder-oscar.profile.json b/cocoder/profiles/cocoder-oscar.profile.json index d79f525..50c729f 100644 --- a/cocoder/profiles/cocoder-oscar.profile.json +++ b/cocoder/profiles/cocoder-oscar.profile.json @@ -5,10 +5,10 @@ "lanes": { "oscar": { "persona": "oscar", - "adapter": "codex", - "adapterProfile": "gpt-5.5", - "canWrite": false, - "writeBoundary": [], + "adapter": "claude", + "adapterProfile": "", + "canWrite": true, + "writeBoundary": ["cocoder/PRIORITIES.md", "cocoder/SESSION_LOG.md", "cocoder/SESSION_LOG_ARCHIVE.md", "cocoder/priorities/", "cocoder/plans/", "cocoder/tickets/", "cocoder/decisions/"], "excludedWriteBoundary": [], "resultContract": "job-result", "evidenceClassDefault": "B" @@ -18,8 +18,8 @@ "adapter": "codex", "adapterProfile": "gpt-5.5", "canWrite": true, - "writeBoundary": ["packages/", "docs/", "templates/", "cocoder/routes/", "cocoder/profiles/", "cocoder/priority-boundaries/", "cocoder/personas/", "cocoder/local/scratch/"], - "excludedWriteBoundary": ["cocoder/priorities/", "cocoder/decisions/", "cocoder/PRIORITIES.md", "cocoder/SESSION_LOG.md", "cocoder/memory/", "package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", ".github/"], + "writeBoundary": ["packages/", "docs/", "templates/", "examples/", ".github/", "cocoder/", "README.md", "ARCHITECTURE.md", "LICENSE", "NOTICE", "CONTRIBUTING.md", "package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", ".nvmrc", ".gitignore"], + "excludedWriteBoundary": ["cocoder/priorities/", "cocoder/decisions/", "cocoder/PRIORITIES.md", "cocoder/SESSION_LOG.md", "cocoder/SESSION_LOG_ARCHIVE.md", "cocoder/tickets/", "cocoder/local/", "local/", "secrets/"], "resultContract": "job-result", "evidenceClassDefault": "B" }, diff --git a/cocoder/routes/oscar-lead.json b/cocoder/routes/oscar-lead.json index 012e2a4..b7d80b2 100644 --- a/cocoder/routes/oscar-lead.json +++ b/cocoder/routes/oscar-lead.json @@ -4,9 +4,9 @@ "lead": "oscar", "teammates": ["bob"], "lanes": ["oscar", "bob"], - "supportedPriorityOwners": ["v0.1-foundation", "v0.3-workspace-lifecycle"], + "supportedPriorityOwners": ["v0.1-foundation", "v0.3-workspace-lifecycle", "v0.4-oz-control-plane", "v0.5-orchestration-services"], "gates": ["startup-packet", "profile-preflight", "write-boundary"], - "writePolicy": "one-writer", + "writePolicy": "bounded-writers", "laneRequirements": { "oscar": { "requiredCapabilities": ["interactive"], diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..be4d718 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,56 @@ +# CoCoder FAQ + +## Can I use CoCoder commercially? + +Yes. CoCoder's code is licensed under Apache-2.0, which permits commercial use, modification, distribution, and private use subject to the license terms. + +The license covers the tool and code. It does not grant rights to imply endorsement, sponsorship, or ownership of the "CoCoder" name, logo, or product identity. If you fork or ship a derivative, make the product name and provenance clear. + +Commit guidance: + +- Keep Apache-2.0 license notices and required attribution intact. +- Commit your workspace's public governance files under `cocoder/` when they are part of the project. +- Do not commit private secrets, local credentials, or machine-specific overrides. + +## What should I commit? + +Commit shared project state: + +- `cocoder/PRIORITIES.md` +- `cocoder/SESSION_LOG.md` +- `cocoder/decisions/` +- `cocoder/memory/` +- Public routes, profiles, priority boundaries, persona contracts, and prompt fragments you intentionally maintain for the workspace + +Do not commit private or generated state: + +- `/local/` +- `/cocoder/local/` +- Secret files, tokens, API keys, `.env*`, and private playbooks +- `node_modules/`, `dist/`, `.turbo/`, and other dependency, build, or cache artifacts + +See [`getting-started.md`](./getting-started.md) for the install-level and workspace-level storage diagram. + +## Can I use the CoCoder name? + +Use the name "CoCoder" to identify the upstream project and to preserve attribution. Do not use it to brand a fork, hosted service, plugin, or integration in a way that suggests official status unless you have permission from the project owner. + +For derivatives, choose a distinct name and state that it is based on CoCoder. + +## Does CoCoder collect telemetry? + +No. CoCoder v0.1 collects zero analytics and ships no telemetry. Local commands may write local run records, audit files, and result artifacts under ignored `local/` paths so the operator can inspect what happened on that machine. + +## Can I sync CoCoder with Syncthing or another file sync tool? + +Be careful. Syncing tracked project files is fine, but do not sync `local/` secrets broadly across machines. In particular: + +- Do not sync `/local/secrets/`. +- Do not sync `/cocoder/local/secrets/`. +- Review any sync rule that includes `local/`, run transcripts, or private playbooks. + +If you need multi-machine setup, copy only the tracked repo state first, then recreate secrets and machine-local config on each machine. + +## Where do I learn the first launch flow? + +Start with [`getting-started.md`](./getting-started.md). For the Oz browser launch surface, see [`oz-launch.md`](./oz-launch.md). For local daemon security checks, see [`oz-security-checklist.md`](./oz-security-checklist.md). diff --git a/docs/freshness-policy.md b/docs/freshness-policy.md new file mode 100644 index 0000000..04bd76a --- /dev/null +++ b/docs/freshness-policy.md @@ -0,0 +1,66 @@ +# Documentation Freshness Policy + +**Status:** Draft, implemented by Sub-Playbook D Solve +**Last verified:** 2026-05-27 (policy authored; no automated freshness gate added in this run) + +CoCoder docs should say when they were last checked against the implementation. Freshness stamps do not prove correctness by themselves, but they make stale claims visible during review. + +## Stamp format + +Use a short stamp near the top of durable architecture, ADR, and public docs: + +```markdown +**Status:** Draft, Active, Accepted, Superseded, or Archived +**Last verified:** YYYY-MM-DD (short evidence note) +``` + +The evidence note should name the strongest check available: a command, test suite, audit run, founder review, or targeted manual verification. + +## Architecture verification + +[`ARCHITECTURE.md`](../ARCHITECTURE.md) is the public system map. Update its verification stamp when a change alters storage zones, launch flow, security boundaries, package layout, or public-facing architecture claims. + +An architecture stamp should cite evidence such as: + +- a passing focused test or package suite +- a launch or compose dry run +- a doc-reference audit +- a founder-approved architecture review + +Do not update the stamp for unrelated wording-only edits unless the reviewer actually re-checked the architectural claims. + +## ADR verification + +ADRs under [`cocoder/decisions/`](../cocoder/decisions/) record decisions, not task notes. When implementation catches up to an ADR, or when behavior changes away from it, update the ADR status and verification stamp in the same review window. + +Recommended ADR states: + +- **Proposed** - not ratified yet. +- **Accepted** - ratified and current. +- **Implemented** - ratified and verified in code or docs. +- **Superseded** - replaced by a newer ADR. + +If an ADR is superseded, link the replacement decision and avoid editing old rationale except for the status note. + +## Public doc audit cadence + +Run a lightweight doc audit during each release-candidate pass and after any change that touches: + +- launch commands or tmux behavior +- workspace storage zones +- Oz security or launch behavior +- persona, route, or profile contracts +- public onboarding instructions + +Minimum local checks: + +```bash +pnpm exec cocoder check-doc-refs --root "$COCODER_HOME" --workspace-root "$COCODER_HOME" +pnpm exec cocoder validate-contracts +``` + +For docs that describe an operator path, pair static checks with a real or dry-run command and record the limitation in the `Last verified` note. + +## Deferred Oz freshness panel + +The Oz freshness panel is deferred to v0.2. In v0.1, freshness remains a documentation and review discipline enforced by stamps, check commands, and release closeout notes. diff --git a/docs/getting-started.md b/docs/getting-started.md index 4c34ccc..d8840aa 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,67 +1,178 @@ # Getting started with CoCoder -Minimal path from install to first workspace init. Sub-Playbook D extends this into a full stranger-test doc set. +This is the shortest v0.1 path from a clean clone to a first orchestrated launch. It assumes you want to run CoCoder against an application repository that lives outside the CoCoder install tree. -## 1. Install +Target time: 30 minutes or less on a machine that already has Node.js and pnpm. -Prerequisites: Node.js version from `.nvmrc`, pnpm 10.x. +## 1. Install CoCoder + +Prerequisites: + +- Node.js version from `.nvmrc` +- pnpm 10.x +- tmux +- At least one configured CLI adapter named by the selected profile ```bash -git clone -cd CoCoder +git clone ~/dev/CoCoder +cd ~/dev/CoCoder pnpm install pnpm -F cocoder-cli build +pnpm exec cocoder validate-contracts ``` -Verify: +Keep the install path handy: ```bash -pnpm exec cocoder validate-contracts +export COCODER_HOME="$PWD" +``` + +## 2. Choose an out-of-tree workspace + +Do not initialize a normal application workspace inside the CoCoder install. The install repo already contains its own dogfood workspace at `/cocoder/`; your app should be elsewhere. + +Example: + +```bash +mkdir -p ~/dev/my-app +cd ~/dev/my-app +git init +``` + +## 3. Initialize CoCoder in the workspace + +Run `cocoder init` from the application repo root: + +```bash +pnpm --dir "$COCODER_HOME" exec cocoder init \ + --workspace-root "$PWD" \ + --cocoder-home "$COCODER_HOME" +``` + +This creates `/cocoder/` from `templates/workspace-cocoder/`. Re-run with `--merge true` after CoCoder updates; user-edited tracked files are preserved. + +Storage zones: + +```text +CoCoder install repo + / + packages/ tracked engine code + docs/ tracked public docs + templates/ tracked workspace templates + local/ ignored install-level state + workspaces/ Oz registry, run records, audit files + secrets/ install-level private secrets + +Application workspace + / + cocoder/ tracked workspace governance + PRIORITIES.md priorities you can launch + SESSION_LOG.md recent run notes + decisions/ workspace decisions + memory/ onboarding notes and codebase map + local/ ignored workspace-local state + config.yaml private per-workspace config + playbooks/ private operator notes + secrets/ do not commit or sync broadly ``` -## 2. Initialize a workspace +Commit the tracked workspace governance files. Do not commit either `local/` zone. -From **your application repository** (not inside the CoCoder install tree): +## 4. Run the workspace audit stubs ```bash -cd /path/to/your-app -pnpm exec cocoder init --workspace-root . --cocoder-home /path/to/CoCoder +pnpm --dir "$COCODER_HOME" exec cocoder audit-workspace --workspace-root "$PWD" +pnpm --dir "$COCODER_HOME" exec cocoder refresh-memory --workspace-root "$PWD" +``` + +Read and edit the generated memory files enough that a first run has context: + +- the onboarding questions file under `cocoder/memory/` +- `cocoder/memory/codebase-map.md` +- `cocoder/memory/tech-stack.md` + +Full stack detection is v0.2 scope; in v0.1 these are starter files for the operator to refine. + +## 5. Add a first launch priority + +For the first v0.1 smoke launch, add an active priority row to `cocoder/PRIORITIES.md` using the starter route's supported slug: + +```markdown +| v0.1-foundation | First CoCoder launch smoke test | Active | Oscar | ``` -This materializes `cocoder/` from `templates/workspace-cocoder/`. Re-run with `--merge true` after CoCoder updates; user-edited tracked files are preserved. +After your own routes and priority boundaries exist, replace this with project-specific slugs. See [`custom-personas.md`](./custom-personas.md) for route and persona extension conventions. -## 3. Audit stub (optional) +## 6. Dry-run composition + +From the application workspace root: ```bash -pnpm exec cocoder audit-workspace --workspace-root . -pnpm exec cocoder refresh-memory --workspace-root . +pnpm --dir "$COCODER_HOME" exec cocoder compose-launch \ + --profile "$COCODER_HOME/cocoder/profiles/cocoder-oscar.profile.json" \ + --route "$COCODER_HOME/cocoder/routes/oscar-lead.json" \ + --priority-slug v0.1-foundation \ + --priority-file "$PWD/cocoder/PRIORITIES.md" \ + --session-log "$PWD/cocoder/SESSION_LOG.md" \ + --workspace-root "$PWD" \ + --workspace-slug my-app ``` -Read `cocoder/memory/onboarding-questions.md` and `cocoder/memory/codebase-map.md`. Full stack detection is v0.2 scope. +The dry run should return `"ok": true`. Fix validation errors before launching. Common causes are a missing active priority row, an unavailable adapter, or a route/profile mismatch. -## 4. First persona launch (outline) +## 7. First launch from CLI -1. Add a priority to `cocoder/PRIORITIES.md`. -2. Copy or author `profiles/`, `routes/`, and `priority-boundaries/` (see dogfood files in the CoCoder install or `examples/personas/phil-primitive-builder/`). -3. Dry-run composition: +Start a visible terminal launch: ```bash -pnpm exec cocoder compose-launch \ - --profile cocoder/profiles/your.profile.json \ - --route cocoder/routes/your.route.json \ - --priority-slug your-slug +pnpm --dir "$COCODER_HOME" exec cocoder launch \ + --profile "$COCODER_HOME/cocoder/profiles/cocoder-oscar.profile.json" \ + --route "$COCODER_HOME/cocoder/routes/oscar-lead.json" \ + --priority-slug v0.1-foundation \ + --priority-file "$PWD/cocoder/PRIORITIES.md" \ + --session-log "$PWD/cocoder/SESSION_LOG.md" \ + --workspace-root "$PWD" \ + --workspace-slug my-app \ + --socket-name cocoder-my-app \ + --execute true \ + --attach iterm ``` -4. Inspect the JSON output; fix validation errors before `launch --execute true`. +`--attach iterm` is best-effort on macOS. If no GUI terminal opens, use the `attachCommands` in the JSON output to attach to the tmux sessions manually. -See `docs/custom-personas.md` for custom persona authoring and `templates/playbooks/new-workspace-setup.md` for a first-week operator playbook. +The first run is successful when the launcher creates a run directory under `/local/workspaces/my-app/runs/` and both lanes receive their launch prompts. If a lane reports a blocker, keep the result artifacts and use the reported evidence instead of deleting the run directory. -## 5. Oz dashboard (optional) +## 8. First launch from Oz -For a browser UI to register workspaces, scan priorities, launch runs, and inspect evidence: +Oz is the browser launch surface for v0.1. ```bash -pnpm exec cocoder oz start +pnpm --dir "$COCODER_HOME" exec cocoder oz start --cocoder-home "$COCODER_HOME" +pnpm --dir "$COCODER_HOME" exec cocoder oz register \ + --id my-app \ + --workspace-root "$PWD" \ + --tmux-socket cocoder-my-app \ + --cocoder-home "$COCODER_HOME" ``` -Open `http://127.0.0.1:7878/`. See [`docs/oz-launch.md`](./oz-launch.md) for the full Oz operator flow (replaces retired `.command` double-click wrappers). +Open `http://127.0.0.1:7878/`, then use **Priorities** to launch the active priority for `my-app`. The dashboard launch path opens visible panes by default when possible. + +For the full dashboard operator flow, see [`oz-launch.md`](./oz-launch.md). For local daemon security expectations before regular use, see [`oz-security-checklist.md`](./oz-security-checklist.md). + +## 9. What to commit + +Commit: + +- `/cocoder/PRIORITIES.md` +- `/cocoder/SESSION_LOG.md` +- `/cocoder/decisions/` +- `/cocoder/memory/` +- Public persona, route, boundary, and prompt files you intentionally add under `/cocoder/` + +Do not commit: + +- `/local/` +- `/cocoder/local/` +- Secret files, `.env*`, run transcripts containing private material, or generated dependency/build/cache directories + +See [`faq.md`](./faq.md) for commercial-use, trademark, telemetry, and sync cautions. diff --git a/docs/orchestration.md b/docs/orchestration.md new file mode 100644 index 0000000..d40ad7b --- /dev/null +++ b/docs/orchestration.md @@ -0,0 +1,75 @@ +# CoCoder Orchestration + +**Status:** Draft, implemented by Sub-Playbook D Solve +**Last verified:** 2026-05-27 (docs pass only; no runtime behavior changed) + +CoCoder launches visible, bounded agent lanes around one selected priority. A run is a working record: launch prompt, startup packet, lane panes, evidence paths, and result artifacts. + +## Tmux model + +Each lane runs in its own tmux session. The launch command creates the sessions, writes lane prompts under the run directory, and starts the configured adapter in each pane. + +Common shapes: + +- **Lead lane** - usually Oscar. Reconciles priority fit, dispatches bounded packets, and owns closeout status updates. +- **Builder lane** - usually Bob. Edits files only inside its write boundary, verifies the packet, then writes result artifacts. +- **Verifier or QA lanes** - optional route members for test building, browser automation, or review. + +Detached runs are normal. Use the launch output's attach commands, or `--attach iterm`, to open panes for a human operator. See [`oz-launch.md`](./oz-launch.md) for the dashboard launch path. + +## Runs + +A run directory lives under the install-local workspace registry: + +```text +/local/workspaces//runs// + startup-packet.json + events.jsonl + jobs//prompt.md + jobs//result.json + jobs//result.md + send-to-.sh + watch--completion.sh +``` + +The startup packet is orientation, not a work order. In `wait-for-lead-dispatch` mode, teammate lanes load the prompt and packet, then stay idle until the lead sends a concrete dispatch. + +Configuration resolution, workspace roots, and private `local/` overrides are described in [`configuration.md`](./configuration.md). + +## Dispatch rules + +The lead dispatch should state: + +- the priority and plan slice +- files or directories in scope +- files or directories out of scope +- required verification +- result expectations when they add detail beyond the lane launch prompt + +Lane launch prompts remain authoritative for identity fields such as persona, adapter, write capability, and result paths. If a later dispatch conflicts with those fields, the lane should follow the launch prompt and report the conflict. + +## Evidence capture + +Evidence belongs in the run result, not only in chat. A useful result names the command, file, report path, screenshot, or diff that supports the claim and states its limitation. + +Evidence classes: + +- **Class A** - founder-pristine, packaged, staging, production, or real user-path proof. +- **Class B** - local, mocked, dry-run, static, or dev-path proof. + +Most v0.1 builder checks are Class B unless they exercise a real operator path from a clean or founder-controlled environment. + +## Session-wrap flow + +When a lane finishes its packet: + +1. Run the required checks, or state why they could not run. +2. Write `jobs//result.json` using the launch prompt's result contract. +3. Write `jobs//result.md` with the same closeout in human-readable form. +4. Stop work for that packet. + +Result artifacts close the lane for the current run. Do not delete, rename, or overwrite them to accept another packet; start a fresh run until packet ledgers are first-class. + +## Extension points + +Routes, profiles, personas, prompt fragments, and priority boundaries define who can join a run and what they may write. See [`custom-personas.md`](./custom-personas.md) for the public extension conventions. diff --git a/docs/oz-control-plane-design/Oz.html b/docs/oz-control-plane-design/Oz.html new file mode 100644 index 0000000..ab0f2e7 --- /dev/null +++ b/docs/oz-control-plane-design/Oz.html @@ -0,0 +1,33 @@ + + + + +Oz — CoCoder Control Plane + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/docs/oz-control-plane-design/README.md b/docs/oz-control-plane-design/README.md new file mode 100644 index 0000000..fea4da4 --- /dev/null +++ b/docs/oz-control-plane-design/README.md @@ -0,0 +1,130 @@ +# Oz — Control Plane Prototype + +The interactive prototype for **Oz**, the CoCoder control plane. This is a high-fidelity working mock — not production code — meant to anchor the dev team's implementation. Open `Oz.html` in a browser to run it. + +--- + +## What this is + +- A single-page React prototype (no build step) demonstrating every screen, state, and interaction described in the design brief. +- Stable visual reference: type, color, spacing, components, and copy are committed. +- Behavioral reference: clicking through the prototype demonstrates the intended flows. +- A spec embedded in the UI itself — **turn on dev annotations** (see below). + +This is the source of truth for *what* to build. The dev team owns *how*. + +--- + +## How to read it + +Open `Oz.html` in any modern browser. Key things to try: + +1. **Talk to Oz.** Type in the terminal. Try `status`, `launch the next priority`, `promote #4`, `full`, `partial`. Click the decision callout when run-1 is blocked. +2. **Click a running priority.** The Run Detail drawer slides out next to it with the gold-edge handoff cue. +3. **Open Run history.** Top-right of the priorities panel. +4. **Switch workspaces.** Use the tabs at the top of the dashboard. Each tab is its own Oz instance. +5. **Inspect every persona.** Personas screen. Note the linked CLI + Model dropdowns and sub-agent hierarchy. +6. **Craft a new persona.** Header button on Personas — fills a priority for the team to build. +7. **First-run setup.** Open Tweaks (bottom-left toggle) → Workspace state → First run. + +### Dev annotations (the embedded spec) + +Open the **Tweaks panel** (toggle in the toolbar) → flip **Show dev annotations** on. Eighteen numbered gold pins appear on key UI surfaces. Click any pin for a short component spec — what it is, how it behaves, and the data it touches. A floating **"Dev notes"** button (bottom-right) opens the full index. + +Treat the dev-notes list as the binding implementation spec. + +--- + +## File layout + +``` +Oz.html Entry point. Loads everything below. +oz.css App styles. Extends the design system. +design-system/ CoBuilder Fusion design system tokens (drop-in). + colors_and_type.css 93 CSS variables, typography classes + fonts/ Josefin Sans (display) +data.js Seed data — workspaces, runs, priorities, + personas, CLIs, dependencies, settings. + NOT the schema spec; just demo content. +dev-notes.js Component specs for the dev team. + 18 numbered entries that overlay the UI. +components.jsx Shared primitives — Sidebar, TopBar, + WorkspaceTabs, Modal, Button, StatusChip, + DevNote, DevNotesPanel, Card. +dashboard.jsx Dashboard composition — PrioritiesPanel + (incl. AdhocPriorityRow + PriorityRow), + OzChatPanel, RunDetail drawer, + RunHistoryModal, first-run setup card. +screens.jsx Workspaces, CLIs, Personas, Settings, + Dependencies panel, NewWorkspaceModal, + CraftPersonaModal. +app.jsx Root component. State, routing, tweaks + wiring, Oz bot reply simulator. +tweaks-panel.jsx Tweaks panel host (theme, density, + workspace state, dev-mode toggle). +``` + +No bundler, no npm install. React + Babel are loaded from unpkg. + +--- + +## Hard rules baked in + +These come from the design brief and should survive the rewrite: + +- **Five top-level nav items only:** Dashboard · Workspaces · CLIs · Personas · Settings. Runs and Priorities are panels inside Dashboard, never standalone pages. +- **One Oz per workspace.** Multiple workspaces loaded = multiple independent headless Oz instances. Persist Oz state keyed on `workspace.id`. (See dev note 17.) +- **Oz is the command center.** Anything the founder can do via a button, they must be able to ask Oz to do. Keep parity. +- **Never expose JSON.** Forms, toggles, selectors. Always. +- **Workspace context is pervasive.** Switching tabs swaps Oz, priorities, runs, and conversation atomically. +- **External orchestration sessions.** Today the runs execute in iTerm. The Run Detail "Transcript" tab is Oz's read-only window into the externally-running session. v2 will embed an Electron terminal. +- **Persona roster:** Oz · Oscar · Bob · Talia · Quinn · Doc. New personas are built by the team via the Craft modal — not configured directly. +- **System dependencies separate from CLI auth.** Settings → System dependencies probes iTerm2 / cmux. CLIs screen probes Claude Code / Codex / Cursor-agent / Gemini / Grok. + +--- + +## What this prototype does NOT decide + +- The wire protocol between Oz and the orchestrator +- How the externally-running session reports back (probably a websocket per run + persisted transcript) +- The actual persona prompt scaffolding (Oscar builds these at runtime) +- Auth, identity, billing — out of scope for the control plane +- Schema / persistence model + +The dev team owns these. + +--- + +## Data model (informal, from the prototype state shape) + +``` +Workspace { + id, name, description, icon, roots: [Root], created +} +Root { + id, name, path, role: "primary" | "writable" | "readonly" +} +Priority { + id, name, summary, status, labels: [string], + runId?: string, // points to the live or last run executing this priority + spec?: PersonaSpec // present when priority is a "build new persona" +} +Run { + id, title, status: "running"|"blocked"|"complete"|"failed"|"stopped", + priorityId?: string, // null = ad-hoc + personas: [string], cli, startedAt, progress, lastEvent, attachCmd, + transcript: [TranscriptLine], evidence: [EvidenceItem] +} +Persona { + id, name, role, description, cli, model, runMode: "visible"|"headless", + subAgents: [{ id, name, cli, model }], icon, headless?: boolean +} +Cli { + id, name, vendor, version, status: "ok"|"auth-failed"|"not-installed", + lastTested, errorDetail?, models: [string] +} +Dependency { + id, name, vendor, purpose, status: "ok"|"not-installed", + version, lastChecked, installCmd, icon, note? +} +``` diff --git a/docs/oz-control-plane-design/app.jsx b/docs/oz-control-plane-design/app.jsx new file mode 100644 index 0000000..f7c39b5 --- /dev/null +++ b/docs/oz-control-plane-design/app.jsx @@ -0,0 +1,524 @@ +// Oz — App root. State, routing, Oz chat simulation, Tweaks wiring. + +const { useState, useEffect, useRef, useMemo, useCallback } = React; +const A = window; + +// ───────── Default tweaks ───────── +const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ + "theme": "dark", + "density": "comfortable", + "workspaceState": "configured", + "devMode": false, + "showInlineFlows": false +}/*EDITMODE-END*/; + +// ───────── Tiny Oz bot ───────── +function buildOzReply(userText, ctx) { + const t = userText.toLowerCase(); + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + const runs = ctx.runs; + const activeCount = runs.filter(r => r.status === "running" || r.status === "blocked").length; + + if (/^status|how.*we doing|what.*going on/.test(t)) { + return { + role: "oz", + body: `**Workspace status:** ${activeCount} active run${activeCount === 1 ? '' : 's'} · ${ctx.priorities.length} priorities queued. Run-1 is blocked on your call about replay scope. Run-2 (ad-hoc audit) is also waiting on a Grok question.`, + time, + }; + } + if (/launch.*next|launch.*top|launch the next/.test(t)) { + const next = ctx.priorities[0]; + if (!next) return { role: "oz", body: "Nothing on the priority list. Want to draft one?", time }; + return { + role: "oz", + body: `Launching **${next.name}**. Spinning up Planner → Builder → Reviewer on claude-code. Visible mode (iTerm).`, + time, + attachments: [{ kind: "queued-run", priorityId: next.id, title: next.name }], + }; + } + if (/ad-?hoc|review|refactor|audit|investigate/.test(t)) { + return { + role: "oz", + body: `Drafting an ad-hoc run for that. Recommended team: Reviewer + Researcher. Approve?`, + time, + }; + } + if (/reorder|promote|move .* to (top|first)/.test(t)) { + const m = t.match(/#?(\d+)/); + return { + role: "oz", + body: m ? `Moved priority #${m[1]} to the top. Run order updated.` : "Got it — which priority would you like to promote? Send `promote #4` or drag it in the list.", + time, + }; + } + if (/decision|full plan|partial/.test(t) || t === "full" || t === "partial") { + const choice = /partial/.test(t) ? "partial" : "full"; + return { + role: "oz", + body: `Decision recorded: replay **${choice} plan** on sub-agent handoffs. Resuming run-1 — Reviewer is finishing its audit now.`, + time, + }; + } + if (/new persona|craft.*persona|build.*persona/.test(t)) { + return { + role: "oz", + body: "Filed it as a priority. Builder + Architect will scaffold prompts, sub-agents, and tests. ETA depends on the role you sketch.", + time, + }; + } + // default + return { + role: "oz", + body: "Heard. I'm thinking — give me a moment.", + time, + }; +} + +// ───────── App ───────── +const App = () => { + const [tweaks, setTweaks] = useState({ ...TWEAK_DEFAULTS }); + const [route, setRoute] = useState("dashboard"); + const [theme, setTheme] = useState(tweaks.theme); + + // Mutable data state (cloned from window.OZ_DATA) + const seed = window.OZ_DATA; + const [activeWsId, setActiveWsId] = useState(seed.activeWorkspaceId); + const [loadedWsIds, setLoadedWsIds] = useState([seed.activeWorkspaceId]); + const [workspaces, setWorkspaces] = useState(seed.workspaces); + const [prioritiesMap, setPrioritiesMap] = useState(seed.priorities); + const [runsMap, setRunsMap] = useState(seed.runs); + const [ozChatMap, setOzChatMap] = useState(seed.ozChat); + const [clis, setClis] = useState(seed.clis); + const [personas, setPersonas] = useState(seed.personas); + const [settings, setSettings] = useState(seed.settings); + const [dependencies, setDependencies] = useState(seed.dependencies || []); + const [selectedRunId, setSelectedRunId] = useState(null); + const [ozTyping, setOzTyping] = useState(false); + + // Modals + const [newWsOpen, setNewWsOpen] = useState(false); + const [craftPersonaOpen, setCraftPersonaOpen] = useState(false); + const [devNotesOpen, setDevNotesOpen] = useState(false); + const [runHistoryOpen, setRunHistoryOpen] = useState(false); + + // Theme sync (tweaks + manual toggle in topbar) + useEffect(() => { + document.documentElement.dataset.theme = theme; + }, [theme]); + useEffect(() => { setTheme(tweaks.theme); }, [tweaks.theme]); + + // Density via CSS var + useEffect(() => { + document.documentElement.style.setProperty('--oz-density-scale', tweaks.density === 'compact' ? '0.92' : '1'); + }, [tweaks.density]); + + // Active workspace ctx + const activeWs = workspaces.find(w => w.id === activeWsId); + const priorities = prioritiesMap[activeWsId] || []; + const runs = runsMap[activeWsId] || []; + const ozMessages = ozChatMap[activeWsId] || []; + + // First-run / empty workspace state + const emptyState = tweaks.workspaceState === "first-run" ? "first-run" : null; + + // ───────── Handlers ───────── + + const updateWs = (next) => { + setWorkspaces(ws => ws.map(w => w.id === next.id ? next : w)); + }; + const createWs = () => { setNewWsOpen(true); }; + + const handleCreateWs = ({ name, description, root }) => { + const id = "ws-" + Math.random().toString(36).slice(2, 7); + const newWs = { + id, name, description, + icon: "ph-thin ph-cube", + roots: [{ id: "r-" + Math.random().toString(36).slice(2, 6), name: root.name, path: root.path, role: "primary" }], + created: new Date().toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' }), + }; + setWorkspaces(ws => [...ws, newWs]); + setPrioritiesMap(m => ({ ...m, [id]: [] })); + setRunsMap(m => ({ ...m, [id]: [] })); + setOzChatMap(m => ({ ...m, [id]: [{ + id: "init", + role: "oz", + body: `Workspace **${name}** is up. Primary root is \`${root.path}\`. Tell me what we're building first.`, + time: new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) + }] })); + setLoadedWsIds(arr => arr.includes(id) ? arr : [...arr, id]); + setActiveWsId(id); + setSelectedRunId(null); + setRoute("dashboard"); + }; + const deleteWs = (id) => { + setWorkspaces(ws => ws.filter(w => w.id !== id)); + if (activeWsId === id && workspaces.length > 1) { + setActiveWsId(workspaces.find(w => w.id !== id).id); + } + }; + + const loadWorkspaceTab = (id) => { + if (!loadedWsIds.includes(id)) setLoadedWsIds(arr => [...arr, id]); + setActiveWsId(id); + setSelectedRunId(null); + }; + const closeWorkspaceTab = (id) => { + if (loadedWsIds.length <= 1) return; + const nextLoaded = loadedWsIds.filter(x => x !== id); + setLoadedWsIds(nextLoaded); + if (activeWsId === id) { + setActiveWsId(nextLoaded[Math.max(0, nextLoaded.length - 1)]); + setSelectedRunId(null); + } + }; + const selectWorkspaceTab = (id) => { setActiveWsId(id); setSelectedRunId(null); }; + + const reorderPriorities = (from, to) => { + setPrioritiesMap(m => { + const arr = [...(m[activeWsId] || [])]; + const [moved] = arr.splice(from, 1); + arr.splice(to, 0, moved); + return { ...m, [activeWsId]: arr }; + }); + }; + + const onLaunchPriority = (priority) => { + // Launch a new run from this priority + const runId = "run-" + Math.random().toString(36).slice(2, 6); + const newRun = { + id: runId, title: priority.name, priorityId: priority.id, + status: "running", + personas: ["Planner", "Builder", "Reviewer"], + cli: "claude-code", + startedAt: "just now", progress: 0.05, + lastEvent: "Run started. Planner is decomposing the priority.", + attachCmd: `cocoder attach ${runId}`, + transcript: [ + { role: "system", body: `Run started against ${activeWs.roots.find(r => r.role === "primary")?.name || "primary root"}.` }, + { role: "Planner", body: "Reading the priority spec. Decomposing into steps." }, + ], + evidence: [], + }; + setRunsMap(m => ({ ...m, [activeWsId]: [newRun, ...(m[activeWsId] || [])] })); + setPrioritiesMap(m => { + const arr = (m[activeWsId] || []).map(p => p.id === priority.id ? { ...p, status: "in-progress", runId } : p); + return { ...m, [activeWsId]: arr }; + }); + // Oz announces the launch + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "u-" + runId, role: "user", body: `Launch "${priority.name}"`, time }, + { id: "o-" + runId, role: "oz", body: `Launching **${priority.name}**. Planner is up. I'll watch.`, time, attachments: [{ kind: "run-card", runId }] }, + ] + })); + }; + + const onAdhoc = () => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "ad-" + Date.now(), role: "user", body: "Run something ad-hoc — describe the task here.", time }, + { id: "or-" + Date.now(), role: "oz", body: "Tell me what to do. Common ad-hocs: **code review** of a PR, **refactor** a module, **research** prior-art, **audit** a surface. What's the task?", time }, + ] + })); + }; + + const onAddPriority = () => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "ap-" + Date.now(), role: "oz", body: "What's the priority? Sketch it in a sentence — I'll write it up and add it to the list.", time }, + ] + })); + }; + + const onSend = (text) => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + const userMsg = { id: "u-" + Date.now(), role: "user", body: text, time }; + setOzChatMap(m => ({ ...m, [activeWsId]: [...(m[activeWsId] || []), userMsg] })); + setOzTyping(true); + setTimeout(() => { + const reply = buildOzReply(text, { priorities, runs }); + reply.id = "o-" + Date.now(); + setOzChatMap(m => ({ ...m, [activeWsId]: [...(m[activeWsId] || []), reply] })); + setOzTyping(false); + }, 700 + Math.random() * 500); + }; + + const onDecision = (choice) => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "ud-" + Date.now(), role: "user", body: choice === "full" ? "Replay the full plan." : "Just replay the last 2 messages.", time }, + { id: "od-" + Date.now(), role: "oz", body: `Decision recorded — **${choice === "full" ? "full plan" : "partial"} replay** on fallback. Resuming run-1.`, time }, + ] + })); + // Unblock run-1 + setRunsMap(m => { + const arr = (m[activeWsId] || []).map(r => r.id === "run-1" ? { ...r, status: "running", lastEvent: "Decision received. Builder continuing." } : r); + return { ...m, [activeWsId]: arr }; + }); + }; + + const onRunAction = (action, runId) => { + if (action === "stop") { + setRunsMap(m => ({ ...m, [activeWsId]: (m[activeWsId] || []).map(r => r.id === runId ? { ...r, status: "stopped", lastEvent: "Stopped by founder." } : r) })); + } else if (action === "ask-oz") { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + const run = runs.find(r => r.id === runId); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "aoz-" + Date.now(), role: "user", body: `What's going on with ${runId}?`, time }, + { id: "aor-" + Date.now(), role: "oz", body: `${runId} — ${run?.title}. Last event: ${run?.lastEvent}`, time, attachments: [{ kind: "run-card", runId }] }, + ] + })); + } else if (action === "retry") { + setRunsMap(m => ({ ...m, [activeWsId]: (m[activeWsId] || []).map(r => r.id === runId ? { ...r, status: "running", startedAt: "just now", lastEvent: "Retrying." } : r) })); + } + }; + + const onTestCli = (id) => { + setClis(cs => cs.map(c => { + if (c.id !== id) return c; + if (c.status === "auth-failed") return { ...c, status: "auth-failed", lastTested: "just now" }; + if (c.status === "not-installed") return { ...c, status: "not-installed", lastTested: "just now" }; + return { ...c, status: "ok", lastTested: "just now" }; + })); + }; + + const onChangePersona = (id, next) => { + setPersonas(ps => ps.map(p => p.id === id ? next : p)); + }; + const onAddSub = (pid) => { + const subId = "sub-" + Math.random().toString(36).slice(2, 6); + setPersonas(ps => ps.map(p => p.id === pid ? { ...p, subAgents: [...p.subAgents, { id: subId, name: "New sub-agent", cli: "claude-code", model: "Default" }] } : p)); + }; + const onRemoveSub = (pid, sid) => { + setPersonas(ps => ps.map(p => p.id === pid ? { ...p, subAgents: p.subAgents.filter(s => s.id !== sid) } : p)); + }; + const onUpdateSub = (pid, sid, next) => { + setPersonas(ps => ps.map(p => p.id === pid ? { ...p, subAgents: p.subAgents.map(s => s.id === sid ? next : s) } : p)); + }; + const onNewPersonaAsPriority = () => { + setCraftPersonaOpen(true); + }; + + const handleSubmitNewPersona = ({ name, summary, spec, placeAtTop }) => { + const newP = { + id: "np-" + Date.now(), + name, summary, + status: "ready", + labels: ["persona-build"], + spec, + }; + setPrioritiesMap(m => { + const arr = m[activeWsId] || []; + return { ...m, [activeWsId]: placeAtTop ? [newP, ...arr] : [...arr, newP] }; + }); + // Oz acknowledges + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "cp-" + Date.now(), role: "oz", + body: `Filed **${name}** as a ${placeAtTop ? 'top' : 'queued'} priority. Oscar will scaffold the persona—prompt, sub-agents, tests included.`, + time, + }, + ] + })); + setRoute("dashboard"); + }; + + const onRecheckDep = (id) => { + setDependencies(deps => deps.map(d => d.id === id ? { ...d, lastChecked: "just now" } : d)); + }; + + // ───────── Tweaks panel ───────── + const setTweak = (key, val) => { + if (typeof key === "object") { + setTweaks(t => ({ ...t, ...key })); + window.parent.postMessage({ type: '__edit_mode_set_keys', edits: key }, '*'); + } else { + setTweaks(t => ({ ...t, [key]: val })); + window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: val } }, '*'); + } + }; + + // Tweaks host protocol + const [tweaksOpen, setTweaksOpen] = useState(false); + useEffect(() => { + const handler = (e) => { + if (!e?.data) return; + if (e.data.type === "__activate_edit_mode") setTweaksOpen(true); + else if (e.data.type === "__deactivate_edit_mode") setTweaksOpen(false); + }; + window.addEventListener("message", handler); + window.parent.postMessage({ type: '__edit_mode_available' }, '*'); + return () => window.removeEventListener("message", handler); + }, []); + + // ───────── Render ───────── + const screenTitle = NAV_ITEMS.find(n => n.id === route)?.label || "Oz"; + + const devCtxValue = useMemo(() => ({ on: !!tweaks.devMode }), [tweaks.devMode]); + + return ( + +
+ { setRoute(r); setSelectedRunId(null); }} + runs={runs} priorities={priorities} user={seed.user} /> +
+ +
+ {route === "dashboard" && ( + + )} + {route === "workspaces" && ( + setRoute("dashboard")} /> + )} + {route === "clis" && ( + alert("Add CLI flow (mock)")} /> + )} + {route === "personas" && ( + + )} + {route === "settings" && ( + + )} +
+
+ + {/* Modals */} + setNewWsOpen(false)} + onCreate={handleCreateWs} + /> + setCraftPersonaOpen(false)} + clis={clis} + onSubmit={handleSubmitNewPersona} + /> + + {/* Dev notes panel + floating toggle */} + setDevNotesOpen(false)} + notes={window.DEV_NOTES || []} + /> + {tweaks.devMode && !devNotesOpen && ( + + )} + + {tweaksOpen && ( + setTweaksOpen(false)}> + + setTweak("theme", v)} + /> + + + setTweak("workspaceState", v)} + /> + + + setTweak("density", v)} + /> + + + { setTweak("devMode", v); if (v) setDevNotesOpen(true); }} + /> + {tweaks.devMode && ( + setDevNotesOpen(o => !o)}> + {devNotesOpen ? "Hide notes panel" : "Show notes panel"} + + )} + + + setRoute("dashboard")}>Go to Dashboard + setSelectedRunId("run-1")}>Open run-1 (active) + setSelectedRunId("run-4")}>Open run-4 (failed) + { setRoute("dashboard"); setActiveWsId("ws-vault"); }}>Switch to quiet workspace + + + )} +
+
+ ); +}; + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/docs/oz-control-plane-design/components.jsx b/docs/oz-control-plane-design/components.jsx new file mode 100644 index 0000000..04be575 --- /dev/null +++ b/docs/oz-control-plane-design/components.jsx @@ -0,0 +1,584 @@ +// Shared primitives + app shell (Sidebar, TopBar, WorkspacePicker) + +const { useState, useEffect, useRef, useMemo, useCallback } = React; + +// ───────── Icon wrapper (Phosphor thin) ───────── +const Icon = ({ name, size, style }) => ( + +); + +// ───────── Status chips ───────── +const STATUS_LABEL = { + running: "Running", + blocked: "Needs decision", + complete: "Complete", + failed: "Failed", + stopped: "Stopped", + queued: "Queued", + ready: "Ready", + "in-progress": "In progress", + ok: "Ready", + "auth-failed": "Auth failed", + "not-installed": "Not installed", +}; +const STATUS_ICON = { + running: null, // uses pulsing dot + blocked: "warning-circle", + complete: "check-circle", + failed: "x-circle", + stopped: "stop-circle", + queued: "circle-dashed", + ready: "circle", + "in-progress": null, + ok: "check-circle", + "auth-failed": "warning-circle", + "not-installed": "minus-circle", +}; +const STATUS_VARIANT = { + running: "running", "in-progress": "running", + blocked: "blocked", + complete: "complete", ok: "complete", + failed: "failed", "auth-failed": "failed", + stopped: "stopped", "not-installed": "stopped", + queued: "queued", ready: "queued", +}; + +const StatusChip = ({ status, label }) => { + const variant = STATUS_VARIANT[status] || "queued"; + const iconName = STATUS_ICON[status]; + return ( + + {variant === "running" ? : iconName && } + {label || STATUS_LABEL[status] || status} + + ); +}; + +// ───────── Button ───────── +const Button = ({ variant = "secondary", size, icon, children, ...rest }) => ( + +); + +// ───────── Sidebar ───────── +const NAV_ITEMS = [ + { id: "dashboard", label: "Dashboard", icon: "squares-four" }, + { id: "workspaces", label: "Workspaces", icon: "folders" }, + { id: "clis", label: "CLIs", icon: "terminal-window" }, + { id: "personas", label: "Personas", icon: "users-three" }, + { id: "settings", label: "Settings", icon: "gear-six" }, +]; + +const Sidebar = ({ route, setRoute, runs, priorities, user }) => { + const activeRuns = runs.filter(r => r.status === "running" || r.status === "blocked").length; + return ( + + ); +}; + +// ───────── Workspace tabs (top bar — loaded workspaces, like browser tabs) ───────── +const WorkspaceTab = ({ ws, isActive, runs, onSelect, onClose, canClose }) => { + const activeRunCount = (runs || []).filter(r => r.status === "running" || r.status === "blocked").length; + return ( +
onSelect(ws.id)} + className="oz-ws-tab" + data-active={isActive ? "true" : "false"} + > + + {ws.name} + {activeRunCount > 0 && ( + + )} + {canClose && ( + + )} +
+ ); +}; + +const WorkspaceTabs = ({ workspaces, loadedIds, activeId, runsMap, onSelect, onClose, onLoad, onCreate }) => { + const [adderOpen, setAdderOpen] = useState(false); + const adderRef = useRef(null); + + useEffect(() => { + if (!adderOpen) return; + const handler = (e) => { if (adderRef.current && !adderRef.current.contains(e.target)) setAdderOpen(false); }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [adderOpen]); + + const loaded = loadedIds.map(id => workspaces.find(w => w.id === id)).filter(Boolean); + const unloaded = workspaces.filter(w => !loadedIds.includes(w.id)); + + return ( +
+ +
+ {loaded.map(w => ( + 1} + /> + ))} +
+ +
+ + + {adderOpen && ( +
+
+ Load workspace +
+ {unloaded.length === 0 ? ( +
+ All workspaces already loaded. +
+ ) : unloaded.map(w => ( +
{ onLoad(w.id); setAdderOpen(false); }} + style={{ + display: 'flex', alignItems: 'center', gap: 10, + padding: '9px 10px', borderRadius: 'var(--cb-radius-md)', + cursor: 'pointer', + }} + onMouseEnter={e => e.currentTarget.style.background = 'var(--cb-hover)'} + onMouseLeave={e => e.currentTarget.style.background = 'transparent'} + > +
+
+
{w.name}
+
+ {w.description || '—'} +
+
+ + {w.roots.length} root{w.roots.length === 1 ? '' : 's'} + +
+ ))} +
+
{ onCreate(); setAdderOpen(false); }} + style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '9px 10px', cursor: 'pointer', color: 'var(--cb-accent)', fontSize: 12, borderRadius: 'var(--cb-radius-md)' }} + onMouseEnter={e => e.currentTarget.style.background = 'var(--cb-hover)'} + onMouseLeave={e => e.currentTarget.style.background = 'transparent'} + > + + New workspace… + ⌘N +
+
+ )} +
+
+ ); +}; + +// ───────── Top bar ───────── +const TopBar = ({ title, workspaces, activeId, loadedIds, runsMap, onSelectWs, onCloseWs, onLoadWs, onCreateWs, theme, setTheme, route }) => ( +
+ {route === "dashboard" ? ( + + ) : ( +
{title}
+ )} + {route !== "dashboard" &&
} + +
+ + Search runs, priorities… + ⌘K +
+ + + +
+); + +// ───────── Reusable: section header inside content ───────── +const ScreenHeader = ({ title, subtitle, actions }) => ( +
+
+

+ {title} +

+ {subtitle && ( +
+ {subtitle} +
+ )} +
+ {actions &&
{actions}
} +
+); + +// ───────── Card ───────── +const Card = ({ children, style, onClick, active }) => ( +
+ {children} +
+); + +// ───────── Modal ───────── +const Modal = ({ open, onClose, title, subtitle, icon, footer, children, width = 640 }) => { + useEffect(() => { + if (!open) return; + const onKey = (e) => { if (e.key === "Escape") onClose(); }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + }, [open, onClose]); + + if (!open) return null; + return ( +
+
e.stopPropagation()} + style={{ + width: width, maxWidth: '100%', maxHeight: 'calc(100vh - 48px)', + background: 'var(--cb-bg-soft)', + border: '1px solid var(--cb-border-strong)', + borderRadius: 'var(--cb-radius-xl)', + boxShadow: '0 24px 60px rgba(0,0,0,0.55), inset 0 1px 0 0 var(--cb-glass-highlight)', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + position: 'relative', + animation: 'ozSlideIn 240ms ease-out', + }} + > +
+
+ +
+ {icon && ( +
+ +
+ )} +
+

{title}

+ {subtitle && ( +
+ {subtitle} +
+ )} +
+ +
+ +
+ {children} +
+ + {footer && ( +
+ {footer} +
+ )} +
+
+ ); +}; + +// ───────── Export to window ───────── +// ───────── Dev Annotations ───────── +const DevModeContext = React.createContext({ on: false, register: () => {}, unregister: () => {} }); + +const useDevMode = () => React.useContext(DevModeContext); + +const DevNote = ({ n, anchor = "top-left" }) => { + const { on } = useDevMode(); + const [open, setOpen] = useState(false); + const ref = useRef(null); + const meta = (window.DEV_NOTES || []).find(x => x.n === n) || { title: `Note ${n}`, body: "Missing in registry." }; + const { title, body } = meta; + + useEffect(() => { + if (!open) return; + const handler = (e) => { + if (ref.current && !ref.current.contains(e.target)) setOpen(false); + }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [open]); + + if (!on) return null; + const positions = { + "top-left": { top: 6, left: 6 }, + "top-right": { top: 6, right: 6 }, + "bottom-left": { bottom: 6, left: 6 }, + "bottom-right": { bottom: 6, right: 6 }, + }; + return ( +
+ + {open && ( +
+
+ {n} +
{title}
+
+
+ {body} +
+
+ )} +
+ ); +}; + +// ───────── Dev Annotations side panel ───────── +const DevNotesPanel = ({ open, onClose, notes }) => { + if (!open) return null; + return ( +
+
+ +
Dev notes
+ + {notes.length} pins + + +
+
+ Numbered pins overlay every annotated component. Click a pin in the UI for component-level docs; the list here is the index. +
+ {notes.map(note => ( +
+
{note.n}
+
+
{note.title}
+
{note.body}
+
+
+ ))} +
+
+ ); +}; + +Object.assign(window, { + Icon, StatusChip, Button, Sidebar, TopBar, WorkspaceTabs, ScreenHeader, Card, Modal, + DevModeContext, useDevMode, DevNote, DevNotesPanel, + NAV_ITEMS, STATUS_LABEL, +}); diff --git a/docs/oz-control-plane-design/dashboard.jsx b/docs/oz-control-plane-design/dashboard.jsx new file mode 100644 index 0000000..768b753 --- /dev/null +++ b/docs/oz-control-plane-design/dashboard.jsx @@ -0,0 +1,1174 @@ +// Dashboard — the operator's home. Priorities | Oz terminal | Runs (with run-detail drawer). + +const D = window; + +// ───────── Priority row (draggable, run-aware) ───────── +const PriorityRow = ({ priority, index, onLaunch, onDrag, isDragging, isDropTarget, onSelectRun, runs, showDevPin9, selectedRunId }) => { + const linkedRun = priority.runId ? runs.find(r => r.id === priority.runId) : null; + const isRunning = linkedRun && (linkedRun.status === "running" || linkedRun.status === "blocked"); + const isBlocked = linkedRun && linkedRun.status === "blocked"; + const isSelected = linkedRun && linkedRun.id === selectedRunId; + + return ( +
onDrag("start", index, e)} + onDragOver={(e) => { e.preventDefault(); onDrag("over", index, e); }} + onDragEnd={(e) => onDrag("end", index, e)} + onDrop={(e) => { e.preventDefault(); onDrag("drop", index, e); }} + onClick={() => isRunning && onSelectRun(linkedRun.id)} + style={{ + background: isSelected ? 'var(--cb-accent-muted)' : isRunning ? 'var(--cb-accent-subtle)' : 'var(--cb-surface-glass)', + border: `1px solid ${isSelected ? 'var(--cb-accent)' : isBlocked ? 'rgba(212,118,110,0.30)' : isRunning ? 'var(--cb-accent-30)' : 'var(--cb-border)'}`, + borderRight: isSelected ? '1px solid var(--cb-accent)' : undefined, + borderRadius: isSelected ? 'var(--cb-radius-md) 0 0 var(--cb-radius-md)' : 'var(--cb-radius-md)', + padding: '11px 12px 12px', + marginBottom: 8, + marginRight: isSelected ? -17 : 0, + paddingRight: isSelected ? 24 : 12, + opacity: isDragging ? 0.4 : 1, + boxShadow: isDropTarget ? '0 0 0 2px var(--cb-accent-30)' : + isSelected ? '0 4px 16px rgba(201,169,110,0.18)' : 'none', + cursor: isRunning ? 'pointer' : 'grab', + transition: 'box-shadow 120ms ease-out, background 120ms ease-out, margin-right 200ms ease-out, padding-right 200ms ease-out, border-radius 200ms ease-out', + position: 'relative', + zIndex: isSelected ? 5 : 1, + }} + > + {/* Selected: notch arrow pointing into the run drawer */} + {isSelected && ( +
+ )} + {/* Running accent bar */} + {isRunning && !isSelected && ( +
+ )} + +
+
+ + {String(index + 1).padStart(2, '0')} +
+
+
+ {priority.name} +
+
+ {priority.summary} +
+
+
+ +
+ {linkedRun ? ( + + ) : ( + + )} + {priority.labels.map(l => ( + {l} + ))} +
+ {!isRunning && ( + { e.stopPropagation(); onLaunch(priority); }}> + Launch + + )} +
+
+ + {/* Inline run summary — only when running */} + {isRunning && ( +
+ {showDevPin9 && } +
+ + {linkedRun.id} · {linkedRun.startedAt} + +
+ {linkedRun.personas.slice(0, 4).map(p => ( + {p} + ))} +
+
+
+ {isBlocked && } + {linkedRun.lastEvent} +
+ {linkedRun.progress != null && ( +
+
+
+ )} +
+ )} +
+ ); +}; + +// ───────── Ad-hoc priority row (pinned at top, behaves like any priority) ───────── +const AdhocPriorityRow = ({ adhocRuns, onLaunch, onSelectRun, selectedRunId }) => { + const activeCount = adhocRuns.filter(r => r.status === "running" || r.status === "blocked").length; + const hasSelected = adhocRuns.some(r => r.id === selectedRunId); + + return ( +
0 ? 'var(--cb-accent-30)' : 'var(--cb-border)'}`, + borderRight: hasSelected ? '1px solid var(--cb-accent)' : undefined, + borderRadius: hasSelected ? 'var(--cb-radius-md) 0 0 var(--cb-radius-md)' : 'var(--cb-radius-md)', + padding: '11px 12px 12px', + marginBottom: 8, + marginRight: hasSelected ? -17 : 0, + paddingRight: hasSelected ? 24 : 12, + position: 'relative', + transition: 'all 200ms ease-out', + boxShadow: hasSelected ? '0 4px 16px rgba(201,169,110,0.18)' : 'none', + zIndex: hasSelected ? 5 : 1, + }}> + +
+
+ + +
+
+
+ Ad-hoc + pinned +
+
+ Refactors · code reviews · research · audits — work that doesn't fit a priority. +
+
+
+ +
+ {activeCount > 0 ? ( + + {activeCount} active + + ) : ( + + )} +
+ { e.stopPropagation(); onLaunch(); }}> + Launch run + +
+
+ + {/* Inline list of ad-hoc runs */} + {adhocRuns.length > 0 && ( +
+ {adhocRuns.map((r, idx) => { + const isSel = r.id === selectedRunId; + const isBlocked = r.status === "blocked"; + const isLive = r.status === "running" || r.status === "blocked"; + return ( +
onSelectRun(r.id)} style={{ + padding: '8px 10px 8px 10px', + background: isSel ? 'var(--cb-accent-15)' : 'transparent', + border: `1px solid ${isSel ? 'var(--cb-accent-30)' : 'var(--cb-border)'}`, + borderRadius: 'var(--cb-radius-sm)', + marginBottom: idx === adhocRuns.length - 1 ? 0 : 6, + cursor: 'pointer', + transition: 'all 120ms ease-out', + position: 'relative', + overflow: 'hidden', + }} + onMouseEnter={e => { if (!isSel) e.currentTarget.style.background = 'var(--cb-hover)'; }} + onMouseLeave={e => { if (!isSel) e.currentTarget.style.background = 'transparent'; }}> + {isLive && ( +
+ )} +
+ + {r.id} + {r.startedAt} +
+
+ {r.title} +
+ {r.lastEvent && ( +
+ {r.lastEvent} +
+ )} +
+ ); + })} +
+ )} +
+ ); +}; + +// ───────── Priorities panel ───────── + +// ───────── Priorities panel (now includes ad-hoc runs section) ───────── +const PrioritiesPanel = ({ priorities, runs, onReorder, onLaunch, onAdhoc, onAddPriority, onSelectRun, onOpenRunHistory, selectedRunId }) => { + const [drag, setDrag] = useState({ from: null, over: null }); + + const handleDrag = (type, index) => { + if (type === "start") setDrag({ from: index, over: null }); + else if (type === "over") setDrag(d => ({ ...d, over: index })); + else if (type === "drop") { + if (drag.from !== null && drag.from !== index) onReorder(drag.from, index); + setDrag({ from: null, over: null }); + } else if (type === "end") setDrag({ from: null, over: null }); + }; + + const adhocRuns = runs.filter(r => !r.priorityId && (r.status === "running" || r.status === "blocked")); + const totalRuns = runs.length; + + return ( +
+ +
+ +
Priorities
+ {priorities.length} +
+ + +
+
+ +
+ {/* Ad-hoc — always-pinned priority */} + !r.priorityId && (r.status === "running" || r.status === "blocked"))} + onLaunch={onAdhoc} + onSelectRun={onSelectRun} + selectedRunId={selectedRunId} + /> + + {priorities.length === 0 ? ( +
+
+
Nothing queued
+
+ Ask Oz to draft your first priority, or add one yourself. +
+ Add priority +
+ ) : ( + <> +
+ QUEUE · ↑ TOP = NEXT UP + +
+ {priorities.map((p, i) => ( + r.id === p.runId && (r.status === "running" || r.status === "blocked"))} + /> + ))} + + )} +
+
+ ); +}; + +// ───────── Oz chat: message ───────── +const ChatMessage = ({ msg, runs, onSelectRun, onDecision }) => { + const isOz = msg.role === "oz"; + const isUser = msg.role === "user"; + const roleLabel = isOz ? "Oz" : isUser ? "You" : msg.role; + const roleColor = isOz ? 'var(--cb-accent)' : isUser ? 'var(--cb-text)' : 'var(--cb-text-secondary)'; + + return ( +
+
+ {roleLabel} + {isOz && orchestrator · headless} + + {msg.time} + +
+
$1') }} /> + + {/* Inline run cards */} + {msg.attachments && msg.attachments.map((a, i) => { + if (a.kind === "run-card") { + const run = runs.find(r => r.id === a.runId); + if (!run) return null; + return ( +
onSelectRun(run.id)} style={{ + marginTop: 10, + padding: 12, + background: 'var(--cb-bg-soft)', + border: '1px solid var(--cb-border)', + borderRadius: 'var(--cb-radius-md)', + cursor: 'pointer', + display: 'flex', alignItems: 'center', gap: 12, + transition: 'border-color 120ms ease-out', + position: 'relative', + }} onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--cb-accent-30)'} + onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--cb-border)'}> + +
+
+ + {run.id} +
+
{run.title}
+
+ {run.personas.join(' · ')} · started {run.startedAt} +
+
+ +
+ ); + } + return null; + })} + + {/* Decision callout */} + {msg.flag === "decision" && ( +
+ + +
+ Oz is waiting for your call before run-1 can continue. +
+
+ onDecision("full")}>Replay full plan + onDecision("partial")}>Partial +
+
+ )} +
+ ); +}; + +// ───────── Oz chat panel ───────── +const QUICK_PROMPTS = [ + { label: "Status check", prompt: "Status across the workspace?" }, + { label: "Launch next priority", prompt: "Launch the next priority." }, + { label: "Ad-hoc run", prompt: "Run an ad-hoc " }, + { label: "Reorder priorities", prompt: "Promote #4 to the top." }, +]; + +const OzChatPanel = ({ messages, runs, workspaceName, onSend, onSelectRun, onDecision, ozTyping }) => { + const [text, setText] = useState(""); + const bodyRef = useRef(null); + + useEffect(() => { + if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight; + }, [messages.length, ozTyping]); + + const send = () => { + if (!text.trim()) return; + onSend(text); + setText(""); + }; + + return ( +
+ {/* Decorative corner accents */} +
+
+ +
+ + +
+ +
+ Oz Terminal +
+
+ headless oz · bound to {workspaceName} +
+
+ + watching + + +
+ +
+ {messages.map(m => ( + + ))} + {ozTyping && ( +
+ Oz + + {[0, 1, 2].map(i => ( + + ))} + +
+ )} +
+ + {/* Input */} +
+
+ + {QUICK_PROMPTS.map(qp => ( + + ))} +
+
+ +