@@ -413,6 +413,12 @@ _abort_stale_rebase() {
413413# accepting the archive move: git rm the old path (if still present) and git add the
414414# archived path (if present). Non-archive conflicts cause an immediate abort.
415415#
416+ # v3 ticket system (.tickets-tracker/): ticket event JSON files and the index
417+ # (.tickets-tracker/<id>/*.json, .tickets-tracker/.index.json) are managed on a
418+ # separate orphan branch and are excluded from the main repo's tracked files.
419+ # However, if they appear as conflicts during rebase (e.g., during worktree sync),
420+ # they are always safe ticket-data files and can be auto-resolved by accepting ours.
421+ #
416422# Usage: call from the git pull --rebase failure handler in _phase_sync.
417423# Must be called while a rebase is in progress (REBASE_HEAD exists).
418424# Returns 0 on success (rebase continued), 1 on failure (rebase aborted).
@@ -452,15 +458,14 @@ _auto_resolve_archive_conflicts() {
452458 return 1
453459 fi
454460
455- # Safety check: ALL conflicts must be archive-type ticket files.
456- # Archive-type = either .tickets/archive/xxx.md or .tickets/xxx.md being
457- # deleted in favor of .tickets/archive/xxx.md.
461+ # Safety check: ALL conflicts must be ticket-data files (safe to auto-resolve).
462+ # Ticket data lives in .tickets-tracker/<id>/*.json event files or .tickets-tracker/.index.json.
458463 local _non_archive_conflicts=0
459464 while IFS= read -r _file; do
460465 [[ -z " $_file " ]] && continue
461466 case " $_file " in
462- .tickets/archive/ * .md | .tickets/ * .md )
463- # These are archive-type ticket paths — safe to auto-resolve
467+ .tickets-tracker/ * .json | .tickets-tracker/ * / * .json )
468+ # v3: ticket event JSON files or index — safe to auto-resolve
464469 ;;
465470 * )
466471 _non_archive_conflicts=$(( _non_archive_conflicts + 1 ))
@@ -474,30 +479,20 @@ _auto_resolve_archive_conflicts() {
474479 return 1
475480 fi
476481
477- # All conflicts are archive-type — resolve each one.
478- # For rename/delete : accept the deletion (git rm old path) and
479- # accept the addition of the archive path (git add archive path, if it exists) .
482+ # All conflicts are ticket-data files — resolve each one.
483+ # For JSON event file conflicts : accept ours (git add) — event files are
484+ # append-only and our version is always the authoritative local state .
480485 local _resolved=0
481486 local _failed=0
482487
483488 while IFS= read -r _file; do
484489 [[ -z " $_file " ]] && continue
485490
486- # Determine if this is the old path or the archive path
487- if [[ " $_file " == .tickets/archive/* .md ]]; then
488- # This is the archive destination — add it if it exists in working tree
491+ if [[ " $_file " == .tickets-tracker/* .json || " $_file " == .tickets-tracker/* /* .json ]]; then
492+ # v3: Ticket event JSON files / index — accept ours (git add if present, git rm if absent)
489493 if [[ -f " $_file " ]]; then
490494 git add " $_file " 2> /dev/null && _resolved=$(( _resolved + 1 )) || _failed=$(( _failed + 1 ))
491495 else
492- # File doesn't exist in working tree — this side deleted it; skip
493- _resolved=$(( _resolved + 1 ))
494- fi
495- elif [[ " $_file " == .tickets/* .md ]]; then
496- # This is the old (pre-archive) path — remove it if still present
497- if [[ -f " $_file " ]]; then
498- git rm --force --quiet " $_file " 2> /dev/null && _resolved=$(( _resolved + 1 )) || _failed=$(( _failed + 1 ))
499- else
500- # Already gone — mark as resolved
501496 git rm --quiet --cached " $_file " 2> /dev/null || true
502497 _resolved=$(( _resolved + 1 ))
503498 fi
@@ -558,35 +553,30 @@ _auto_resolve_archive_conflicts() {
558553 continue
559554 fi
560555
561- # Validate that all new conflicts are still archive-type .
556+ # Validate that all new conflicts are still ticket-data files .
562557 local _new_non_archive=0
563558 while IFS= read -r _nf; do
564559 [[ -z " $_nf " ]] && continue
565560 case " $_nf " in
566- .tickets/archive/ * .md | .tickets/ * .md ) ;;
561+ .tickets-tracker/ * .json | .tickets-tracker/ * / * .json ) ;;
567562 * ) _new_non_archive=$(( _new_non_archive + 1 )) ;;
568563 esac
569564 done <<< " $_new_all"
570565
571566 if [[ " $_new_non_archive " -gt 0 ]]; then
572- echo " INFO: _auto_resolve_archive_conflicts: non-archive conflicts in subsequent commit — aborting auto-resolve." >&2
567+ echo " INFO: _auto_resolve_archive_conflicts: non-ticket conflicts in subsequent commit — aborting auto-resolve." >&2
573568 git rebase --abort 2> /dev/null || true
574569 return 1
575570 fi
576571
577- # Resolve the new archive conflicts.
572+ # Resolve the new ticket-data conflicts (v3 JSON event files) .
578573 local _new_resolved=0 _new_failed=0
579574 while IFS= read -r _nf; do
580575 [[ -z " $_nf " ]] && continue
581- if [[ " $_nf " == .tickets/archive/* .md ]]; then
576+ if [[ " $_nf " == .tickets-tracker/* .json || " $_nf " == .tickets-tracker/* /* .json ]]; then
577+ # v3: ticket event JSON / index — accept ours
582578 if [[ -f " $_nf " ]]; then
583579 git add " $_nf " 2> /dev/null && _new_resolved=$(( _new_resolved + 1 )) || _new_failed=$(( _new_failed + 1 ))
584- else
585- _new_resolved=$(( _new_resolved + 1 ))
586- fi
587- elif [[ " $_nf " == .tickets/* .md ]]; then
588- if [[ -f " $_nf " ]]; then
589- git rm --force --quiet " $_nf " 2> /dev/null && _new_resolved=$(( _new_resolved + 1 )) || _new_failed=$(( _new_failed + 1 ))
590580 else
591581 git rm --quiet --cached " $_nf " 2> /dev/null || true
592582 _new_resolved=$(( _new_resolved + 1 ))
@@ -1015,11 +1005,11 @@ _phase_sync() {
10151005 fi
10161006 _abort_stale_rebase
10171007 if ! git pull --rebase 2>&1 ; then
1018- # Attempt auto-resolution of archive rename/delete conflicts before giving up.
1019- # Archive conflicts occur when a previous merge archived tickets and the remote
1020- # has a different archive commit — always safe to resolve automatically.
1008+ # Attempt auto-resolution of ticket-data conflicts before giving up.
1009+ # Ticket-data conflicts (v2 archive rename/delete or v3 JSON event files)
1010+ # are always safe to resolve automatically.
10211011 if _auto_resolve_archive_conflicts 2>&1 ; then
1022- echo " OK: Archive rename/delete conflicts auto-resolved during pull --rebase."
1012+ echo " OK: Ticket-data conflicts auto-resolved during pull --rebase."
10231013 else
10241014 if $STASHED ; then git stash pop --quiet 2> /dev/null || true ; fi
10251015 _set_phase_status " pull_rebase" " conflict"
@@ -1033,8 +1023,8 @@ _phase_sync() {
10331023 echo " Restoring stashed changes..."
10341024 if ! git stash pop --quiet 2> /dev/null; then
10351025 # Stash pop conflicted — discard the stash. The pre-stash files were
1036- # .tickets/ files , which the merge step will overwrite
1037- # anyway. Keeping an unmerged stash pop would block all subsequent ops.
1026+ # ticket data files ( .tickets/ or .tickets-tracker/) , which the merge
1027+ # step will overwrite anyway. Keeping an unmerged stash pop would block all subsequent ops.
10381028 echo " WARNING: Stash pop had conflicts — resetting. Merge step will reconcile."
10391029 git reset --merge 2> /dev/null || true
10401030 git stash drop --quiet 2> /dev/null || true
@@ -1067,7 +1057,7 @@ _phase_merge() {
10671057 MERGE_MSG=" $LAST_MSG (merge $BRANCH )"
10681058
10691059 # Capture pre-merge SHA so we can later detect whether the merge contained
1070- # non-.tickets/ changes (used for the CI trigger check after push).
1060+ # non-ticket-data changes (used for the CI trigger check after push).
10711061 PRE_MERGE_SHA=$( git rev-parse HEAD)
10721062
10731063 echo " Merging $BRANCH into main..."
@@ -1162,9 +1152,10 @@ _phase_validate() {
11621152 # Stage any post-merge artifacts (.gitignore entries for worktree dirs)
11631153 git add .gitignore 2> /dev/null || true
11641154
1165- # Auto-stage .tickets/ changes — CI failure tracking pushes ticket commits directly
1166- # to main, and the merge can leave .tickets/ files dirty. These are data files, not
1167- # code, so auto-staging into the merge commit is safe.
1155+ # Auto-stage ticket-data changes ($TICKETS_DIR/) — CI failure tracking pushes ticket
1156+ # commits directly to main, and the merge can leave ticket data files dirty.
1157+ # These are data files, not code, so auto-staging into the merge commit is safe.
1158+ # (v2: .tickets/*.md files; v3: $TICKETS_DIR/ may be absent if fully on .tickets-tracker/ branch)
11681159 git add " $TICKETS_DIR " / 2> /dev/null || true
11691160
11701161 # Check for dirty tracked files (modified but not staged) excluding tickets dir
0 commit comments