Skip to content

fix(providers): F-7 + F-10 + F-5 — provider-aware aliases, gh query, canopy issues#19

Merged
ashmitb95 merged 1 commit intomainfrom
fix/issue-alias-resolver
May 2, 2026
Merged

fix(providers): F-7 + F-10 + F-5 — provider-aware aliases, gh query, canopy issues#19
ashmitb95 merged 1 commit intomainfrom
fix/issue-alias-resolver

Conversation

@ashmitb95
Copy link
Copy Markdown
Owner

Summary

Three related fixes from docs/test-findings.md. The headline is F-7 — provider-aware alias resolver (the test plan's P0). F-10 was found incidentally while smoke-testing F-5.

F-7 (P0) — canopy issue works for any provider

Before: canopy issue 5 against a github_issues provider failed with unknown_alias because the resolver was hardcoded to look for Linear-shaped IDs (SIN-412). M5 shipped the Provider Protocol but the alias-resolution layer above it never learned about non-Linear shapes.

After: every alias form works:

canopy issue 5                                 → ✓
canopy issue '#5'                              → ✓
canopy issue 'owner/repo#5'                    → ✓
canopy issue 'https://github.com/o/r/issues/5' → ✓  (URL extracted to canonical "o/r#5")
canopy issue SIN-412                           → ✓  (Linear path unchanged)
canopy issue auth-flow                         → ✓  (feature alias unchanged)

Design: new IssueProvider.parse_alias(alias) -> str | None Protocol method. Returns canonicalised id when the provider recognises the shape; None falls through to feature-lane lookup. LinearProvider.parse_alias matches [A-Z]+-\d+; GitHubIssuesProvider.parse_alias matches bare/hash/owner-repo/URL. resolve_linear_id renamed to resolve_issue_id (provider-aware); old name kept as deprecated wrapper that preserves the legacy no_linear_id error code for back-compat. The MCP issue_get tool now also routes through resolve_issue_id, so feature aliases work there too.

F-10 (incidental) — gh search issues query was malformed

Found while verifying F-5. GitHubIssuesProvider.list_my_issues built a query string like repo:owner/repo is:open assignee:@me and passed it positionally to gh search issues. But the search verb treats positional args as search text — the qualifiers got quoted as a single text token and the GitHub API returned Invalid search query.

Unit tests mocked _gh_json and asserted on constructed args, so they happily passed even though the args were nonsense to the real CLI. This is exactly the gap the integration test plan was meant to catch — and did.

Fix: switch to gh issue list --repo ... --state open --assignee @me --label ... (the right verb form, matches Phil's branch).

F-5 (P2) — canopy issues plural

Added cmd_issues mirroring mcp__canopy__issue_list_my_issues. Empty list when no provider configured. Both --json and human-readable rendering of canonical Issue shape.

Tests

+21 across:

  • test_aliases.pyresolve_issue_id behaviour (Linear path, GH path with mocked provider, feature-lookup fallback, error codes); deprecated resolve_linear_id wrapper preserves legacy code.
  • test_providers_linear.pyparse_alias matches Linear IDs only, returns None for GH-shaped / feature-name input.
  • test_providers_github_issues.pyparse_alias matches bare/hash/owner-repo/URL; F-10 regression: list_my_issues uses gh issue list with the right flag set.
  • test_providers_types.py + test_providers_registry.py — Protocol stubs gain parse_alias so isinstance(stub, IssueProvider) still passes.

Suite: 628 → 644 passing.

Test plan

  • python -m pytest -q (644 pass)
  • Manual against canopy-test with [issue_provider] = github_issues:
    • canopy issue 5, canopy issue '#5', canopy issue '<URL>' all return the canonical Issue shape
    • canopy issues --json returns [] (no assigned issues)
    • With one issue assigned: canopy issues renders #5 in_progress Test: ...

After this PR

Remaining from test-findings.md:

  • F-3 (doctor mcp_orphans check + reaper) — backlog, separate PR.
  • F-4 (docs/mcp.md OAuth-headless note) — small docs PR.

…canopy issues CLI

F-7 (P0) — provider-aware alias resolver
==========================================

`canopy issue 5` and `canopy issue '#5'` against a github_issues
provider failed with `unknown_alias` because the resolver was hardcoded
to look for Linear-shaped IDs. M5 shipped the Provider Protocol but the
alias-resolution layer above it never learned about non-Linear shapes.

This PR:

- Adds `IssueProvider.parse_alias(alias) -> str | None` to the Protocol.
  Returns the canonicalised id when the provider recognises the shape,
  None otherwise (so the resolver falls through to feature-lane lookup).
- LinearProvider.parse_alias matches `[A-Z]+-\d+` (case-insensitive).
- GitHubIssuesProvider.parse_alias matches bare-N, `#N`,
  `owner/repo#N`, and the full GH URL form (extracts `owner/repo#N`).
- Renames the alias resolver: `resolve_linear_id` → `resolve_issue_id`.
  The new resolver calls `provider.parse_alias(alias)` first; if no
  provider match, falls back to feature-name lookup with provider-aware
  error messages. The old `resolve_linear_id` is kept as a deprecated
  wrapper (preserves the legacy `no_linear_id` error code for back-compat).
- The MCP `issue_get` tool now also routes through `resolve_issue_id`,
  so feature aliases work there too (was provider-only before).

Smoke against canopy-test (with [issue_provider] = github_issues):
  canopy issue 5                           → returns issue #5 ✓
  canopy issue '#5'                        → returns issue #5 ✓
  canopy issue 'https://.../issues/5'      → returns issue #5 ✓

F-10 (incidental) — gh search query construction was malformed
==============================================================

While verifying F-5, found that GitHubIssuesProvider.list_my_issues
was building a query string like `repo:owner/repo is:open assignee:@me`
and passing it positionally to `gh search issues`. But the search verb
treats positional args as search *text* — the qualifiers got quoted as
a single text token and GitHub returned `Invalid search query.`

Unit tests at the `_gh_json` boundary mocked the call and asserted on
constructed args, so they happily passed even though the args were
nonsense to the real CLI. This is exactly the gap the integration test
plan was meant to catch — and did.

Fix: switch to `gh issue list --repo ... --state open --assignee @me
--label ...` (the right verb form). Matches Phil's branch.

F-5 (P2) — `canopy issues` (plural) CLI
========================================

Added `cmd_issues` mirroring `mcp__canopy__issue_list_my_issues`.
Renders the canonical Issue shape in human-readable form; `--json`
emits the list directly. Empty when no provider configured (no
auto-complete signal) — matches MCP behavior.

Tests: +21 across test_aliases (resolve_issue_id behaviour, deprecated
wrapper preserves legacy error code), test_providers_linear (parse_alias
recognises Linear IDs only), test_providers_github_issues (parse_alias
recognises bare/hash/owner-repo/URL; gh issue list call shape — F-10
regression test). Suite: 628 → 644 passing.

After this PR: F-1, F-2 (retracted), F-5, F-6, F-7, F-9, F-10 all
fixed. Remaining from test-findings: F-3 (doctor mcp_orphans —
backlog), F-4 (mcp.md OAuth-headless doc — separate small PR).
@ashmitb95 ashmitb95 merged commit 87177ee into main May 2, 2026
4 checks passed
@ashmitb95 ashmitb95 deleted the fix/issue-alias-resolver branch May 2, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant