Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 164 additions & 125 deletions README.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions src/canopy/actions/aliases.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""Alias resolution for read primitives.

The agent (and humans) pass a single alias like ``DOC-3029`` to any read
The agent (and humans) pass a single alias like ``TEAM-101`` to any read
tool and canopy figures out what to fetch. Each tool also accepts its
native specific form for direct lookups when the caller already has a
concrete reference.

Supported alias forms:
- Feature alias: feature name (e.g. ``auth-flow``) or Linear issue ID
(e.g. ``DOC-3029``). Resolves via ``FeatureCoordinator._resolve_name``
(e.g. ``TEAM-101``). Resolves via ``FeatureCoordinator._resolve_name``
+ ``features.json`` ``linear_issue`` field.
- PR specific: ``<repo>#<pr_number>`` (e.g. ``docsum-api#1287``) or
a GitHub PR URL.
- Branch specific: ``<repo>:<branch>`` (e.g. ``docsum-api:doc-3029``).
- PR specific: ``<repo>#<pr_number>`` (e.g. ``api#142``) or a GitHub PR
URL.
- Branch specific: ``<repo>:<branch>`` (e.g. ``api:auth-flow``).
- **Slot id:** ``worktree-N`` resolves to the feature currently in that
slot. ``BlockerError(empty_slot)`` when the slot is empty;
``BlockerError(unknown_slot)`` when N is out of range.
Expand Down Expand Up @@ -59,7 +59,7 @@ def resolve_feature(workspace: Workspace, alias: str) -> str:

Step 4 lets single-repo features resolve without an explicit
features.json entry. Without it, queries like ``canopy comments
doc-1002-api-only`` fail when only one repo carries the branch.
auth-flow-api-only`` fail when only one repo carries the branch.
"""
# Step 0: slot-id alias form — must come before _resolve_name, which
# treats unknown strings as implicit feature names.
Expand Down
8 changes: 4 additions & 4 deletions src/canopy/actions/drift.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
can use the same primitive.

v1 assumes ``expected_branch == feature_name`` per repo. Per-repo branch
overrides (e.g., ``doc-3010-UI-fixes`` in api vs ``DOC-3010-UI-fixes-2``
in ui) will be added when the feature lane schema gains per-repo branch
mapping. For now, exact match against feature name.
overrides (e.g., ``auth-flow`` in api vs ``auth-flow-v2`` in ui) will be
added when the feature lane schema gains per-repo branch mapping. For
now, exact match against feature name.
"""
from __future__ import annotations

Expand Down Expand Up @@ -181,7 +181,7 @@ def _compute_feature_drift(lane, heads: dict) -> FeatureDrift:

for repo_name in lane.repos:
# Use lane.branch_for to honor per-repo branch overrides
# (handles cases like doc-3010-UI-fixes vs DOC-3010-UI-fixes-2).
# (handles cases like auth-flow vs auth-flow-v2 across repos).
expected = lane.branch_for(repo_name)
head = heads.get(repo_name)
if head is None:
Expand Down
9 changes: 4 additions & 5 deletions src/canopy/actions/review_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
context budget goes to comprehension, not to figuring out which feedback
is current.

Validated by the user against four real PRs (DOC-3008, DOC-3010,
DOC-3029, DOC-2827) — every comment classified correctly using only
timestamp + path matching, no NLP. Bot threads are NOT filtered by
author here: a ``claude[bot]`` thread may carry the only actionable
feedback, and the temporal heuristic handles staleness regardless.
Uses timestamp + path matching only — no NLP. Bot threads are NOT
filtered by author here: a ``claude[bot]`` thread may carry the only
actionable feedback, and the temporal heuristic handles staleness
regardless.
"""
from __future__ import annotations

Expand Down
2 changes: 1 addition & 1 deletion src/canopy/actions/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ def _branch_for_in_repo(
"""Return the branch name for ``feature`` in ``repo_name``.

Honors the lane's ``branches`` map for per-repo branch overrides
(e.g. doc-3010 in api vs DOC-3010-v2 in ui)."""
(e.g. auth-flow in api vs auth-flow-v2 in ui)."""
from ..features.coordinator import FeatureCoordinator
coord = FeatureCoordinator(workspace)
try:
Expand Down
2 changes: 1 addition & 1 deletion src/canopy/actions/triage.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def _group_by_feature(
branch matches the lane's expected branch *for that repo*
(using the per-repo ``branches`` override map when set, else
feature name). This is what groups
``doc-1003-fixes`` (api) + ``DOC-1003-fixes-v2`` (ui) into one
``auth-flow`` (api) + ``auth-flow-v2`` (ui) into one
feature lane.
3. Remaining (repo, branch) pairs that weren't consumed become
implicit features: each branch becomes a feature, multi-repo
Expand Down
6 changes: 3 additions & 3 deletions src/canopy/agent_setup/skills/using-canopy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ Canopy also returns **pre-classified state**: review comments are temporally fil

## Session start — call `feature_resume` first

When the user opens a chat and references a feature alias (a Linear ID like `DOC-2226`, 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.
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.

Patterns that trigger this:
- A bare alias as the first non-trivial token: `"DOC-2226"`, `"DOC-2226, let's address comments"`, `"jump into auth-flow"`.
- Explicit return: `"I am back on DOC-2226"`, `"resuming auth-flow"`.
- A bare alias as the first non-trivial token: `"TEAM-101"`, `"TEAM-101, let's address comments"`, `"jump into auth-flow"`.
- Explicit return: `"I am back on TEAM-101"`, `"resuming auth-flow"`.
- Topic-shift to another feature mid-session.

Once you have the brief, look at `intent_hints` (sorted by `priority`) and pair the top hint with what the user said. Examples:
Expand Down
6 changes: 3 additions & 3 deletions src/canopy/features/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ class FeatureLane:
# Optional per-repo branch override. When unset, ``branch_for(repo)``
# returns the feature name (the historical default). When set,
# consumers should always go through ``branch_for`` to get the right
# branch name per repo. Used for cases like docsum's
# ``doc-3010-UI-fixes`` (api) vs ``DOC-3010-UI-fixes-2`` (ui) where
# the same feature has different branch names per repo.
# branch name per repo. Used for cases like ``auth-flow`` (api) vs
# ``auth-flow-v2`` (ui) where the same feature has different branch
# names per repo.
branches: dict[str, str] = field(default_factory=dict)

# Populated at query time (not persisted)
Expand Down
4 changes: 2 additions & 2 deletions src/canopy/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,8 @@ def github_get_pr(alias: str) -> dict:
"""Fetch PR data per repo for an alias.

Accepts:
- Feature alias (e.g. 'DOC-3029') -> all PRs in the lane
- <repo>#<pr_number> (e.g. 'docsum-api#1287') -> specific PR
- Feature alias (e.g. 'TEAM-101') -> all PRs in the lane
- <repo>#<pr_number> (e.g. 'api#142') -> specific PR
- GitHub PR URL -> specific PR
"""
from ..actions.reads import github_get_pr as _impl
Expand Down
Loading