feat(lanes): widen stage auto-detection, fix terminal UTF-8 decode

Add detect rules for plan/review/gate/tests/intake nodes (Agent subagent
matches, TaskCreate, broader test-runner regex) so more of the pipeline
lights up without an explicit ccam stage call, and fix a review/gate
detect overlap where senior-gate-reviewer also matched the generic
reviewer pattern. Also fix TerminalView's TextDecoder dropping the
`stream: true` flag, which corrupted multi-byte UTF-8 chars split across
WebSocket frames.
This commit is contained in:
2026-08-21 16:20:34 +07:00
parent 7c2992fdc8
commit 937c63361b
4 changed files with 77 additions and 24 deletions
+7 -1
View File
@@ -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"')) {
+19 -18
View File
@@ -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
+34 -3
View File
@@ -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": "(?<!senior-gate-)[Rr]eviewer|Evidence Collector|Accessibility Auditor"
}
]
},
@@ -107,6 +128,16 @@
"verification",
"sr-gate",
"gate-blocked"
],
"detect": [
{
"tool": "Skill",
"match": "verification-before-completion"
},
{
"tool": "Agent",
"match": "senior-gate-reviewer|Reality Checker"
}
]
},
{
+17 -2
View File
@@ -21,6 +21,10 @@
{
"tool": "Bash",
"match": "\\bccam\\b[^;&|]*\\bfeature activate\\b"
},
{
"tool": "Agent",
"match": "^Explore$"
}
]
},
@@ -42,6 +46,13 @@
{
"tool": "Write",
"match": "docs/superpowers/specs/lane-.*\\.md"
},
{
"tool": "Agent",
"match": "^Plan$|Software Architect"
},
{
"tool": "TaskCreate"
}
]
},
@@ -129,6 +140,10 @@
{
"tool": "Skill",
"match": "code-review|requesting-code-review|receiving-code-review"
},
{
"tool": "Agent",
"match": "(?<!senior-gate-)[Rr]eviewer"
}
]
},
@@ -148,7 +163,7 @@
"detect": [
{
"tool": "Agent",
"match": "qc-local"
"match": "qc-local|Evidence Collector|Accessibility Auditor"
},
{
"tool": "Bash",
@@ -170,7 +185,7 @@
"detect": [
{
"tool": "Agent",
"match": "senior-gate-reviewer"
"match": "senior-gate-reviewer|Reality Checker"
},
{
"tool": "Skill",