From f0ae876472b598f35403e0cfc9780f83d1487f68 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Tue, 11 Aug 2026 12:15:16 +0700 Subject: [PATCH] feat(lanes): add pipeline-template picker to the Workspace lane header Lets a lane's pipeline template be switched live from the dashboard (the same PATCH /api/lanes/:id the CLI's `ccam lanes pipeline` uses), so lanes created before the picker shipped don't need the terminal. Both ship-feature skills now force their own template before their first `ccam stage` call, so the human never has to pick correctly at lane creation. --- .claude/skills/ship-feature-lane/SKILL.md | 1 + .claude/skills/ship-feature/SKILL.md | 7 +++ client/src/i18n/locales/en/lanes.json | 2 + client/src/i18n/locales/vi/lanes.json | 2 + client/src/pages/Workspace.tsx | 57 ++++++++++++++++++- .../pages/__tests__/Run.defaultCwd.test.tsx | 4 ++ client/src/pages/__tests__/Workspace.test.tsx | 9 +++ .../pages/__tests__/screens.snapshot.test.tsx | 1 + docs/LANES.md | 14 +++-- 9 files changed, 91 insertions(+), 6 deletions(-) diff --git a/.claude/skills/ship-feature-lane/SKILL.md b/.claude/skills/ship-feature-lane/SKILL.md index b178dfb..598069d 100644 --- a/.claude/skills/ship-feature-lane/SKILL.md +++ b/.claude/skills/ship-feature-lane/SKILL.md @@ -13,6 +13,7 @@ You are running the autonomous feature pipeline for **one CCAM lane**. The human LANE_DIR="$(pwd)" # the lane clone IS your cwd — CCAM resolves the lane from this, never a hardcoded path ``` - CCAM resolves your lane from `cwd` automatically (longest path-boundary prefix match) — there is no marker file to check and no separate assign step. If `ccam stage` or `ccam feature activate` ever fails with "no lane found", you are not inside a lane's working directory; stop and tell the human. +- **Pipeline template is this skill's contract, not whatever the lane started on.** Before your first `ccam stage` call, run `ccam lanes pipeline ship-feature` (no id needed — resolves from `cwd` like `ccam stage`; idempotent, a no-op if already set) so every stage below resolves against the 16-node `ship-feature` map. The human is never expected to pick this in Add Lane or anywhere else — you force it to match the skill actually running. - All stage updates go through `ccam stage [--status ] [--evidence "..."]` — **call it at the start of every stage** (this is also the heartbeat, visible on the dashboard). - **Integration toggles: check, don't assume.** `ccam lanes integration tracker`, `ccam lanes integration dev_qc`, `ccam lanes integration ci_wait` each exit 0 (on) or 1 (off), reading the profile's `integrations.env`. No agent exists yet to actually FILE a ticket or run dev-QC even when a toggle reads on (`ticketer`/`dev-qc` are a later task) — so regardless of the check's result, Stage 9 (ticket) stays skipped, Stage 13's dev-QC and dev-CI-wait halves stay skipped, and Stage 10's CI watch keeps using the plain `gh pr checks` path (`ccam ci` doesn't exist yet). Check the toggle where noted below anyway, so the evidence you record is honest about whether the PROFILE wants the integration on, distinct from whether CCAM can act on it yet. - **Heartbeat during long stages.** Implementing (Stage 1), CI waits (Stage 10), and the watch/post-merge polls (Stages 12–13) can run many minutes between stage transitions — bump the heartbeat with `ccam stage ` after each commit and on each poll iteration, so the dashboard doesn't false-flag a working lane as stalled. diff --git a/.claude/skills/ship-feature/SKILL.md b/.claude/skills/ship-feature/SKILL.md index d3293e9..e914d4b 100644 --- a/.claude/skills/ship-feature/SKILL.md +++ b/.claude/skills/ship-feature/SKILL.md @@ -19,6 +19,13 @@ Each phase below starts with `ccam stage `, which is what puts the phase on the lane's pipeline map. The nodes are the `default` template's: `intake → plan → implement → tests → review → gate → ship → done`. +Set the template yourself, don't rely on how the lane was created: before your +first `ccam stage` call, run `ccam lanes pipeline default` (no id needed — +resolves from `cwd`; a no-op if the lane is already on it). This is what makes +the choice invisible to the human — whichever of `ship-feature` or +`ship-feature-lane` actually runs is what decides the template, not a picker +they have to get right in advance. + `ccam stage` needs a lane owning the current directory. If it reports no lane, this repo was never adopted (`ccam lanes add --cwd $(pwd)` fixes it) — carry on with the workflow and skip the stage calls; they are reporting, not control diff --git a/client/src/i18n/locales/en/lanes.json b/client/src/i18n/locales/en/lanes.json index a5cec59..81f3019 100644 --- a/client/src/i18n/locales/en/lanes.json +++ b/client/src/i18n/locales/en/lanes.json @@ -94,6 +94,8 @@ "locks.held_one": "{{count}} lock held", "locks.held_other": "{{count}} locks held", "moreActions": "More actions", + "pipelinePicker.label": "Pipeline template", + "pipelinePicker.stageMismatch": "Stage \"{{stage}}\" matches no node in \"{{pipeline}}\" — declare one of: {{nodes}}", "preflightError": "Could not load the current lane facts.", "preflightErrorWithMessage": "Could not load the current lane facts: {{message}}", "runtime.boot": "▶ up", diff --git a/client/src/i18n/locales/vi/lanes.json b/client/src/i18n/locales/vi/lanes.json index 0f89052..1e8fb74 100644 --- a/client/src/i18n/locales/vi/lanes.json +++ b/client/src/i18n/locales/vi/lanes.json @@ -94,6 +94,8 @@ "locks.held_one": "Đang giữ {{count}} khóa", "locks.held_other": "Đang giữ {{count}} khóa", "moreActions": "Thêm hành động", + "pipelinePicker.label": "Mẫu pipeline", + "pipelinePicker.stageMismatch": "Stage \"{{stage}}\" không khớp node nào trong \"{{pipeline}}\" — khai báo một trong: {{nodes}}", "preflightError": "Không thể tải trạng thái làn đường hiện tại.", "preflightErrorWithMessage": "Không thể tải trạng thái làn đường hiện tại: {{message}}", "runtime.boot": "▶ chạy", diff --git a/client/src/pages/Workspace.tsx b/client/src/pages/Workspace.tsx index d5b6a9b..cd2189c 100644 --- a/client/src/pages/Workspace.tsx +++ b/client/src/pages/Workspace.tsx @@ -132,6 +132,9 @@ export function Workspace() { const [addLaneOpen, setAddLaneOpen] = useState(false); const [viewedFeatureSlug, setViewedFeatureSlug] = useState(null); const [features, setFeatures] = useState([]); + const [pipelineTemplates, setPipelineTemplates] = useState< + { id: string; name: string; nodes: { id: string }[] }[] + >([]); const [viewedFeature, setViewedFeature] = useState(null); const [proofFeatures, setProofFeatures] = useState([]); @@ -202,7 +205,10 @@ export function Workspace() { api.run .binary() .then(setBinaryStatus) - .catch(() => setBinaryStatus({ found: false, path: null })); + // Fetch failure (server unreachable, proxy misrouted, etc.) isn't proof + // `claude` is missing from PATH — leave the probe unresolved rather than + // showing a misleading "claude missing" banner for an unrelated fault. + .catch(() => undefined); api.run .list() .then(setActiveRuns) @@ -211,6 +217,10 @@ export function Workspace() { .history(50) .then((r) => setRunHistory(r.items)) .catch(() => undefined); + api.lanes + .pipelines() + .then((r) => setPipelineTemplates(r.pipelines)) + .catch(() => undefined); void refreshLanes(); api.run .cwds() @@ -856,6 +866,28 @@ export function Workspace() { } }; + // Mirrors `ccam lanes pipeline