Skip to content

feat: repo-scoping (brief auto-scope + viewer auto-focus), reconcile one-click apply, dedupe-tiers#370

Merged
evemcgivern merged 13 commits into
mainfrom
dev
Jun 16, 2026
Merged

feat: repo-scoping (brief auto-scope + viewer auto-focus), reconcile one-click apply, dedupe-tiers#370
evemcgivern merged 13 commits into
mainfrom
dev

Conversation

@evemcgivern

Copy link
Copy Markdown
Contributor

Deploy of the cwd-aware repo-scoping feature pair plus several CLI/viewer improvements bundled since the last release.

Highlights

Repo scoping (cwd-aware) — #358 / #357

  • which-repo resolver maps a directory to a configured repo (local clone path first, then git remote) — the shared substrate both surfaces use.
  • CLI: brief auto-scopes to the repo of the cwd when --repo is omitted (one-line banner; --repo=all for the full view; brief_auto_scope: false opt-out). Archived-reopen callouts are scoped to the same repo.
  • Viewer: the Tracks lens auto-focuses on the open workspace folder's repo (by GitHub slug), with a sticky manual override, re-arm on folder change, and a workPlan.autoFocusRepo opt-out.

Reconcile one-click apply — #221

  • The viewer's Check Label Drift preview now offers an Apply reconcile action that performs the write through the public-repo leak guard, instead of sending you to a terminal.

dedupe-tiers — #359 / #361

  • New dedupe-tiers CLI command removes private track copies a shared .work-plan/ twin supersedes (refuses any whose private copy holds issue refs the shared one lacks). The viewer surfaces a read-only ⚠ tier-duplicate advisory naming the command.

CI — #5

  • install.sh is now smoke-tested on Linux in CI (help, real install, --target override, work_plan.py --help).

Chore

  • Removed a dead variable in drift detection and pinned the intentional CLOSED-broad / OPEN-narrow asymmetry with tests.

Surfaces published this deploy

  • npm @stylusnexus/work-plan (CLI changed).
  • VS Code stylusnexus.work-plan-viewer 0.13.0 (Marketplace + Open VSX).
  • agent-plugins catalog repinned to the new tag.

CLI floor for the viewer unchanged (≥ 2026.06.15); all new viewer features degrade gracefully on an older CLI.

evemcgivern and others added 13 commits June 15, 2026 14:52
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…acks (#359) (#360)

Promoting a track to the shared tier normally moves its private original out,
but bulk/manual promotion — or a failed unlink during push-track — leaves the
private copy behind. discover_tracks then resolves the collision ("using
shared") but warns "exists in both shared and private" on every invocation with
no built-in cleanup path; the only fix was hand-deleting.

- lib/tracks.py: add find_tier_duplicates() (returns colliding (shared,private)
  pairs across active + archived tiers without warning/dropping) and
  issue_refs() (frontmatter github.issues ∪ body #NNNN). Make both existing
  "exists in both" warnings actionable (point at `dedupe-tiers`).
- commands/dedupe_tiers.py: new `dedupe-tiers [--repo] [--apply]`. Dry-run
  report by default; --apply removes private orphans whose issue refs are a
  subset of the shared twin's, and REFUSES any with refs the shared lacks (no
  silent data loss). Deletion lands in notes_root and the dispatcher's
  auto-commit makes it undoable.
- hygiene: insert a report-only dedupe-tiers step (now 4 steps).

Docs (README) land separately on dev.

Tests: issue_refs union/non-int; pairing only colliding tracks; dry-run keeps
files; --apply removes subset orphan; --apply keeps diverged orphan; repo
filter; usage guard.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(export): emit tier_duplicates in the JSON read surface (#361)

The viewer is otherwise blind to shared/private tier duplicates —
discover_tracks drops the private copy with a stderr-only WARN that the
extension never reads. Add an additive top-level tier_duplicates array so the
viewer can surface them as a read-only health signal:

  {repo, folder, name, shared_path, private_path, safe}

safe = issue_refs(private) ⊆ issue_refs(shared), mirroring dedupe-tiers'
no-data-loss invariant so the viewer can distinguish auto-removable orphans
from diverged ones needing manual review. Schema stays 1 (additive, same as
untracked #288). find_tier_duplicates now returns early when cfg has no
notes_root (no private tier ⇒ no duplicates), keeping it safe to call with a
bare cfg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(vscode): surface tier duplicates as a read-only health advisory (#361)

Consume the new export.tier_duplicates field and render a ⚠ advisory row under
each affected repo: 'N tracks duplicated across tiers', with the dedupe-tiers
--repo=<folder> command in the description and a safe-vs-diverged breakdown in
the tooltip. The node has NO command — surfacing only; the destructive cleanup
stays in the CLI, so the viewer never deletes a track file.

- model.ts: TierDuplicate interface + Export.tier_duplicates (additive).
- treeModel.ts: RepoNode.tierDuplicates (populated like untracked) +
  TierDupWarningNode.
- tree.ts: pin the advisory at the top of the repo's children (both empty and
  populated repos); getTreeItem renders it; getParent wires it to the repo.
- treeModel.test.ts: population matched/unmatched + MOCKUP empty default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…363)

The extension's Check Label Drift was preview-only — the draft dumped to
the output channel with no way to act on it, forcing a terminal trip to
apply. After showing the preview it now offers an "Apply reconcile"
action that runs the non-draft reconcile (`--yes`) through executeWrite,
so the public-repo leak-guard still gates the write.

- write.ts: new reconcileApply WriteAction → ["reconcile", "--yes", "--", track]
- extension.ts: lift the draft run out of withProgress so the spinner
  clears before the Apply prompt; on Apply, re-run + refresh the tree
- README: document the apply path

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The unittest matrix runs the CLI on ubuntu-latest but never invokes the
installer, and it downloads the yq binary directly rather than via the
README one-liners. Add a `smoke-install` job that mechanizes the
installer-centric acceptance criteria from #5: `install.sh --help`, a
real install into a scratch target, the `--target` override (Codex
layout), and `work_plan.py --help` — asserting each lands.

Leaves only the genuinely-manual checks on #5: the per-distro
package-manager one-liners (apt/pacman/dnf) and non-Ubuntu distros,
which a GitHub-hosted Ubuntu runner can't prove.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…365)

Phase 1 of the cwd-aware repo-scoping pair. Adds the shared resolver both
the `brief` auto-scope (#358) and the viewer auto-focus (#357) will call,
so the two can't drift on how they map a directory to a configured repo.

- lib/cwd_repo.py: resolve_repo_for_dir(cfg, dir) — local clone path first,
  git origin remote as fallback, local wins the tiebreak; returns None on no
  match / ambiguity / non-repo. Read-only, never raises (bounded _git).
- commands/which_repo.py: `which-repo [--json]` surface for the viewer to
  spawn with cwd=workspaceFolder; human form gates exit 0/1, --json always
  exits 0 with {"key": ...}.
- Registered in both SUBCOMMANDS and DESCRIPTIONS.
- Tests: resolver (local/nested/remote/tiebreak/no-match/non-repo/ambiguous),
  URL normalizer (scp/https/ssh/.git), and dispatch+docs registration.

No user-visible behavior change yet — Phases 2 and 3 wire it into brief and
the viewer.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#358) (#366)

Run `brief` from inside a configured repo's checkout and it now scopes to
that repo automatically, with a one-line banner — instead of showing every
repo and forcing a manual --repo. Uses the Phase-1 resolver (clone path,
then git remote).

- One effective repo_key is computed once and threaded through both the
  track filter and the archived-reopen pass, so they can't diverge.
- `--repo=all` forces the full cross-repo view (and short-circuits
  auto-detect); explicit `--repo=<key>` is unchanged and prints no banner.
- Opt out with `brief_auto_scope: false` in config.yml.
- Auto-scope only engages when the detected repo actually has tracks — the
  convenience never hides tracks you'd otherwise see.
- README: both command tables updated; which-repo documented.

Closes #358.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3 of the cwd-aware repo-scoping pair. When the open workspace folder
is a configured repo, the viewer now defaults the Tracks lens to that repo
(by GitHub slug — the field the repo lens filters on) instead of showing
every repo's issues, whose numbers collide.

- cli.ts: CliRunner gains an optional {cwd} so a probe can target a folder;
  makeSpawnRunner threads it to spawn. New whichRepo(run, cwd) parses
  `which-repo --json`, never throws.
- autofocus.ts (pure, unit-tested): lensShouldApply (a user lens is sticky —
  auto never overrides it) + pickAutoFocusSlug (first folder that resolves to
  a slug; multi-root aware).
- tree.ts: _lensSource + setLens(lens, source="user") so existing call sites
  stay user-marked unchanged; resetLensSource() re-arms on folder change.
- extension.ts: autoFocusRepo() after initial load + on
  onDidChangeWorkspaceFolders; setLens(..., "auto") respects the override.
- Setting workPlan.autoFocusRepo (default true) to opt out.
- vscode/README.md: lens + Configuration docs.

Focus by slug (not config key) per the spec-review HIGH finding. Provider/
extension glue is thin over the pure, tested autofocus + whichRepo functions.

Closes #357.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ry (#368)

The defect scan flagged `looks_open` (F841 — assigned, never used; dead
since the initial commit). It was the CLOSED-side trigger of an early
symmetric draft (`gh CLOSED and looks_open`); the shipped broad check
`not looks_closed` is a strict superset of it, so removing the variable
changes no behavior.

Drift detection is asymmetric on purpose: CLOSED is terminal (broad — a
closed issue whose row doesn't read closed is drift), OPEN is not (narrow —
only an explicit closed marker contradicts it). A symmetric `not looks_open`
open-side would false-positive every in-progress row.

- drift.py: drop the dead var; comment the why of the asymmetry.
- test_drift.py: 4 tests pinning the previously-untested OPEN side +
  ambiguous-body cases, so nobody "restores symmetry" back into noise.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Hand-bump the extension version + Status line ahead of the deploy (they
must move together — the listing's first paragraph has drifted before).

0.13.0 bundles the viewer-side work landing this deploy: repo auto-focus
(#357), one-click Apply on Check Label Drift (#221), and the read-only
tier-duplicate advisory (#361). CLI floor unchanged (>= 2026.06.15); all
three degrade gracefully on an older CLI.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@evemcgivern evemcgivern merged commit ebd6045 into main Jun 16, 2026
18 checks passed
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