[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XCSDAB6RVN5038XRJ4F4FB":3,"skill-same:01M6XCSDAB6RVN5038XRJ4F4FB":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":56,"repositoryHtmlUrl":57,"repositoryHomepageUrl":58,"ownerHtmlUrl":59,"ownerBlogUrl":60,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M6XCSDAB6RVN5038XRJ4F4FB","canary-lab",null,"Use when the user wants Canary Lab to take a product repo end to end — \"test this app\", \"onboard this repo\", \"run a flight\", \"evaluate what I just built\" — through the flight pipeline (start_flight \u002F get_flight \u002F respond_flight_checkpoint over MCP). One conducted pipeline continues a configured suite or takes bare repo(s) to a green, covered, healed run and publishes a downloadable Report; Parallel setup continues as Canary-owned background work. Use a focused skill only when the user asks for that single capability without asking for a Flight or \u002Fcanary-lab.","ferterahadi\u002Fcanary-lab","ferterahadi","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F148174839?v=4","01M6XCHCY39C7QEF3JZJ3VZGNZ","Fer Terahadi","01M6XCHD1ZE9926NBC97ZMZZRY",4,"MIT","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fclaude\u002Fskills\u002Fcanary-lab\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"canary lab","canary","lab","get","when","bare","capability","suite","respond","app","end","MCP","green","take","only","work","pipeline","start","product","run","2026-11-10 10:12:30",false,"---\nname: canary-lab\ndescription: Use when the user wants Canary Lab to take a product repo end to end — \"test this app\", \"onboard this repo\", \"run a flight\", \"evaluate what I just built\" — through the flight pipeline (start_flight \u002F get_flight \u002F respond_flight_checkpoint over MCP). One conducted pipeline continues a configured suite or takes bare repo(s) to a green, covered, healed run and publishes a downloadable Report; Parallel setup continues as Canary-owned background work. Use a focused skill only when the user asks for that single capability without asking for a Flight or \u002Fcanary-lab.\ntype: skill\n---\n\n# Canary Lab — Flight\n\nAn explicit request for a Flight or `\u002Fcanary-lab` always means this pipeline. Do\nnot reinterpret its desired outcome (for example, \"drive coverage to 100%\") as\npermission to substitute focused author, run, or coverage workflows. For an\nexisting configured suite, call `start_flight(feature, coverage_target)`; Canary\nreads durable stage evidence and continues non-destructively from the first\nstage that does not meet the requested target.\n\n## User input through MCP 2.0\n\nUse the native Canary connector in the requesting chat so SDK 2.0 elicitation\n(`input_required`) reaches the human there. Do not substitute a shell MCP client,\nanswer a form yourself, or recreate the question in chat. Existing explicit\ninstructions and autopilot choices still apply without another ask.\n\nForm decisions have one shared record. The native form links to the same approval\nin Canary Notifications, also surfaced above the Flight page. On `needs-input`\nwith `approvalId` and `reviewUrl`, show that link and call `wait_for_approval` with\nthe ID; repeat on `still_waiting`. The human answers once in either surface.\nA browser answer resolves the server decision; some clients keep their native\nform visible until the human dismisses it. Dismissal then returns the stored\nresult. Client decline\u002Fcancel alone does not represent a human decision.\n\nWithout an `approvalId`, leave `needs-input` pending after decline\u002Fcancel, stale\ninput, or an unfinished UI action; do not automatically retry or repeat the\nquestion. Setup\u002Freconnection can use chat while MCP is unavailable. Never collect\npasswords, API keys, or access tokens in chat or form elicitation; use the returned\nCanary UI URL for secret entry.\n\n## MCP Invocation\n\nRead `coverageUpdate` in related tool replies: it is the suite's current\nfreshness, revision, owner and next required action. Canary's UI must update\nwithout a manual refresh. Stale\u002Funavailable\u002Fupdating coverage is historical,\nnot current; an old passing run cannot hide a newer failure. While actively\nmonitoring the suite, use `wait_for_feature_change(feature, afterRevision,\ntimeout_ms:30000)`; omit the revision to catch up after reconnecting. Tool replies\ndeliver this into the agent's context; an idle host is not promised an unsolicited\nwake-up. Respect active owners and launch permissions. Resume from the earliest\ninvalid stage, reuse valid prior stages, and re-read the ledger after recovery.\n\nSetup and the plugin expose one public Canary Lab MCP tool: `exec` (usually\nrendered as `mcp__Canary_Lab__exec`). Every\nCanary Lab tool name below is the exact `command` value, not a separate public\ntool. For a feature-scoped command, replace both placeholders in this shape:\n\n```json\n{\"command\":\"\u003Cexact_tool_name>\",\"arguments\":{\"feature\":\"\u003Cfeature_name>\"}}\n```\n\nThis is the envelope shape, not every command's complete schema. Add the\nfields that command declares inside `arguments`; call `describe_tool` when a\nfield is uncertain.\n\nNever invent a wrapper verb such as `learn` or `call`, embed JSON in a command\nstring, or turn arguments into flags. Keep fields such as `confirm: true` inside\n`arguments`. Use `list_tools`, `search_tools`, or `describe_tool` as the\n`command` when discovery is needed. A deliberately selected focused or `full`\nprofile still exposes atomic tools for debugging; the setup-installed path is\n`compact` + `exec`.\n\nThe server computes every stage verdict. **Start the flight with `stage_producer:\n\"external\"`** — the default for any MCP caller, so you get it even without passing it — so this client does every THINKING\nstep: scout, docs, PRD summary, spec and coverage authoring and mapping,\nrun healing, and localized evaluation export. The server still handles similarity,\nscaffolding, environment capture, the test run, Parallel setup. Pass `stage_producer: \"internal\"`\ninstead only when the user wants a hands-off flight, or when this client cannot do the\nwork (no file tools, no subagents, permission refused) — and note a single step can\ndegrade on its own by answering `run-internally` on its checkpoint, so a whole flight\nrarely needs to be internal. See *Doing the stage work yourself* below. The flight is Canary Lab's\nfront door: one command\u002Ftool takes\none or more bare product repos to a green, healed, covered run that ends in\nan evaluation export. These tools arrive via the Canary Lab MCP server. If\nthis client is already connected (the plugin connects with `compact`), skip\nthis step. To configure the same connection manually: `npx canary-lab mcp\n--profile compact`.\n\n## Workspace Bootstrap\n\nBefore calling Canary Lab MCP tools, make sure the workspace and UI server are available.\n\n1. Find the LIVE server first: read `~\u002F.canary-lab\u002Factive-servers.json`. It records `projectRoot`, `port` and `pid` for every UI that registered. A stopped server's entry LINGERS — the file is only rewritten when the next server registers — so an entry is a candidate, not proof: the health check in step 4 is what confirms it. One entry → that is your server and its `port`; several → take the one whose `projectRoot` is the workspace the user means. Do NOT start from a guessed port.\n2. Only when nothing live serves the workspace you want, fall back to the user-level registry at `~\u002F.canary-lab\u002Fworkspaces.json` (Windows: resolve from the home directory, e.g. `%USERPROFILE%\\.canary-lab\\workspaces.json`). Exactly one workspace → use it. Multiple → ask which, listing each `name` and `path`.\n3. If the registry is missing or empty, ask the user to run `npx canary-lab setup` from the Canary Lab workspace.\n4. Check the MCP health endpoint on the port you found in step 1. Only if no live entry gave you one, read `port` from the workspace's `canary-lab.config.json` (fallback `7421`). Then `curl -s http:\u002F\u002F127.0.0.1:\u003Cport>\u002Fmcp\u002Fhealth` — success is a JSON response. If it does not respond, run `npx canary-lab mcp doctor` to discover the active URL.\n5. A healthy response does **not** settle the question on its own — VERIFY `projectRoot` against the workspace you intended. A stale UI left behind by a demo or a tarball smoke test answers a port just as convincingly as the right one, and that is exactly how a flight ends up running in someone's throwaway workspace instead of theirs. `projectRoot` matches → continue, and tell the user which workspace you are in. It names a DIFFERENT workspace → this is the wrong server: go back to step 1, do not adopt it. It is under a temp directory (`\u002Ftmp`, `\u002Fprivate\u002Fvar\u002Ffolders`, `%TEMP%`) → never auto-select it; those are throwaway demo workspaces that vanish, so use one only when the user names it explicitly. Only when the user wants a workspace no live server is serving does anything need starting or stopping.\n6. If the health check fails, start `npx canary-lab ui` from the selected workspace in a visible long-running terminal when the host supports that; if this client cannot run long-lived commands, ask the user to run `npx canary-lab ui` from the workspace and confirm when it's up. The port comes from `canary-lab.config.json` (default `7421`); do not pass `--port` (it was removed).\n7. Resolve this skill's ARGUMENTS against that workspace before calling any tool. A bare name — `flight-app` — is a directory in the workspace root (`\u003Cworkspace>\u002Fflight-app`); it is not a suite name and not a repo to hunt for elsewhere on the machine. The Getting Started guide emits exactly that shape, with the intent string quoted beside it, so pass the resolved path as `repoPaths` and that string as `description`. An absolute path is used as given. If the named directory is not in the workspace, say so and ask — never substitute a similarly-named repo from somewhere else, and never invent the intent.\n8. A healthy `\u002Fmcp\u002Fhealth` means the server is live. On the setup-installed `compact` profile, atomic names such as `get_feature_coverage` are deliberately absent from `tools\u002Flist`; only `exec` is public. Call `exec` with `{\"command\":\"list_tools\",\"arguments\":{}}` before concluding the connection is missing. Only an unknown-tool error for `exec` means this session is not connected — ask the user to run `npx canary-lab setup --force` and reconnect\u002Frestart the client, then retry. Never drive `\u002Fmcp` with a hand-written HTTP\u002FJSON-RPC client (curl included; the health check above is the only direct HTTP use): a custom client bypasses client detection and reconnect handling.\n\n## Flight (end-to-end pipeline)\n\nIf `start_flight` returns `type: \"getting_started_busy\"`, a Getting Started demo already owns the workspace. Follow the returned active target (a run, Flight, coverage job, draft, portify, or export) in its current owner; do not start another workflow.\n\n1. `start_flight(repoPaths, description)` runs ONE background pipeline from bare repo(s) to a green, covered, healed run that publishes a downloadable Report before Parallel setup (similarity → scout → scaffold → env → docs → PRD → Tests & coverage → Test run → Auto-repair → Report → Parallel setup). Large apps therefore produce both harness evidence and the Report without first waiting for port-injection work. Report completion ends the foreground user journey: as soon as `get_flight` exposes `links.evaluationZip`, tell the user and end your turn while Canary continues Parallel setup as persistent background work. The server conducts every stage and computes every verdict — you only approve checkpoints; do not run the stages yourself or start a separate run\u002Fcoverage pass alongside an active flight. A flight started with `stage_producer: \"external\"` — the default for any MCP caller, so you get it even without passing it — instead asks you to execute the thinking steps — scout \u002F docs \u002F prd-summary \u002F specs↔coverage \u002F the run's heal loop \u002F a localized export — in-band via `external-work` checkpoints (see *Doing the stage work yourself*). Pass a stable `session_id` and useful `conversation_name` so the UI identifies this external agent session; use the client's real session id when available. Pass `external_session_url` only when it opens this exact conversation. Do not pass `client_kind` — the MCP bridge detects Claude\u002FCodex from the connection.\n   A Parallel setup workflow already started independently from the Flight page is adopted when the conductor reaches that stage; the Flight never starts a duplicate.\n2. Follow with `get_flight(flightId)` and do what its `next:` field says. On a human `waiting-for-approval` checkpoint with no answer yet, call `respond_flight_checkpoint(flightId)` without a choice to request elicitation. It applies the answer to the reviewed checkpoint. For an already-authorized response, call it with `choice` (one of `checkpoint.options`), `values` (a missing-env KEY→value map), or `data` (`{ configSource }` for config-approval — the feature is already scaffolded, so this writes through to its REAL on-disk feature.config.cjs; `redraft` re-runs the repo scan). Under autopilot (the default) the terminal `export-mode` checkpoint answers itself — `raw` for an internal flight, `localized` for one started with `stage_producer: \"external\"` (the rewrite is thinking, so an external flight does it externally by default; the localized rewrite then arrives as an `external-work` hand-off). A flight started with `autopilot: false` parks there to pick `raw` (fast report) vs `localized` (agent-rewritten reasoning) either way. A checkpoint payload over the inline budget is omitted from the tool result — review it in the web UI flight view, then respond here. An `external-work` payload is the exception: it degrades to `promptPath` (plus `promptOmitted: true`) rather than being dropped, because the payload IS the task — `Read` that file for the full prompt.\n3. Autopilot is ON by default: checkpoints with a safe default answer themselves (config-approval→approve, prd-source→continue when requirement docs already exist and collect-repo-docs when none do, coverage-stuck→accept-partial, portify-gate→run, portify-apply→apply, run-failed→export-as-is, export-mode→raw — or localized when the flight's stage producer is external), each logged `[autopilot]` on its stage. The flight still parks on `similarity-choice` and `missing-env` (no safe default) and on any RE-parked checkpoint — including a `prd-source` whose collector came back empty. A stage you explicitly RE-ENTER (`from_stage` \u002F `redo`) always parks its FIRST checkpoint even under autopilot — choosing to re-run a step IS the intent to answer it differently. Start with `autopilot: false` when you plan to distill THIS conversation into requirement docs at the `prd-source` stop: add them with `write_feature_doc` — distilled from this conversation (`content`) or linking a local file (`link_path`, symlinked so the user's original stays live) — then respond `continue`. Write the docs FIRST: `continue` only appears in `checkpoint.options` once requirement docs exist on disk, so answering it on a doc-less park is rejected rather than accepted empty. Or have Canary's agent gather the docs guided by the flight's frozen intent: respond `collect-repo-docs` (copies in repo docs relevant to the intent) or `infer-from-diff` (derives requirements from the branch diff vs base) — an optional `feedback` string on respond_flight_checkpoint rides a retry into the agent's prompt. If the checkpoint's `data.lastAttempt` is present, a previous gather already came back EMPTY (`outcome`: `empty` | `no-output` | `no-diff`, with the agent's own `reason`) — do NOT simply repeat that same choice; the material is not in these repos. Supply the docs yourself, or re-run the agent only with `feedback` naming what it missed, or after the user points the flight at different repos. A parked `portify-gate` is the Parallel setup ask after Report and BEFORE any Portify agent\u002Fdouble-boot cost: `run` starts the portify workflow (declared native port injection or a sibling feature's saved overlay is verified first — the agent only runs if verification fails), `skip` keeps the feature serial and the flight continues. A parked `portify-apply` is a verified-diff review: `apply` saves the overlay (nothing lands in the product repos — runs apply it into throwaway per-run worktrees), `revise` REQUIRES `feedback: \"\u003Cwhat to change>\"` and re-runs the agent + double-boot re-verify (the checkpoint re-parks with the new diff), `cancel` discards the edits and SKIPS the stage — the flight continues without Parallel setup (the feature stays serial; a later flight can retry).\n4. ONE flight record per feature — never a silent second manifest. Re-calling `start_flight` follows an active flight and resumes a paused one from its first open stage; a settled one requires `redo: true` (restart from stage 1) or `from_stage: \"\u003Cstage>\"` (jump to a chosen stage — prerequisites are checked and a rejection names the missing artifact, e.g. no specs yet). **An explicit restart resets only affected stage artifacts.** Tests & coverage and Test run keep Parallel setup while resetting their downstream run and current Report stage. `from_stage: \"portify\"` resets Parallel setup but keeps the run and downloadable Report. `from_stage: \"evaluation-export\"` builds a new Report and keeps completed archives in history. A full `redo: true` resets all stages. Plain resume (no flag) never wipes; warn the user before any other `redo`\u002F`from_stage` when they may still want a step's artifacts. The CLI equivalents are `--redo` and `--from-stage \u003Ckey>`. `fresh: true` skips resuming a paused flight and starts over instead. `yolo: true` skips every checkpoint except missing env secrets (export defaults to raw — localized, delivered as an `external-work` hand-off, when the stage producer is external: yolo skips asks, never work delivery); `autopilot: false` parks at every checkpoint instead of self-answering the safe ones. `agent: \"claude\" | \"codex\"` picks which CLI conducts the flight's stage agents — sticky per record (jump\u002Fcontinue reuse the stored one; only `redo: true` may change it; the run stage's auto-heal follows the workspace heal setting instead).\n5. A flight's **repos and intent are frozen against MID-PIPELINE re-entry**. On `from_stage` \u002F resume, **OMIT `repoPaths` and `description`** — the stored values are reused; passing DIFFERENT ones is rejected with `type: \"flight_frozen\"`. A full restart (`redo: true`) discards every stage's evidence and artifacts, so THERE new values are accepted: pass new `repoPaths`\u002F`description` to start fresh and they replace the stored ones (omit them to reuse). Deleting a flight (web UI only — no tool or CLI flag) remains for removing the record itself, not for changing inputs. Whenever you re-enter a stage BECAUSE something was wrong, pass `feedback: \"\u003Cwhat went wrong>\"` alongside `redo`\u002F`from_stage` — it is appended to the ENTRY stage's agent prompt. Without it the re-run has no idea the last attempt was rejected and produces the same thing again.\n6. A flight parked `status: \"paused\"`, `pauseReason: \"queued\"` is **waiting its turn** behind another flight on the same repo(s) (a broad intent split into per-feature flights runs them sequentially). It **starts automatically** when that repo frees — narrate it as queued, not stuck, and do NOT ask the user to resume it. Only if they want it started early, re-calling `start_flight` resumes it now. (`stage-failed` and `restart` are the pauses a re-call picks up. `pauseReason: \"user\"` is different in kind: the user pressed Stop and Canary stopped that flight's live work — spawned agents, its run, a portify workflow, an export. Do NOT resume it unprompted; report it as stopped and let them ask. If you were mid `external-work` for it, discard that result rather than submitting it.)\n7. **When the user says stop, stop it — do not just stop polling.** `pause_flight(flightId)` is the resumable stop: it ends the flight's live work (the stage's spawned agent, its test run including a repair in progress, a portify workflow, an export) and returns only once that work is actually stopped, keeping every stage's evidence — a later `start_flight` on the same repos resumes from the first open stage. `abort_flight(flightId, confirm: true)` is terminal: same teardown, no resume, and a queued sibling on the same repo(s) may start once the repos free. Prefer pause unless they want the flight abandoned. A verified portify review awaiting an answer survives a pause deliberately. Abandoning a flight by simply ceasing to `get_flight` leaves the whole pipeline running. `stop_flight_agent(flightId, confirm: true)` is the narrow option: it kills only the agent the CURRENT stage is running. Be honest with the user about what that does — the flight is waiting on that stage, so the attempt FAILS and the flight parks `stage-failed`; it does not carry on. What it buys over a pause is that the test run and the export are left alone, the stage keeps its error instead of resetting to not-started, and a queued sibling flight on the same repo(s) is released. Reach for it when one agent is misbehaving and everything else should stay up; reach for `pause_flight` when the user wants the flight held.\n8. As soon as `links.evaluationZip` appears — including while status is still `running` on Parallel setup — point the user at opening the Report (video playback where the tests drive a browser, plus the per-test reasoning in `evaluation.html`) and end your turn. Reviewing that export IS the core Canary Lab loop, not an optional extra. Canary owns Parallel setup as persistent background work; do not keep polling merely to hold the conversation open. The Flight page stays live, and a later `get_flight` can check it when the user asks.\n\nA flight can be STARTED from the CLI (`npx canary-lab flight \u003Crepo...> \"\u003Cwhat to test>\"`), the web UI (Flights pill), or this MCP surface — one store, so progress shows everywhere. But a flight is driven by whoever started it: one you start over MCP defaults to `stage_producer: \"external\"`, and the web UI is then **read-only** for it. Its Respond, Pause, Continue and autopilot controls are disabled and point back at you; Abort stays live for when this session dies. During an `external-work` hand-off the UI also offers **Request takeover**: that does not race you with a local agent — it records the request and waits for you to release this one step. A returned, signed URL-mode elicitation is the narrow exception: it enables input only for that exact checkpoint, without transferring ownership. Never send the user to ordinary disabled controls; pause and resume still belong to the driver.\n\n## Doing the stage work yourself\n\nOnly relevant when the flight was started with `stage_producer: \"external\"`\n(sticky per record, like `agent`; a GUI-started flight is always internal\nbecause there is no client to hand work to). Six steps then park on an\n`external-work` checkpoint instead of spawning Canary's own CLI: **scout**\n(survey the repos, draft the feature config), **docs** (gather or infer the\nrequirement docs — request elicitation first; see the docs bullet below), **prd-summary** (distill the docs into testable\nrequirements), **specs↔coverage** (author the spec files, then map them\nonto the requirements — authoring followed by mapping of changed or previously unexamined tests: the\nmapping park asks for `{ mappings[], unmappable[] }` and every test in its\npinned roster must appear in one of them, exactly like\n`submit_external_coverage`), **run\u002Fheal** (the flight\nstarts the run in external-heal mode UNCLAIMED and parks: `claim_heal` with\nyour OWN session id, loop `wait_for_heal_task`, fix APP code — never weaken a\ntest — `signal_run` after each fix, and respond `submit` once the run is\nterminal; a failed run is a valid terminal answer and Canary reads the verdict\nfrom the run record itself — never `abort_run` to escape), and\n**evaluation-export**\n(only when the mode is `localized` — the default for an external flight;\nanswer with `{ slots: [...] }` or the full `{ cases: [...] }` envelope the\nprompt describes, and Canary renders the final evaluation.html itself, status\npreserved).\n\n- Boot verification overlaps docs and requirement preparation after scaffold. Tests\n  wait for both. Only one checkpoint is answerable; pause and abort stop both owners.\n- A docs collector also returns `{ requirements[], variantDimension? } in that\n  handoff, as its prompt specifies. Canary pins this draft to the accepted docs\n  and validates it in the summary stage; a separate summary handoff is needed\n  only when the draft is missing, invalid, or stale.\n- Mapping reuses previously examined test\u002Frequirement pairs, including unmappable\n  answers, while their source, helpers, configuration and requirements stay\n  unchanged. Account for every test in the supplied roster; do not add omitted\n  tests. Coverage is still recomputed from real tags and test evidence.\n\n- `checkpoint.data.prompt` is the task, rendered exactly as Canary's own agent\n  would have received it — fan-out rule included. Split the reading across your\n  own subagents where the prompt says to. If the payload was oversized, `Read`\n  `checkpoint.data.promptPath` instead.\n- A Flight created before Parallel setup became server-owned may still expose\n  `external-work` with `checkpoint.data.stage: \"portify\"` after its Report\n  exists. Do not perform that legacy hand-off. Release it immediately with\n  `choice: \"run-internally\"`, tell the user where `links.evaluationZip` is,\n  and end your turn while Canary continues it in the background.\n- **The `docs` hand-off: discover authorized documents before asking.** Search the\n  flight's repositories and explicit user references, using the frozen intent as\n  the relevance filter. Read contents; use clearly relevant, compatible sources\n  automatically and preserve prior source choices. Never choose by a confidence\n  percentage or infer requirements from code\u002Ftests without authorization.\n  `respond_flight_checkpoint(flightId)` returns discovery instructions when no\n  documents exist. Return `document_resolution: {status: \"resolved\", searched,\n  sources: [{path, sha256, reason}]}` to validate\u002Fimport selected sources.\n  Missing material uses `{status: \"missing\", searched, reason}`; ambiguity\u002Fconflicts\n  use that status plus `searched`, `question`, and `candidates: [{label, sources}]`\n  (1–5 for ambiguity, 2–5 for conflicts).\n  Pass this without `choice` to elicit only the unresolved issue through MCP 2.0.\n  A collector's recorded empty search already establishes that discovery ran.\n  Follow `document_source: \"form\"` or `\"upload\"` when the user chooses to supply\n  material. Only unsupported clients ask the focused question in chat.\n  Use only the selected documents, write the current handoff's required output,\n  and submit with its `handOffId`. Never start a separate coverage job. If the\n  user already chose `infer-from-diff`, follow that authorized handoff directly.\n- Do the work with your own tools, writing to the real paths the prompt names.\n  Then release it: `respond_flight_checkpoint(flightId, choice: \"submit\", data: \u003Cthe shape the prompt asks for>, token: checkpoint.data.handOffId)`.\n- **Pass `token`, and re-check before you submit.** `checkpoint.data.handOffId`\n  identifies the hand-off you were given. These steps run for many minutes and\n  nothing can interrupt you mid-turn, so re-call `get_flight(flightId)` between\n  fan-out rounds, roughly every 10 minutes, and **always immediately before\n  submitting**. If the status is no longer `waiting-for-approval`, or the\n  `handOffId` has changed, the user stopped or re-asked this step: discard your\n  result, do not submit, and tell them. A submit carrying a superseded id is\n  discarded server-side and the step re-parks with\n  `data.lastRejection: \"stale_submission\"` — work you did after that point was\n  never going to count.\n- **A takeover request means stop and release, never submit.** If a re-check\n  carries `checkpoint.data.takeoverRequestedAt`, or a submit returns\n  `type: \"takeover_requested\"`, stop this work plus any subagents\u002Fprocesses you\n  started. Tell the user which files you already changed if they need to review\n  them, then acknowledge with\n  `respond_flight_checkpoint(flightId, choice: \"run-internally\")`. Canary starts\n  its local agent only after that acknowledgement. The UI can force takeover if\n  this session is gone; a later response from you is then rejected.\n- **A parked hand-off has no deadline — so do NOT end your turn while one is open unless elicitation returned `needs-input`.** The flight advances only when you submit: nothing polls it, no timeout rescues it, and a status update to the user is not progress. Keep working through the submit, then follow the flight to its next stage. A read that finds a hand-off with no client contact for 45+ minutes carries `handOffIdle` — that is a step somebody took and abandoned; it is still answerable against the same `handOffId`, so pick it up or tell the user the flight is stalled. If you must stop, say so in the same breath and tell them the flight stays parked until they re-invoke this skill.\n- A `type: \"flight_stopped\"` result means the flight is no longer waiting on you.\n  Discard what you were about to submit, do not retry it, and do not resume the\n  flight yourself — say it was stopped and let the user decide. Files you already\n  wrote stay on disk; if they resume, a fresh hand-off arrives with a new id.\n- **Canary re-validates independently, and its check is the verdict** — the\n  drafted config must parse, the requirement doc must exist on disk, the\n  submitted requirements are reconciled and re-read from the written summary, the specs\n  must compile, and a mapping must account for every roster test before Canary's own\n  tag-writer writes the tags and recomputes the ledger. Reporting success for work\n  that did not land on disk re-parks or fails the stage; it never passes. This is\n  the same bar Canary's own agent is held to, so there is nothing extra asked of\n  you here.\n- Can't do a step — no file tools, permission refused, wrong machine? Answer\n  `choice: \"run-internally\"` and Canary's local agent takes **that one step**.\n  The flight continues either way; nothing is lost.\n- The server-owned stages (similarity, scaffold, env, the run's own Playwright\n  execution, a `raw` export, Parallel setup) stay Canary's. Under an\n  external flight only the heal loop is driven with the `canary-lab-run` tools\n  FROM the flight's own hand-off checkpoint.\n\n## Follow loop\n\nUntil the Report exists, re-call `get_flight(flightId)` while `status` is\n`running` (wait ~20s between calls). Once `links.evaluationZip` appears,\nsurface it and end the foreground turn; Parallel setup does not need\nthis conversation held open. Before then, stop states are `done`, `failed`,\n`paused` (when `pauseReason` is not `queued`). On `waiting-for-approval`,\nrespond to the checkpoint instead of waiting.\n\n| Status | Action |\n| --- | --- |\n| `running` + `links.evaluationZip` | Tell the user the Report is ready, explain Parallel setup continues in Canary's background, and end your turn. Do not poll merely to hold it open. |\n| `running` | Wait ~20s, re-call `get_flight`. |\n| `waiting-for-approval` | Respond via `respond_flight_checkpoint`. |\n| `waiting-for-approval` + `checkpoint.data.takeoverRequestedAt` | Stop external work and release with `choice: \"run-internally\"`; do not submit. |\n| `paused`, `pauseReason: \"queued\"` | Narrate as waiting — do NOT resume it. |\n| `paused` + `attention.state: \"resolved\"` | The earlier failure is historical and current evidence satisfies that stage. Describe remaining work; do not resume automatically. |\n| `paused` + `attention.state: \"unavailable\"` | Current evidence could not be verified. Re-read `get_flight` before recommending recovery. |\n| `paused`, other `pauseReason` | Re-call `start_flight` (OMIT `repoPaths` + `description`) to resume. |\n| `paused` + `remedy` in the result | A stage is blocked by uncommitted repo changes. Help the user clean each listed repo — `git stash push -u` (undoable) or commit — then `start_flight` resumes and the stage retries. |\n| `done` | Point the user at `links.evaluationZip`. |\n| `failed` | Report the failure; a re-call to `start_flight` resumes from the failed stage. |\n\n## Guardrails\n\n- **On `from_stage` \u002F resume: OMIT `repoPaths` and `description`** — frozen against mid-pipeline re-entry. On `redo: true` you MAY pass new ones: a full restart replaces the stored inputs. Both `redo` and `from_stage` wipe the re-entered steps' on-disk artifacts (user-added docs included) — resume never does.\n- `start_flight` accepts `session_id`, `conversation_name`, and `external_session_url` for Activity provenance. Keep the same values on resume\u002Fredo\u002Fjump. The other Flight tools do not take them.\n- The flight conducts run\u002Fheal\u002Fcoverage\u002Fportify itself. While a flight is active, do not drive those stages with the focused skills — answer the flight's checkpoints instead.\n- Only when the user asks for one capability on its own, without asking for a Flight or `\u002Fcanary-lab`, use the matching focused skill: `canary-lab-run`, `canary-lab-verify`, `canary-lab-author`, `canary-lab-coverage`, `canary-lab-portify`, `canary-lab-export`.\n\nBroken document links are repaired before source discovery: the owning command elicits the moved file's new path on the Canary server. On `document-relinked`, retry that command with the same arguments. Keep the symlink and existing baseline; never omit the missing source or create recovery copies. Cancel\u002Fdecline leaves work pending. Unsupported clients use Relink in the returned Canary UI.\n","# Canary Lab — Flight\n\nAn explicit request for a Flight or `\u002Fcanary-lab` always means this pipeline. Do\nnot reinterpret its desired outcome (for example, \"drive coverage to 100%\") as\npermission to substitute focused author, run, or coverage workflows. For an\nexisting configured suite, call `start_flight(feature, coverage_target)`; Canary\nreads durable stage evidence and continues non-destructively from the first\nstage that does not meet the requested target.\n\n## User input through MCP 2.0\n\nUse the native Canary connector in the requesting chat so SDK 2.0 elicitation\n(`input_required`) reaches the human there. Do not substitute a shell MCP client,\nanswer a form yourself, or recreate the question in chat. Existing explicit\ninstructions and autopilot choices still apply without another ask.\n\nForm decisions have one shared record. The native form links to the same approval\nin Canary Notifications, also surfaced above the Flight page. On `needs-input`\nwith `approvalId` and `reviewUrl`, show that link and call `wait_for_approval` with\nthe ID; repeat on `still_waiting`. The human answers once in either surface.\nA browser answer resolves the server decision; some clients keep their native\nform visible until the human dismisses it. Dismissal then returns the stored\nresult. Client decline\u002Fcancel alone does not represent a human decision.\n\nWithout an `approvalId`, leave `needs-input` pending after decline\u002Fcancel, stale\ninput, or an unfinished UI action; do not automatically retry or repeat the\nquestion. Setup\u002Freconnection can use chat while MCP is unavailable. Never collect\npasswords, API keys, or access tokens in chat or form elicitation; use the returned\nCanary UI URL for secret entry.\n\n## MCP Invocation\n\nRead `coverageUpdate` in related tool replies: it is the suite's current\nfreshness, revision, owner and next required action. Canary's UI must update\nwithout a manual refresh. Stale\u002Funavailable\u002Fupdating coverage is historical,\nnot current; an old passing run cannot hide a newer failure. While actively\nmonitoring the suite, use `wait_for_feature_change(feature, afterRevision,\ntimeout_ms:30000)`; omit the revision to catch up after reconnecting. Tool replies\ndeliver this into the agent's context; an idle host is not promised an unsolicited\nwake-up. Respect active owners and launch permissions. Resume from the earliest\ninvalid stage, reuse valid prior stages, and re-read the ledger after recovery.\n\nSetup and the plugin expose one public Canary Lab MCP tool: `exec` (usually\nrendered as `mcp__Canary_Lab__exec`). Every\nCanary Lab tool name below is the exact `command` value, not a separate public\ntool. For a feature-scoped command, replace both placeholders in this shape:\n\n```json\n{\"command\":\"\u003Cexact_tool_name>\",\"arguments\":{\"feature\":\"\u003Cfeature_name>\"}}\n```\n\nThis is the envelope shape, not every command's complete schema. Add the\nfields that command declares inside `arguments`; call `describe_tool` when a\nfield is uncertain.\n\nNever invent a wrapper verb such as `learn` or `call`, embed JSON in a command\nstring, or turn arguments into flags. Keep fields such as `confirm: true` inside\n`arguments`. Use `list_tools`, `search_tools`, or `describe_tool` as the\n`command` when discovery is needed. A deliberately selected focused or `full`\nprofile still exposes atomic tools for debugging; the setup-installed path is\n`compact` + `exec`.\n\nThe server computes every stage verdict. **Start the flight with `stage_producer:\n\"external\"`** — the default for any MCP caller, so you get it even without passing it — so this client does every THINKING\nstep: scout, docs, PRD summary, spec and coverage authoring and mapping,\nrun healing, and localized evaluation export. The server still handles similarity,\nscaffolding, environment capture, the test run, Parallel setup. Pass `stage_producer: \"internal\"`\ninstead only when the user wants a hands-off flight, or when this client cannot do the\nwork (no file tools, no subagents, permission refused) — and note a single step can\ndegrade on its own by answering `run-internally` on its checkpoint, so a whole flight\nrarely needs to be internal. See *Doing the stage work yourself* below. The flight is Canary Lab's\nfront door: one command\u002Ftool takes\none or more bare product repos to a green, healed, covered run that ends in\nan evaluation export. These tools arrive via the Canary Lab MCP server. If\nthis client is already connected (the plugin connects with `compact`), skip\nthis step. To configure the same connection manually: `npx canary-lab mcp\n--profile compact`.\n\n## Workspace Bootstrap\n\nBefore calling Canary Lab MCP tools, make sure the workspace and UI server are available.\n\n1. Find the LIVE server first: read `~\u002F.canary-lab\u002Factive-servers.json`. It records `projectRoot`, `port` and `pid` for every UI that registered. A stopped server's entry LINGERS — the file is only rewritten when the next server registers — so an entry is a candidate, not proof: the health check in step 4 is what confirms it. One entry → that is your server and its `port`; several → take the one whose `projectRoot` is the workspace the user means. Do NOT start from a guessed port.\n2. Only when nothing live serves the workspace you want, fall back to the user-level registry at `~\u002F.canary-lab\u002Fworkspaces.json` (Windows: resolve from the home directory, e.g. `%USERPROFILE%\\.canary-lab\\workspaces.json`). Exactly one workspace → use it. Multiple → ask which, listing each `name` and `path`.\n3. If the registry is missing or empty, ask the user to run `npx canary-lab setup` from the Canary Lab workspace.\n4. Check the MCP health endpoint on the port you found in step 1. Only if no live entry gave you one, read `port` from the workspace's `canary-lab.config.json` (fallback `7421`). Then `curl -s http:\u002F\u002F127.0.0.1:\u003Cport>\u002Fmcp\u002Fhealth` — success is a JSON response. If it does not respond, run `npx canary-lab mcp doctor` to discover the active URL.\n5. A healthy response does **not** settle the question on its own — VERIFY `projectRoot` against the workspace you intended. A stale UI left behind by a demo or a tarball smoke test answers a port just as convincingly as the right one, and that is exactly how a flight ends up running in someone's throwaway workspace instead of theirs. `projectRoot` matches → continue, and tell the user which workspace you are in. It names a DIFFERENT workspace → this is the wrong server: go back to step 1, do not adopt it. It is under a temp directory (`\u002Ftmp`, `\u002Fprivate\u002Fvar\u002Ffolders`, `%TEMP%`) → never auto-select it; those are throwaway demo workspaces that vanish, so use one only when the user names it explicitly. Only when the user wants a workspace no live server is serving does anything need starting or stopping.\n6. If the health check fails, start `npx canary-lab ui` from the selected workspace in a visible long-running terminal when the host supports that; if this client cannot run long-lived commands, ask the user to run `npx canary-lab ui` from the workspace and confirm when it's up. The port comes from `canary-lab.config.json` (default `7421`); do not pass `--port` (it was removed).\n7. Resolve this skill's ARGUMENTS against that workspace before calling any tool. A bare name — `flight-app` — is a directory in the workspace root (`\u003Cworkspace>\u002Fflight-app`); it is not a suite name and not a repo to hunt for elsewhere on the machine. The Getting Started guide emits exactly that shape, with the intent string quoted beside it, so pass the resolved path as `repoPaths` and that string as `description`. An absolute path is used as given. If the named directory is not in the workspace, say so and ask — never substitute a similarly-named repo from somewhere else, and never invent the intent.\n8. A healthy `\u002Fmcp\u002Fhealth` means the server is live. On the setup-installed `compact` profile, atomic names such as `get_feature_coverage` are deliberately absent from `tools\u002Flist`; only `exec` is public. Call `exec` with `{\"command\":\"list_tools\",\"arguments\":{}}` before concluding the connection is missing. Only an unknown-tool error for `exec` means this session is not connected — ask the user to run `npx canary-lab setup --force` and reconnect\u002Frestart the client, then retry. Never drive `\u002Fmcp` with a hand-written HTTP\u002FJSON-RPC client (curl included; the health check above is the only direct HTTP use): a custom client bypasses client detection and reconnect handling.\n\n## Flight (end-to-end pipeline)\n\nIf `start_flight` returns `type: \"getting_started_busy\"`, a Getting Started demo already owns the workspace. Follow the returned active target (a run, Flight, coverage job, draft, portify, or export) in its current owner; do not start another workflow.\n\n1. `start_flight(repoPaths, description)` runs ONE background pipeline from bare repo(s) to a green, covered, healed run that publishes a downloadable Report before Parallel setup (similarity → scout → scaffold → env → docs → PRD → Tests & coverage → Test run → Auto-repair → Report → Parallel setup). Large apps therefore produce both harness evidence and the Report without first waiting for port-injection work. Report completion ends the foreground user journey: as soon as `get_flight` exposes `links.evaluationZip`, tell the user and end your turn while Canary continues Parallel setup as persistent background work. The server conducts every stage and computes every verdict — you only approve checkpoints; do not run the stages yourself or start a separate run\u002Fcoverage pass alongside an active flight. A flight started with `stage_producer: \"external\"` — the default for any MCP caller, so you get it even without passing it — instead asks you to execute the thinking steps — scout \u002F docs \u002F prd-summary \u002F specs↔coverage \u002F the run's heal loop \u002F a localized export — in-band via `external-work` checkpoints (see *Doing the stage work yourself*). Pass a stable `session_id` and useful `conversation_name` so the UI identifies this external agent session; use the client's real session id when available. Pass `external_session_url` only when it opens this exact conversation. Do not pass `client_kind` — the MCP bridge detects Claude\u002FCodex from the connection.\n   A Parallel setup workflow already started independently from the Flight page is adopted when the conductor reaches that stage; the Flight never starts a duplicate.\n2. Follow with `get_flight(flightId)` and do what its `next:` field says. On a human `waiting-for-approval` checkpoint with no answer yet, call `respond_flight_checkpoint(flightId)` without a choice to request elicitation. It applies the answer to the reviewed checkpoint. For an already-authorized response, call it with `choice` (one of `checkpoint.options`), `values` (a missing-env KEY→value map), or `data` (`{ configSource }` for config-approval — the feature is already scaffolded, so this writes through to its REAL on-disk feature.config.cjs; `redraft` re-runs the repo scan). Under autopilot (the default) the terminal `export-mode` checkpoint answers itself — `raw` for an internal flight, `localized` for one started with `stage_producer: \"external\"` (the rewrite is thinking, so an external flight does it externally by default; the localized rewrite then arrives as an `external-work` hand-off). A flight started with `autopilot: false` parks there to pick `raw` (fast report) vs `localized` (agent-rewritten reasoning) either way. A checkpoint payload over the inline budget is omitted from the tool result — review it in the web UI flight view, then respond here. An `external-work` payload is the exception: it degrades to `promptPath` (plus `promptOmitted: true`) rather than being dropped, because the payload IS the task — `Read` that file for the full prompt.\n3. Autopilot is ON by default: checkpoints with a safe default answer themselves (config-approval→approve, prd-source→continue when requirement docs already exist and collect-repo-docs when none do, coverage-stuck→accept-partial, portify-gate→run, portify-apply→apply, run-failed→export-as-is, export-mode→raw — or localized when the flight's stage producer is external), each logged `[autopilot]` on its stage. The flight still parks on `similarity-choice` and `missing-env` (no safe default) and on any RE-parked checkpoint — including a `prd-source` whose collector came back empty. A stage you explicitly RE-ENTER (`from_stage` \u002F `redo`) always parks its FIRST checkpoint even under autopilot — choosing to re-run a step IS the intent to answer it differently. Start with `autopilot: false` when you plan to distill THIS conversation into requirement docs at the `prd-source` stop: add them with `write_feature_doc` — distilled from this conversation (`content`) or linking a local file (`link_path`, symlinked so the user's original stays live) — then respond `continue`. Write the docs FIRST: `continue` only appears in `checkpoint.options` once requirement docs exist on disk, so answering it on a doc-less park is rejected rather than accepted empty. Or have Canary's agent gather the docs guided by the flight's frozen intent: respond `collect-repo-docs` (copies in repo docs relevant to the intent) or `infer-from-diff` (derives requirements from the branch diff vs base) — an optional `feedback` string on respond_flight_checkpoint rides a retry into the agent's prompt. If the checkpoint's `data.lastAttempt` is present, a previous gather already came back EMPTY (`outcome`: `empty` | `no-output` | `no-diff`, with the agent's own `reason`) — do NOT simply repeat that same choice; the material is not in these repos. Supply the docs yourself, or re-run the agent only with `feedback` naming what it missed, or after the user points the flight at different repos. A parked `portify-gate` is the Parallel setup ask after Report and BEFORE any Portify agent\u002Fdouble-boot cost: `run` starts the portify workflow (declared native port injection or a sibling feature's saved overlay is verified first — the agent only runs if verification fails), `skip` keeps the feature serial and the flight continues. A parked `portify-apply` is a verified-diff review: `apply` saves the overlay (nothing lands in the product repos — runs apply it into throwaway per-run worktrees), `revise` REQUIRES `feedback: \"\u003Cwhat to change>\"` and re-runs the agent + double-boot re-verify (the checkpoint re-parks with the new diff), `cancel` discards the edits and SKIPS the stage — the flight continues without Parallel setup (the feature stays serial; a later flight can retry).\n4. ONE flight record per feature — never a silent second manifest. Re-calling `start_flight` follows an active flight and resumes a paused one from its first open stage; a settled one requires `redo: true` (restart from stage 1) or `from_stage: \"\u003Cstage>\"` (jump to a chosen stage — prerequisites are checked and a rejection names the missing artifact, e.g. no specs yet). **An explicit restart resets only affected stage artifacts.** Tests & coverage and Test run keep Parallel setup while resetting their downstream run and current Report stage. `from_stage: \"portify\"` resets Parallel setup but keeps the run and downloadable Report. `from_stage: \"evaluation-export\"` builds a new Report and keeps completed archives in history. A full `redo: true` resets all stages. Plain resume (no flag) never wipes; warn the user before any other `redo`\u002F`from_stage` when they may still want a step's artifacts. The CLI equivalents are `--redo` and `--from-stage \u003Ckey>`. `fresh: true` skips resuming a paused flight and starts over instead. `yolo: true` skips every checkpoint except missing env secrets (export defaults to raw — localized, delivered as an `external-work` hand-off, when the stage producer is external: yolo skips asks, never work delivery); `autopilot: false` parks at every checkpoint instead of self-answering the safe ones. `agent: \"claude\" | \"codex\"` picks which CLI conducts the flight's stage agents — sticky per record (jump\u002Fcontinue reuse the stored one; only `redo: true` may change it; the run stage's auto-heal follows the workspace heal setting instead).\n5. A flight's **repos and intent are frozen against MID-PIPELINE re-entry**. On `from_stage` \u002F resume, **OMIT `repoPaths` and `description`** — the stored values are reused; passing DIFFERENT ones is rejected with `type: \"flight_frozen\"`. A full restart (`redo: true`) discards every stage's evidence and artifacts, so THERE new values are accepted: pass new `repoPaths`\u002F`description` to start fresh and they replace the stored ones (omit them to reuse). Deleting a flight (web UI only — no tool or CLI flag) remains for removing the record itself, not for changing inputs. Whenever you re-enter a stage BECAUSE something was wrong, pass `feedback: \"\u003Cwhat went wrong>\"` alongside `redo`\u002F`from_stage` — it is appended to the ENTRY stage's agent prompt. Without it the re-run has no idea the last attempt was rejected and produces the same thing again.\n6. A flight parked `status: \"paused\"`, `pauseReason: \"queued\"` is **waiting its turn** behind another flight on the same repo(s) (a broad intent split into per-feature flights runs them sequentially). It **starts automatically** when that repo frees — narrate it as queued, not stuck, and do NOT ask the user to resume it. Only if they want it started early, re-calling `start_flight` resumes it now. (`stage-failed` and `restart` are the pauses a re-call picks up. `pauseReason: \"user\"` is different in kind: the user pressed Stop and Canary stopped that flight's live work — spawned agents, its run, a portify workflow, an export. Do NOT resume it unprompted; report it as stopped and let them ask. If you were mid `external-work` for it, discard that result rather than submitting it.)\n7. **When the user says stop, stop it — do not just stop polling.** `pause_flight(flightId)` is the resumable stop: it ends the flight's live work (the stage's spawned agent, its test run including a repair in progress, a portify workflow, an export) and returns only once that work is actually stopped, keeping every stage's evidence — a later `start_flight` on the same repos resumes from the first open stage. `abort_flight(flightId, confirm: true)` is terminal: same teardown, no resume, and a queued sibling on the same repo(s) may start once the repos free. Prefer pause unless they want the flight abandoned. A verified portify review awaiting an answer survives a pause deliberately. Abandoning a flight by simply ceasing to `get_flight` leaves the whole pipeline running. `stop_flight_agent(flightId, confirm: true)` is the narrow option: it kills only the agent the CURRENT stage is running. Be honest with the user about what that does — the flight is waiting on that stage, so the attempt FAILS and the flight parks `stage-failed`; it does not carry on. What it buys over a pause is that the test run and the export are left alone, the stage keeps its error instead of resetting to not-started, and a queued sibling flight on the same repo(s) is released. Reach for it when one agent is misbehaving and everything else should stay up; reach for `pause_flight` when the user wants the flight held.\n8. As soon as `links.evaluationZip` appears — including while status is still `running` on Parallel setup — point the user at opening the Report (video playback where the tests drive a browser, plus the per-test reasoning in `evaluation.html`) and end your turn. Reviewing that export IS the core Canary Lab loop, not an optional extra. Canary owns Parallel setup as persistent background work; do not keep polling merely to hold the conversation open. The Flight page stays live, and a later `get_flight` can check it when the user asks.\n\nA flight can be STARTED from the CLI (`npx canary-lab flight \u003Crepo...> \"\u003Cwhat to test>\"`), the web UI (Flights pill), or this MCP surface — one store, so progress shows everywhere. But a flight is driven by whoever started it: one you start over MCP defaults to `stage_producer: \"external\"`, and the web UI is then **read-only** for it. Its Respond, Pause, Continue and autopilot controls are disabled and point back at you; Abort stays live for when this session dies. During an `external-work` hand-off the UI also offers **Request takeover**: that does not race you with a local agent — it records the request and waits for you to release this one step. A returned, signed URL-mode elicitation is the narrow exception: it enables input only for that exact checkpoint, without transferring ownership. Never send the user to ordinary disabled controls; pause and resume still belong to the driver.\n\n## Doing the stage work yourself\n\nOnly relevant when the flight was started with `stage_producer: \"external\"`\n(sticky per record, like `agent`; a GUI-started flight is always internal\nbecause there is no client to hand work to). Six steps then park on an\n`external-work` checkpoint instead of spawning Canary's own CLI: **scout**\n(survey the repos, draft the feature config), **docs** (gather or infer the\nrequirement docs — request elicitation first; see the docs bullet below), **prd-summary** (distill the docs into testable\nrequirements), **specs↔coverage** (author the spec files, then map them\nonto the requirements — authoring followed by mapping of changed or previously unexamined tests: the\nmapping park asks for `{ mappings[], unmappable[] }` and every test in its\npinned roster must appear in one of them, exactly like\n`submit_external_coverage`), **run\u002Fheal** (the flight\nstarts the run in external-heal mode UNCLAIMED and parks: `claim_heal` with\nyour OWN session id, loop `wait_for_heal_task`, fix APP code — never weaken a\ntest — `signal_run` after each fix, and respond `submit` once the run is\nterminal; a failed run is a valid terminal answer and Canary reads the verdict\nfrom the run record itself — never `abort_run` to escape), and\n**evaluation-export**\n(only when the mode is `localized` — the default for an external flight;\nanswer with `{ slots: [...] }` or the full `{ cases: [...] }` envelope the\nprompt describes, and Canary renders the final evaluation.html itself, status\npreserved).\n\n- Boot verification overlaps docs and requirement preparation after scaffold. Tests\n  wait for both. Only one checkpoint is answerable; pause and abort stop both owners.\n- A docs collector also returns `{ requirements[], variantDimension? } in that\n  handoff, as its prompt specifies. Canary pins this draft to the accepted docs\n  and validates it in the summary stage; a separate summary handoff is needed\n  only when the draft is missing, invalid, or stale.\n- Mapping reuses previously examined test\u002Frequirement pairs, including unmappable\n  answers, while their source, helpers, configuration and requirements stay\n  unchanged. Account for every test in the supplied roster; do not add omitted\n  tests. Coverage is still recomputed from real tags and test evidence.\n\n- `checkpoint.data.prompt` is the task, rendered exactly as Canary's own agent\n  would have received it — fan-out rule included. Split the reading across your\n  own subagents where the prompt says to. If the payload was oversized, `Read`\n  `checkpoint.data.promptPath` instead.\n- A Flight created before Parallel setup became server-owned may still expose\n  `external-work` with `checkpoint.data.stage: \"portify\"` after its Report\n  exists. Do not perform that legacy hand-off. Release it immediately with\n  `choice: \"run-internally\"`, tell the user where `links.evaluationZip` is,\n  and end your turn while Canary continues it in the background.\n- **The `docs` hand-off: discover authorized documents before asking.** Search the\n  flight's repositories and explicit user references, using the frozen intent as\n  the relevance filter. Read contents; use clearly relevant, compatible sources\n  automatically and preserve prior source choices. Never choose by a confidence\n  percentage or infer requirements from code\u002Ftests without authorization.\n  `respond_flight_checkpoint(flightId)` returns discovery instructions when no\n  documents exist. Return `document_resolution: {status: \"resolved\", searched,\n  sources: [{path, sha256, reason}]}` to validate\u002Fimport selected sources.\n  Missing material uses `{status: \"missing\", searched, reason}`; ambiguity\u002Fconflicts\n  use that status plus `searched`, `question`, and `candidates: [{label, sources}]`\n  (1–5 for ambiguity, 2–5 for conflicts).\n  Pass this without `choice` to elicit only the unresolved issue through MCP 2.0.\n  A collector's recorded empty search already establishes that discovery ran.\n  Follow `document_source: \"form\"` or `\"upload\"` when the user chooses to supply\n  material. Only unsupported clients ask the focused question in chat.\n  Use only the selected documents, write the current handoff's required output,\n  and submit with its `handOffId`. Never start a separate coverage job. If the\n  user already chose `infer-from-diff`, follow that authorized handoff directly.\n- Do the work with your own tools, writing to the real paths the prompt names.\n  Then release it: `respond_flight_checkpoint(flightId, choice: \"submit\", data: \u003Cthe shape the prompt asks for>, token: checkpoint.data.handOffId)`.\n- **Pass `token`, and re-check before you submit.** `checkpoint.data.handOffId`\n  identifies the hand-off you were given. These steps run for many minutes and\n  nothing can interrupt you mid-turn, so re-call `get_flight(flightId)` between\n  fan-out rounds, roughly every 10 minutes, and **always immediately before\n  submitting**. If the status is no longer `waiting-for-approval`, or the\n  `handOffId` has changed, the user stopped or re-asked this step: discard your\n  result, do not submit, and tell them. A submit carrying a superseded id is\n  discarded server-side and the step re-parks with\n  `data.lastRejection: \"stale_submission\"` — work you did after that point was\n  never going to count.\n- **A takeover request means stop and release, never submit.** If a re-check\n  carries `checkpoint.data.takeoverRequestedAt`, or a submit returns\n  `type: \"takeover_requested\"`, stop this work plus any subagents\u002Fprocesses you\n  started. Tell the user which files you already changed if they need to review\n  them, then acknowledge with\n  `respond_flight_checkpoint(flightId, choice: \"run-internally\")`. Canary starts\n  its local agent only after that acknowledgement. The UI can force takeover if\n  this session is gone; a later response from you is then rejected.\n- **A parked hand-off has no deadline — so do NOT end your turn while one is open unless elicitation returned `needs-input`.** The flight advances only when you submit: nothing polls it, no timeout rescues it, and a status update to the user is not progress. Keep working through the submit, then follow the flight to its next stage. A read that finds a hand-off with no client contact for 45+ minutes carries `handOffIdle` — that is a step somebody took and abandoned; it is still answerable against the same `handOffId`, so pick it up or tell the user the flight is stalled. If you must stop, say so in the same breath and tell them the flight stays parked until they re-invoke this skill.\n- A `type: \"flight_stopped\"` result means the flight is no longer waiting on you.\n  Discard what you were about to submit, do not retry it, and do not resume the\n  flight yourself — say it was stopped and let the user decide. Files you already\n  wrote stay on disk; if they resume, a fresh hand-off arrives with a new id.\n- **Canary re-validates independently, and its check is the verdict** — the\n  drafted config must parse, the requirement doc must exist on disk, the\n  submitted requirements are reconciled and re-read from the written summary, the specs\n  must compile, and a mapping must account for every roster test before Canary's own\n  tag-writer writes the tags and recomputes the ledger. Reporting success for work\n  that did not land on disk re-parks or fails the stage; it never passes. This is\n  the same bar Canary's own agent is held to, so there is nothing extra asked of\n  you here.\n- Can't do a step — no file tools, permission refused, wrong machine? Answer\n  `choice: \"run-internally\"` and Canary's local agent takes **that one step**.\n  The flight continues either way; nothing is lost.\n- The server-owned stages (similarity, scaffold, env, the run's own Playwright\n  execution, a `raw` export, Parallel setup) stay Canary's. Under an\n  external flight only the heal loop is driven with the `canary-lab-run` tools\n  FROM the flight's own hand-off checkpoint.\n\n## Follow loop\n\nUntil the Report exists, re-call `get_flight(flightId)` while `status` is\n`running` (wait ~20s between calls). Once `links.evaluationZip` appears,\nsurface it and end the foreground turn; Parallel setup does not need\nthis conversation held open. Before then, stop states are `done`, `failed`,\n`paused` (when `pauseReason` is not `queued`). On `waiting-for-approval`,\nrespond to the checkpoint instead of waiting.\n\n| Status | Action |\n| --- | --- |\n| `running` + `links.evaluationZip` | Tell the user the Report is ready, explain Parallel setup continues in Canary's background, and end your turn. Do not poll merely to hold it open. |\n| `running` | Wait ~20s, re-call `get_flight`. |\n| `waiting-for-approval` | Respond via `respond_flight_checkpoint`. |\n| `waiting-for-approval` + `checkpoint.data.takeoverRequestedAt` | Stop external work and release with `choice: \"run-internally\"`; do not submit. |\n| `paused`, `pauseReason: \"queued\"` | Narrate as waiting — do NOT resume it. |\n| `paused` + `attention.state: \"resolved\"` | The earlier failure is historical and current evidence satisfies that stage. Describe remaining work; do not resume automatically. |\n| `paused` + `attention.state: \"unavailable\"` | Current evidence could not be verified. Re-read `get_flight` before recommending recovery. |\n| `paused`, other `pauseReason` | Re-call `start_flight` (OMIT `repoPaths` + `description`) to resume. |\n| `paused` + `remedy` in the result | A stage is blocked by uncommitted repo changes. Help the user clean each listed repo — `git stash push -u` (undoable) or commit — then `start_flight` resumes and the stage retries. |\n| `done` | Point the user at `links.evaluationZip`. |\n| `failed` | Report the failure; a re-call to `start_flight` resumes from the failed stage. |\n\n## Guardrails\n\n- **On `from_stage` \u002F resume: OMIT `repoPaths` and `description`** — frozen against mid-pipeline re-entry. On `redo: true` you MAY pass new ones: a full restart replaces the stored inputs. Both `redo` and `from_stage` wipe the re-entered steps' on-disk artifacts (user-added docs included) — resume never does.\n- `start_flight` accepts `session_id`, `conversation_name`, and `external_session_url` for Activity provenance. Keep the same values on resume\u002Fredo\u002Fjump. The other Flight tools do not take them.\n- The flight conducts run\u002Fheal\u002Fcoverage\u002Fportify itself. While a flight is active, do not drive those stages with the focused skills — answer the flight's checkpoints instead.\n- Only when the user asks for one capability on its own, without asking for a Flight or `\u002Fcanary-lab`, use the matching focused skill: `canary-lab-run`, `canary-lab-verify`, `canary-lab-author`, `canary-lab-coverage`, `canary-lab-portify`, `canary-lab-export`.\n\nBroken document links are repaired before source discovery: the owning command elicits the moved file's new path on the Canary server. On `document-relinked`, retry that command with the same arguments. Keep the symlink and existing baseline; never omit the missing source or create recovery copies. Cancel\u002Fdecline leaves work pending. Unsupported clients use Relink in the returned Canary UI.\n",{"name":5,"type":52,"description":7},"skill","main","2c85305cab69e5f773d59ea79a64a0b049a98345","agent-integrations\u002Fclaude\u002Fskills\u002Fcanary-lab",0,"https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab","https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fcanary-lab","https:\u002F\u002Fgithub.com\u002Fferterahadi","https:\u002F\u002Fgithub.com\u002Fferterahadi-oddle","Shipping well-tested, production-ready, vibe-coded code using today’s latest form of engineering - harness, agentic, loop, graph, or whatever drops next","2026-11-12 23:36:35",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add ferterahadi\u002Fcanary-lab --skill canary-lab",{"tool":69,"command":70},"bunx","bunx skills add ferterahadi\u002Fcanary-lab --skill canary-lab",{"tool":72,"command":73},"pnpm","pnpm dlx skills add ferterahadi\u002Fcanary-lab --skill canary-lab","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fclaude\u002Fskills\u002Fcanary-lab\u002FSKILL.md",true,[],[78,87,97,114,121,141],{"skillNo":79,"name":80,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":85,"publishedAt":47},"01M6XCWFJ75NNVF0N8C0XGNC17","off-by-one","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fapps\u002Fweb-server\u002Fprompts\u002Fsabotage-skills\u002Foff-by-one\u002Fskill.md",[83,84],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[86],"off by one",{"skillNo":88,"name":89,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":94,"publishedAt":47},"01M6XCWBYW1QHVFKSDQCCRRRDT","multi-failure-cascade","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fapps\u002Fweb-server\u002Fprompts\u002Fsabotage-skills\u002Fmulti-failure-cascade\u002Fskill.md",[92,93],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[95,96],"multi","multi failure cascade",{"skillNo":98,"name":99,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":109,"publishedAt":47},"01M6XCW8XA7QY74Q18D9PGFFMD","broken-delete-contract","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fapps\u002Fweb-server\u002Fprompts\u002Fsabotage-skills\u002Fbroken-delete-contract\u002Fskill.md",[102,103,104],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":105,"categoryName":106,"taxonomyType":107,"taxonomyTypeDesc":108},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[110,111,112,113],"delete","broken","contract","broken delete contract",{"skillNo":115,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":120,"publishedAt":47},"01M6XCW439KA486AN2QZACNYTC","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fplugin\u002Fcanary-lab\u002Fskills\u002Fcanary-lab\u002FSKILL.md",[118,119],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[29,28,27,33,43,42,41,40,39,30,37,36,35,34,45,32,31,44,38,46],{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":129,"publishedAt":47},"01M6XCVYQ31KC4ZXCD5QMRKXEE","canary-lab-verify","Use when verifying a Canary Lab feature against a RUNNING target — a deployed environment (\"verify checkout on staging\", \"run the saved verify config\", \"check the deployed app\") or a locally booted app (\"verify a running app\") — through the verify MCP tools (list\u002Fget\u002Fcreate\u002Fupdate_verification_config, boot_services, execute_verification, get_verification_result). No heal loop. For run + heal use canary-lab-run; for end-to-end onboarding use canary-lab (flight).","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fplugin\u002Fcanary-lab\u002Fskills\u002Fcanary-lab-verify\u002FSKILL.md",[127,128],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[28,130,29,131,27,30,38,132,133,46,134,135,136,137,138,37,36,139,140,31],"verify","canary lab verify","config","create","update","verification","Tools","loop","list","checkout","result",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":152,"publishedAt":47},"01M6XCVSWW705CN3C69FQMPZXN","canary-lab-run","Use when running or healing an EXISTING Canary Lab feature locally — \"run \u003Cfeature>\", \"fix the failing run\", \"rerun 7cvh\", \"drive the heal loop\" — through the repair MCP tools (start_run, wait_for_heal_task, signal_run, get_heal_context, pause_run, cancel_heal, abort_run). Fix failing runs by editing app\u002Fservice code, not tests. For end-to-end onboarding of a new repo use canary-lab (flight); for deployed-environment checks use canary-lab-verify.","https:\u002F\u002Fgithub.com\u002Fferterahadi\u002Fcanary-lab\u002Fblob\u002Fmain\u002Fagent-integrations\u002Fplugin\u002Fcanary-lab\u002Fskills\u002Fcanary-lab-run\u002FSKILL.md",[147,148,149],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":150,"categoryName":151,"taxonomyType":107,"taxonomyTypeDesc":108},"01JBSEEDCAT000000000000030","开发工程师",[153,27,46,28,29,37,30,154,155,137,136,44,156,38,31,157,158,36,159,160],"canary lab run","fix","context","code","rerun","wait","new repo","failing"]