sequant 2.9.0 → 2.10.0
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +14 -3
- package/dist/bin/cli.js +97 -17
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +56 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +20 -7
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +75 -10
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +215 -390
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +3 -1
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/src/commands/assess-render.d.ts +23 -0
- package/dist/src/commands/assess-render.js +60 -0
- package/dist/src/commands/doctor.js +8 -13
- package/dist/src/commands/init.js +13 -1
- package/dist/src/commands/locks.js +2 -0
- package/dist/src/commands/logs.js +14 -5
- package/dist/src/commands/merge.js +154 -2
- package/dist/src/commands/ready-tui-adapter.js +6 -1
- package/dist/src/commands/ready.d.ts +25 -3
- package/dist/src/commands/ready.js +39 -13
- package/dist/src/commands/resume.d.ts +113 -0
- package/dist/src/commands/resume.js +193 -0
- package/dist/src/commands/run-display.js +37 -4
- package/dist/src/commands/run-flags.d.ts +42 -1
- package/dist/src/commands/run-flags.js +53 -1
- package/dist/src/commands/run-progress.js +26 -1
- package/dist/src/commands/run.js +11 -10
- package/dist/src/commands/state.js +7 -0
- package/dist/src/commands/status.d.ts +9 -0
- package/dist/src/commands/status.js +24 -1
- package/dist/src/commands/sync.js +90 -19
- package/dist/src/commands/update.js +28 -5
- package/dist/src/lib/ac-parser.d.ts +14 -0
- package/dist/src/lib/ac-parser.js +99 -5
- package/dist/src/lib/assess/index.d.ts +10 -0
- package/dist/src/lib/assess/index.js +9 -0
- package/dist/src/lib/assess/renderer.d.ts +64 -0
- package/dist/src/lib/assess/renderer.js +481 -0
- package/dist/src/lib/assess/types.d.ts +224 -0
- package/dist/src/lib/assess/types.js +241 -0
- package/dist/src/lib/ci/outputs.d.ts +15 -2
- package/dist/src/lib/ci/outputs.js +17 -5
- package/dist/src/lib/cli-flags.d.ts +33 -0
- package/dist/src/lib/cli-flags.js +46 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +62 -0
- package/dist/src/lib/errors.d.ts +78 -6
- package/dist/src/lib/errors.js +133 -15
- package/dist/src/lib/locks/index.d.ts +3 -3
- package/dist/src/lib/locks/index.js +2 -2
- package/dist/src/lib/locks/lock-manager.d.ts +20 -2
- package/dist/src/lib/locks/lock-manager.js +47 -3
- package/dist/src/lib/locks/types.d.ts +34 -3
- package/dist/src/lib/locks/types.js +16 -0
- package/dist/src/lib/mcp-config.d.ts +45 -0
- package/dist/src/lib/mcp-config.js +77 -1
- package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
- package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
- package/dist/src/lib/merge-check/command-result.d.ts +36 -0
- package/dist/src/lib/merge-check/command-result.js +56 -0
- package/dist/src/lib/merge-check/index.d.ts +4 -0
- package/dist/src/lib/merge-check/index.js +1 -1
- package/dist/src/lib/merge-check/report.js +5 -1
- package/dist/src/lib/merge-check/types.d.ts +9 -0
- package/dist/src/lib/merge-check/watch.d.ts +121 -0
- package/dist/src/lib/merge-check/watch.js +215 -0
- package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
- package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
- package/dist/src/lib/settings.d.ts +14 -0
- package/dist/src/lib/settings.js +61 -8
- package/dist/src/lib/shutdown.d.ts +42 -2
- package/dist/src/lib/shutdown.js +75 -5
- package/dist/src/lib/skills-check.d.ts +26 -0
- package/dist/src/lib/skills-check.js +31 -0
- package/dist/src/lib/stacks.d.ts +110 -1
- package/dist/src/lib/stacks.js +244 -5
- package/dist/src/lib/templates.d.ts +74 -0
- package/dist/src/lib/templates.js +171 -16
- package/dist/src/lib/test-tautology-detector.d.ts +9 -2
- package/dist/src/lib/test-tautology-detector.js +153 -12
- package/dist/src/lib/version-check.d.ts +31 -0
- package/dist/src/lib/version-check.js +45 -2
- package/dist/src/lib/workflow/batch-executor.d.ts +125 -6
- package/dist/src/lib/workflow/batch-executor.js +477 -44
- package/dist/src/lib/workflow/chain-resume.d.ts +13 -11
- package/dist/src/lib/workflow/chain-resume.js +24 -20
- package/dist/src/lib/workflow/completed-status.d.ts +52 -0
- package/dist/src/lib/workflow/completed-status.js +57 -0
- package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
- package/dist/src/lib/workflow/config-resolver.js +54 -2
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
- package/dist/src/lib/workflow/drivers/aider.js +2 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +14 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +20 -2
- package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
- package/dist/src/lib/workflow/git-diff-utils.js +77 -4
- package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
- package/dist/src/lib/workflow/heartbeat.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +44 -1
- package/dist/src/lib/workflow/log-writer.js +75 -6
- package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
- package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
- package/dist/src/lib/workflow/phase-executor.js +457 -52
- package/dist/src/lib/workflow/phase-registry.js +2 -1
- package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
- package/dist/src/lib/workflow/platforms/github.js +152 -0
- package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
- package/dist/src/lib/workflow/qa-cache.js +22 -9
- package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
- package/dist/src/lib/workflow/ready-gate.js +25 -9
- package/dist/src/lib/workflow/run-log-schema.d.ts +14 -0
- package/dist/src/lib/workflow/run-log-schema.js +47 -1
- package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
- package/dist/src/lib/workflow/run-orchestrator.js +141 -20
- package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/skills-preflight.js +112 -0
- package/dist/src/lib/workflow/state-cleanup.js +9 -2
- package/dist/src/lib/workflow/state-manager.d.ts +33 -0
- package/dist/src/lib/workflow/state-manager.js +83 -0
- package/dist/src/lib/workflow/state-schema.d.ts +26 -0
- package/dist/src/lib/workflow/state-schema.js +47 -0
- package/dist/src/lib/workflow/types.d.ts +126 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +28 -2
- package/dist/src/lib/workflow/worktree-manager.js +102 -23
- package/dist/src/mcp/tools/run.d.ts +9 -1
- package/dist/src/mcp/tools/run.js +20 -1
- package/dist/src/mcp/tools/status.js +6 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
- package/dist/src/ui/tui/ElapsedTimer.js +12 -10
- package/dist/src/ui/tui/IssueBox.js +10 -2
- package/dist/src/ui/tui/row-cap.js +2 -1
- package/dist/src/ui/tui/theme.d.ts +7 -0
- package/dist/src/ui/tui/theme.js +9 -0
- package/package.json +8 -7
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +20 -7
- package/templates/hooks/pre-tool.sh +75 -10
- package/templates/scripts/cleanup-worktree.sh +182 -19
- package/templates/scripts/new-feature.sh +264 -16
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +215 -390
- package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +26 -6
- package/templates/skills/fullsolve/SKILL.md +3 -1
- package/templates/skills/loop/SKILL.md +38 -2
- package/templates/skills/merger/SKILL.md +2 -2
- package/templates/skills/qa/SKILL.md +337 -57
- package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
- package/templates/skills/qa/references/call-site-review.md +2 -2
- package/templates/skills/qa/references/code-review-checklist.md +2 -2
- package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/templates/skills/qa/references/quality-gates.md +21 -7
- package/templates/skills/qa/references/testing-requirements.md +1 -1
- package/templates/skills/qa/scripts/quality-checks.sh +11 -11
- package/templates/skills/release/SKILL.md +1 -1
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +2 -0
- package/templates/skills/test/SKILL.md +1 -1
- package/dist/src/lib/phase-spinner.d.ts +0 -146
- package/dist/src/lib/phase-spinner.js +0 -255
- package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
- package/dist/src/lib/workflow/pr-operations.js +0 -326
- package/dist/src/lib/workflow/run-summary.d.ts +0 -36
- package/dist/src/lib/workflow/run-summary.js +0 -142
|
@@ -60,7 +60,23 @@ mkdir -p "$_LOG_DIR" 2>/dev/null || _LOG_DIR="${_TMPDIR}"
|
|
|
60
60
|
|
|
61
61
|
TIMING_LOG="${_LOG_DIR}/claude-timing.log"
|
|
62
62
|
HOOK_LOG="${_LOG_DIR}/claude-hook.log"
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
# Parallel-group marker naming (#881). The scheme lives in one sourced helper
|
|
65
|
+
# next to this hook so the writer (/exec skill) and the readers (this hook and
|
|
66
|
+
# post-tool.sh) cannot drift. PARALLEL_MARKER_PREFIX is project-scoped, so a
|
|
67
|
+
# parallel group in another project no longer collides in the shared temp dir.
|
|
68
|
+
_MARKER_HELPER="$(dirname "${BASH_SOURCE[0]:-$0}")/parallel-marker.sh"
|
|
69
|
+
if [[ -f "$_MARKER_HELPER" ]]; then
|
|
70
|
+
# shellcheck source=parallel-marker.sh disable=SC1091
|
|
71
|
+
source "$_MARKER_HELPER"
|
|
72
|
+
PARALLEL_MARKER_PREFIX="$(parallel_marker_prefix)"
|
|
73
|
+
PARALLEL_MARKER_PROJECT_ROOT="$(parallel_marker_project_root)"
|
|
74
|
+
else
|
|
75
|
+
# Helper missing (unexpected): fall back to the pre-#881 global prefix so the
|
|
76
|
+
# hook still functions, accepting the old cross-project collision risk.
|
|
77
|
+
PARALLEL_MARKER_PREFIX="${_TMPDIR}/claude-parallel-"
|
|
78
|
+
PARALLEL_MARKER_PROJECT_ROOT=""
|
|
79
|
+
fi
|
|
64
80
|
|
|
65
81
|
# === HELPERS ===
|
|
66
82
|
|
|
@@ -237,17 +253,20 @@ if [[ "$TOOL_NAME" == "Bash" ]]; then
|
|
|
237
253
|
fi
|
|
238
254
|
|
|
239
255
|
# === AGENT ID DETECTION ===
|
|
240
|
-
# For parallel agents, detect group ID from marker files
|
|
241
|
-
#
|
|
256
|
+
# For parallel agents, detect group ID from marker files. The glob is scoped to
|
|
257
|
+
# the current project's marker prefix (#881) so a foreign project's marker never
|
|
258
|
+
# labels this session's timing rows.
|
|
242
259
|
AGENT_ID=""
|
|
260
|
+
_MARKER_BASE=$(basename "$PARALLEL_MARKER_PREFIX")
|
|
243
261
|
# Find marker files using find (works in both bash and zsh)
|
|
244
262
|
while IFS= read -r marker; do
|
|
245
263
|
if [[ -n "$marker" && -f "$marker" ]]; then
|
|
246
|
-
# Extract group ID
|
|
247
|
-
AGENT_ID=$(basename "$marker"
|
|
264
|
+
# Extract group ID: strip the project-scoped prefix and the suffix.
|
|
265
|
+
AGENT_ID=$(basename "$marker" .marker)
|
|
266
|
+
AGENT_ID=${AGENT_ID#"$_MARKER_BASE"}
|
|
248
267
|
break
|
|
249
268
|
fi
|
|
250
|
-
done < <(find "${_TMPDIR}" -maxdepth 1 -name "
|
|
269
|
+
done < <(find "${_TMPDIR}" -maxdepth 1 -name "${_MARKER_BASE}*.marker" 2>/dev/null)
|
|
251
270
|
|
|
252
271
|
# === TIMING START ===
|
|
253
272
|
# Include agent ID in log format if available (AC-4)
|
|
@@ -617,6 +636,36 @@ if [[ "$TOOL_NAME" == "Bash" ]] && seg_match 'git commit'; then
|
|
|
617
636
|
fi
|
|
618
637
|
fi
|
|
619
638
|
|
|
639
|
+
# Resolve a path to its canonical form, tolerating components that do not exist
|
|
640
|
+
# yet. Plain `realpath` fails outright on a missing path, and BSD/macOS realpath
|
|
641
|
+
# has no `-m`. The old code fell back to the raw string on failure, which broke
|
|
642
|
+
# the comparison below whenever the worktree path crossed a symlink: a Write
|
|
643
|
+
# creating a NEW file resolved to nothing (raw "/tmp/.../x.ts") while the
|
|
644
|
+
# existing worktree dir resolved through the symlink ("/private/tmp/.../"), so
|
|
645
|
+
# the prefix test failed and writes *inside* the worktree were blocked. macOS
|
|
646
|
+
# hits this every time, since /tmp is a symlink to /private/tmp.
|
|
647
|
+
#
|
|
648
|
+
# Walk up to the nearest existing ancestor, canonicalise that, then re-append
|
|
649
|
+
# the missing tail so both sides normalise identically.
|
|
650
|
+
resolve_path_allow_missing() {
|
|
651
|
+
local p="$1"
|
|
652
|
+
local suffix=""
|
|
653
|
+
|
|
654
|
+
while [[ ! -e "$p" && "$p" != "/" && "$p" != "." && -n "$p" ]]; do
|
|
655
|
+
suffix="/$(basename "$p")$suffix"
|
|
656
|
+
local parent
|
|
657
|
+
parent="$(dirname "$p")"
|
|
658
|
+
[[ "$parent" == "$p" ]] && break
|
|
659
|
+
p="$parent"
|
|
660
|
+
done
|
|
661
|
+
|
|
662
|
+
local resolved
|
|
663
|
+
resolved="$(realpath "$p" 2>/dev/null || echo "$p")"
|
|
664
|
+
# Avoid a doubled slash when the resolved ancestor is "/"
|
|
665
|
+
[[ "$resolved" == "/" ]] && resolved=""
|
|
666
|
+
echo "${resolved}${suffix}"
|
|
667
|
+
}
|
|
668
|
+
|
|
620
669
|
# === WORKTREE PATH ENFORCEMENT ===
|
|
621
670
|
# Enforces that file operations stay within the designated worktree
|
|
622
671
|
# Sources for worktree path (in priority order):
|
|
@@ -631,10 +680,21 @@ if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
|
|
|
631
680
|
EXPECTED_WORKTREE="$SEQUANT_WORKTREE"
|
|
632
681
|
fi
|
|
633
682
|
|
|
634
|
-
# Priority 2: Fall back to parallel marker file
|
|
683
|
+
# Priority 2: Fall back to parallel marker file. The glob prefix is already
|
|
684
|
+
# scoped to this project's hash (#881, AC-2), so a foreign project's marker
|
|
685
|
+
# is not matched here. As defense-in-depth (AC-3), also verify the marker's
|
|
686
|
+
# stored owning project root (line 2) matches the current one before trusting
|
|
687
|
+
# it — a stale or hand-placed marker with a colliding name is ignored rather
|
|
688
|
+
# than allowed to redirect enforcement.
|
|
635
689
|
if [[ -z "$EXPECTED_WORKTREE" ]]; then
|
|
636
690
|
for marker in "${PARALLEL_MARKER_PREFIX}"*.marker; do
|
|
637
691
|
if [[ -f "$marker" ]]; then
|
|
692
|
+
MARKER_PROJECT_ROOT=$(sed -n '2p' "$marker" 2>/dev/null || true)
|
|
693
|
+
if [[ -n "$MARKER_PROJECT_ROOT" && -n "$PARALLEL_MARKER_PROJECT_ROOT" \
|
|
694
|
+
&& "$MARKER_PROJECT_ROOT" != "$PARALLEL_MARKER_PROJECT_ROOT" ]]; then
|
|
695
|
+
# Foreign owner — ignore this marker entirely.
|
|
696
|
+
continue
|
|
697
|
+
fi
|
|
638
698
|
# Read expected worktree path from marker file (first line)
|
|
639
699
|
EXPECTED_WORKTREE=$(head -1 "$marker" 2>/dev/null || true)
|
|
640
700
|
break
|
|
@@ -660,9 +720,14 @@ if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
|
|
|
660
720
|
fi
|
|
661
721
|
|
|
662
722
|
if [[ -n "$FILE_PATH" ]]; then
|
|
663
|
-
# Resolve to absolute
|
|
664
|
-
|
|
665
|
-
|
|
723
|
+
# Resolve to canonical absolute paths for consistent comparison.
|
|
724
|
+
# resolve_path_allow_missing (defined above) tolerates a file that
|
|
725
|
+
# does not exist yet; a bare `realpath` fallback-to-raw-string broke
|
|
726
|
+
# new-file Writes whenever the worktree path crossed a symlink
|
|
727
|
+
# (macOS /tmp -> /private/tmp): the new file stayed raw while the
|
|
728
|
+
# existing worktree dir resolved, so the prefix test always failed.
|
|
729
|
+
REAL_FILE_PATH=$(resolve_path_allow_missing "$FILE_PATH")
|
|
730
|
+
REAL_WORKTREE=$(resolve_path_allow_missing "$EXPECTED_WORKTREE")
|
|
666
731
|
|
|
667
732
|
# Check if file path is within the expected worktree
|
|
668
733
|
if [[ "$REAL_FILE_PATH" != "$REAL_WORKTREE"* ]]; then
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
3
|
# Clean up a worktree after PR is merged
|
|
4
|
-
# Usage: ./scripts/cleanup-worktree.sh [flags] <branch
|
|
4
|
+
# Usage: ./scripts/cleanup-worktree.sh [flags] <branch>
|
|
5
|
+
# <branch> accepts a bare issue number (`123`), a QUOTED glob
|
|
6
|
+
# (`'feature/123-*'`), or the full branch name — all resolve to the same
|
|
7
|
+
# worktree and, since #838, to the same underlying branch ref.
|
|
8
|
+
# A bare number is ANCHORED to the issue-number position (#844): `838` resolves
|
|
9
|
+
# `feature/838-*` and never `feature/1838-other-work`. When the argument matches
|
|
10
|
+
# more than one worktree branch the script lists every candidate and exits
|
|
11
|
+
# non-zero instead of silently deleting the first.
|
|
12
|
+
# Quote the glob: this script resolves the pattern itself, so it must arrive
|
|
13
|
+
# unexpanded. zsh (the default macOS shell) aborts on an unmatched unquoted
|
|
14
|
+
# glob before the script ever runs; bash would pass it through literally.
|
|
5
15
|
# Example: ./scripts/cleanup-worktree.sh feature/123-add-user-dashboard
|
|
6
16
|
#
|
|
7
17
|
# The remote branch is deleted ONLY when the branch's PR is MERGED (the
|
|
@@ -28,7 +38,20 @@ NC='\033[0m'
|
|
|
28
38
|
# Print CLI usage/help (kept in sync with the header comment above).
|
|
29
39
|
print_usage() {
|
|
30
40
|
cat <<'USAGE'
|
|
31
|
-
Usage: ./scripts/cleanup-worktree.sh [flags] <branch
|
|
41
|
+
Usage: ./scripts/cleanup-worktree.sh [flags] <branch>
|
|
42
|
+
|
|
43
|
+
<branch> may be any of (all resolve to the same worktree):
|
|
44
|
+
838 a bare issue number — anchored to the issue-number
|
|
45
|
+
position, so it never matches feature/1838-other-work
|
|
46
|
+
'feature/838-*' a glob — MUST be quoted (see below)
|
|
47
|
+
feature/838-fix-thing the full branch name
|
|
48
|
+
|
|
49
|
+
An argument that matches more than one worktree branch is rejected: the script
|
|
50
|
+
lists every candidate and exits non-zero rather than deleting the first.
|
|
51
|
+
|
|
52
|
+
Quote the glob form. This script resolves the pattern itself, so it must
|
|
53
|
+
arrive unexpanded; zsh aborts on an unmatched unquoted glob before the
|
|
54
|
+
script ever runs.
|
|
32
55
|
|
|
33
56
|
Clean up a feature worktree. The remote branch is deleted ONLY when the
|
|
34
57
|
branch's PR is MERGED (the documented post-merge contract) or when an
|
|
@@ -101,17 +124,149 @@ if [ -z "$BRANCH_NAME" ]; then
|
|
|
101
124
|
exit 1
|
|
102
125
|
fi
|
|
103
126
|
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
# Resolve $BRANCH_NAME to exactly one worktree AND its full branch ref. Both are
|
|
128
|
+
# captured because every downstream consumer (`gh pr list --head`, `git branch
|
|
129
|
+
# -D`, `git push origin --delete`) needs the EXACT ref, or it silently no-ops on
|
|
130
|
+
# a shorthand and the script prints "Cleanup complete!" while leaving the branch
|
|
131
|
+
# behind (the #838 defect $RESOLVED_BRANCH closes).
|
|
132
|
+
#
|
|
133
|
+
# Matching is TIERED and anchored (#844). The old matcher was a single
|
|
134
|
+
# unanchored substring test with first-match-wins and no ambiguity check:
|
|
135
|
+
#
|
|
136
|
+
# case "$_wt_branch" in *"$BRANCH_NAME"* | $BRANCH_NAME)
|
|
137
|
+
#
|
|
138
|
+
# so `838` matched `feature/1838-other-work`, and whichever worktree porcelain
|
|
139
|
+
# emitted first was torn down — local AND remote, since #838. Porcelain sorts
|
|
140
|
+
# linked worktrees lexicographically by basename (`1838- < 838-`), so the
|
|
141
|
+
# mis-resolution was deterministic, not a race. The tiers below anchor bare
|
|
142
|
+
# numbers to the issue-number position and make any 2+-way match fatal.
|
|
143
|
+
#
|
|
144
|
+
# Tiers (first tier with any match decides; 2+ matches in a tier is fatal):
|
|
145
|
+
# 1. exact — branch == argument. Guards coexisting feature/838-fix and
|
|
146
|
+
# feature/838-fix-more (both legal via new-feature.sh's title
|
|
147
|
+
# truncation): the full name must resolve, not trip ambiguity.
|
|
148
|
+
# 2. anchored — branch's last path segment == argument; plus, when the
|
|
149
|
+
# argument is all digits, the sequant issue-number branch shapes
|
|
150
|
+
# parseIssueNumberFromBranch (worktree-discovery.ts) recognizes:
|
|
151
|
+
# feature/<N>-*, feature/<N>, issue-<N>, <N>-*.
|
|
152
|
+
# 3. glob — only when the argument itself contains * ? or [ : a plain
|
|
153
|
+
# `case` glob over the branch ref. Keeps the documented, quoted
|
|
154
|
+
# `feature/<issue>-*` form working, now behind the ambiguity gate.
|
|
155
|
+
#
|
|
156
|
+
# bash 3.2 (macOS floor): no arrays/mapfile. Candidates live in a
|
|
157
|
+
# newline-delimited string of "<path>\t<branch>"; each tier re-scans it via
|
|
158
|
+
# process substitution (which keeps loop-set counters in the current shell — a
|
|
159
|
+
# pipe would lose them to a subshell). `while IFS= read -r` preserves whitespace
|
|
160
|
+
# in paths (the #575 hardening).
|
|
161
|
+
_TAB=$(printf '\t')
|
|
162
|
+
|
|
163
|
+
# Collect candidate worktrees: porcelain stanzas carrying a branch ref, minus
|
|
164
|
+
# the main worktree (first entry — without this, `'*'`/`'feature/*'` could
|
|
165
|
+
# resolve to and `git worktree remove` it) and minus the `exec-agent-*`
|
|
166
|
+
# sub-worktree branches parallel isolation leaves behind (real porcelain
|
|
167
|
+
# candidates, per this script's own reaping below).
|
|
168
|
+
CANDIDATES=""
|
|
169
|
+
_wt_path=""
|
|
170
|
+
while IFS= read -r _line; do
|
|
171
|
+
case "$_line" in
|
|
172
|
+
"worktree "*)
|
|
173
|
+
_wt_path="${_line#worktree }"
|
|
174
|
+
;;
|
|
175
|
+
"branch refs/heads/"*)
|
|
176
|
+
_wt_branch="${_line#branch refs/heads/}"
|
|
177
|
+
if [ "$_wt_path" = "$MAIN_WORKTREE" ]; then
|
|
178
|
+
continue
|
|
179
|
+
fi
|
|
180
|
+
case "$_wt_branch" in
|
|
181
|
+
exec-agent-*) continue ;;
|
|
182
|
+
esac
|
|
183
|
+
CANDIDATES="${CANDIDATES}${_wt_path}${_TAB}${_wt_branch}
|
|
184
|
+
"
|
|
185
|
+
;;
|
|
186
|
+
esac
|
|
187
|
+
done < <(git worktree list --porcelain)
|
|
188
|
+
|
|
189
|
+
# Classify the argument once: a bare issue number (all digits) enables the
|
|
190
|
+
# anchored issue-number shapes; a glob metacharacter enables the glob tier.
|
|
191
|
+
_is_number=false
|
|
192
|
+
case "$BRANCH_NAME" in
|
|
193
|
+
''|*[!0-9]*) ;;
|
|
194
|
+
*) _is_number=true ;;
|
|
195
|
+
esac
|
|
196
|
+
_is_glob=false
|
|
197
|
+
case "$BRANCH_NAME" in
|
|
198
|
+
*[*?[]*) _is_glob=true ;;
|
|
199
|
+
esac
|
|
200
|
+
|
|
201
|
+
# Test one branch ref ($2) against one tier ($1); returns 0 on match. Called
|
|
202
|
+
# only as an `if` condition, so a non-zero return never trips `set -e`.
|
|
203
|
+
_branch_matches() {
|
|
204
|
+
case "$1" in
|
|
205
|
+
exact)
|
|
206
|
+
[ "$2" = "$BRANCH_NAME" ]
|
|
207
|
+
;;
|
|
208
|
+
anchored)
|
|
209
|
+
# Last path segment equals the argument (covers `838-spaced`).
|
|
210
|
+
if [ "${2##*/}" = "$BRANCH_NAME" ]; then
|
|
211
|
+
return 0
|
|
212
|
+
fi
|
|
213
|
+
# All-digit argument: the sequant issue-number branch shapes.
|
|
214
|
+
if [ "$_is_number" = true ]; then
|
|
215
|
+
case "$2" in
|
|
216
|
+
feature/"$BRANCH_NAME"-* | feature/"$BRANCH_NAME" \
|
|
217
|
+
| issue-"$BRANCH_NAME" | "$BRANCH_NAME"-*)
|
|
218
|
+
return 0
|
|
219
|
+
;;
|
|
220
|
+
esac
|
|
221
|
+
fi
|
|
222
|
+
return 1
|
|
223
|
+
;;
|
|
224
|
+
glob)
|
|
225
|
+
case "$2" in
|
|
226
|
+
$BRANCH_NAME) return 0 ;;
|
|
227
|
+
esac
|
|
228
|
+
return 1
|
|
229
|
+
;;
|
|
230
|
+
esac
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Walk the tiers in order; the first tier with any match decides. A tier with
|
|
234
|
+
# 2+ matches is fatal (AC-2/AC-3): list every candidate and exit non-zero
|
|
235
|
+
# rather than silently deleting the first.
|
|
236
|
+
WORKTREE_PATH=""
|
|
237
|
+
RESOLVED_BRANCH=""
|
|
238
|
+
for _tier in exact anchored glob; do
|
|
239
|
+
# The glob tier only participates when the argument is itself a glob.
|
|
240
|
+
if [ "$_tier" = glob ] && [ "$_is_glob" != true ]; then
|
|
241
|
+
continue
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
_match_count=0
|
|
245
|
+
_match_list=""
|
|
246
|
+
while IFS="$_TAB" read -r _cand_path _cand_branch; do
|
|
247
|
+
[ -n "$_cand_branch" ] || continue
|
|
248
|
+
if _branch_matches "$_tier" "$_cand_branch"; then
|
|
249
|
+
_match_count=$((_match_count + 1))
|
|
250
|
+
if [ "$_match_count" -eq 1 ]; then
|
|
251
|
+
WORKTREE_PATH="$_cand_path"
|
|
252
|
+
RESOLVED_BRANCH="$_cand_branch"
|
|
253
|
+
fi
|
|
254
|
+
_match_list="${_match_list} ${_cand_branch}${_TAB}${_cand_path}
|
|
255
|
+
"
|
|
256
|
+
fi
|
|
257
|
+
done < <(printf '%s' "$CANDIDATES")
|
|
258
|
+
|
|
259
|
+
if [ "$_match_count" -gt 1 ]; then
|
|
260
|
+
echo -e "${RED}❌ Error: '$BRANCH_NAME' is ambiguous — it matches ${_match_count} worktree branches:${NC}" >&2
|
|
261
|
+
printf '%s' "$_match_list" >&2
|
|
262
|
+
echo -e "${RED}Refusing to guess. Re-run with the full branch name to disambiguate.${NC}" >&2
|
|
263
|
+
exit 1
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
if [ "$_match_count" -eq 1 ]; then
|
|
267
|
+
break
|
|
268
|
+
fi
|
|
269
|
+
done
|
|
115
270
|
|
|
116
271
|
if [ -z "$WORKTREE_PATH" ]; then
|
|
117
272
|
echo -e "${RED}❌ Error: Worktree not found for branch: $BRANCH_NAME${NC}"
|
|
@@ -121,12 +276,17 @@ if [ -z "$WORKTREE_PATH" ]; then
|
|
|
121
276
|
exit 1
|
|
122
277
|
fi
|
|
123
278
|
|
|
124
|
-
echo -e "${BLUE}🧹 Cleaning up worktree for: $
|
|
279
|
+
echo -e "${BLUE}🧹 Cleaning up worktree for: $RESOLVED_BRANCH${NC}"
|
|
125
280
|
echo -e "${BLUE}Path: $WORKTREE_PATH${NC}"
|
|
126
281
|
echo ""
|
|
127
282
|
|
|
128
|
-
# Check if PR is merged
|
|
129
|
-
|
|
283
|
+
# Check if PR is merged. Queries the RESOLVED ref (#838): `--head` is an exact
|
|
284
|
+
# match, so passing the caller's shorthand here reported "not merged" for every
|
|
285
|
+
# invocation form except a full literal branch name — including the
|
|
286
|
+
# `feature/<issue>-*` form the skills and docs prescribe. A warning that always
|
|
287
|
+
# fires carries no signal and makes --yes/--force the mandatory incantation,
|
|
288
|
+
# which re-arms the very bypass reflex #750 removed.
|
|
289
|
+
PR_STATUS=$(gh pr list --head "$RESOLVED_BRANCH" --state merged --json number,state --jq '.[0].state' 2>/dev/null || echo "")
|
|
130
290
|
|
|
131
291
|
# Confirmation gate — only reached when the PR is NOT merged. When MERGED we
|
|
132
292
|
# short-circuit past this entirely (no prompt, no TTY check) so the documented
|
|
@@ -179,9 +339,11 @@ fi
|
|
|
179
339
|
echo -e "${BLUE}📂 Removing worktree...${NC}"
|
|
180
340
|
git worktree remove "$WORKTREE_PATH" --force
|
|
181
341
|
|
|
182
|
-
# Delete local branch
|
|
342
|
+
# Delete local branch. Uses the RESOLVED ref (#838) — `git branch -D 817` finds
|
|
343
|
+
# no such branch and, being `|| true`-suppressed, failed silently: the script
|
|
344
|
+
# reported "Cleanup complete!" while leaving the branch behind.
|
|
183
345
|
echo -e "${BLUE}🌿 Deleting local branch...${NC}"
|
|
184
|
-
git branch -D "$
|
|
346
|
+
git branch -D "$RESOLVED_BRANCH" 2>/dev/null || true
|
|
185
347
|
|
|
186
348
|
# Delete remote branch — hard-gated on merge state. Only delete when the PR is
|
|
187
349
|
# MERGED or an explicit override flag (--delete-remote/--force) was passed.
|
|
@@ -189,7 +351,8 @@ git branch -D "$BRANCH_NAME" 2>/dev/null || true
|
|
|
189
351
|
# PR's head branch makes GitHub close the PR unmerged, stranding the work.
|
|
190
352
|
if [ "$PR_STATUS" = "MERGED" ] || [ "$DELETE_REMOTE" = true ]; then
|
|
191
353
|
echo -e "${BLUE}☁️ Deleting remote branch...${NC}"
|
|
192
|
-
|
|
354
|
+
# Resolved ref (#838): same silent-no-op class as the local delete above.
|
|
355
|
+
git push origin --delete "$RESOLVED_BRANCH" 2>/dev/null || true
|
|
193
356
|
else
|
|
194
357
|
echo -e "${YELLOW}⏭️ Skipped remote-branch delete (PR not merged; pass --delete-remote or --force to override).${NC}"
|
|
195
358
|
fi
|