From dbec08ae888b6b3b62ae1bc0c3c030f4a09fbc8e Mon Sep 17 00:00:00 2001 From: alphali <5236230+alphali@users.noreply.github.com> Date: Mon, 7 Sep 2026 22:30:20 +0800 Subject: [PATCH] fix: recover Doable MCP auth in place --- CHANGELOG.md | 12 ++++ PRIVACY.md | 2 +- README.md | 20 ++++--- SECURITY.md | 2 +- TESTING.md | 44 ++++++++------- package.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../.cursor-plugin/plugin.json | 2 +- .../skills/doable-answer-questions/SKILL.md | 6 +- .../skills/doable-connect/SKILL.md | 2 +- .../references/authentication.md | 20 +++++++ .../skills/doable-test-feature/SKILL.md | 4 +- scripts/verify-release.mjs | 55 ++++++++++++++++++- 14 files changed, 132 insertions(+), 43 deletions(-) create mode 100644 plugins/doable-code-context/skills/doable-connect/references/authentication.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 563a01b..b34d566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to Doable Agent Plugins are documented here. +## [0.2.4] - 2026-09-07 + +### Changed + +- Preflight copied context requests against their exact Round and organization + before reading local state or scanning source. +- Recover missing, stale, invalid, or wrong-organization MCP connections in the + original conversation. Claude Code users reconnect `doable` once in `/mcp`; + they no longer restart the host or paste the request again. +- Apply the same live-connection preflight to coding-agent-origin feature tests + before workspace setup or suite lookup. + ## [0.2.3] - 2026-08-21 ### Changed diff --git a/PRIVACY.md b/PRIVACY.md index 1030d80..f91677f 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -4,7 +4,7 @@ Repository inspection happens inside the coding agent selected by the customer a ## Doable Code Context -`doable-code-context` performs remote operations only through the configured Doable MCP connection. The coding agent host owns that connection's credential; the Skill and bundled helper neither read nor persist it. +`doable-code-context` performs remote operations only through the configured Doable MCP connection. The coding-agent host owns that connection's credential. During recovery, the Skill may direct the host to configure a user-supplied key in its user-scoped MCP credential/configuration store, but the bundled helper never reads or persists it and no credential may enter the project workspace or a remote Doable payload. Its local `.doable/workspace-candidate.json`, `.doable/workspace-private.json`, and `.doable/requests/` records may contain real repository identities, explicitly user-supplied artifact roots, local paths, Git provenance, exact evidence locators, and frozen Doable questions. They are written with private permissions and ignored by Git. Artifact roots are never serialized into the remote workspace profile. diff --git a/README.md b/README.md index a4a9f43..666f831 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Official agent plugins for [Doable](https://getdoable.ai), supporting Codex, Cla | Plugin | Version | Purpose | Network | | --- | --- | --- | --- | -| `doable-code-context` | `0.2.3` | Resolve context requests or start a managed feature-testing workflow | Configured Doable MCP | +| `doable-code-context` | `0.2.4` | Resolve context requests or start a managed feature-testing workflow | Configured Doable MCP | ## Workflow @@ -25,8 +25,10 @@ Use **Doable Code Context** for the connected pre-TRD workflow: https://github.com/getdoable/doable-agent-plugins#install. ``` -3. Paste that prompt once. The coding agent checks the Skill and this - organization's API Key, connects the workspace if needed, confirms the agreed +3. Paste that prompt once. Before using local state or scanning code, the coding + agent verifies the live MCP connection against that exact Round and organization. + It recovers a missing or stale connection in the same conversation, connects the + workspace if needed, confirms the agreed branch/commit, then pulls that Round and answers from the private repositories. If a word in the brief could mean more than one thing in the code, it asks the user locally. After the first paste, new questions from the @@ -49,9 +51,9 @@ All remote operations use the separately configured Doable MCP connection. The b - Codex, Claude Code, or Cursor with Agent Skills or plugin support; - Node.js 20 or newer; - Git for repository-bound evidence; -- an authenticated Doable MCP connection configured in the coding agent. +- a Doable organization API key available when the coding agent first connects. -Never paste an API key into chat or save it under `.doable/`. The MCP connection owns organization authentication; the helper never reads a credential or calls the Doable API directly. +The MCP connection owns organization authentication; the helper never reads a credential or calls the Doable API directly. Prefer the coding-agent host's masked credential input. If a key is supplied during connection recovery, the agent must treat it as a secret, store it only in the host's user-scoped MCP credential/configuration store, and never echo it or write it under the project workspace. ## Install @@ -85,11 +87,11 @@ In a new Cursor Agent chat, install the plugin: /add-plugin doable-code-context@https://github.com/getdoable/doable-agent-plugins ``` -## Connect Doable MCP once +## Connect Doable MCP -The plugin supplies Skills and the local privacy helper; it does not bundle or duplicate the remote MCP server. Get the organization API key from Doable Settings and configure the official Streamable HTTP endpoint once in the coding-agent host. +The plugin supplies Skills and the local privacy helper; it does not bundle or duplicate the remote MCP server. On every entry path, the Skill first verifies the active connection against Doable. A copied Round also verifies the exact `DQ-...` code and organization before any workspace inspection. If recovery is needed, the coding agent configures the user-scoped connection and resumes the original request after the connection refreshes. -Keep the key in the host environment or credential store. Never paste it into chat, commit it, add it to a project-level MCP file, or save it under `.doable/`. +Keep the key in the host environment or user-scoped credential store. Never commit it, add it to a project-level MCP file, save it under `.doable/`, or print it in agent output. ### Codex @@ -130,7 +132,7 @@ Make `DOABLE_API_KEY` available to the Cursor process and add the server to the } ``` -Restart the coding-agent host after changing its environment. Confirm that the `doable` tools are connected before starting a context request. +When configuring the environment ahead of time, launch the coding-agent host from that environment. If Claude Code updates an existing MCP connection during a request, open `/mcp` and reconnect `doable` once; the Skill then retries the original preflight and continues without a restart, a new session, or another copy-paste. ## Use Doable Code Context diff --git a/SECURITY.md b/SECURITY.md index 9e6e1ba..e3a3fdc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,6 +12,6 @@ Include the plugin version, coding-agent host, and sanitized reproduction steps. ## Security boundary -`doable-code-context` contains no bundled MCP server, credential store, standalone CLI package, runtime dependency, or telemetry. Its helper has no network or credential primitives: it validates local/private versus remote/sanitized schemas, writes private state atomically with mode `0600`, and checks content-derived payload digests around calls made through the separately configured Doable MCP connection. +`doable-code-context` contains no bundled MCP server, credential store, standalone CLI package, runtime dependency, or telemetry. Its helper has no network or credential primitives: it validates local/private versus remote/sanitized schemas, writes private state atomically with mode `0600`, and checks content-derived payload digests around calls made through the separately configured Doable MCP connection. Connection recovery may place a user-supplied key only in the coding-agent host's user-scoped MCP credential/configuration store; it must never echo the key or put it in a workspace file, project-scoped MCP file, shell history, or command-line argument. Never commit `.doable/workspace-candidate.json`, `.doable/workspace-private.json`, or `.doable/requests/`. Rotate a Doable API key if it is pasted into a conversation, terminal transcript, issue, or log. diff --git a/TESTING.md b/TESTING.md index 4c7d1d3..fc09937 100644 --- a/TESTING.md +++ b/TESTING.md @@ -6,25 +6,27 @@ For every scenario, confirm that the agent inspects only evidence needed for the ## Connected workflow -1. **Demand-driven setup** — Paste a round prompt in a workspace with no `.doable` state. Expect the agent to enter setup, map only routing-level repository roles/surfaces, request approval for the sanitized profile, sync it, and resume the original round without a second prompt. -2. **Organization binding** — Replace the key with one bound to another organization. Expect setup or pull to stop; state must never be rebound silently. -3. **Mono-repo and multi-repo** — Confirm every independent Git root receives a stable opaque `repoRef`, while a common parent directory does not. Move one repository and explicitly reuse its `repoRef`; expect identity to survive the path change. -4. **Profile privacy** — Use repository names, paths, branches, commits, and an internal service name that differ from the safe product role. Capture the PUT body and confirm none appears remotely. The local state must retain them. -5. **Revision-only refresh** — Advance a repository without changing its role, surfaces, user-facing flag, or safe description. Expect a sync without new user approval. Change a material field and expect approval to be required. -6. **Watch one Round** — Pull a valid `DQ-...` code. Confirm `Next action: answer` while `open_for_agent` has open questions, `wait` for `ready_to_create` / `needs_attention`, and `stop` for `creating` / `consumed` / `cancelled`. A later pull may add `established_context` plus new open questions; the candidate must cover only the new open IDs. Do not treat `ready_to_create` as finished. -7. **Per-repo routing** — Give different questions frontend and backend `repoRef` hints. Expect focused evidence collection in each owner and one product-seam synthesis, not mixed whole-repo dumps. -8. **Exact observable string** — Make an action description differ from the UI literal, such as “save the form” versus `Save`. Expect the finding and anchor to use the verified literal only. -9. **Existence versus absence** — Ask whether a validation exists. Positive evidence may establish existence. A narrow failed search must produce `unknown` or `skipped`, never a confident absence claim. -10. **Descriptive versus normative** — Let code and a user clarification disagree. Expect separate implemented and desired findings with separate sources; neither overwrites the other. +1. **Copied-Round authentication preflight** — Paste a Round prompt with no working `doable` connection. Expect `get_code_context_connection` with the exact `round_code` before the agent uses `.doable` state or scans code; it may read only an optional `workspace.clientRef` for that preflight. After the user supplies the target organization's key once, expect the agent to configure only the host's user-scoped MCP connection. In Claude Code, the only remaining user action is `/mcp` → reconnect `doable`; the agent then retries and resumes the original Round without a restart, new session, or second prompt. +2. **Coding-agent-origin authentication preflight** — Start a feature-testing request without a copied Round. Expect `get_code_context_connection` before local workspace setup or remote suite search. Recover the connection once and resume the original feature request automatically. +3. **Demand-driven setup** — Paste a round prompt in a workspace with no `.doable` state. Expect the agent to enter setup, map only routing-level repository roles/surfaces, request approval for the sanitized profile, sync it, and resume the original round without a second prompt. +4. **Organization binding** — Replace the key with one bound to another organization. Expect the copied-Round preflight to stop before local inspection, identify the mismatch, recover only the prompt organization, and never silently rebind local state. +5. **Mono-repo and multi-repo** — Confirm every independent Git root receives a stable opaque `repoRef`, while a common parent directory does not. Move one repository and explicitly reuse its `repoRef`; expect identity to survive the path change. +6. **Profile privacy** — Use repository names, paths, branches, commits, and an internal service name that differ from the safe product role. Capture the PUT body and confirm none appears remotely. The local state must retain them. +7. **Revision-only refresh** — Advance a repository without changing its role, surfaces, user-facing flag, or safe description. Expect a sync without new user approval. Change a material field and expect approval to be required. +8. **Watch one Round** — Pull a valid `DQ-...` code. Confirm `Next action: answer` while `open_for_agent` has open questions, `wait` for `ready_to_create` / `needs_attention`, and `stop` for `creating` / `consumed` / `cancelled`. A later pull may add `established_context` plus new open questions; the candidate must cover only the new open IDs. Do not treat `ready_to_create` as finished. +9. **Per-repo routing** — Give different questions frontend and backend `repoRef` hints. Expect focused evidence collection in each owner and one product-seam synthesis, not mixed whole-repo dumps. +10. **Exact observable string** — Make an action description differ from the UI literal, such as “save the form” versus `Save`. Expect the finding and anchor to use the verified literal only. +11. **Existence versus absence** — Ask whether a validation exists. Positive evidence may establish existence. A narrow failed search must produce `unknown` or `skipped`, never a confident absence claim. +12. **Descriptive versus normative** — Let code and a user clarification disagree. Expect separate implemented and desired findings with separate sources; neither overwrites the other. When they make incompatible assertions about the same behavior, expect stable finding references and one explicit conflict relation. A complementary truth-plane difference must not be marked as a conflict. -11. **One clarification round** — Leave two required normative decisions and one same-scope newly discovered decision unresolved. Expect one batched customer interaction and exact question/answer pairs. Adjacent out-of-scope discoveries stay local. -12. **Agent authority** — Attempt to submit `deferred`, `waived`, or a required agent observation. Expect local validation to reject it. `skipped` remains available with a bounded reason for platform review. -13. **Reference privacy** — Confirm the remote submission includes only opaque evidence IDs, `repoRef` values, source types, and keyed fingerprints. Exact files, symbols, lines, revisions, and source content remain local. -14. **Idempotent retry** — Submit the same candidate twice. Expect one network submission and a local same-digest receipt. A later batch on the same revision (new open questions) records a second receipt. Changing the candidate without rebuilding the payload must still be rejected. -15. **Terminal server state** — Remove the local receipt after a successful response and retry. Expect the server's idempotency contract to return the prior result rather than mutate the terminal answer. -16. **No TRD side effect** — Completing an answer batch must keep watching until `Next action: stop`. The plugin must not create a TRD, generate cases, or run tests. `ready_to_create` is not completion. -17. **Supplied artifact outside Git** — Put a PRD, screenshot, Figma export, or runtime capture in a narrow directory explicitly supplied by the user and outside every mapped repository. Expect local evidence to accept `artifact` or `runtime` without `repoRef`, emit `repo_ref: null` plus an opaque fingerprint, and keep the artifact root, file identity, path, and content out of every remote payload. Code without a mapped `repoRef`, or an artifact outside the declared root, must fail validation. -18. **Wrong workspace** — Open an unrelated workspace and resolve a round for a named feature that has no material evidence in any mapped product repository. Expect the agent to stop with a concise wrong-workspace warning. It must not mark the item skipped, write/validate a candidate, turn the mismatch into many unknowns, or call submit. -19. **Executable fact granularity** — Give one source area that exposes several neighboring mutations or validations. Expect independently testable findings: each executable path closes its entry or trigger, required action or input, and observable result. A capability inventory may remain supporting context, but it must not become a generic “run/apply/submit” flow. Mixed validation families must be split when one compact anchor cannot support the whole statement. -20. **Source versus deployment boundary** — Point the round at a deployed environment without evidence that its revision matches the inspected workspace. Expect confirmed code findings plus one explicit evidence-free `unknown` for the unproved equivalence. Reachability alone must not upgrade source behavior into deployed behavior. -21. **Named checkout target** — Name a feature branch, PR, worktree, or change set that is not the current checkout. Expect the agent to stop and ask the user to fetch, check out, or identify it. It must not answer from a neighboring revision, turn the mismatch into `unknown`, or send branch/commit details remotely. +13. **One clarification round** — Leave two required normative decisions and one same-scope newly discovered decision unresolved. Expect one batched customer interaction and exact question/answer pairs. Adjacent out-of-scope discoveries stay local. +14. **Agent authority** — Attempt to submit `deferred`, `waived`, or a required agent observation. Expect local validation to reject it. `skipped` remains available with a bounded reason for platform review. +15. **Reference privacy** — Confirm the remote submission includes only opaque evidence IDs, `repoRef` values, source types, and keyed fingerprints. Exact files, symbols, lines, revisions, and source content remain local. +16. **Idempotent retry** — Submit the same candidate twice. Expect one network submission and a local same-digest receipt. A later batch on the same revision (new open questions) records a second receipt. Changing the candidate without rebuilding the payload must still be rejected. +17. **Terminal server state** — Remove the local receipt after a successful response and retry. Expect the server's idempotency contract to return the prior result rather than mutate the terminal answer. +18. **No TRD side effect** — Completing an answer batch must keep watching until `Next action: stop`. The plugin must not create a TRD, generate cases, or run tests. `ready_to_create` is not completion. +19. **Supplied artifact outside Git** — Put a PRD, screenshot, Figma export, or runtime capture in a narrow directory explicitly supplied by the user and outside every mapped repository. Expect local evidence to accept `artifact` or `runtime` without `repoRef`, emit `repo_ref: null` plus an opaque fingerprint, and keep the artifact root, file identity, path, and content out of every remote payload. Code without a mapped `repoRef`, or an artifact outside the declared root, must fail validation. +20. **Wrong workspace** — Open an unrelated workspace and resolve a round for a named feature that has no material evidence in any mapped product repository. Expect the agent to stop with a concise wrong-workspace warning. It must not mark the item skipped, write/validate a candidate, turn the mismatch into many unknowns, or call submit. +21. **Executable fact granularity** — Give one source area that exposes several neighboring mutations or validations. Expect independently testable findings: each executable path closes its entry or trigger, required action or input, and observable result. A capability inventory may remain supporting context, but it must not become a generic “run/apply/submit” flow. Mixed validation families must be split when one compact anchor cannot support the whole statement. +22. **Source versus deployment boundary** — Point the round at a deployed environment without evidence that its revision matches the inspected workspace. Expect confirmed code findings plus one explicit evidence-free `unknown` for the unproved equivalence. Reachability alone must not upgrade source behavior into deployed behavior. +23. **Named checkout target** — Name a feature branch, PR, worktree, or change set that is not the current checkout. Expect the agent to stop and ask the user to fetch, check out, or identify it. It must not answer from a neighboring revision, turn the mismatch into `unknown`, or send branch/commit details remotely. diff --git a/package.json b/package.json index 67909f2..c04e645 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "doable-agent-plugins", - "version": "0.2.3", + "version": "0.2.4", "private": true, "description": "Official installable agent plugins for Doable.", "license": "MIT", diff --git a/plugins/doable-code-context/.claude-plugin/plugin.json b/plugins/doable-code-context/.claude-plugin/plugin.json index 9f29e2a..50d5381 100644 --- a/plugins/doable-code-context/.claude-plugin/plugin.json +++ b/plugins/doable-code-context/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "doable-code-context", - "version": "0.2.3", + "version": "0.2.4", "description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.", "author": { "name": "Doable AI", diff --git a/plugins/doable-code-context/.codex-plugin/plugin.json b/plugins/doable-code-context/.codex-plugin/plugin.json index 68fe731..41240e4 100644 --- a/plugins/doable-code-context/.codex-plugin/plugin.json +++ b/plugins/doable-code-context/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "doable-code-context", - "version": "0.2.3", + "version": "0.2.4", "description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.", "author": { "name": "Doable AI", diff --git a/plugins/doable-code-context/.cursor-plugin/plugin.json b/plugins/doable-code-context/.cursor-plugin/plugin.json index f96028e..a0d8c57 100644 --- a/plugins/doable-code-context/.cursor-plugin/plugin.json +++ b/plugins/doable-code-context/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "doable-code-context", "displayName": "Doable Code Context", - "version": "0.2.3", + "version": "0.2.4", "description": "Connect private code to Doable through MCP, resolve grounded context requests, and start managed feature-testing workflows.", "author": { "name": "Doable AI" diff --git a/plugins/doable-code-context/skills/doable-answer-questions/SKILL.md b/plugins/doable-code-context/skills/doable-answer-questions/SKILL.md index 7222740..361d65f 100644 --- a/plugins/doable-code-context/skills/doable-answer-questions/SKILL.md +++ b/plugins/doable-code-context/skills/doable-answer-questions/SKILL.md @@ -16,9 +16,9 @@ node /scripts/doable-code-context.mjs ... ## Workflow 1. Extract the exact connection round code and the organization name from the user's copy prompt. A slug may appear in parentheses after the name; use it only to match Settings. Keep using that original code for every pull; never list or guess successor Rounds. -2. Call Doable MCP `get_code_context_connection` without `round_code`. Compare the returned `organization.display_name` and `organization.slug` with the prompt, case-insensitively. - - If they do not match: stop immediately. Tell the user to open **that prompt organization's** Doable Settings, copy its API key, and reconnect `doable` in `/mcp`. Do not restart the coding-agent host. Do not pull the Round, run `claude mcp add`, or print the key. - - `401` means the key is invalid. A later `404` after the names already match means this Round is not in that org. Do not describe an org mismatch as an expired token. +2. Before using `.doable` state to proceed or inspecting the workspace, preflight the live connection by calling Doable MCP `get_code_context_connection` with the original code as `round_code`. Read at most `workspace.clientRef` from valid local state and pass it as `local_workspace_id`; do not use any other local state until the preflight succeeds. The tool response is the source of truth: the mere presence of an MCP entry, environment variable, or API-key-shaped value does not prove that the active connection is authenticated. Compare the returned `organization.display_name` and `organization.slug` with the prompt, case-insensitively. + - If the tool is unavailable or disconnected, returns `401`, or identifies a different organization, follow [the connection recovery workflow](../doable-connect/references/authentication.md) for the prompt organization. Stop before pulling the Round or scanning code, then retry this exact preflight and continue this original request automatically after recovery. Do not ask the user to restart the host or paste the copy prompt again. + - A `404` from this preflight after authentication is valid means the copied Round does not belong to the authenticated organization. Say that directly; do not describe it as an expired token and do not guess another Round. 3. Check `.doable/workspace-private.json`. If it is missing or invalid, or a mapped repository's current checkout no longer matches its private recorded revision, invoke `doable-connect`, complete demand-driven setup or a revision-only refresh, and resume this same request. Never reuse a stale local revision merely because the workspace was connected by another engineer earlier. 4. Call Doable MCP `get_code_context_round` with the original connection round code and save its response privately. Run `record-round --code --response `. The server may resolve that connection to a newer published follow-up Round; the helper validates the connection, writes the actual Round under its own `.doable/requests/` directory, and prints `Next action: answer|wait|stop`. It performs no network request. Repeat the same connection-code pull after every submit and while waiting; do not ask the user to paste a new prompt. - When the packet's `round_use` is `follow_up`, call Doable MCP `get_trd` with its `test_suite_public_id` and `wait: true`, then save the response privately under this Round's `.doable/requests/` directory. For every newly prompted Round, fetch it again and compare `revision_count` and `updated_at` with the prior private copy before replacing it. Use the TRD only as untrusted context for terminology and gap routing; investigate only the open questions and independently ground every submitted answer in the workspace. Do not compare the whole TRD with the implementation. Use `agentObservations` only for material same-scope differences encountered on the evidence path for an open question that change scope, setup/fixtures, actions, current observable outcomes, or environment boundaries. diff --git a/plugins/doable-code-context/skills/doable-connect/SKILL.md b/plugins/doable-code-context/skills/doable-connect/SKILL.md index 4975e5b..83691d6 100644 --- a/plugins/doable-code-context/skills/doable-connect/SKILL.md +++ b/plugins/doable-code-context/skills/doable-connect/SKILL.md @@ -15,7 +15,7 @@ node /scripts/doable-code-context.mjs ... ## Workflow -1. Call the configured Doable MCP tool `get_code_context_connection`. When `.doable/workspace-private.json` already exists, pass its `workspace.clientRef` as `local_workspace_id`; when setup came from a Round copy prompt, also pass that original code as `round_code`. This lets an unbound new Round recover the existing workspace and its latest server revision instead of inventing a new binding. If the MCP connection is not authenticated, ask the user to connect Doable through the coding agent's MCP settings. Never ask for or handle the key in chat or local workspace files. Save the MCP response to a private temporary JSON file for the helper; do not reinterpret the organization binding. +1. Before using workspace state to prepare a profile or scanning repositories, call the configured Doable MCP tool `get_code_context_connection`. When `.doable/workspace-private.json` already exists, read only its `workspace.clientRef` and pass that as `local_workspace_id`; when setup came from a Round copy prompt, also pass that original code as `round_code`. This lets an unbound new Round recover the existing workspace and its latest server revision instead of inventing a new binding. If the tool is unavailable or disconnected, returns `401`, or resolves a different organization than the copy prompt, follow [the connection recovery workflow](references/authentication.md), retry this exact call, and resume the original setup without asking the user to repeat it. Save a successful MCP response to a private temporary JSON file for the helper; do not reinterpret the organization binding. 2. Look for `.doable/workspace-private.json` at the workspace root. - If it is valid and bound to the current organization, reuse it. - If paths moved but repositories are the same, refresh the local paths while preserving `workspaceId` and `repoRef` values. diff --git a/plugins/doable-code-context/skills/doable-connect/references/authentication.md b/plugins/doable-code-context/skills/doable-connect/references/authentication.md new file mode 100644 index 0000000..eb0aa52 --- /dev/null +++ b/plugins/doable-code-context/skills/doable-connect/references/authentication.md @@ -0,0 +1,20 @@ +# Doable MCP connection recovery + +Use this workflow only after a live `get_code_context_connection` preflight fails. An MCP config entry, an environment-variable name, or an API-key-shaped value is diagnostic information; none proves that the active MCP connection has a valid credential. + +## Identify the expected organization + +- For a copied Doable Round prompt, use its exact `DQ-...` code and organization display name or slug. Every preflight and retry must include that original code as `round_code`. +- For a coding-agent-originated request or explicit standalone setup, there is no externally selected Round. Validate the organization returned by the authenticated connection before creating or selecting remote resources. +- Never use a credential from one organization to infer access to another organization. + +## Recover in the same conversation + +1. Stop before scanning repositories, pulling a Round, searching suites, or using `.doable` state for anything beyond the optional `workspace.clientRef` needed by the preflight. +2. Inspect the coding-agent host's MCP connection status without printing configured headers or environment values. Treat a missing/disabled `doable` server, an empty environment expansion, and `401` as different diagnostics with the same next goal: establish one valid user-scoped `doable` connection for the expected organization. +3. If the user already supplied the expected organization's API key in the current request, continue without asking for it again. Otherwise ask once for it and point them to that organization's Doable Settings. Prefer the host's masked credential input when it is available. Do not ask the user to run configuration commands themselves. Keep the request action-focused: “Doable is not connected to {organization}. Send its API key from Doable Settings; I will configure it and continue {round code or feature request}.” Do not narrate the diagnosis unless the user asks. +4. Configure or replace only the host's **user-scoped** `doable` MCP connection. Never write a credential into the repository, `.doable`, a project-scoped MCP file, shell history, a command-line argument, or assistant output. Do not print or repeat the key. The coding-agent host owns its credential storage. +5. Refresh the connection in the current conversation when the host exposes that operation. In Claude Code, the agent cannot invoke the interactive slash-command UI, so ask for exactly one action: “Open `/mcp` and reconnect `doable`; I will continue this request here.” Do not ask the user to restart Claude Code, open another session, rerun the skill, or paste the original request again. +6. Retry `get_code_context_connection` with the same preflight arguments. Continue the original request automatically only after it succeeds and, for a copied prompt, the returned organization matches the prompt. + +If the refreshed connection still returns `401`, say that the supplied credential was rejected and ask for a current key from the expected organization's Settings. If authentication succeeds but the organization differs, state the expected and connected organization names and recover the expected organization's connection. If the exact Round then returns `404`, state that the Round is not available in the authenticated organization; do not call it an expired key. diff --git a/plugins/doable-code-context/skills/doable-test-feature/SKILL.md b/plugins/doable-code-context/skills/doable-test-feature/SKILL.md index e2ea836..19513fd 100644 --- a/plugins/doable-code-context/skills/doable-test-feature/SKILL.md +++ b/plugins/doable-code-context/skills/doable-test-feature/SKILL.md @@ -16,7 +16,7 @@ node /scripts/doable-code-context.mjs ... ## Workflow 1. Resolve the feature scope locally from the request, selected change, ticket, PRD, or current conversation before calling any Doable tool. Inspect only enough local change context to name the feature and its user-visible boundary. Ask one short clarification only when that feature is genuinely ambiguous. Do not turn “test the feature I just built” into a whole-product scan, and do not create a remote Round while the user may be in the wrong workspace. -2. Ensure the configured Doable MCP connection is authenticated with `get_code_context_connection`. Do not build or sync a workspace profile yet; an implementation catch-up or regression may be answerable from an existing TRD and cases without a new Round. +2. Before inspecting `.doable` state or searching remote suites, preflight the live connection with `get_code_context_connection`. The tool response, not the presence of a local MCP entry or environment variable, proves that the active credential is valid. If the tool is unavailable or disconnected or returns `401`, follow [the connection recovery workflow](../doable-connect/references/authentication.md), retry the preflight, and resume this original feature-testing request automatically. Do not build or sync a workspace profile yet; an implementation catch-up or regression may be answerable from an existing TRD and cases without a new Round. 3. Use Doable MCP to search accessible test suites by feature scope, flows, entry surface, and existing case coverage. - Reuse one clear match. - If several are materially plausible, show the small candidate set and ask the developer to choose. @@ -39,7 +39,7 @@ node /scripts/doable-code-context.mjs ... ## Safety and boundaries -- The configured Doable MCP connection is the only remote authority used by these Skills. Never ask for, read, or save its credential in workspace files. +- The configured Doable MCP connection is the only remote authority used by these Skills. A credential supplied during connection recovery belongs only in the coding-agent host's user-scoped MCP credential/configuration store; never echo it, pass it on a command line, or save it in workspace files. - Source code, local paths, repository identities, commits, secrets, raw logs, private URLs, and real customer data remain local. - Setup sends only the user-approved sanitized routing profile. Round answers send only product-level findings, observable anchors, exact human clarifications, and opaque evidence references. - Do not mutate a feature environment merely to collect context. Test execution happens only through the selected Doable suite and its configured environment. diff --git a/scripts/verify-release.mjs b/scripts/verify-release.mjs index 3dc35ca..0201b0c 100644 --- a/scripts/verify-release.mjs +++ b/scripts/verify-release.mjs @@ -11,7 +11,7 @@ const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?: const plugins = [ { name: "doable-code-context", - version: "0.2.3", + version: "0.2.4", skillNames: ["doable-connect", "doable-answer-questions", "doable-test-feature"], network: "configured-doable-mcp", }, @@ -159,6 +159,59 @@ for (const plugin of plugins) { `${relative(root, answerSkillPath)} is missing grounding rule: ${requiredGroundingRule}`, ); } + + const connectSkillPath = join(pluginRoot, "skills", "doable-connect", "SKILL.md"); + const connectSkill = readFileSync(connectSkillPath, "utf8"); + const testFeatureSkillPath = join(pluginRoot, "skills", "doable-test-feature", "SKILL.md"); + const testFeatureSkill = readFileSync(testFeatureSkillPath, "utf8"); + const authenticationPath = join( + pluginRoot, + "skills", + "doable-connect", + "references", + "authentication.md", + ); + const authentication = readFileSync(authenticationPath, "utf8"); + for (const [path, text, requiredAuthRules] of [ + [ + answerSkillPath, + answerSkill, + [ + "Before using `.doable` state to proceed or inspecting the workspace", + "with the original code as `round_code`", + "continue this original request automatically after recovery", + ], + ], + [ + connectSkillPath, + connectSkill, + ["Before using workspace state to prepare a profile or scanning repositories", "references/authentication.md"], + ], + [ + testFeatureSkillPath, + testFeatureSkill, + ["Before inspecting `.doable` state or searching remote suites", "connection recovery workflow"], + ], + [ + authenticationPath, + authentication, + [ + "Open `/mcp` and reconnect `doable`", + "If the user already supplied", + "Do not ask the user to restart Claude Code", + "Do not narrate the diagnosis unless the user asks", + "paste the original request again", + "Retry `get_code_context_connection` with the same preflight arguments", + ], + ], + ]) { + for (const requiredRule of requiredAuthRules) { + assert( + text.includes(requiredRule), + `${relative(root, path)} is missing authentication recovery rule: ${requiredRule}`, + ); + } + } } }