diff --git a/client/src/components/run/TerminalView.tsx b/client/src/components/run/TerminalView.tsx index 78e9dfb9..a96079ed 100644 --- a/client/src/components/run/TerminalView.tsx +++ b/client/src/components/run/TerminalView.tsx @@ -76,7 +76,13 @@ export function TerminalView({ runId, wsBaseUrl }: TerminalViewProps) { }; sock.onmessage = (event) => { const isArrayBuffer = Object.prototype.toString.call(event.data) === "[object ArrayBuffer]"; - const data = isArrayBuffer ? decoder.decode(event.data as ArrayBuffer) : event.data; + // `stream: true` carries a UTF-8 multi-byte char's trailing bytes over + // to the next decode() call when a PTY chunk splits mid-character — + // without it, a Vietnamese/accented char split across two WS frames + // decodes to garbage on both sides (U+FFFD or stray bytes). + const data = isArrayBuffer + ? decoder.decode(event.data as ArrayBuffer, { stream: true }) + : event.data; if (typeof data === "string") { // A JSON control frame is the only thing that starts with `{"type"`. if (data.startsWith('{"type"')) { diff --git a/docs/LANES.md b/docs/LANES.md index 8610117e..a75137e7 100644 --- a/docs/LANES.md +++ b/docs/LANES.md @@ -780,12 +780,12 @@ a `detect` array on each node. The built-in `default.json` | Node | Detect rules | |---|---| -| `intake` | none — see below | -| `plan` | `Skill` matching `brainstorming\|writing-plans`; `Write` matching `docs/.*plan.*\.md` | -| `implement` | `Edit` (any); `Write` to any path NOT under a `docs/` directory (`^(?!.*(?:^\|/)docs/)`) | -| `tests` | `Bash` matching `\b(npm (run )?test\|pytest\|vitest\|jest\|go test\|cargo test\|node\s+--test)\b` | -| `review` | `Skill` matching `code-review\|requesting-code-review`; `Bash` matching `\bgh\b[^;&\|]*\bpr diff\b` | -| `gate` | none — see below | +| `intake` | `Agent` matching subagent `Explore` — a lane sitting at nothing else yet, but already exploring, shows *something* instead of idle | +| `plan` | `Skill` matching `brainstorming\|writing-plans`; `Write` matching `docs/.*plan.*\.md`; `Agent` matching `Plan\|Software Architect\|Backend Architect\|UX Architect`; `TaskCreate` (any) | +| `implement` | `Skill` matching `executing-plans\|subagent-driven-development\|test-driven-development`; `Edit` (any); `Write` to any path NOT under a `docs/` directory (`^(?!.*(?:^\|/)docs/)`) | +| `tests` | `Bash` matching `\b(npm (run )?test\|pytest\|vitest\|jest\|go test\|cargo test\|node\s+--test\|rspec\|phpunit\|dotnet test\|mvn test\|gradle test)\b`; `Agent` matching `Test Results Analyzer\|API Tester\|Performance Benchmarker` | +| `review` | `Skill` matching `code-review\|requesting-code-review\|receiving-code-review`; `Bash` matching `\bgh\b[^;&\|]*\bpr diff\b`; `Agent` matching a name containing `reviewer`/`Reviewer` (excluding `senior-gate-reviewer`, a lookbehind), `Evidence Collector`, or `Accessibility Auditor` | +| `gate` | `Skill` matching `verification-before-completion`; `Agent` matching `senior-gate-reviewer\|Reality Checker` — entering the gate, never its verdict | | `ship` | `Bash` matching a `git push` **invocation** — `git`, then option groups, then `push` as the first non-flag token — or `gh … pr create` | | `done` | none — see below | @@ -820,14 +820,14 @@ actually runs: | Node | Detect rules | |---|---| -| `intake` | `Skill` matching `brainstorming`; `Bash` matching `ccam … feature activate` | -| `plan` | `Skill` matching `writing-plans`; `Write` matching `docs/superpowers/specs/lane-.*\.md` | +| `intake` | `Skill` matching `brainstorming`; `Bash` matching `ccam … feature activate`; `Agent` matching subagent `Explore` | +| `plan` | `Skill` matching `writing-plans`; `Write` matching `docs/superpowers/specs/lane-.*\.md`; `Agent` matching `Plan\|Software Architect`; `TaskCreate` (any) | | `implementing` | `Skill` matching `test-driven-development\|executing-plans\|subagent-driven-development\|systematic-debugging`; `Edit`/`Write` to any path NOT under `docs/superpowers/specs/` (resp. not under `docs/`) | | `gates` | `Bash` matching `ccam … hook ci-gate` or `ccam … sync-base --check` | | `e2e-feature` | `Bash` matching `ccam … up --qc` or `ccam … hook e2e` | -| `review` | `Skill` matching `code-review\|requesting-code-review\|receiving-code-review` | -| `qc` | `Agent` matching `qc-local`; `Bash` matching `ccam … lanes proof-link` | -| `gate` | `Agent` matching `senior-gate-reviewer`; `Skill` matching `verification-before-completion` | +| `review` | `Skill` matching `code-review\|requesting-code-review\|receiving-code-review`; `Agent` matching a name containing `reviewer`/`Reviewer` (excluding `senior-gate-reviewer`, a lookbehind) | +| `qc` | `Agent` matching `qc-local\|Evidence Collector\|Accessibility Auditor`; `Bash` matching `ccam … lanes proof-link` | +| `gate` | `Agent` matching `senior-gate-reviewer\|Reality Checker`; `Skill` matching `verification-before-completion` | | `publishing` | `Skill` matching `finishing-a-development-branch`; `Bash` matching a `git push` **invocation** (the same pattern `default.json`'s `ship` uses) | | `pr-open` | `Bash` matching `gh … pr create` | | `watching-pr` | `Bash` matching `gh … pr view` | @@ -849,13 +849,14 @@ forward-only AND never overrides a higher declared stage, a fix-loop re-entry that drops back to `gates` is invisible to detection — only the skill's `ccam stage gates` moves the lane back down. -`intake`, `gate`, and `done` in `default.json` deliberately have no rules. -`intake` is where a lane starts — there is no tool event that means "just claimed," so there is -nothing to detect. `gate` and `done` are explicitly out of scope for -inference (see the task's "Out of scope" list): a gate's pass/fail is a human -or skill decision, and `done` is the one state detection must never reach on -its own — inferring either would let a heuristic assert an outcome instead of -observing activity. +`done` in `default.json` deliberately has no rule — it is explicitly out of +scope for inference (see the task's "Out of scope" list): `done` is the one +state detection must never reach on its own, since inferring it would let a +heuristic assert an outcome instead of observing activity. `intake` and `gate` +now carry entry-activity rules (an `Explore` agent, a `senior-gate-reviewer`/ +`Reality Checker` agent or `verification-before-completion` skill) — these +detect that the *activity* of exploring or gate-reviewing started, never a +gate's pass/fail verdict, so the "no outcome inference" rule still holds. To add a rule, drop a template into the directory named by the `DASHBOARD_PIPELINES_DIR` environment variable (see "Custom pipeline diff --git a/server/data/pipelines/default.json b/server/data/pipelines/default.json index cbb21a92..d11a84af 100644 --- a/server/data/pipelines/default.json +++ b/server/data/pipelines/default.json @@ -11,6 +11,12 @@ "assigned", "claimed", "start" + ], + "detect": [ + { + "tool": "Agent", + "match": "^Explore$" + } ] }, { @@ -31,6 +37,13 @@ { "tool": "Write", "match": "docs/.*plan.*\\.md" + }, + { + "tool": "Agent", + "match": "^Plan$|Software Architect|Backend Architect|UX Architect" + }, + { + "tool": "TaskCreate" } ] }, @@ -47,7 +60,7 @@ "detect": [ { "tool": "Skill", - "match": "executing-plans|subagent-driven-development" + "match": "executing-plans|subagent-driven-development|test-driven-development" }, { "tool": "Edit" @@ -72,7 +85,11 @@ "detect": [ { "tool": "Bash", - "match": "\\b(npm (run )?test|pytest|vitest|jest|go test|cargo test|node\\s+--test)\\b" + "match": "\\b(npm (run )?test|pytest|vitest|jest|go test|cargo test|node\\s+--test|rspec|phpunit|dotnet test|mvn test|gradle test)\\b" + }, + { + "tool": "Agent", + "match": "Test Results Analyzer|API Tester|Performance Benchmarker" } ] }, @@ -89,11 +106,15 @@ "detect": [ { "tool": "Skill", - "match": "code-review|requesting-code-review" + "match": "code-review|requesting-code-review|receiving-code-review" }, { "tool": "Bash", "match": "\\bgh\\b[^;&|]*\\bpr diff\\b" + }, + { + "tool": "Agent", + "match": "(?