Skip to content

Commit 6089afe

Browse files
fix: resolve 8 code bugs across hooks, scripts, and agent definitions (merge worktree-20260329-143246)
2 parents ee42059 + fa03657 commit 6089afe

16 files changed

+890
-4
lines changed

.test-index

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ plugins/dso/scripts/validate-issues.sh:tests/scripts/test-validate-issues-timest
8585
plugins/dso/scripts/validate-phase.sh:tests/test-validate-phase-portability.sh,tests/scripts/test-validate-phase-v2-removal.sh
8686
plugins/dso/scripts/validate.sh:tests/plugin/test-validate-work-portability.sh,tests/hooks/test-validate-review-output.sh,tests/hooks/test-validate-crash-detection.sh,tests/scripts/test-validate-test-batched-integration.sh,tests/scripts/test-validate-flock-timeout.sh,tests/scripts/test-validate-background.sh,tests/scripts/test-validate-skip-ci-flag.sh,tests/scripts/test-validate-issues.sh,tests/scripts/test-validate-config.sh,tests/scripts/test-validate-script-writes-integration.sh,tests/scripts/test-validate-config-driven.sh,tests/scripts/test-validate-state-lifecycle.sh,tests/test-validate-phase-portability.sh,tests/scripts/test-validate-comment-accuracy.sh,tests/scripts/test-validate-e2e-missing-target.sh
8787
plugins/dso/scripts/worktree-cleanup.sh:tests/scripts/test_worktree_cleanup_startup_config.py
88+
plugins/dso/scripts/worktree-sync-from-main.sh:tests/scripts/test-worktree-sync-from-main.sh
8889
plugins/dso/skills/batch-overlap-check/SKILL.md:plugins/dso/tests/test-sprint-skill-step10-no-merge-to-main.sh,tests/plugin/test-audit-skill-resolution.sh,tests/hooks/test-fix-bug-skill.sh,tests/hooks/test-generate-claude-md-skill.sh,tests/hooks/test-init-skill.sh,tests/scripts/test-qualify-skill-refs.sh,tests/scripts/test-skill-path-refs.sh,tests/scripts/test-check-skill-refs.sh,tests/skills/test_end_skill_final_verification_step.py,tests/skills/test_implementation_plan_skill_tdd_enforcement.py,tests/skills/test-quick-ref-skill.sh,tests/skills/test_fix_bug_skill.py,tests/skills/test_end_skill_summary_displays_stored_learnings.py,tests/skills/test_end_skill_learnings_step_before_commit.py,tests/skills/test-design-skills-cross-stack.sh,tests/skills/test_end_skill_dirty_worktree_resolution.py,tests/skills/test_fix_bug_skill_escalated_section.py,tests/skills/test_end_skill_bug_tickets_before_commit.py
8990
plugins/dso/skills/brainstorm/SKILL.md:plugins/dso/tests/test-sprint-skill-step10-no-merge-to-main.sh,tests/plugin/test-audit-skill-resolution.sh,tests/hooks/test-fix-bug-skill.sh,tests/hooks/test-generate-claude-md-skill.sh,tests/hooks/test-init-skill.sh,tests/scripts/test-qualify-skill-refs.sh,tests/scripts/test-skill-path-refs.sh,tests/scripts/test-check-skill-refs.sh,tests/skills/test_end_skill_final_verification_step.py,tests/skills/test_implementation_plan_skill_tdd_enforcement.py,tests/skills/test-quick-ref-skill.sh,tests/skills/test_fix_bug_skill.py,tests/skills/test_end_skill_summary_displays_stored_learnings.py,tests/skills/test_end_skill_learnings_step_before_commit.py,tests/skills/test-design-skills-cross-stack.sh,tests/skills/test_end_skill_dirty_worktree_resolution.py,tests/skills/test_fix_bug_skill_escalated_section.py,tests/skills/test_end_skill_bug_tickets_before_commit.py,tests/hooks/test-sub-agent-guard.sh,tests/scripts/test-v2-skills-cleanup.sh,tests/skills/test-brainstorm-web-research.sh,tests/skills/test-brainstorm-scenario-analysis.sh,tests/skills/test-brainstorm-approval-gate.sh
9091
plugins/dso/skills/shared/docs/reviewers/agent-clarity.md:tests/reviewers/test-agent-clarity-epic-calibration.sh

plugins/dso/agents/conflict-analyzer.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
---
22
name: conflict-analyzer
33
model: sonnet
4+
description: Analyzes git merge conflicts, classifies each conflicted file, and proposes a resolution with confidence scoring.
5+
tools:
6+
- Bash
7+
- Read
8+
- Glob
9+
- Grep
410
---
511

612
# Conflict Analyzer

plugins/dso/hooks/lib/red-zone.sh

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
# For Python: matches 'def marker_name' or 'def marker_name('
2828
# For Bash: matches 'marker_name()' or 'marker_name (' or '# marker_name' pattern
2929
# For plain text / other: matches any line containing the marker name
30+
# For Class::method format (pytest class-based tests): when marker_name contains '::',
31+
# split on '::' to get class_name and method_name, then scan the file for the
32+
# class definition first, and only match the method def within that class scope.
3033
# Returns the line number on stdout, or -1 if not found.
3134
# Emits WARNING to stderr if marker provided but not found.
3235
get_red_zone_line_number() {
@@ -40,8 +43,48 @@ get_red_zone_line_number() {
4043
return 0
4144
fi
4245

43-
local line_num=0
4446
local found_line=-1
47+
48+
# Handle Class::method format (pytest class-based test IDs)
49+
if [[ "$marker_name" == *::* ]]; then
50+
local class_name="${marker_name%%::*}"
51+
local method_name="${marker_name#*::}"
52+
# Word-boundary patterns for class and method
53+
local pat_class="(^|[^a-zA-Z0-9_-])${class_name}([^a-zA-Z0-9_-]|\$)"
54+
local pat_method="(^|[^a-zA-Z0-9_-])${method_name}([^a-zA-Z0-9_-]|\$)"
55+
local line_num=0
56+
local in_class=0
57+
while IFS= read -r line || [[ -n "$line" ]]; do
58+
(( line_num++ )) || true
59+
[[ "$line" =~ ^[[:space:]]*# ]] && continue
60+
# Detect class definition: "class ClassName" or "class ClassName:"
61+
if [[ $in_class -eq 0 ]] && [[ "$line" =~ $pat_class ]]; then
62+
in_class=1
63+
continue
64+
fi
65+
# Once inside the class, look for the method definition
66+
if [[ $in_class -eq 1 ]]; then
67+
# A new top-level class/def (no leading whitespace) signals we left the class
68+
if [[ "$line" =~ ^[^[:space:]] ]] && [[ ! -z "$line" ]]; then
69+
# Allow "class " or "def " at top level — we've left the previous class
70+
in_class=0
71+
continue
72+
fi
73+
if [[ "$line" =~ $pat_method ]]; then
74+
found_line=$line_num
75+
break
76+
fi
77+
fi
78+
done < "$full_path"
79+
80+
if [[ $found_line -eq -1 ]]; then
81+
echo "WARNING: RED marker '${marker_name}' not found in test file: ${test_file}" >&2
82+
fi
83+
echo "$found_line"
84+
return 0
85+
fi
86+
87+
local line_num=0
4588
# Word-boundary pattern: marker_name not adjacent to other identifier chars [a-zA-Z0-9_-]
4689
# Hyphens are included so that searching for 'test-foo' does not match 'test-foo-bar',
4790
# and searching for 'test' does not accidentally match 'test-foo'.
@@ -195,6 +238,9 @@ read_red_markers_by_test_file() {
195238
local right="${line#*:}"
196239

197240
# Parse each comma-separated test entry
241+
# Declare parts and part as local to prevent clobbering caller variables
242+
# when read_red_markers_by_test_file is called directly (not in a subshell).
243+
local parts part
198244
IFS=',' read -ra parts <<< "$right"
199245
for part in "${parts[@]}"; do
200246
# Trim leading/trailing whitespace

plugins/dso/hooks/lib/review-gate-bypass-sentinel.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,37 @@ hook_review_bypass_sentinel() {
179179
fi
180180
fi
181181

182+
# --- Pattern k: Direct writes/deletions to .tickets-tracker/ internals ---
183+
# Block commands that modify .tickets-tracker/ files directly (echo/cat/tee/printf
184+
# with redirect, cp/mv, rm) but allow read-only commands and authorized writers
185+
# (ticket CLI scripts: ticket*.sh, ticket-*.py).
186+
# Also protects .git/worktrees/-tickets-tracker/ (git worktree metadata).
187+
if [[ "$COMMAND" == *".tickets-tracker/"* ]] || [[ "$COMMAND" == *"worktrees/-tickets-tracker/"* ]]; then
188+
# Exemption: ticket CLI scripts are authorized writers
189+
if [[ "$COMMAND" == *"ticket-"*".sh"* ]] || [[ "$COMMAND" == *"ticket-"*".py"* ]] || \
190+
[[ "$COMMAND" == *"ticket init"* ]] || [[ "$COMMAND" == *"ticket-init"* ]] || \
191+
[[ "$COMMAND" == *"ticket-lib"* ]]; then
192+
return 0
193+
fi
194+
# Exemption: git operations within ticket scripts (git -C .tickets-tracker/ ...)
195+
if [[ "$COMMAND" =~ git[[:space:]]+-C[[:space:]]+[^[:space:]]*\.tickets-tracker ]]; then
196+
return 0
197+
fi
198+
# Exemption: read-only commands (cat, head, tail, ls, find, grep without redirect)
199+
if [[ "$COMMAND" =~ ^[[:space:]]*(cat|head|tail|ls|find|grep|wc|stat)[[:space:]] ]] && \
200+
[[ ! "$COMMAND" =~ \> ]]; then
201+
return 0
202+
fi
203+
# Check for write/delete patterns
204+
if [[ "$COMMAND" =~ \>[[:space:]]*[^[:space:]]*(\.tickets-tracker/|worktrees/-tickets-tracker/) ]] || \
205+
[[ "$COMMAND" =~ (tee)[[:space:]]*[^[:space:]]*(\.tickets-tracker/|worktrees/-tickets-tracker/) ]] || \
206+
[[ "$COMMAND" =~ (cp|mv)[[:space:]].*(\.tickets-tracker/|worktrees/-tickets-tracker/) ]] || \
207+
[[ "$COMMAND" =~ (echo|printf)[[:space:]].*\>.*(\.tickets-tracker/|worktrees/-tickets-tracker/) ]] || \
208+
[[ "$COMMAND" =~ rm[[:space:]].*(\.tickets-tracker/|worktrees/-tickets-tracker/) ]]; then
209+
echo "BLOCKED [bypass-sentinel]: direct modification of .tickets-tracker/ detected. Use ticket CLI commands (ticket create, ticket comment, etc.) instead." >&2
210+
trap - ERR; return 2
211+
fi
212+
fi
213+
182214
return 0
183215
}

plugins/dso/hooks/record-test-status.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ read_test_index_for_source() {
8787
fi
8888

8989
# Split right side on commas and emit each non-empty test path (with optional [marker])
90+
# Declare parts and part as local to prevent clobbering caller variables.
91+
local parts part
9092
IFS=',' read -ra parts <<< "$right"
9193
for part in "${parts[@]}"; do
9294
# Trim leading/trailing whitespace
@@ -144,6 +146,8 @@ find_global_red_marker_for_test() {
144146
# Split on first colon: left = source, right = comma-separated tests
145147
local right="${line#*:}"
146148

149+
# Declare parts and part as local to prevent clobbering caller variables.
150+
local parts part
147151
IFS=',' read -ra parts <<< "$right"
148152
for part in "${parts[@]}"; do
149153
# Trim whitespace

plugins/dso/scripts/acli-integration.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,12 @@ def update_issue(
310310
"--json",
311311
]
312312
for field, value in kwargs.items():
313-
cmd.extend([f"--{field}", str(value)])
313+
if field == "description":
314+
# Convert description to ADF (same as create_issue) — Jira REST API
315+
# v3 requires ADF format for description fields.
316+
cmd.extend([f"--{field}", json.dumps(_text_to_adf(str(value)))])
317+
else:
318+
cmd.extend([f"--{field}", str(value)])
314319

315320
result = _run_acli(cmd, acli_cmd=acli_cmd)
316321
return json.loads(result.stdout)

plugins/dso/scripts/ticket-create.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ while [ $# -gt 0 ]; do
8383
description="$2"
8484
shift 2
8585
;;
86+
-p)
87+
priority="$2"
88+
shift 2
89+
;;
8690
*)
8791
# Positional: treat as parent_id (backward-compatible)
8892
parent_id="$1"
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
# worktree-sync-from-main.sh — Sync a worktree branch with the latest main
4+
#
5+
# Fetches origin/main and merges it into the current worktree branch, then
6+
# fetches the latest ticket state from origin/tickets so the worktree's
7+
# .tickets-tracker/ is current.
8+
#
9+
# Referenced from WORKTREE-GUIDE.md and sprint/SKILL.md Step 3.
10+
# Run before launching sub-agent batches to ensure ticket state and code are current.
11+
#
12+
# Usage:
13+
# worktree-sync-from-main.sh [--skip-tickets] [--skip-code]
14+
#
15+
# Options:
16+
# --skip-tickets Skip syncing the tickets branch
17+
# --skip-code Skip merging origin/main into the worktree branch
18+
# --help Print this usage message and exit
19+
#
20+
# Exit codes:
21+
# 0 — Sync complete (or no-op if already up to date)
22+
# 1 — Fatal error (merge conflict that could not be auto-resolved; not in worktree)
23+
#
24+
# Notes:
25+
# - Must be run from inside a worktree (i.e., .git is a file, not a directory)
26+
# - Non-ticket merge conflicts are reported and exit 1 so the caller can
27+
# invoke /dso:resolve-conflicts before continuing
28+
# - Ticket sync failures are non-fatal (stale ticket state < blocked batch)
29+
30+
set -euo pipefail
31+
32+
# ── Argument parsing ─────────────────────────────────────────────────────────
33+
34+
SKIP_TICKETS=0
35+
SKIP_CODE=0
36+
37+
for arg in "$@"; do
38+
case "$arg" in
39+
--skip-tickets) SKIP_TICKETS=1 ;;
40+
--skip-code) SKIP_CODE=1 ;;
41+
--help)
42+
cat <<'USAGE'
43+
Usage: worktree-sync-from-main.sh [--skip-tickets] [--skip-code]
44+
45+
--skip-tickets Skip syncing the tickets branch
46+
--skip-code Skip merging origin/main into the worktree branch
47+
--help Print this usage message and exit
48+
49+
Syncs the current worktree branch with the latest origin/main and pulls the
50+
latest ticket state from origin/tickets into .tickets-tracker/.
51+
USAGE
52+
exit 0
53+
;;
54+
*)
55+
echo "ERROR: Unknown option: $arg" >&2
56+
echo "Run '$0 --help' for usage." >&2
57+
exit 1
58+
;;
59+
esac
60+
done
61+
62+
# ── Locate repo root ─────────────────────────────────────────────────────────
63+
64+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
65+
if [ -z "$REPO_ROOT" ]; then
66+
echo "ERROR: Not inside a git repository." >&2
67+
exit 1
68+
fi
69+
70+
# Must be called from a worktree (.git is a file, not a directory)
71+
if [ -d "$REPO_ROOT/.git" ]; then
72+
echo "ERROR: Not inside a worktree. Run from a worktree session, not the main repo." >&2
73+
exit 1
74+
fi
75+
76+
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
77+
if [ -z "$CURRENT_BRANCH" ]; then
78+
echo "ERROR: Could not determine current branch (detached HEAD?)." >&2
79+
exit 1
80+
fi
81+
82+
echo "Syncing worktree '$CURRENT_BRANCH' from main..." >&2
83+
84+
# ── 1) Merge origin/main into the worktree branch ────────────────────────────
85+
86+
if [ "$SKIP_CODE" -eq 0 ]; then
87+
echo " Fetching origin/main..." >&2
88+
if ! git fetch origin main --quiet 2>/dev/null; then
89+
echo " WARNING: git fetch origin main failed — skipping code sync." >&2
90+
else
91+
# Check if already up to date
92+
LOCAL_SHA=$(git rev-parse HEAD)
93+
ORIGIN_MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
94+
MERGE_BASE=$(git merge-base HEAD origin/main 2>/dev/null || echo "")
95+
96+
if [ "$MERGE_BASE" = "$ORIGIN_MAIN_SHA" ]; then
97+
echo " OK: Worktree branch is already up to date with origin/main." >&2
98+
else
99+
echo " Merging origin/main into $CURRENT_BRANCH..." >&2
100+
if ! git merge origin/main --no-edit -q 2>&1; then
101+
echo "ERROR: Merge of origin/main failed. Resolve conflicts then re-run." >&2
102+
echo " Use /dso:resolve-conflicts for guided conflict resolution." >&2
103+
exit 1
104+
fi
105+
echo " OK: Merged origin/main into $CURRENT_BRANCH." >&2
106+
fi
107+
fi
108+
else
109+
echo " Skipping code sync (--skip-code)." >&2
110+
fi
111+
112+
# ── 2) Sync ticket state from origin/tickets ─────────────────────────────────
113+
114+
if [ "$SKIP_TICKETS" -eq 0 ]; then
115+
# Locate .tickets-tracker/ — it is a git worktree on the tickets branch
116+
# mounted relative to the main (non-worktree) checkout. From a worktree,
117+
# git rev-parse --git-common-dir points to the main repo's .git directory.
118+
MAIN_GIT_DIR=$(git rev-parse --git-common-dir 2>/dev/null || echo "")
119+
MAIN_REPO=""
120+
if [ -n "$MAIN_GIT_DIR" ]; then
121+
MAIN_REPO=$(dirname "$MAIN_GIT_DIR")
122+
fi
123+
124+
TRACKER_DIR=""
125+
if [ -n "$MAIN_REPO" ] && [ -d "$MAIN_REPO/.tickets-tracker" ]; then
126+
TRACKER_DIR="$MAIN_REPO/.tickets-tracker"
127+
elif [ -d "$REPO_ROOT/.tickets-tracker" ]; then
128+
TRACKER_DIR="$REPO_ROOT/.tickets-tracker"
129+
fi
130+
131+
if [ -z "$TRACKER_DIR" ]; then
132+
echo " INFO: .tickets-tracker/ not found — skipping ticket sync." >&2
133+
elif ! git -C "$TRACKER_DIR" rev-parse --verify tickets &>/dev/null; then
134+
echo " INFO: tickets branch not found in .tickets-tracker/ — skipping ticket sync." >&2
135+
else
136+
echo " Syncing tickets branch from origin..." >&2
137+
if git -C "$TRACKER_DIR" fetch origin tickets --quiet 2>/dev/null; then
138+
REMOTE_EXISTS=$(git -C "$TRACKER_DIR" rev-parse --verify origin/tickets 2>/dev/null || echo "")
139+
if [ -n "$REMOTE_EXISTS" ]; then
140+
if git -C "$TRACKER_DIR" pull --rebase origin tickets --quiet 2>/dev/null; then
141+
echo " OK: Ticket state synced from origin/tickets." >&2
142+
else
143+
echo " WARNING: Ticket rebase from origin/tickets failed — continuing with local state." >&2
144+
fi
145+
else
146+
echo " INFO: origin/tickets not available — using local ticket state." >&2
147+
fi
148+
else
149+
echo " WARNING: git fetch origin tickets failed — using local ticket state." >&2
150+
fi
151+
fi
152+
else
153+
echo " Skipping ticket sync (--skip-tickets)." >&2
154+
fi
155+
156+
echo "Sync complete." >&2

0 commit comments

Comments
 (0)