feat: proactive auto-slot offer + collision guard, offline heuristic, webview-handler fix#376
Merged
Conversation
…webview handlers (#374) The webview's inline <script> bodies are built as backtick template literals in html.ts. The dep-toggle querySelector used `\"`, which the template literal collapses to a bare `"`, emitting `data-depissue="" + depIssue + ""` — adjacent string literals, a hard SyntaxError. That parse error killed the ENTIRE messaging IIFE, so every webview click handler (track select, focus toggle, and the new #216 graph zoom/pan/export controls) was silently dead. Shipped in #257 (0.9.0); surfaced now via the new graph controls. Fix: single-quote the selector string so no escaping is needed. Add a regression guard that extracts every inline <script> and new Function()-parses it — the old tests asserted on the HTML string but never executed the JS. Verified the guard fails on the buggy line and passes on the fix; reproduced dead/working controls in a real browser harness. 681 tests pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(slot): compare-and-swap membership guard (#241 phase 1) Slotting writes meta["github"]["issues"], and write_file is a blind overwrite — two sessions (or a teammate's pull) racing the same track silently lose one writer's add. Add lib/membership_guard.py: - issues_fingerprint(meta): deterministic sha256[:16] of the sorted issues list ONLY, so unrelated concurrent edits (refresh-md stamping last_touched, handoff rewriting the body) don't trip a false abort. - guarded_membership_write(...): re-reads the file immediately before writing, merges the add/remove delta onto the FRESH frontmatter, and writes back the fresh body unchanged (preserves a concurrent body-only edit). With expect=<fp> it aborts on a membership change since the caller's snapshot, returning {stale, current, ...} instead of clobbering; expect=None never aborts. slot/batch-slot route every write through the guard and accept --expect=<fp>. The re-read+merge is unconditional (strictly safer; identical observable result for a lone writer); only the abort is --expect-gated, so manual slot is unchanged. Gate order is confirm-first (public-repo) then expect-last (staleness, at write time) to minimize the TOCTOU window. In --expect mode advisory notes go to stderr so stdout stays pure JSON for the {stale} signal the viewer parses. Tests: guard unit tests (real temp-file round-trip) incl. body preservation + stale abort; slot --expect match/mismatch/confirm-then-stale ordering. Existing slot/batch-slot/move drivers updated to mock the guard's parse_file/write_file. 1134 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(slot): shared-tier rebase guard before write (#241 phase 2) The fingerprint CAS (phase 1) closes the same-machine race. Shared-tier tracks also travel via git push/pull, so a teammate's pushed plan change is a cross-machine race the CAS alone can't see. Add a rebase guard: - plan_worktree.rebase_onto_origin(worktree, branch): fetch origin/<branch> then rebase the worktree onto it. Returns True when at-or-ahead (rebased, nothing to replay, or branch unpublished); False on conflict — which it ABORTS first, so the worktree is never left half-rebased. Never raises. - membership_guard.shared_rebase_guard(target, cfg): for a shared track pinned to a plan_branch, resolve its worktree and rebase before writing. No-op for private tracks, legacy shared tracks (no plan_branch), or an unresolvable worktree (degrade). Returns (False, reason) only on un-rebasable divergence. slot/batch-slot run the guard right after the public-repo confirm gate: on divergence they emit {needs_rebase, reason, track} and return without writing, so the viewer re-prompts instead of clobbering a diverged shared branch. A clean rebase pulls the teammate's change in; the phase-1 CAS then trips on the now- stale offer and the viewer re-offers against merged state. Tests: rebase clean/unpublished/conflict-abort/git-unavailable; guard private/legacy/clean/divergence/worktree-unavailable; slot {needs_rebase} abort writes nothing. 1144 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(slot): security-review fixes for the shared-tier guard (#241 phase 2) From the /security-review pass on the shared-git path: - MAJOR (1.1): rebase_onto_origin now uses `git rebase --autostash`. The normal flow writes the file then commits, so the worktree's .work-plan/ is routinely dirty at rebase time; plain rebase refused the dirty precondition and reported a spurious {needs_rebase} with a misleading "diverged" reason. Autostash shelves + reapplies local plan edits so a clean rebase succeeds. - MINOR (5.1, surfaced pre-existing): the advisory `gh issue view` milestone check sits between the rebase guard and the write and was un-try/excepted — a missing gh (FileNotFoundError) or non-JSON stdout would crash the command after a successful rebase. Wrapped in slot + batch-slot; the write still completes. Regression test added. - Docs (2.1/2.2/3): reworded the membership_guard docstrings to stop overselling atomicity — it's a check-then-act (not a locked CAS), the shared rebase is best-effort-onto-origin-as-of-last-fetch (cross-machine atomicity relies on non-ff push rejection + rebase-on-next-write), and the body/other frontmatter fields are preserved (only the issues list is replaced) modulo yq re-serialization. Accepted as-is (non-blocking): blanket except in shared_rebase_guard (fail-open is correct for collaborative planning state), worktree cache GC (pre-existing nit). 1145 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(auto-triage): --json scan, batch_id, v2 abstain-first answers (#241 phase 3) Prepares auto-triage to drive the viewer's Suggested bucket: - --json scan mode emits one JSON object on stdout (batch_id + untracked + tracks + prompt + answers path) so the extension captures the batch_id synchronously. Progress moved to stderr to keep stdout pure JSON. - Per-repo cache files (auto_triage.<repo-slug>.json/.answers.json) so two repos don't clobber the single fixed path; legacy fixed names kept when no --repo (back-compat with the terminal flow + existing tests). - batch_id stamped in the batch and echoed by the answers; --apply warns on a mismatch (a stale older-scan answers file) but still applies. - v2 abstain-first answers schema sniffed in --apply: {version,batch_id, suggestions:[{issue,verdict,track,runner_up,confidence,margin,rationale}]}. Only verdict==suggest with non-narrow margin is slotted; abstains/narrow stay untracked. Legacy v1 [{track,issues}] still applies unchanged. Model-authored fields hardened (int-coerce, skip malformed). - Prompt re-pitched for precision (per-issue, abstain-the-default, grounded rationale, top+runner-up) and the batch now carries each track's scope text so matching isn't just slug-string pattern-matching (ai-engineer review). - SKILL.md: write answers atomically (.tmp+rename) since the viewer watches the file live; use v2 + copy the batch_id; prefer abstain. Tests: --json emits batch_id+prompt+scope; v2 applies only clear suggestions (abstain/narrow left untracked); v1 still applies; batch_id mismatch warns. 1149 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(vscode): thread the #241 staleness/rebase guard through executeWrite (phase 4a) Wires the CLI's compare-and-swap + shared-rebase guards (phases 1-2) to the viewer's write path: - slot/batchSlot WriteActions take an optional `expect` fingerprint; actionToArgs emits it as --expect=<fp> before the `--` separator (omitted → unchanged). - WriteOutcome gains {status:"stale", current} and {status:"needsRebase"}. - executeWrite detects the CLI's {stale}/{needs_rebase} JSON on BOTH the first call and the confirmed re-invocation (a private slot never hits the confirm gate; a public one carries both --confirm and --expect and can still come back stale), via a new guardOutcome() helper. Callers re-offer on fresh state instead of treating a declined write as success. Tests: --expect emitted before '--' (slot + batchSlot); back-compat without expect; first-call stale; needs_rebase; confirm-then-stale carrying both flags; normal write not falsely tripped. 686 pass, tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(vscode): auto-slot Suggested bucket + accept/dismiss commands (#241 phase 4b) The visible viewer layer on the #241 CLI foundation. Opt-in via workPlan.autoSlotSuggestions (default off). - Suggest Tracks (on the Untracked bucket / a repo) runs `auto-triage --json`, records the batch_id, relays the AI prompt to the output channel, and fs.watches the CLI-emitted answers_path (verbatim; 300ms debounce, cold-open read, disposed on deactivate). - A "Suggested" sub-bucket (clear margin + confidence >= threshold → one-click Accept) and a "Needs review" sub-bucket (narrow/low-confidence → open-only) nest as the first children of Untracked. Bucketed issues are removed from the plain untracked list so each shows once. Rationale-led labels; confidence in the tooltip (never color-only). sparkle/lightbulb icons, count badges. - Accept computes the CAS fingerprint of the target track's live issue list and slots via executeWrite({kind:"slot"|"batchSlot", expect}); branches on written / cancelled / stale (re-offer) / needsRebase (warn). Reuses confirmPublicWrite, refreshAfterWrite, withWriteProgress, the candidate-track QuickPick. Accept All groups by track; Dismiss / Dismiss All persist to workspaceState (autoSlot.dismissed.<repo>.<n>). Threshold change re-buckets. - New: src/fingerprint.ts (byte-matches the Python issues_fingerprint — cross-language vector verified) + src/suggestions.ts (tolerant v2 parse, batch_id validation, three-tier bucketing, abstain/dismiss exclusion). - package.json: 5 commands (palette-reachable + node menus), 2 config props. - vscode/README.md feature bullet (## Status left for the deploy bump). Built by a delegated implementer, reviewed here: fixed a plain-list/sub-bucket duplication; independently verified the fingerprint cross-language match, tsc clean, and 711 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: README rows for slot --expect + auto-triage --json/v2 (#241) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(auto-triage): offline heuristic suggestion mode (#373) The #241 Suggested bucket only populated when a Claude session wrote the answers. This adds a no-LLM fallback so it works standalone. CLI: - lib/heuristic_triage.py: pure, deterministic scorer. Per untracked issue, scores each candidate track on local signals only (milestone match, track-label overlap incl. the track/<slug> default, title/scope keyword overlap), abstain-first (a track must clear a min score), margin clear/narrow from the top-vs-runner gap, grounded rationale naming the matched signal. - `auto-triage --heuristic` runs the scorer and writes the v2 answers file itself (atomic .tmp+replace, stamped source:"heuristic", same batch_id as the scan), so --apply and the viewer consume it unchanged. Combines with --json. Viewer: - autoTriageScan({heuristic}); a "Suggest Tracks (offline heuristic)" command (shared scan driver with the LLM variant). The CLI writes the answers during the scan, so the watcher's cold read populates the bucket immediately — no session. suggestions.ts threads `source`; the Suggested group shows "· heuristic" + a lower-trust tooltip when source==heuristic. Tests: scorer (milestone/label/keyword/abstain/margin/clamp/malformed); --heuristic writes the answers file with source + correlating batch_id; source threading in parseSuggestions. CLI 1159, VS Code 713, tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
0.13.0 already shipped (2026-06-16), so bump to 0.14.0 — republishing 0.13.0 would no-op on the Marketplace (--skip-duplicate). Prepend the 0.14.0 Status entry: proactive auto-slot suggestions (#241) + offline heuristic (#373) + the critical webview-handler fix (#374). CLI floor unchanged (2026.06.15). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
evemcgivern
added a commit
that referenced
this pull request
Jun 18, 2026
This was referenced Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deploy of the auto-slot feature set plus a critical webview regression fix. Three issues.
feat: proactive auto-slot offer + collision guard (#241)
Offer to slot untracked GitHub issues into existing tracks, with an AI-suggested destination per issue, and the hard collision-prevention the issue requires. Built in 4 phases:
slot/batch-slottake--expect=<fp>(sha256 of the track's issue list); the write re-reads + merges onto fresh frontmatter and aborts{stale}instead of clobbering. Preserves concurrent body/other-field edits.plan_branch, fetch +rebase --autostashonto origin before writing;{needs_rebase}abort on un-rebasable divergence./security-review'd.auto-triage --jsonscan +batch_id+ a v2 abstain-first answers schema (legacy v1 still applies).workPlan.autoSlotSuggestions) — Suggested (one-click Accept) / Needs-review sub-buckets under Untracked; Accept slots through the guard + public-repo modal, branching on stale/needsRebase. 5 commands, fs.watch, dismiss state.feat: offline heuristic suggestion mode (#373)
auto-triage --heuristicscores untracked issues against candidate tracks on local signals (milestone / track-label / title-scope keyword overlap), abstain-first, and writes the v2 answers file itself (source: "heuristic") — so the Suggested bucket works with no Claude session (lower-trust, offline). New viewer command Suggest Tracks (offline heuristic); heuristic suggestions are flagged lower-trust.fix(vscode): webview handlers dead since 0.9.0 (#374)
Escaped quotes in an inline-script template literal collapsed to a
SyntaxError, killing the entire messaging IIFE — every webview click handler (track select, focus toggle, the 0.12.0 graph zoom/pan/export controls) was silently dead from 0.9.0 onward. Fixed; added a parse-guard test thatnew Function()-checks every inline script so this class fails CI.Ships
@stylusnexus/work-plan(CLI: the guard,auto-triage --json/--heuristic, v2 answers).2026.06.15).Tests: CLI 1159 · VS Code 715 · tsc clean. Cross-language CAS fingerprint verified.