diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index 35f5dd564..88fb383e6 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -16,12 +16,14 @@ module.exports = { // - electron/mcp/validation.ts — string-only branch/UUID validators; the renderer // shares them so it never offers a branch the IPC layer would reject // - electron/shared/ — renderer-safe constants/pure helpers (no Node/Electron imports) + // - electron/documents/shared.ts — agent ids/labels the document workspace shows pathNot: [ '^electron/ipc/channels\\.ts', '^electron/mcp/prompt-detect\\.ts', '^electron/mcp/validation\\.ts', '^electron/agent-hooks/status\\.ts', '^electron/shared/', + '^electron/documents/shared\\.ts', ], }, }, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c59bebcaf..bb68fe4f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,13 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + # Runners have no global Git identity, and tests that let the app create a + # repository of its own commit into it without a local one to fall back on. + - name: Give Git an identity + run: | + git config --global user.email "ci@example.com" + git config --global user.name "CI" + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '22' diff --git a/.gitignore b/.gitignore index 7d5ed2181..ff871a58d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ update-test/ # Keep generated docs content ignored, but track the authored architecture overview. docs/* !docs/architecture-overview.html +!docs/document-workspaces.md # Sandbox bind-mount artifacts from user home (not project files). # Root-anchored so legitimate nested files with these names are still tracked. diff --git a/docs/document-workspaces.md b/docs/document-workspaces.md new file mode 100644 index 000000000..180d2bc41 --- /dev/null +++ b/docs/document-workspaces.md @@ -0,0 +1,360 @@ +# Document Workspaces + +**A project type in Parallel Code for exploring problems in prose** + +Status: experimental, behind the _Document workspaces_ switch in Settings → Experimental. +Slices 1 to 4 of the plan below are built (the loop, comparison, annotations, HTML pages). + +Parallel Code already runs several agents against the same codebase and keeps the results +isolated, attributable, and comparable. Document Workspaces applies the same machine to +writing and thinking: architecture notes, specs, ADRs, research. Several agents get the +same passage and the same base version. You read the alternatives and decide what enters +the document. + +``` + ┌────────────── Claude Code ──────────────● accepted + │ \ + ────────● base ─┤ ●────── + │ / + └────────────── Codex ───────────────○ discarded +``` + +## What is built + +### Using it + +1. Enable _Document workspaces_ under Settings → Experimental. +2. Click **+** next to Projects and choose **Document project…**. Type or browse to a + folder and name the project; a new Markdown document is named after it. Start with a + spec, architecture decision, or design note. Existing Markdown and HTML documents can + still be opened from the folder list. Neither has to exist: type a new name onto + the end of the path and that folder is the project. A folder that already holds + documents lists them, to open one of those instead. The dialog says what it will do — + create the folder, `git init`, create the file, make the first commit — and does it on + confirm. +3. The workspace opens full-window with two tabs, **Document** and **History**; comparing + proposals happens in a modal over either. + A **Files** tab in the right panel lists every file of the project; click one to open + it, and click a relative link inside a document to follow it to the file (and heading) + it points at. Web links open in the browser. The history, the composer and the runs + follow the open document. +4. The composer is a popover over the prose, never inside it, and stays out of the way + until there is something to compose: select text, click a block, or press **§** next + to a heading to open it on a passage, or choose **Revise document** in the toolbar to + open it on the whole document. On a passage it sits right under it (above it when the + foot is close), at full strength and with the cursor in it, and follows the passage as + you scroll. It steps back once you leave it. Type an instruction and press Enter. Hovering a + block shows five icons in the gap just above it, clear of the prose so a click meant + for the passage cannot land on one, each named as you point at it: task, proposals, + note and ask pick the block and open the composer on that mode (its tabs carry the same + icons), and the pencil, set apart, opens the block's source for editing. The picked + block's icons step aside while the composer is up on it; the other blocks keep theirs, + so one click moves the composer to another block. The **×** in the composer's corner, + or Esc, lets go of the passage and closes the composer. + The composer's four tabs are the four things one does with a passage; **Edit with agent** and + **Proposals** are the same instruction sent two ways: + - **Edit with agent** types it into the long-running agent session on the right, which + works in the checkout as you watch. Its edits show up in the viewer as they land and + are committed as `Manual edits` before the next one-shot run. + - **Proposals**, the default for a new composer, runs headless candidates in their own worktrees. The agents sit in + plain view: click one to add or drop it, and its count to have it draft more than + once; a counter says how many of the allowed candidates are in play. **Models and + main session** folds away the tuning: each candidate gets a **Model** field and, where + the CLI has one, a **Reasoning** level, so one agent can run its main session on one + model and an alternate on another; both are the CLI's defaults until you choose, and + the choice is remembered per project, agent and candidate. + The toolbar's **Full width** lets the document use the whole pane instead of a reading + column; the choice is remembered. Mermaid diagrams, here and in every other Markdown + surface of the app, carry an enlarge button that opens them at the size of the window. +5. The right-hand panel has three tabs and a draggable seam (double-click it to reset the + width; the width is remembered). **Agent** is the terminal a task has: the bar above it + shows the last prompt sent, the chips switch between agents, **+** adds another one + (as tabs by default, or side by side), and the prompt box below sends to the first + agent. A session that exits offers **Restart**, **Resume** and a switch to another + agent; one that has exited starts afresh the next time the tab is shown. The processes + survive closing the workspace, and the agent last used comes back after the app + restarts. A Markdown path the agent prints opens in the viewer when it lies inside the + project. The tab stays mounted behind the other two, so switching tabs keeps the + scrollback and scroll position. The tab flags _!_ while an agent waits for an answer. **Runs** lists the one-shot runs as they finish, + each revision or merge saying which proposals it came from. Click a ready candidate in a + finished run to review that proposal; **View output** opens its log. Running, failed and + already-decided candidates still open their output. + Runs that wait for a decision also appear as a strip over the agent, so nothing sits + unnoticed behind another tab. **Review** (one candidate) or **Compare** (several), or the + _n to review_ button in the header, opens the compare view over the whole window: the original + on the left and one proposal on the right. Proposal tabs support arrow keys, Home and End, + and preserve notes, selected changes and reading position when switching. **Show all proposals** + restores the resizable multi-column layout. Each proposal starts with a one-line summary; + **Reasoning and notes** expands the full rationale and your review note. Warnings remain + visible. The original and proposal start at the same height, even with details expanded. + **Apply proposal** (or **Apply n of m changes**) stays below the scrolling document. Apply one, + reject all, or choose **Refine this candidate** and describe what should change. With + two or more proposals, **Merge with agent** hands the comparing to an agent: pick the + proposals and the agent, adjust the guidance, and it drafts one merged proposal from + the diffs and your notes on each candidate, saying which parts came from where. The + merged version lands in Runs like any other proposal and still needs acceptance. + A candidate with more than one change carries a checkbox beside each + of them: every change is kept until you clear its box, the passages you dropped recede + in place so you can still read what you turned down, and the button then says + **Accept n of m changes** and takes only those. **Preview result** shows the complete + document with the selected changes applied and declined passages restored from the base. + **Back to changes** preserves your choices. Acceptance waits for the combination to be + verified and is disabled if it cannot be combined cleanly. Refinement uses the same agent and model in a fresh isolated run, + starts from that candidate's content, and leaves the original proposal available. + The revision appears in Runs and still needs acceptance. +6. **History** is `git log` for the document with the `Parallel-*` trailers parsed: + what changed, which agent, which instruction, which base. Show the diff or render the + older version. Revert any entry. +7. **Annotations.** The same composer offers **Note** and **Ask** beside **Edit with agent**. A note + attaches to the passage as a symbol in its margin; hover it, tab to it, or click it to + pin it open, and the bubble opens over the document. A question is a bubble an agent + answers into, running read-only in the checkout; question and answer stay visible + together. **Ask follow-up** under an answer continues the conversation with the agent + that answered: the follow-up is a new question on the same passage, and the agent is + handed the earlier exchange along with it. Resolve (`r`) collapses a bubble to one + line, Delete removes it with an Undo + in the toolbar, and **Make task** reopens the composer on the passage with the bubble's + text (and answer) as the instruction; the bubble then collapses as `task`. + +8. **Edit a block.** Hover a block and click its pencil, or click the block and choose + **Edit block** in the toolbar. Edit its + Markdown or HTML source, then **Save block** to update the document directly. Surrounding + content and line endings are preserved. If the file changed meanwhile, saving refuses + to overwrite it and keeps your text available to copy. Edits made this way, or in an + outside editor, show as an _uncommitted edits_ chip in the header; click it to discard + them and return to the committed version. Next to the editor button, a folder button + opens the project in the file manager. + +### How it works + +- **Editing happens in the block editor, your editor, or the interactive session.** The app + watches the open file and re-renders; an external change drops any active selection. The Agent + tab is a task's AI terminal and prompt box (`TaskAITerminal`, `PromptInput`) over a hidden + task per project, id `doc-agent-`, kept out of the task order so it is neither + listed nor persisted; its first agent's pty carries the same id, so reopening the workspace + re-attaches, and the agent it ran is remembered on the project. Each time the tab is + shown, the agents are re-armed to attach (a restart clears that for its one spawn) and + one that exited is reset, since the terminal spawns it afresh. Removing the project + kills the sessions. A scoped instruction, _your words, then Document, Scope and the + passage verbatim_, goes to the selected agent as a prompt: typed straight in when the agent looks + idle (quiet output, no open question, and for Claude no hook turn in flight), else + queued as the task's initial prompt, which the prompt box sends once the agent is + ready; a second instruction is refused while one waits. Because the agent edits the + checkout directly, its work is committed as `Manual edits` by the next one-shot + dispatch rather than as a proposal to compare. +- **Every file of the project is one click away.** The file tree is `git ls-files` + (tracked and untracked, ignores respected) minus `.parallel/`, `.worktrees/` and `.git/`, + capped at 5,000 entries, refreshed on every new head. Opening a file swaps the watcher + and the snapshot; annotations are filtered to the open file's path, and runs against + other documents say which. Links resolve against the open document's folder, never + above the project root. +- **Output is kept.** Each candidate's readable log is appended to + `.parallel/logs//.log` as it streams (the rail shows the tail); the + folder is git-excluded through `.git/info/exclude`. The output dialog reads it back, + capped at the last 2 MB, and re-reads every second while the candidate runs. +- **Dispatch commits pending edits first** as a plain `Manual edits` commit so every run + has a real base. Only tracked files count as pending edits: untracked scratch files + and everything under `.parallel/` stay out of that commit. +- **Headless agents.** Each candidate runs the official CLI in print mode inside its own + worktree under `.worktrees/parallel-doc/`: `claude -p --output-format stream-json` + with tools limited to Read/Edit/Write/Glob/Grep, `codex exec --json --sandbox +workspace-write` (the sandbox blocks writes outside the worktree), + `gemini -p --output-format json --approval-mode auto_edit` (`plan`, Gemini's read-only + mode, for annotation questions). Process exit means the + proposal is ready. Cancelling kills the CLI's whole process group. OpenCode and Copilot + expose an unrestricted shell in print mode and are not offered until they can be + restricted. A model or reasoning level chosen in the composer goes to the CLI as + `claude --model … --effort …`, `codex --model … -c model_reasoning_effort=…` or + `gemini --model …`, is kept on the candidate in the run record, and lands in the + `Parallel-Model` and `Parallel-Effort` trailers of the proposal and integration commits. +- **The main session stays warm.** One agent owns the project's main session (choose it in + the composer). Its worktree is persistent (`.worktrees/parallel-doc-main`) so the working + directory, and with it the provider's prompt cache, never changes; each run resumes the + session by id (`claude --resume`, `codex exec resume`). After the canonical document + moves, the next prompt to that session carries the diff since it last saw the file. + Other agents, and extra candidates from the main agent, are one-shot alternates. While + the main session is working, a new run can only use alternates. +- **Scope is enforced, not trusted.** Files the agent touched or staged outside the + document are reverted before the proposal commit and listed on the candidate; the + proposal commit is verified to contain the document alone. Hunks inside the document + but outside the selected passage are counted and flagged. +- **Structured rationale.** Every prompt asks the agent to end with a JSON block: + summary, changes, assumptions, questions, warnings. It opens each candidate in the + compare view and becomes the commit message. +- **One proposal commit per candidate** on a `parallel-doc/-