@@ -9,7 +9,7 @@ set -euo pipefail
99# pre-check [--db] Pre-batch safety checks (session usage, git, optional DB)
1010# preflight [--start-db] Pre-flight Docker & DB check (before diagnostic sub-agents)
1111# file-overlap <file>... Detect file conflicts from multiple agent result files
12- # lock-acquire <label> Session lock via tk (debug-everything only)
12+ # lock-acquire <label> Session lock via ticket CLI (debug-everything only)
1313# lock-release <issue-id> Release session lock (debug-everything only)
1414# lock-status <label> Check if a session lock exists
1515# cleanup-stale-containers Remove Docker containers for worktrees that no longer exist
@@ -25,7 +25,7 @@ set -euo pipefail
2525set -euo pipefail
2626SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
2727PLUGIN_SCRIPTS=" $SCRIPT_DIR "
28- TK =" ${TK :- $SCRIPT_DIR / tk } "
28+ TICKET_CMD =" ${TICKET_CMD :- $SCRIPT_DIR / ticket } "
2929
3030REPO_ROOT=$( git rev-parse --show-toplevel 2> /dev/null || echo " " )
3131if [ -z " $REPO_ROOT " ]; then
@@ -236,7 +236,7 @@ cmd_file_overlap() {
236236
237237# ─── lock-acquire ────────────────────────────────────────────────────────────
238238#
239- # Acquires a tk -based session lock. Used by debug-everything Phase 1 ONLY.
239+ # Acquires a ticket-CLI -based session lock. Used by debug-everything Phase 1 ONLY.
240240#
241241# Usage:
242242# agent-batch-lifecycle.sh lock-acquire "debug-everything"
@@ -251,53 +251,68 @@ cmd_file_overlap() {
251251cmd_lock_acquire () {
252252 local label=" ${1:? Missing lock label} "
253253
254- # Check for existing lock by scanning TICKETS_DIR for in_progress lock tickets
254+ # Check for existing lock using ticket CLI (v3): list all tickets, find in_progress
255+ # lock tasks matching the label.
255256 local lock_id=" "
256- if [ -d " $TICKETS_DIR " ]; then
257- while IFS= read -r ticket_file; do
258- [ -z " $ticket_file " ] && continue
259- # Check if this ticket has the right title and is in_progress.
260- # Tickets store their title as a markdown H1 heading after the YAML
261- # frontmatter (e.g., "# [LOCK] debug-everything"), NOT as a YAML
262- # `title:` field. Match the H1 form to correctly find synced tickets.
263- if grep -qE " ^# \\ [LOCK\\ ] $label $" " $ticket_file " 2> /dev/null; then
264- if grep -q " ^status: in_progress" " $ticket_file " 2> /dev/null; then
265- lock_id=$( grep -m1 " ^id:" " $ticket_file " | sed ' s/^id: *//' | tr -d ' "' " '" || echo " " )
266- break
267- fi
268- fi
269- done < <( find " $TICKETS_DIR " -maxdepth 1 -name " *.md" 2> /dev/null)
270- fi
257+ lock_id=$( export LOCK_LABEL=" $label " ; " $TICKET_CMD " list 2> /dev/null | python3 -c "
258+ import json, sys, os
259+ label = os.environ.get('LOCK_LABEL', '')
260+ tickets = json.load(sys.stdin)
261+ for t in tickets:
262+ if (t.get('ticket_type') == 'task'
263+ and t.get('status') == 'in_progress'
264+ and t.get('title', '') == '[LOCK] ' + label):
265+ print(t['ticket_id'])
266+ break
267+ " 2> /dev/null || echo " " )
271268
272269 if [ -n " $lock_id " ]; then
273- # Check if the lock's worktree still exists
270+ # Check if the lock's worktree still exists by reading ticket notes via ticket CLI
274271 local notes
275- notes=$( " $TK " show " $lock_id " 2> /dev/null | grep -oE ' Worktree: [^ ]+' | sed ' s/Worktree: //' || echo " " )
272+ notes=$( " $TICKET_CMD " show " $lock_id " 2> /dev/null | python3 -c "
273+ import json, sys
274+ t = json.load(sys.stdin)
275+ for c in t.get('comments', []):
276+ body = c.get('body', '')
277+ if 'Worktree: ' in body:
278+ for part in body.split('|'):
279+ part = part.strip()
280+ if part.startswith('Worktree: '):
281+ print(part[len('Worktree: '):].strip())
282+ break
283+ break
284+ " 2> /dev/null || echo " " )
276285 if [ -n " $notes " ] && [ -d " $notes " ]; then
277286 # Live lock — blocked
278287 echo " LOCK_BLOCKED: $lock_id "
279288 echo " LOCK_WORKTREE: $notes "
280289 return 1
281290 else
282- # Stale lock — reclaim (add-note before close: tk rejects notes on closed tickets)
283- " $TK " add-note " $lock_id " " Stale lock — worktree no longer exists" 2> /dev/null || true
284- " $TK " close " $lock_id " 2> /dev/null || true
291+ # Stale lock — reclaim (comment before close)
292+ " $TICKET_CMD " comment " $lock_id " " Stale lock — worktree no longer exists" 2> /dev/null || true
293+ # Read current status (ticket show) before: ticket transition <id> <current> closed
294+ local current_status
295+ current_status=$( " $TICKET_CMD " show " $lock_id " 2> /dev/null | python3 -c " import json,sys; print(json.load(sys.stdin).get('status','in_progress'))" 2> /dev/null || echo " in_progress" )
296+ " $TICKET_CMD " transition " $lock_id " " $current_status " closed 2> /dev/null || true
285297 echo " LOCK_STALE: $lock_id "
286298 fi
287299 fi
288300
289- # Create new lock — tk create outputs just the ticket ID (single line, no decoration )
301+ # Create new lock using v3 ticket CLI (ticket create task "<title>" )
290302 local new_id
291- new_id=$( " $TK " create " [LOCK] $label " -t task -p 0 2>&1 | tr -d ' [:space:]' )
303+ new_id=$( " $TICKET_CMD " create task " [LOCK] $label " 2>&1 | tr -d ' [:space:]' )
292304
293305 if [ -z " $new_id " ] || echo " $new_id " | grep -qiE ' error|fail' ; then
294306 echo " ERROR: Failed to create lock ticket"
295307 echo " OUTPUT: $new_id "
296308 return 1
297309 fi
298310
299- " $TK " status " $new_id " in_progress 2> /dev/null || true
300- " $TK " add-note " $new_id " " Session: $( date -Iseconds) | Worktree: $REPO_ROOT " 2> /dev/null || true
311+ # Read current status before transitioning to in_progress
312+ local new_status
313+ new_status=$( " $TICKET_CMD " show " $new_id " 2> /dev/null | python3 -c " import json,sys; print(json.load(sys.stdin).get('status','open'))" 2> /dev/null || echo " open" )
314+ " $TICKET_CMD " transition " $new_id " " $new_status " in_progress 2> /dev/null || true
315+ " $TICKET_CMD " comment " $new_id " " Session: $( date -Iseconds) | Worktree: $REPO_ROOT " 2> /dev/null || true
301316
302317 echo " LOCK_ID: $new_id "
303318 return 0
@@ -314,9 +329,12 @@ cmd_lock_release() {
314329 local lock_id=" ${1:? Missing lock ticket ID} "
315330 local reason=" ${2:- Session complete} "
316331
317- # add-note before close: tk rejects notes on closed tickets
318- " $TK " add-note " $lock_id " " Closed: $reason " 2> /dev/null || true
319- " $TK " close " $lock_id " 2> /dev/null || true
332+ # Comment before close (ticket CLI rejects comments on closed tickets)
333+ " $TICKET_CMD " comment " $lock_id " " Closed: $reason " 2> /dev/null || true
334+ # Read current status before transitioning to closed
335+ local current_status
336+ current_status=$( " $TICKET_CMD " show " $lock_id " 2> /dev/null | python3 -c " import json,sys; print(json.load(sys.stdin).get('status','in_progress'))" 2> /dev/null || echo " in_progress" )
337+ " $TICKET_CMD " transition " $lock_id " " $current_status " closed 2> /dev/null || true
320338 echo " LOCK_RELEASED: $lock_id "
321339 return 0
322340}
@@ -335,22 +353,19 @@ cmd_lock_release() {
335353cmd_lock_status () {
336354 local label=" ${1:? Missing lock label} "
337355
338- # Scan TICKETS_DIR for in_progress lock tickets matching the label
356+ # Query ticket CLI (v3) for in_progress lock tasks matching the label
339357 local lock_id=" "
340- if [ -d " $TICKETS_DIR " ]; then
341- while IFS= read -r ticket_file; do
342- [ -z " $ticket_file " ] && continue
343- # Tickets store their title as a markdown H1 heading after the YAML
344- # frontmatter (e.g., "# [LOCK] debug-everything"), NOT as a YAML
345- # `title:` field. Match the H1 form to correctly find synced tickets.
346- if grep -qE " ^# \\ [LOCK\\ ] $label $" " $ticket_file " 2> /dev/null; then
347- if grep -q " ^status: in_progress" " $ticket_file " 2> /dev/null; then
348- lock_id=$( grep -m1 " ^id:" " $ticket_file " | sed ' s/^id: *//' | tr -d ' "' " '" || echo " " )
349- break
350- fi
351- fi
352- done < <( find " $TICKETS_DIR " -maxdepth 1 -name " *.md" 2> /dev/null)
353- fi
358+ lock_id=$( export LOCK_LABEL=" $label " ; " $TICKET_CMD " list 2> /dev/null | python3 -c "
359+ import json, sys, os
360+ label = os.environ.get('LOCK_LABEL', '')
361+ tickets = json.load(sys.stdin)
362+ for t in tickets:
363+ if (t.get('ticket_type') == 'task'
364+ and t.get('status') == 'in_progress'
365+ and t.get('title', '') == '[LOCK] ' + label):
366+ print(t['ticket_id'])
367+ break
368+ " 2> /dev/null || echo " " )
354369
355370 if [ -n " $lock_id " ]; then
356371 echo " LOCKED: $lock_id "
0 commit comments