sequant 2.8.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.
Files changed (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +23 -4
  4. package/dist/bin/cli.js +99 -18
  5. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  6. package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
  7. package/dist/marketplace/external_plugins/sequant/README.md +58 -2
  8. package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
  9. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +38 -10
  10. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +405 -67
  11. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
  12. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +250 -344
  13. package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +10 -7
  14. package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
  15. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
  16. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +4 -2
  17. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
  18. package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
  19. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
  20. package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
  21. package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
  22. package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
  23. package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  24. package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
  25. package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
  26. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
  27. package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
  28. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
  29. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
  30. package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +11 -3
  31. package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
  32. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
  33. package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
  34. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
  35. package/dist/src/commands/assess-render.d.ts +23 -0
  36. package/dist/src/commands/assess-render.js +60 -0
  37. package/dist/src/commands/doctor.js +8 -13
  38. package/dist/src/commands/init.js +13 -1
  39. package/dist/src/commands/locks.js +2 -0
  40. package/dist/src/commands/logs.js +20 -6
  41. package/dist/src/commands/merge.js +154 -2
  42. package/dist/src/commands/ready-tui-adapter.js +6 -1
  43. package/dist/src/commands/ready.d.ts +25 -3
  44. package/dist/src/commands/ready.js +39 -13
  45. package/dist/src/commands/resume.d.ts +113 -0
  46. package/dist/src/commands/resume.js +193 -0
  47. package/dist/src/commands/run-display.d.ts +20 -0
  48. package/dist/src/commands/run-display.js +117 -5
  49. package/dist/src/commands/run-flags.d.ts +42 -1
  50. package/dist/src/commands/run-flags.js +53 -1
  51. package/dist/src/commands/run-progress.js +26 -1
  52. package/dist/src/commands/run.js +11 -10
  53. package/dist/src/commands/state.js +7 -0
  54. package/dist/src/commands/stats.js +47 -0
  55. package/dist/src/commands/status.d.ts +9 -0
  56. package/dist/src/commands/status.js +24 -1
  57. package/dist/src/commands/sync.js +90 -19
  58. package/dist/src/commands/update.js +28 -5
  59. package/dist/src/lib/ac-parser.d.ts +14 -0
  60. package/dist/src/lib/ac-parser.js +99 -5
  61. package/dist/src/lib/assess/index.d.ts +10 -0
  62. package/dist/src/lib/assess/index.js +9 -0
  63. package/dist/src/lib/assess/renderer.d.ts +64 -0
  64. package/dist/src/lib/assess/renderer.js +481 -0
  65. package/dist/src/lib/assess/types.d.ts +224 -0
  66. package/dist/src/lib/assess/types.js +241 -0
  67. package/dist/src/lib/assess-collision-detect.d.ts +19 -2
  68. package/dist/src/lib/assess-collision-detect.js +68 -4
  69. package/dist/src/lib/ci/outputs.d.ts +15 -2
  70. package/dist/src/lib/ci/outputs.js +17 -5
  71. package/dist/src/lib/cli-flags.d.ts +33 -0
  72. package/dist/src/lib/cli-flags.js +46 -0
  73. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
  74. package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
  75. package/dist/src/lib/cli-ui/run-renderer.js +79 -9
  76. package/dist/src/lib/errors.d.ts +84 -6
  77. package/dist/src/lib/errors.js +142 -17
  78. package/dist/src/lib/locks/index.d.ts +3 -3
  79. package/dist/src/lib/locks/index.js +2 -2
  80. package/dist/src/lib/locks/lock-manager.d.ts +20 -2
  81. package/dist/src/lib/locks/lock-manager.js +47 -3
  82. package/dist/src/lib/locks/types.d.ts +34 -3
  83. package/dist/src/lib/locks/types.js +16 -0
  84. package/dist/src/lib/manifest.js +1 -17
  85. package/dist/src/lib/mcp-config.d.ts +45 -0
  86. package/dist/src/lib/mcp-config.js +77 -1
  87. package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
  88. package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
  89. package/dist/src/lib/merge-check/command-result.d.ts +36 -0
  90. package/dist/src/lib/merge-check/command-result.js +56 -0
  91. package/dist/src/lib/merge-check/index.d.ts +4 -0
  92. package/dist/src/lib/merge-check/index.js +1 -1
  93. package/dist/src/lib/merge-check/report.js +5 -1
  94. package/dist/src/lib/merge-check/types.d.ts +9 -0
  95. package/dist/src/lib/merge-check/watch.d.ts +121 -0
  96. package/dist/src/lib/merge-check/watch.js +215 -0
  97. package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
  98. package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
  99. package/dist/src/lib/settings.d.ts +14 -0
  100. package/dist/src/lib/settings.js +61 -8
  101. package/dist/src/lib/shutdown.d.ts +42 -2
  102. package/dist/src/lib/shutdown.js +75 -5
  103. package/dist/src/lib/skills-check.d.ts +26 -0
  104. package/dist/src/lib/skills-check.js +31 -0
  105. package/dist/src/lib/stacks.d.ts +110 -1
  106. package/dist/src/lib/stacks.js +244 -5
  107. package/dist/src/lib/templates.d.ts +74 -0
  108. package/dist/src/lib/templates.js +171 -16
  109. package/dist/src/lib/test-tautology-detector.d.ts +9 -2
  110. package/dist/src/lib/test-tautology-detector.js +153 -12
  111. package/dist/src/lib/version-check.d.ts +31 -0
  112. package/dist/src/lib/version-check.js +46 -7
  113. package/dist/src/lib/workflow/batch-executor.d.ts +138 -6
  114. package/dist/src/lib/workflow/batch-executor.js +550 -54
  115. package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
  116. package/dist/src/lib/workflow/chain-preflight.js +199 -0
  117. package/dist/src/lib/workflow/chain-resume.d.ts +118 -0
  118. package/dist/src/lib/workflow/chain-resume.js +170 -0
  119. package/dist/src/lib/workflow/completed-status.d.ts +52 -0
  120. package/dist/src/lib/workflow/completed-status.js +57 -0
  121. package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
  122. package/dist/src/lib/workflow/config-resolver.js +54 -2
  123. package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
  124. package/dist/src/lib/workflow/dependency-markers.js +79 -0
  125. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
  126. package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
  127. package/dist/src/lib/workflow/drivers/aider.js +2 -0
  128. package/dist/src/lib/workflow/drivers/claude-code.d.ts +21 -0
  129. package/dist/src/lib/workflow/drivers/claude-code.js +50 -8
  130. package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
  131. package/dist/src/lib/workflow/error-classifier.js +14 -1
  132. package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
  133. package/dist/src/lib/workflow/git-diff-utils.js +77 -4
  134. package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
  135. package/dist/src/lib/workflow/heartbeat.js +90 -0
  136. package/dist/src/lib/workflow/log-writer.d.ts +44 -1
  137. package/dist/src/lib/workflow/log-writer.js +81 -14
  138. package/dist/src/lib/workflow/metrics-schema.d.ts +42 -3
  139. package/dist/src/lib/workflow/metrics-schema.js +16 -0
  140. package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
  141. package/dist/src/lib/workflow/phase-executor.d.ts +234 -13
  142. package/dist/src/lib/workflow/phase-executor.js +533 -56
  143. package/dist/src/lib/workflow/phase-registry.js +2 -1
  144. package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
  145. package/dist/src/lib/workflow/platforms/github.js +152 -0
  146. package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
  147. package/dist/src/lib/workflow/qa-cache.js +22 -9
  148. package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
  149. package/dist/src/lib/workflow/ready-gate.js +25 -9
  150. package/dist/src/lib/workflow/run-log-schema.d.ts +37 -0
  151. package/dist/src/lib/workflow/run-log-schema.js +92 -2
  152. package/dist/src/lib/workflow/run-orchestrator.d.ts +33 -0
  153. package/dist/src/lib/workflow/run-orchestrator.js +429 -47
  154. package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
  155. package/dist/src/lib/workflow/skills-preflight.js +112 -0
  156. package/dist/src/lib/workflow/state-cleanup.js +9 -2
  157. package/dist/src/lib/workflow/state-manager.d.ts +33 -0
  158. package/dist/src/lib/workflow/state-manager.js +83 -0
  159. package/dist/src/lib/workflow/state-schema.d.ts +26 -0
  160. package/dist/src/lib/workflow/state-schema.js +47 -0
  161. package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
  162. package/dist/src/lib/workflow/status-derivation.js +27 -0
  163. package/dist/src/lib/workflow/types.d.ts +149 -4
  164. package/dist/src/lib/workflow/types.js +1 -0
  165. package/dist/src/lib/workflow/worktree-manager.d.ts +70 -2
  166. package/dist/src/lib/workflow/worktree-manager.js +201 -52
  167. package/dist/src/mcp/tools/run.d.ts +11 -1
  168. package/dist/src/mcp/tools/run.js +22 -1
  169. package/dist/src/mcp/tools/status.js +6 -0
  170. package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
  171. package/dist/src/ui/tui/ElapsedTimer.js +12 -10
  172. package/dist/src/ui/tui/IssueBox.js +10 -2
  173. package/dist/src/ui/tui/row-cap.js +2 -1
  174. package/dist/src/ui/tui/theme.d.ts +7 -0
  175. package/dist/src/ui/tui/theme.js +9 -0
  176. package/package.json +9 -10
  177. package/templates/hooks/parallel-marker.sh +50 -0
  178. package/templates/hooks/post-tool.sh +38 -10
  179. package/templates/hooks/pre-tool.sh +405 -67
  180. package/templates/scripts/cleanup-worktree.sh +282 -30
  181. package/templates/scripts/new-feature.sh +264 -16
  182. package/templates/skills/_shared/references/trust-model.md +18 -0
  183. package/templates/skills/assess/SKILL.md +250 -344
  184. package/templates/skills/assess/references/predicted-collision-detection.md +10 -7
  185. package/templates/skills/docs/SKILL.md +4 -4
  186. package/templates/skills/exec/SKILL.md +26 -6
  187. package/templates/skills/fullsolve/SKILL.md +4 -2
  188. package/templates/skills/loop/SKILL.md +38 -2
  189. package/templates/skills/merger/SKILL.md +2 -2
  190. package/templates/skills/qa/SKILL.md +337 -57
  191. package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
  192. package/templates/skills/qa/references/call-site-review.md +2 -2
  193. package/templates/skills/qa/references/code-review-checklist.md +2 -2
  194. package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  195. package/templates/skills/qa/references/quality-gates.md +21 -7
  196. package/templates/skills/qa/references/testing-requirements.md +1 -1
  197. package/templates/skills/qa/scripts/quality-checks.sh +11 -11
  198. package/templates/skills/reflect/SKILL.md +27 -13
  199. package/templates/skills/reflect/references/documentation-tiers.md +80 -68
  200. package/templates/skills/reflect/references/phase-reflection.md +31 -15
  201. package/templates/skills/release/SKILL.md +11 -3
  202. package/templates/skills/setup/SKILL.md +5 -5
  203. package/templates/skills/spec/SKILL.md +2 -0
  204. package/templates/skills/spec/references/verification-criteria.md +1 -1
  205. package/templates/skills/test/SKILL.md +1 -1
  206. package/dist/src/lib/phase-spinner.d.ts +0 -146
  207. package/dist/src/lib/phase-spinner.js +0 -255
  208. package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
  209. package/dist/src/lib/workflow/pr-operations.js +0 -326
  210. package/dist/src/lib/workflow/run-summary.d.ts +0 -36
  211. package/dist/src/lib/workflow/run-summary.js +0 -142
@@ -1,8 +1,30 @@
1
1
  #!/bin/bash
2
2
 
3
3
  # Clean up a worktree after PR is merged
4
- # Usage: ./scripts/cleanup-worktree.sh <branch-name>
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
16
+ #
17
+ # The remote branch is deleted ONLY when the branch's PR is MERGED (the
18
+ # documented post-merge contract) or when an explicit override flag is passed.
19
+ # Local teardown (worktree + local branch) always runs so the branch lock is
20
+ # freed for a subsequent `gh pr merge --delete-branch`.
21
+ #
22
+ # Flags:
23
+ # -y, --yes Skip the confirmation prompt (non-interactive confirm).
24
+ # Does NOT override the merge gate on remote deletion.
25
+ # --delete-remote Override the merge gate and delete the remote branch even
26
+ # when the PR is not merged (still honors the TTY confirm).
27
+ # --force Implies both --yes and --delete-remote.
6
28
 
7
29
  set -e
8
30
 
@@ -13,7 +35,78 @@ YELLOW='\033[1;33m'
13
35
  RED='\033[0;31m'
14
36
  NC='\033[0m'
15
37
 
16
- BRANCH_NAME=$1
38
+ # Print CLI usage/help (kept in sync with the header comment above).
39
+ print_usage() {
40
+ cat <<'USAGE'
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.
55
+
56
+ Clean up a feature worktree. The remote branch is deleted ONLY when the
57
+ branch's PR is MERGED (the documented post-merge contract) or when an
58
+ explicit override flag is passed. Local teardown (worktree + local branch)
59
+ always runs so the branch lock is freed for a subsequent
60
+ `gh pr merge --delete-branch`.
61
+
62
+ Flags:
63
+ -y, --yes Skip the confirmation prompt (non-interactive confirm).
64
+ Does NOT override the merge gate on remote deletion.
65
+ --delete-remote Override the merge gate and delete the remote branch even
66
+ when the PR is not merged (still honors the confirm gate).
67
+ --force Implies both --yes and --delete-remote.
68
+ -h, --help Show this help and exit.
69
+
70
+ Example:
71
+ ./scripts/cleanup-worktree.sh feature/123-add-user-dashboard
72
+ USAGE
73
+ }
74
+
75
+ # Parse flags. ASSUME_YES bypasses the confirmation prompt; DELETE_REMOTE
76
+ # overrides the merge gate on remote deletion. The first non-flag argument is
77
+ # the branch name. --force is the combined opt-in (both behaviors).
78
+ BRANCH_NAME=""
79
+ ASSUME_YES=false
80
+ DELETE_REMOTE=false
81
+ while [ $# -gt 0 ]; do
82
+ case "$1" in
83
+ -y|--yes)
84
+ ASSUME_YES=true
85
+ ;;
86
+ --delete-remote)
87
+ DELETE_REMOTE=true
88
+ ;;
89
+ --force)
90
+ ASSUME_YES=true
91
+ DELETE_REMOTE=true
92
+ ;;
93
+ -h|--help)
94
+ print_usage
95
+ exit 0
96
+ ;;
97
+ -*)
98
+ echo -e "${RED}❌ Error: Unknown flag: $1${NC}" >&2
99
+ print_usage >&2
100
+ exit 1
101
+ ;;
102
+ *)
103
+ if [ -z "$BRANCH_NAME" ]; then
104
+ BRANCH_NAME="$1"
105
+ fi
106
+ ;;
107
+ esac
108
+ shift
109
+ done
17
110
 
18
111
  # Resolve main worktree (first entry in porcelain output) so subsequent git
19
112
  # commands run from a stable cwd even if the caller invoked us from inside the
@@ -23,25 +116,157 @@ MAIN_WORKTREE=$(git worktree list --porcelain | sed -n 's/^worktree //p' | head
23
116
 
24
117
  # Check if branch name provided
25
118
  if [ -z "$BRANCH_NAME" ]; then
26
- echo -e "${RED}❌ Error: Branch name required${NC}"
27
- echo "Usage: ./scripts/cleanup-worktree.sh <branch-name>"
28
- echo ""
29
- echo "Active worktrees:"
30
- git worktree list
119
+ echo -e "${RED}❌ Error: Branch name required${NC}" >&2
120
+ print_usage >&2
121
+ echo "" >&2
122
+ echo "Active worktrees:" >&2
123
+ git worktree list >&2
31
124
  exit 1
32
125
  fi
33
126
 
34
- # Find worktree path. Parse porcelain (which separates path/branch onto distinct
35
- # lines) so paths containing whitespace survive intact, and so we match against
36
- # the branch ref rather than against a free-form `grep` over the entire line
37
- # the latter false-matches whenever the branch name appears in the path string.
38
- WORKTREE_PATH=$(git worktree list --porcelain | awk -v target="$BRANCH_NAME" '
39
- /^worktree / { sub(/^worktree /, ""); path = $0 }
40
- /^branch refs\/heads\// {
41
- sub(/^branch refs\/heads\//, "")
42
- if (index($0, target) > 0) { print path; exit }
43
- }
44
- ')
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
45
270
 
46
271
  if [ -z "$WORKTREE_PATH" ]; then
47
272
  echo -e "${RED}❌ Error: Worktree not found for branch: $BRANCH_NAME${NC}"
@@ -51,19 +276,36 @@ if [ -z "$WORKTREE_PATH" ]; then
51
276
  exit 1
52
277
  fi
53
278
 
54
- echo -e "${BLUE}🧹 Cleaning up worktree for: $BRANCH_NAME${NC}"
279
+ echo -e "${BLUE}🧹 Cleaning up worktree for: $RESOLVED_BRANCH${NC}"
55
280
  echo -e "${BLUE}Path: $WORKTREE_PATH${NC}"
56
281
  echo ""
57
282
 
58
- # Check if PR is merged
59
- PR_STATUS=$(gh pr list --head "$BRANCH_NAME" --state merged --json number,state --jq '.[0].state' 2>/dev/null || echo "")
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 "")
60
290
 
291
+ # Confirmation gate — only reached when the PR is NOT merged. When MERGED we
292
+ # short-circuit past this entirely (no prompt, no TTY check) so the documented
293
+ # post-merge happy path is unchanged.
61
294
  if [ "$PR_STATUS" != "MERGED" ]; then
62
295
  echo -e "${YELLOW}⚠️ Warning: PR for this branch is not merged${NC}"
63
- read -p "Are you sure you want to delete this worktree? (y/n) " -n 1 -r
64
- echo
65
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
66
- echo -e "${BLUE}Cancelled.${NC}"
296
+ if [ "$ASSUME_YES" = true ]; then
297
+ echo -e "${BLUE}Proceeding (--yes/--force).${NC}"
298
+ elif [ -t 0 ]; then
299
+ read -p "Are you sure you want to delete this worktree? (y/n) " -n 1 -r
300
+ echo
301
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
302
+ echo -e "${BLUE}Cancelled.${NC}"
303
+ exit 0
304
+ fi
305
+ else
306
+ # Non-interactive (no TTY) and no confirm flag: exit safely instead of
307
+ # stalling on `read`. Pass --yes/--force to proceed without a prompt.
308
+ echo -e "${BLUE}Non-interactive context and PR not merged — pass --yes or --force to proceed. Exiting without changes.${NC}"
67
309
  exit 0
68
310
  fi
69
311
  fi
@@ -97,13 +339,23 @@ fi
97
339
  echo -e "${BLUE}📂 Removing worktree...${NC}"
98
340
  git worktree remove "$WORKTREE_PATH" --force
99
341
 
100
- # 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.
101
345
  echo -e "${BLUE}🌿 Deleting local branch...${NC}"
102
- git branch -D "$BRANCH_NAME" 2>/dev/null || true
346
+ git branch -D "$RESOLVED_BRANCH" 2>/dev/null || true
103
347
 
104
- # Delete remote branch
105
- echo -e "${BLUE}☁️ Deleting remote branch...${NC}"
106
- git push origin --delete "$BRANCH_NAME" 2>/dev/null || true
348
+ # Delete remote branch — hard-gated on merge state. Only delete when the PR is
349
+ # MERGED or an explicit override flag (--delete-remote/--force) was passed.
350
+ # Otherwise leave the remote branch (and any open PR) intact: deleting an open
351
+ # PR's head branch makes GitHub close the PR unmerged, stranding the work.
352
+ if [ "$PR_STATUS" = "MERGED" ] || [ "$DELETE_REMOTE" = true ]; then
353
+ echo -e "${BLUE}☁️ Deleting remote branch...${NC}"
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
356
+ else
357
+ echo -e "${YELLOW}⏭️ Skipped remote-branch delete (PR not merged; pass --delete-remote or --force to override).${NC}"
358
+ fi
107
359
 
108
360
  # Update main
109
361
  echo -e "${BLUE}📥 Updating main branch...${NC}"