canopy-cli 3.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. canopy/__init__.py +2 -0
  2. canopy/actions/__init__.py +32 -0
  3. canopy/actions/aliases.py +421 -0
  4. canopy/actions/augments.py +55 -0
  5. canopy/actions/bootstrap.py +249 -0
  6. canopy/actions/bot_resolutions.py +123 -0
  7. canopy/actions/bot_status.py +133 -0
  8. canopy/actions/commit.py +511 -0
  9. canopy/actions/conflicts.py +314 -0
  10. canopy/actions/doctor.py +1459 -0
  11. canopy/actions/draft_replies.py +185 -0
  12. canopy/actions/drift.py +241 -0
  13. canopy/actions/errors.py +115 -0
  14. canopy/actions/evacuate.py +192 -0
  15. canopy/actions/feature_state.py +607 -0
  16. canopy/actions/historian.py +612 -0
  17. canopy/actions/ide_workspace.py +49 -0
  18. canopy/actions/last_visit.py +83 -0
  19. canopy/actions/migrate_slots.py +313 -0
  20. canopy/actions/preflight_state.py +97 -0
  21. canopy/actions/push.py +199 -0
  22. canopy/actions/reads.py +304 -0
  23. canopy/actions/resume.py +582 -0
  24. canopy/actions/review_filter.py +135 -0
  25. canopy/actions/ship.py +399 -0
  26. canopy/actions/slot_details.py +208 -0
  27. canopy/actions/slot_load.py +383 -0
  28. canopy/actions/slots.py +221 -0
  29. canopy/actions/stash.py +230 -0
  30. canopy/actions/switch.py +775 -0
  31. canopy/actions/switch_preflight.py +192 -0
  32. canopy/actions/thread_actions.py +88 -0
  33. canopy/actions/thread_resolutions.py +101 -0
  34. canopy/actions/triage.py +286 -0
  35. canopy/agent/__init__.py +5 -0
  36. canopy/agent/runner.py +129 -0
  37. canopy/agent_setup/__init__.py +264 -0
  38. canopy/agent_setup/skills/augment-canopy/SKILL.md +116 -0
  39. canopy/agent_setup/skills/using-canopy/SKILL.md +191 -0
  40. canopy/cli/__init__.py +0 -0
  41. canopy/cli/main.py +4152 -0
  42. canopy/cli/render.py +98 -0
  43. canopy/cli/ui.py +150 -0
  44. canopy/features/__init__.py +2 -0
  45. canopy/features/coordinator.py +1256 -0
  46. canopy/git/__init__.py +0 -0
  47. canopy/git/hooks.py +173 -0
  48. canopy/git/multi.py +435 -0
  49. canopy/git/repo.py +859 -0
  50. canopy/git/templates/post-checkout.py +67 -0
  51. canopy/graph/__init__.py +0 -0
  52. canopy/integrations/__init__.py +0 -0
  53. canopy/integrations/github.py +983 -0
  54. canopy/integrations/linear.py +307 -0
  55. canopy/integrations/precommit.py +239 -0
  56. canopy/mcp/__init__.py +0 -0
  57. canopy/mcp/client.py +329 -0
  58. canopy/mcp/server.py +1797 -0
  59. canopy/providers/__init__.py +105 -0
  60. canopy/providers/github_issues.py +289 -0
  61. canopy/providers/linear.py +341 -0
  62. canopy/providers/types.py +149 -0
  63. canopy/workspace/__init__.py +4 -0
  64. canopy/workspace/config.py +378 -0
  65. canopy/workspace/context.py +224 -0
  66. canopy/workspace/discovery.py +197 -0
  67. canopy/workspace/workspace.py +173 -0
  68. canopy_cli-3.1.0.dist-info/METADATA +282 -0
  69. canopy_cli-3.1.0.dist-info/RECORD +71 -0
  70. canopy_cli-3.1.0.dist-info/WHEEL +4 -0
  71. canopy_cli-3.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: using-canopy
3
+ description: Use when working in a multi-repo workspace that has canopy.toml or .canopy/ — prefer canopy MCP tools (mcp__canopy__*) over raw git/gh/bash to avoid path-management mistakes and to get pre-classified state and review data.
4
+ ---
5
+
6
+ # Using canopy
7
+
8
+ Canopy is a multi-repo workspace orchestrator. When you see `canopy.toml` or a `.canopy/` directory at the workspace root, canopy is configured. The `mcp__canopy__*` tools are your primary surface for repo, branch, and PR operations in that workspace.
9
+
10
+ ## Why prefer canopy over raw git/gh
11
+
12
+ The single biggest agent failure mode in multi-repo work is path mistakes — `cd /wrong/repo && git status`, `git checkout` in repo A when you meant repo B, `pnpm test` in the API repo because the previous shell call left you there. Canopy eliminates this class of bug by accepting only **semantic** inputs (`feature`, `repo`, alias) and resolving paths internally. You literally cannot `cd` to the wrong place because you never specify a path.
13
+
14
+ Canopy also returns **pre-classified state**: review comments are temporally filtered into `actionable_threads` vs `likely_resolved_threads`, features have computed states like `ready_to_commit` / `drifted` / `awaiting_review`, and every action returns structured `next_actions` you can follow without re-deriving the rules.
15
+
16
+ ## Session start — call `feature_resume` first
17
+
18
+ When the user opens a chat and references a feature alias (a Linear issue ID like `TEAM-101`, a feature name, a slot id like `worktree-1`, or a PR URL/`<repo>#<n>`), call `mcp__canopy__feature_resume(<alias>)` *before* acting on whatever intent they've stated. This is a compound action: it resolves the alias, switches the canonical slot if it's not already there, refreshes from GitHub + Linear, and returns a structured brief with `intent_hints` for the most likely next actions.
19
+
20
+ Patterns that trigger this:
21
+ - A bare alias as the first non-trivial token: `"TEAM-101"`, `"TEAM-101, let's address comments"`, `"jump into auth-flow"`.
22
+ - Explicit return: `"I am back on TEAM-101"`, `"resuming auth-flow"`.
23
+ - Topic-shift to another feature mid-session.
24
+
25
+ Once you have the brief, look at `intent_hints` (sorted by `priority`) and pair the top hint with what the user said. Examples:
26
+
27
+ | User says | Top hints | Your move |
28
+ |---|---|---|
29
+ | "address PR comments" | `address_comments`, `post_drafts` | call `review_comments` with the alias from `intent_hints[0].suggested_args`, then walk through actionable threads |
30
+ | "align with dev" | `align_with_default` | inspect `current_state.branch_position_per_repo`, propose rebase or merge per repo |
31
+ | (nothing specific) | (read top 3 hints) | summarize the brief + hints in 3–5 lines, ask the user which to pursue |
32
+
33
+ Do **not** call `feature_resume` more than once per session per feature unless you've done work that materially changes state (e.g. pushed, resolved a thread, posted replies). The brief is fresh-per-call (refreshes GH/Linear) so repeated calls are wasteful, not stale.
34
+
35
+ `feature_resume` supersedes `mcp__canopy__switch` as the session-start primitive — it does the switch internally plus the full brief. Use `switch` directly only when you want to focus a slot without the overhead of a fresh brief (e.g. mid-session slot rotation).
36
+
37
+ ### Closing out review threads
38
+
39
+ When you finish reviewing a thread:
40
+ - If your commit addresses it: `commit --address <comment_id> --resolve-thread` (or post-process with `mcp__canopy__resolve_thread`).
41
+ - If the comment is wrong: `mcp__canopy__reply_to_thread <thread_id> <body>` with concrete evidence. Pass `resolve_after=true` when the pushback closes the discussion.
42
+
43
+ ## Tool selection — what to use when
44
+
45
+ | What you want to do | Canopy tool | Don't use |
46
+ |---|---|---|
47
+ | What feature should I work on right now? | `mcp__canopy__triage` | per-repo `gh pr list` + manual grouping |
48
+ | Show me everything about a feature | `mcp__canopy__feature_state` | composing many reads yourself |
49
+ | Promote a feature to the canonical slot (the focus primitive) | `mcp__canopy__switch` | `cd repo && git checkout` per repo, or guessing paths |
50
+ | Inspect slot occupancy (dashboard grid) | `mcp__canopy__slots(rich=True)` | hand-rolling `ls .canopy/worktrees/` + per-slot `feature_state` |
51
+ | Pre-warm a cold feature into a slot without changing canonical | `mcp__canopy__slot_load(feature, slot_id?)` | `mcp__canopy__switch` (changes canonical too) |
52
+ | Free a slot without bringing a new feature in | `mcp__canopy__slot_clear(slot_id)` | manual stash + branch checkout |
53
+ | Exchange two slots' occupants | `mcp__canopy__slot_swap(slot_a, slot_b)` | two `slot_load` calls with `replace=True` |
54
+ | Migrate a pre-3.0 workspace to the slot model | `mcp__canopy__migrate_slots` | hand-renaming `.canopy/worktrees/` dirs |
55
+ | Hibernate current focus + start something new | `mcp__canopy__switch(feature, release_current=True)` *(`release_current` is the API param; user-facing label is "hibernate")* | manual stash + checkout dance |
56
+ | Commit across the canonical feature (one message, all repos) | `mcp__canopy__commit(message=...)` *(canonical feature inferred; pass `feature=` for non-canonical)* | `mcp__canopy__run(... 'git commit')` per repo |
57
+ | Push the canonical feature to origin | `mcp__canopy__push()` *(add `set_upstream=True` on first push; the `no_upstream` blocker tells you when)* | `mcp__canopy__run(... 'git push')` per repo |
58
+ | Check whether HEADs match expected | `mcp__canopy__drift` | `cd && git branch --show-current` per repo |
59
+ | Recover from "something is off" — opaque errors, missing paths, stale state | `mcp__canopy__doctor` (then `doctor(fix=True)` if `auto_fixable`) | hunting through stash lists / worktree paths manually |
60
+ | Read PR review comments (temporally filtered) | `mcp__canopy__github_get_pr_comments` | `gh api .../comments` + manual filter |
61
+ | Get PR data (title, decision, draft, ...) | `mcp__canopy__github_get_pr` | `gh pr view --json ...` per repo |
62
+ | Get branch HEAD/divergence/upstream | `mcp__canopy__github_get_branch` | `cd repo && git status -b` |
63
+ | Fetch an issue (Linear / GitHub Issues — provider-agnostic) | `mcp__canopy__issue_get` | direct API |
64
+ | Run a shell command in a specific repo | `mcp__canopy__run` | `cd /path && cmd` (path mistake risk) |
65
+ | Stash dirty changes for a feature | `mcp__canopy__stash_save_feature` | raw `git stash push` |
66
+ | List/restore stashes by feature | `mcp__canopy__stash_list_grouped` / `stash_pop_feature` | `git stash list` + manual filter |
67
+
68
+ ## The daily workflow loop
69
+
70
+ ```
71
+ 1. triage() → pick a feature from the prioritized list
72
+ 2. feature_state(feature) → get current state + next_actions
73
+ 3. follow next_actions[0] → primary CTA (canopy decided what to do next)
74
+ 4. feature_state again → confirm state advanced
75
+ 5. repeat
76
+ ```
77
+
78
+ The `next_actions` array is canopy's recommendation. Trust it unless you have a specific reason not to.
79
+
80
+ ## Aliases
81
+
82
+ Every tool that takes a feature accepts the same alias forms — learn one rule, use everywhere:
83
+ - **Feature name**: `SIN-12-search`
84
+ - **Linear issue ID**: `SIN-12` (resolves through the lane's `linear_issue` field)
85
+ - **Specific PR**: `<repo>#<n>` like `backend#142`
86
+ - **PR URL**: `https://github.com/owner/repo/pull/142`
87
+ - **Specific branch**: `<repo>:<branch>` like `backend:feature/x`
88
+ - **Slot id**: `worktree-1`, `worktree-2`, ... — resolves to the slot's current occupant (Wave 3.0). Lets you say "tell me about worktree-2" without first looking up what's in it.
89
+
90
+ For features whose branch name differs across repos (e.g. `SIN-13-fixes` in backend vs `SIN-13-fixes-v2` in frontend), the lane's `branches` map handles this transparently. You pass the canonical feature alias; canopy resolves per-repo branches.
91
+
92
+ ## Slots (Wave 3.0)
93
+
94
+ Canopy organizes worktrees into **numbered slots** — `.canopy/worktrees/worktree-1/`, `.canopy/worktrees/worktree-2/`, etc. A slot is a stable disk resource; the feature inside it is a transient tenant. When you `switch` from feature X to feature Y, the slot ids don't change — only which feature occupies which slot.
95
+
96
+ **Want to see what's in each slot?** Call `mcp__canopy__slots` (defaults to `rich=True`) — returns the full dashboard grid in a single call: canonical + every warm slot, each with per-repo branch / dirty / ahead-behind / PR / CI / bot threads / linear / computed `feature_state`. Empty slots come back as explicit `null`.
97
+
98
+ The slot vocabulary (CLI + MCP parity):
99
+
100
+ - `mcp__canopy__switch(feature)` — promote a feature to canonical. Slot rotation is automatic. `evict_to=<slot-N>` pins where the outgoing canonical lands; `to_slot=<slot-N>` promotes whatever feature already sits in that slot.
101
+ - `mcp__canopy__slot_load(feature, slot_id?)` — warm a cold feature into a slot **without** changing canonical. Use this to pre-warm a slot before review or before a planned switch. `replace=True` evicts the current occupant to cold first.
102
+ - `mcp__canopy__slot_clear(slot_id)` — evict that slot's occupant to cold (with feature-tagged stash if dirty). The slot itself remains.
103
+ - `mcp__canopy__slot_swap(slot_a, slot_b)` — exchange the occupants. v1 requires identical repo scope on both features.
104
+ - `mcp__canopy__migrate_slots()` — one-shot migration from a pre-3.0 workspace (where worktrees were named after their feature). Idempotent. Run once per workspace if `BlockerError(code='pre_migration')` ever surfaces.
105
+
106
+ **Canonical is the only place to run code.** Slots are passive branch storage. Never `cd` into `.canopy/worktrees/worktree-N/` to launch the app, run tests, or open a dev server — switch the feature into canonical first (`mcp__canopy__switch(feature)`). If you need to *inspect* a warm slot's files without changing focus, that's fine (read-only Read / Grep is harmless), but anything that runs the project should happen against canonical.
107
+
108
+ ## Errors are structured — read them
109
+
110
+ Canopy errors come back as:
111
+ ```json
112
+ {
113
+ "status": "blocked",
114
+ "code": "drift_detected",
115
+ "what": "branches don't match feature lane",
116
+ "expected": {...},
117
+ "actual": {...},
118
+ "fix_actions": [
119
+ {"action": "switch", "args": {"feature": "SIN-12-search"}, "safe": true, "preview": "..."}
120
+ ]
121
+ }
122
+ ```
123
+
124
+ The `fix_actions` array lists recommended recovery steps, ordered most-recommended first. Each entry has `safe: true|false`:
125
+ - `safe: true` → you can call this directly to recover.
126
+ - `safe: false` → surface to the user before invoking (it might lose work or affect remote state).
127
+
128
+ When you see a `BlockerError`, the first step is to read `fix_actions[0]` and decide whether to follow it.
129
+
130
+ ## Recovery: when canopy itself looks broken
131
+
132
+ If a canopy call returns an unexpected error — `KeyError` from a state read, a "feature not found" for one you just created, a worktree path that should exist but doesn't — call `mcp__canopy__doctor` first. It reports 21 codes across 12 categories of state-file drift + install-staleness (including slot-state checks added in Wave 3.0: `slot_dir_orphan`, `slot_entry_orphan`, `slot_branch_mismatch`, `slot_detached_head`), each with `code`, `severity`, `expected`/`actual`, and an `auto_fixable` flag.
133
+
134
+ - `summary.errors == 0` → not a state problem; investigate the original error normally.
135
+ - Errors present, mostly `auto_fixable: true` → call `doctor(fix=True)`; report `fixed`/`skipped` to the user.
136
+ - `auto_fixable: false` (e.g., `features_unknown_repo`, `branches_missing`, `cli_stale`) → surface the issue's `fix_action` text. The human needs to decide.
137
+
138
+ The `mcp__canopy__version` tool returns `{cli_version, mcp_version, schema_version}` for the same handshake.
139
+
140
+ ## Customizing canopy for this workspace
141
+
142
+ If the user wants canopy to behave differently here — *"use ruff for preflight"*, *"track CodeRabbit and Korbit as bots"*, *"the api repo runs `uv run pytest tests/fast` before commits"* — that's a **canopy.toml augment**. Suggest invoking the `augment-canopy` skill, which knows the schema and how to mutate the file safely. Install it with `canopy setup-agent --skill augment-canopy` if it isn't already.
143
+
144
+ ## Cross-session memory (Historian)
145
+
146
+ Each feature has a persistent memory file at `<workspace>/.canopy/memory/<feature>.md` that survives session boundaries. When you call `mcp__canopy__switch(feature)`, the response includes a `memory: <markdown>` field — read it first before re-deriving anything.
147
+
148
+ Three sections in the memory: **Resolutions log** (per-comment outcomes — never compacted), **PR context** (one block per PR), and **Sessions** (newest first; older sessions get trimmed by `historian_compact`).
149
+
150
+ What to call when:
151
+
152
+ - `mcp__canopy__historian_decide(feature, decisions=[{title, rationale}, ...])` — after picking an approach, after a pivot, before pausing. Decisions are deduped per-session by title, so it's safe to call repeatedly.
153
+ - `mcp__canopy__historian_pause(feature, reason)` — when stopping work mid-flow. The next session reads it on switch.
154
+ - `mcp__canopy__historian_defer_comment(feature, comment_id, reason)` — when intentionally skipping a review comment for a stated reason.
155
+ - `mcp__canopy__feature_memory(feature)` — re-read the memory at any point in the same session.
156
+ - `mcp__canopy__historian_compact(feature, keep_sessions=5)` — manual trim when the file grows long. Resolutions + PR context are never compacted.
157
+
158
+ Auto-capture from canopy actions (no extra calls needed):
159
+
160
+ - `mcp__canopy__commit(address=...)` records the resolution into memory automatically (mirrors `bot_resolutions.json`).
161
+ - `mcp__canopy__github_get_pr_comments(alias)` records `comment_read` for each actionable thread + `classifier_resolved` for the temporal-classifier output, deduped per-session.
162
+
163
+ If you decided something but forgot to call `historian_decide`, end the turn with a `<historian-decisions>[{"title": "...", "rationale": "..."}, ...]</historian-decisions>` block. A future Stop hook (autopilot) will tail-parse it and persist (deduped against the explicit calls).
164
+
165
+ ## Bot review comments
166
+
167
+ When `mcp__canopy__feature_state` returns state `awaiting_bot_resolution`, only bot nits (CodeRabbit, Korbit, Cubic, etc.) are blocking — humans haven't requested changes. The `summary` splits the actionable count into `actionable_bot_count` and `actionable_human_count` so you can tell which side needs attention.
168
+
169
+ - `mcp__canopy__bot_comments_status(feature)` returns the per-PR rollup: total / resolved / unresolved + per-thread metadata (id, author, file, body preview).
170
+ - `mcp__canopy__commit(message, address=<comment-id>)` (or `canopy commit --address <id>`) auto-suffixes the commit message with the bot comment's title + URL and persists the resolution to `.canopy/state/bot_resolutions.json`. Resolved comments drop out of `actionable_bot_count` on the next `feature_state` call.
171
+ - Address one comment per commit so the resolution log stays granular and the agent's next `bot-status` call has clean per-comment provenance.
172
+
173
+ ## Anti-patterns
174
+
175
+ - ❌ `cd <repo> && git checkout <branch>` — use `mcp__canopy__switch(feature=...)` so all participating repos move together with verification (and the previously-canonical feature evacuates into a warm slot, preserving its work-in-progress).
176
+ - ❌ `cd .canopy/worktrees/worktree-N/<repo> && pnpm dev` (or `pytest`, or any command that runs the project) — worktrees are passive branch storage. Switch the feature into canonical (`mcp__canopy__switch(feature)`) and run there. Read-only inspection (Read / Grep against a warm slot) is fine; execution is not.
177
+ - ❌ Iterating `gh pr list --author @me` per repo and grouping yourself — `mcp__canopy__triage` already groups by feature lane and applies priority tiers.
178
+ - ❌ `cd <repo> && pnpm test` — use `mcp__canopy__run(repo='repo-b', command='pnpm test')`. The shell state from a previous tool call is not yours.
179
+ - ❌ Parsing `gh api .../pulls/{n}/comments` and writing your own "is this resolved" logic — `mcp__canopy__github_get_pr_comments` returns `actionable_threads` vs `likely_resolved_threads` already.
180
+ - ❌ Calling `git status` in each repo and synthesizing what's dirty/clean — `mcp__canopy__feature_state(feature)` returns this aggregated, plus computed state and next_actions.
181
+ - ❌ Running `git stash push` when there's a feature context — use `mcp__canopy__stash_save_feature(feature, message)` so stashes get tagged and groupable.
182
+ - ❌ `mcp__canopy__run(repo='...', command='git commit ...')` to commit one repo at a time — use `mcp__canopy__commit(message=...)` so the whole canonical feature commits with one message and the wrong-branch / hooks-failed cases come back classified.
183
+ - ❌ `mcp__canopy__run(repo='...', command='git push')` per repo — use `mcp__canopy__push()`. First push needs `set_upstream=True`; the `no_upstream` blocker tells you when (and the fix-action carries the same args + `set_upstream=True` so you can retry mechanically).
184
+
185
+ ## When canopy doesn't apply
186
+
187
+ Use raw `Bash`, `Read`, `Edit` etc. as normal for:
188
+ - Reading and editing source files (canopy doesn't wrap these)
189
+ - Workspace not under canopy management (no `canopy.toml`)
190
+ - Operations on repos not registered in `canopy.toml`
191
+ - One-off utilities that don't need path resolution (ls, find, etc., outside any canopy repo)
canopy/cli/__init__.py ADDED
File without changes