Skip to content

Commit 02c260c

Browse files
JoeOakhartNavaTestclaude
authored
Merge remote-tracking branch 'origin/main' into worktree-20260512-190648 (#112)
* fix(merge-to-main-pr): push version-bump commit to origin/main after PR merge (3024-d618) _phase_version_bump in PR mode was using git commit --amend, creating a divergent SHA that can't be fast-forward pushed. _phase_push was also never called, so the version bump never reached origin/main. Fix: set MERGE_TO_MAIN_PR_MODE=1 before calling _phase_version_bump so it makes a new commit (not amend), then call _phase_push to push it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(merge-to-main-pr): guard MAIN_REPO branch + fix fetch refspec in test (coderabbit) - Ensure MAIN_REPO is on 'main' before _phase_version_bump + _phase_push so git push targets the correct ref (coderabbit PRRT_kwDORoc4TM6BoDBB) - Change `git fetch origin main` to `git fetch origin main:refs/remotes/origin/main` in t_pr_version_bump_pushed_to_origin to prevent stale tracking-ref flakiness (coderabbit PRRT_kwDORoc4TM6BoDBH) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(merge-to-main-pr): remove DSO_MECHANICAL_AMEND from new-commit path (coderabbit PRRT_kwDORoc4TM6BoET5) DSO_MECHANICAL_AMEND=1 is documented and sentinel-guarded as amend-only. The PR-mode new commit (chore: bump version) only stages plugin.json, which is allowlisted in review-gate-allowlist.conf, so no bypass is needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(merge-to-main-pr): address 4 coderabbit findings on version-bump path BoKhK: avoid ${_vf} interpolation into python3 -c source — pass via _VF env var BoLgH: fast-forward local main to origin/main after branch switch before push BoLgc: randomize test branch name with $$ to prevent tmp-path collisions BoIjn: add t_pr_version_bump_main_repo_not_on_main to cover branch-switch path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(merge-to-main-pr): address llm-review cycle-1 findings (PR #111) Critical: add _vf non-empty guard + explicit file close in Python version extraction one-liner; _vf is defined at line 598 of _phase_version_bump (REVIEW-DEFENSE added for false-positive "undefined variable" claim) Important: fail-loudly on MAIN_REPO checkout failure (was silently || true) which could leave version bump running on wrong branch Important: add commit-message assertions to t_pr_version_bump_* tests so the "vunknown" fallback defect is caught by tests REVIEW-DEFENSE: _phase_push called-twice finding is false positive — merge-to-main-direct.sh sourced with MERGE_TO_MAIN_DIRECT_LIB=1, skipping top-level lifecycle; _phase_push called exactly once REVIEW-DEFENSE: RED marker removal for 0a63-754f is correct — t_pr_version_bump_pushed_to_origin is the implementation the marker awaited Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): resolve 5 bugs across sprint, ticket-cli, review-gate, and onboarding - d2e4-3db7: sprint Phase F now checks SPRINT_MODE and routes ci-pr to merge-to-main.sh instead of direct merge-story-branch.sh - 277c-8f37: sprint-story-review.yml adds actionlint/shellcheck/lint-python gate jobs with needs: on the review job - 493a-dfe5: ticket show resolves friendly aliases via full resolve_ticket_id pipeline (was using short-hex-only _ticketlib_resolve_short_id) - 7ddc-1ae7: review-gate-allowlist.conf adds bootstrap/onboarding config artifact patterns so first commit doesn't require review - 6b04-9b62: onboarding SKILL.md adds git add -A after git checkout - to re-stage files cleared by orphan branch checkout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(277c-8f37): replace deprecated fail_on_error with fail_level in actionlint step reviewdog/action-actionlint@v1 deprecated fail_on_error; actionlint itself flagged this during PR CI. Replacing with fail_level: error per current API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(7ddc-1ae7): restore clean .tickets-tracker/** pattern in allowlist Inline # tickets-boundary-ok suffix broke _load_allowlist_patterns which does not strip trailing comments — the full line became the glob, failing to match .tickets-tracker/ paths. Fix: restore clean pattern and add review-gate-allowlist.conf to check-tickets-boundary-allowlist.conf. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(7ddc-1ae7): remove dso-config.conf from bootstrap allowlist .claude/dso-config.conf was incorrectly added to review-gate-allowlist.conf in the batch-2 commit. The classifier reads the same allowlist to identify SCORING_FILES — adding dso-config.conf to the exempt list silently broke the _has_config_file floor rule (dso-config.conf diffs landed in light tier instead of standard/deep). Removing it restores the floor rule. The partial bootstrap allowlist still covers .gitignore, CLAUDE.md, workflow, and script scaffolding files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(7ddc-1ae7): remove CLAUDE.md from bootstrap allowlist CLAUDE.md has a hardcoded safeguard floor rule in review-complexity-classifier.sh (_has_safeguard_file iterates SCORING_FILES only). Adding it to the allowlist puts it in EXEMPT_FILES, silently bypassing the floor rule and landing at light tier. Removing it from the allowlist restores the standard-tier floor for CLAUDE.md changes. Bootstrap scenario still benefits from .gitignore, .semgrep.yml, .pre-commit-config.yaml, .github/workflows/**, .claude/scripts/**, and .claude/project-understanding.md being allowlisted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address llm-review cycle-1 findings Critical: - onboarding: replace git add -A with targeted re-staging using saved staged-file list; prevents application code from being staged into the orphan .tickets-tracker branch Important: - sprint Phase F: export STORY_BRANCH STORY_ID before calling merge-to-main.sh in ci-pr mode so subprocess can access orchestrator variables - ticket_show: add declare -f guard to prevent repeated ticket-lib.sh sourcing in batch contexts (shell cached function detection) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address llm-review cycle-2 findings Critical: - allowlist: remove .github/workflows/** — workflow files carry semantic significance and must be reviewed when modified Important: - ticket_show: add _TICKETLIB_DIR guard for clear diagnostic on missing module variable; pass TRACKER_DIR as TICKETS_TRACKER_DIR to resolve_ticket_id so custom tracker paths are honored by the resolution pipeline - onboarding: fix unquoted $_STAGED_BEFORE expansion using while/read loop to handle filenames with spaces safely - sprint Phase F ci-pr: add exit code check on merge-to-main.sh call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(493a-dfe5): use non-circular alias derivation in test Replace code-under-test alias retrieval (ticket show $id) with independent derivation via ticket-alias-compute.py + wordlist, eliminating the circular dependency flagged in llm-review cycle-2. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address llm-review cycle-3 findings - Add error handling to local mode merge path in sprint Phase F (was silently ignoring non-zero exit from merge-story-branch.sh) - Clarify resolve_ticket_id contract in ticket-lib-api.sh comment (document source location and interface) - Document merge-to-main.sh env-var contract in ci-pr path comment - Fix misleading comment in onboarding SKILL.md (readarray → while/read) - Fix terminology in review-gate-allowlist.conf comment (EXCLUDED → NOT listed here) - Add JSON output validation and tmpdir cleanup to alias resolution test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): skip alias scan for 4+ hyphen inputs in ticket show Inputs with 4+ hyphens (e.g. test inputs like nonexistent-ticket-id-zzz9-yyyy) cannot match any known ticket format (max is 3 hyphens for 16-hex IDs). Previously, resolve_ticket_id would run the expensive O(N*K) alias scan via ticket-alias-resolve.py against all 16K+ tickets, causing test timeouts in CI when check-tag-guards.sh / check-reinvocation.sh used such inputs. Add format-discriminated resolution: hex patterns → fast path, 4+ hyphens → immediate fail, 0-3 hyphens non-hex → full alias pipeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address llm-review cycle-4 findings - Add _snapshot_fail to alias-resolution tests for correct test isolation - Add tests for 4+-hyphen fast-reject and 8-hex short-ID resolution paths - Switch sprint-story-review.yml lint-python to astral-sh/ruff-action@v3 (pre-built action with pinned version replaces pip install ruff) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address llm-review cycle-5 findings - Propagate resolve_ticket_id exit code explicitly; command substitution silently loses the exit status, causing a generic "not found" message to overwrite the real error from the alias-resolution path - Add behavioral proof that ≥4-hyphen fast-path fires before alias scan: unset _TICKETLIB_DIR in test so slow path would produce a distinct error; assert that error contains no _TICKETLIB_DIR reference - Guard ${ticket_id:0:9} slice with length check (>= 9) before use Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address PR #112 review thread findings - Add permissions block to sprint-story-review.yml (contents: read, checks: write); fixes CodeQL/CodeRabbit/Copilot token-scope findings - Pin ludeeus/action-shellcheck@master to @2.0.0 (supply-chain pinning) - Replace tr+wc hyphen counting with pure bash arithmetic in ticket-lib-api.sh - Fix JSON parsing in test to use stdin (not sys.argv) for robustness - Fix misleading "skip" comment: alias unavailability is a hard fail - Fix sprint SKILL.md: use BRANCH env var (not STORY_BRANCH) for merge-to-main-pr.sh which defaults to the current git branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(batch-2): address llm-review cycle-6 findings - Fix hex regex: [a-z0-9] -> [0-9a-f] to reject non-hex alphanumeric inputs (e.g., 'gggg-hhhh') that would have been routed to _ticketlib_resolve_short_id expecting pure hex - Remove fragile _TICKETLIB_DIR error-message assertion from fast-path test; observable behavior (exit non-zero) is the correct assertion - Add short_id format guard in 8-hex test: assert extracted prefix matches [0-9a-f]{4}-[0-9a-f]{4} before proceeding Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2443c42 commit 02c260c

7 files changed

Lines changed: 225 additions & 3 deletions

File tree

.claude/hooks/pre-commit/check-tickets-boundary-allowlist.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ plugins/dso/hooks/lib/merge-helpers.sh
3535

3636
# Exclusion-pattern scripts (classify .tickets-tracker/ paths, do not access them)
3737
plugins/dso/scripts/skip-review-check.sh
38+
plugins/dso/hooks/lib/review-gate-allowlist.conf
3839

3940
# All tests are exempt — the boundary exists to enforce that ticket code can be
4041
# broken out into a separate component later; test helpers that exercise that code

.github/workflows/sprint-story-review.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,52 @@ on:
55
branches:
66
- 'story/**'
77

8+
permissions:
9+
contents: read
10+
checks: write
11+
812
concurrency:
913
group: ${{ github.workflow }}-${{ github.ref }}-${{ vars.SPRINT_SESSION_ID }}
1014
cancel-in-progress: true
1115

1216
jobs:
17+
actionlint:
18+
name: Actionlint
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 5
21+
steps:
22+
- uses: actions/checkout@v4
23+
- name: Run actionlint
24+
uses: reviewdog/action-actionlint@v1
25+
with:
26+
reporter: github-check
27+
fail_level: error
28+
29+
shellcheck:
30+
name: ShellCheck
31+
runs-on: ubuntu-latest
32+
timeout-minutes: 5
33+
steps:
34+
- uses: actions/checkout@v4
35+
- name: Run ShellCheck
36+
uses: ludeeus/action-shellcheck@2.0.0
37+
with:
38+
scandir: './plugins/dso/scripts'
39+
40+
lint-python:
41+
name: Lint Python (ruff)
42+
runs-on: ubuntu-latest
43+
timeout-minutes: 5
44+
steps:
45+
- uses: actions/checkout@v4
46+
- name: Run ruff
47+
uses: astral-sh/ruff-action@v3
48+
with:
49+
args: check plugins/dso/scripts/
50+
1351
review:
1452
name: Per-Story LLM Review
53+
needs: [actionlint, shellcheck, lint-python]
1554
runs-on: ubuntu-latest
1655
timeout-minutes: 15
1756
steps:

plugins/dso/hooks/lib/review-gate-allowlist.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,14 @@ docs/**
6565

6666
# Claude brainstorm/research artifacts
6767
.claude/artifacts/**
68+
69+
# Bootstrap / onboarding config artifacts (written by dso-setup.sh / onboarding
70+
# SKILL.md Batch Groups 2+4; contain no reviewable application code).
71+
# NOTE: .claude/dso-config.conf, settings.json, CLAUDE.md, and .github/workflows/**
72+
# are intentionally NOT listed here — they are NOT exempt, so changes to them ARE reviewed.
73+
# .github/workflows/** in particular drives CI/CD and MUST be reviewed when modified.
74+
.gitignore
75+
.semgrep.yml
76+
.pre-commit-config.yaml
77+
.claude/scripts/**
78+
.claude/project-understanding.md

plugins/dso/scripts/ticket-lib-api.sh

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,37 @@ ticket_show() {
123123
return 1
124124
fi
125125

126-
ticket_id="$(_ticketlib_resolve_short_id "$ticket_id" "$TRACKER_DIR")"
126+
# Resolution strategy (ordered by cost):
127+
# 1. Hex patterns (8-hex or 16-hex): fast O(N-dir) scan via _ticketlib_resolve_short_id
128+
# 2. Inputs with 4+ hyphens: cannot match any known format (max is 3 for 16-hex);
129+
# skip expensive O(N*K) alias scan and fail immediately
130+
# 3. All other inputs (0-3 hyphens, non-hex): potential alias, jira_key, or prefix;
131+
# use full resolve_ticket_id pipeline from ticket-lib.sh (line ~1199)
132+
local _no_hyphens="${ticket_id//-/}"
133+
local _hyphen_count=$(( ${#ticket_id} - ${#_no_hyphens} ))
134+
if [[ "$ticket_id" =~ ^[0-9a-f]{4}-[0-9a-f]{4}(-[0-9a-f]{4}-[0-9a-f]{4})?$ ]]; then
135+
ticket_id="$(_ticketlib_resolve_short_id "$ticket_id" "$TRACKER_DIR")"
136+
elif [[ "$_hyphen_count" -ge 4 ]]; then
137+
echo "Error: Ticket '$ticket_id' not found" >&2
138+
return 1
139+
else
140+
if [[ -z "${_TICKETLIB_DIR:-}" ]]; then
141+
echo "Error: _TICKETLIB_DIR is not set; cannot resolve ticket alias" >&2
142+
return 1
143+
fi
144+
declare -f resolve_ticket_id &>/dev/null || source "$_TICKETLIB_DIR/ticket-lib.sh"
145+
# Pass TRACKER_DIR through TICKETS_TRACKER_DIR so resolve_ticket_id
146+
# uses the same tracker path as ticket_show's directory lookup below.
147+
# Capture exit code separately: command substitution loses the exit status,
148+
# so a failing resolve_ticket_id would silently produce an empty ticket_id
149+
# and fall through to the generic "not found" error, hiding the real cause.
150+
local _resolve_rc=0
151+
ticket_id="$(TICKETS_TRACKER_DIR="$TRACKER_DIR" resolve_ticket_id "$ticket_id")" || _resolve_rc=$?
152+
if [[ $_resolve_rc -ne 0 ]]; then
153+
# resolve_ticket_id already wrote a specific error to stderr; propagate it
154+
return 1
155+
fi
156+
fi
127157

128158
if [ ! -d "$TRACKER_DIR/$ticket_id" ]; then
129159
echo "Error: Ticket '$ticket_id' not found" >&2

plugins/dso/skills/onboarding/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,13 +1213,23 @@ If the git guard passes, initialize the DSO ticket system by creating an orphan
12131213
```bash
12141214
# Create orphan branch for ticket event storage
12151215
cd "$REPO_ROOT"
1216+
# Capture staged files before orphan checkout clears the index
1217+
_STAGED_BEFORE=$(git diff --name-only --cached 2>/dev/null || true)
12161218
git checkout --orphan tickets
12171219
git rm -rf . --quiet 2>/dev/null || true
12181220
mkdir -p .tickets-tracker
12191221
echo "# DSO Ticket System" > .tickets-tracker/README.md
12201222
git add .tickets-tracker/README.md
12211223
git commit -m "chore: initialize ticket system"
12221224
git checkout - # return to previous branch
1225+
# Re-stage only the files that were staged before the orphan checkout
1226+
# (do NOT use git add -A which would stage untracked application code)
1227+
# Use while/read with IFS= to handle filenames with spaces safely
1228+
if [ -n "$_STAGED_BEFORE" ]; then
1229+
while IFS= read -r _staged_file; do
1230+
[ -n "$_staged_file" ] && git add -- "$_staged_file"
1231+
done <<< "$_STAGED_BEFORE"
1232+
fi
12231233
```
12241234

12251235
**Push verification:** After creating the orphan branch, push it to the remote and verify push success. If the push fails, warn the user:

plugins/dso/skills/sprint/SKILL.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,15 +1996,33 @@ grep -n "\[.*\]" .test-index || true
19961996
# Remove any markers for tests that are now passing
19971997
```
19981998
1999-
**Story branch merge (before closure)**: After RED marker cleanup and before closing the story, merge the story branch with the DSO-Story-Merge trailer:
1999+
**Story branch merge (before closure)**: After RED marker cleanup and before closing the story, merge the story branch. The merge path depends on `SPRINT_MODE`:
2000+
- `ci-pr` mode: route through `merge-to-main.sh` to create a GitHub PR — do NOT perform a direct local merge
2001+
- `local` mode (default): direct local merge with `DSO-Story-Merge` trailer via `merge-story-branch.sh`
20002002
20012003
```bash
20022004
# Conflict queue precondition (in-memory orchestrator check):
20032005
if [[ ${#CONFLICT_QUEUE[@]} -gt 0 ]]; then
20042006
echo 'ERROR: conflict queue non-empty — resolve conflicts before merging story branch' >&2
20052007
exit 1
20062008
fi
2007-
bash "$PLUGIN_SCRIPTS/merge-story-branch.sh" "$STORY_BRANCH" "$STORY_ID" # shim-exempt: SKILL.md orchestrator instruction — sprint runs plugin scripts via $PLUGIN_SCRIPTS directly
2009+
if [[ "${SPRINT_MODE:-local}" == "ci-pr" ]]; then
2010+
# ci-pr mode: merge via GitHub PR — do NOT perform a local direct merge
2011+
# merge-to-main-pr.sh uses BRANCH (defaults to current git branch if unset).
2012+
# Export STORY_BRANCH as BRANCH so the script targets the correct story branch
2013+
# even when the orchestrator is not currently checked out on that branch.
2014+
export BRANCH="$STORY_BRANCH"
2015+
bash "$PLUGIN_SCRIPTS/merge-to-main.sh" || { # shim-exempt: SKILL.md orchestrator instruction — sprint runs plugin scripts via $PLUGIN_SCRIPTS directly
2016+
echo "ERROR: merge-to-main.sh failed in ci-pr mode — aborting story merge" >&2
2017+
exit 1
2018+
}
2019+
else
2020+
# local mode: direct local merge with DSO-Story-Merge trailer
2021+
bash "$PLUGIN_SCRIPTS/merge-story-branch.sh" "$STORY_BRANCH" "$STORY_ID" || { # shim-exempt: SKILL.md orchestrator instruction — sprint runs plugin scripts via $PLUGIN_SCRIPTS directly
2022+
echo "ERROR: merge-story-branch.sh failed in local mode — aborting story merge" >&2
2023+
exit 1
2024+
}
2025+
fi
20082026
```
20092027
20102028
### Leakage Detection

tests/scripts/test-ticket-show.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,4 +547,117 @@ except Exception as e:
547547
}
548548
test_ticket_show_preconditions_summary_legacy_placeholder
549549

550+
# [RED: 493a-dfe5-alias-resolve]
551+
# ── Test: ticket show resolves friendly alias ─────────────────────────────────
552+
echo "Test: ticket show resolves friendly alias to ticket details"
553+
test_ticket_show_resolves_friendly_alias() {
554+
_snapshot_fail
555+
local repo
556+
repo=$(_make_test_repo)
557+
558+
local ticket_id=""
559+
ticket_id=$(cd "$repo" && bash "$TICKET_SCRIPT" create story "alias resolution test story" 2>/dev/null | tail -1 || true)
560+
561+
if [ -z "$ticket_id" ]; then
562+
assert_eq "ticket created for alias resolution test" "non-empty" "empty"
563+
rm -rf "$repo"
564+
return
565+
fi
566+
567+
# Derive the alias independently via ticket-alias-compute.py (not via ticket show)
568+
# to avoid circular dependency on the code under test.
569+
local alias=""
570+
local _wordlist="$REPO_ROOT/plugins/dso/resources/ticket-wordlist.txt"
571+
local _alias_script="$REPO_ROOT/plugins/dso/scripts/ticket-alias-compute.py"
572+
if [[ -f "$_alias_script" && -f "$_wordlist" ]]; then
573+
alias=$(python3 "$_alias_script" "$ticket_id" "$_wordlist" 2>/dev/null || true)
574+
fi
575+
576+
if [ -z "$alias" ]; then
577+
# Alias computation unavailable — this is a hard requirement; fail the test
578+
assert_eq "ticket alias derivable from ticket_id" "non-empty-alias" "empty-alias"
579+
rm -rf "$repo"
580+
return
581+
fi
582+
583+
# ticket show using the friendly alias must exit 0 and return the same ticket_id
584+
local alias_output=""
585+
local alias_exit=0
586+
alias_output=$(cd "$repo" && bash "$TICKET_SCRIPT" show "$alias" 2>/dev/null) || alias_exit=$?
587+
588+
assert_eq "ticket show via alias exits 0" "0" "$alias_exit"
589+
590+
# Validate output is parseable JSON before extracting field (parse via stdin, not argv)
591+
local json_valid=""
592+
json_valid=$(echo "$alias_output" | python3 -c "import json,sys; json.load(sys.stdin); print('yes')" 2>/dev/null || echo "no")
593+
assert_eq "ticket show via alias returns valid JSON" "yes" "$json_valid"
594+
595+
local resolved_id=""
596+
resolved_id=$(echo "$alias_output" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('ticket_id',''))" 2>/dev/null || true)
597+
assert_eq "ticket show via alias returns correct ticket_id" "$ticket_id" "$resolved_id"
598+
599+
rm -rf "$repo"
600+
assert_pass_if_clean "test_ticket_show_resolves_friendly_alias"
601+
}
602+
test_ticket_show_resolves_friendly_alias
603+
604+
# ── Test: ticket show rejects inputs with 4+ hyphens without alias scan ───────
605+
echo "Test: ticket show rejects 4+ hyphen inputs fast (no alias scan)"
606+
test_ticket_show_rejects_four_hyphen_input() {
607+
_snapshot_fail
608+
local repo
609+
repo=$(_make_test_repo)
610+
611+
local rc=0
612+
cd "$repo" && bash "$TICKET_SCRIPT" show "nonexistent-ticket-id-zzz9-yyyy" 2>/dev/null || rc=$?
613+
assert_ne "ticket show with 4+ hyphens exits non-zero" "0" "$rc"
614+
615+
rm -rf "$repo"
616+
assert_pass_if_clean "test_ticket_show_rejects_four_hyphen_input"
617+
}
618+
test_ticket_show_rejects_four_hyphen_input
619+
620+
# ── Test: ticket show resolves 8-hex short ID ─────────────────────────────────
621+
echo "Test: ticket show resolves 8-hex short ID to full ticket"
622+
test_ticket_show_resolves_8hex_short_id() {
623+
_snapshot_fail
624+
local repo
625+
repo=$(_make_test_repo)
626+
627+
local ticket_id=""
628+
ticket_id=$(cd "$repo" && bash "$TICKET_SCRIPT" create story "short-id test story" 2>/dev/null | tail -1 || true)
629+
630+
if [ -z "$ticket_id" ]; then
631+
assert_eq "ticket created for short-id test" "non-empty" "empty"
632+
rm -rf "$repo"
633+
return
634+
fi
635+
636+
# Extract the 8-hex prefix (first 9 chars: "xxxx-xxxx"); guard length and format
637+
if [[ ${#ticket_id} -lt 9 ]]; then
638+
assert_eq "ticket_id long enough for 8-hex prefix (>=9 chars)" ">=9" "${#ticket_id}"
639+
rm -rf "$repo"
640+
return
641+
fi
642+
local short_id="${ticket_id:0:9}"
643+
if [[ ! "$short_id" =~ ^[0-9a-f]{4}-[0-9a-f]{4}$ ]]; then
644+
assert_eq "extracted short_id has expected 8-hex format (xxxx-xxxx)" "hex4-hex4" "$short_id"
645+
rm -rf "$repo"
646+
return
647+
fi
648+
local short_output="" short_exit=0
649+
short_output=$(cd "$repo" && bash "$TICKET_SCRIPT" show "$short_id" 2>/dev/null) || short_exit=$?
650+
651+
assert_eq "ticket show via 8-hex exits 0" "0" "$short_exit"
652+
653+
local resolved_id=""
654+
resolved_id=$(python3 -c "import json,sys; d=json.loads(sys.argv[1]); print(d.get('ticket_id',''))" "$short_output" 2>/dev/null || true)
655+
assert_eq "ticket show via 8-hex returns correct ticket_id" "$ticket_id" "$resolved_id"
656+
657+
rm -rf "$repo"
658+
assert_pass_if_clean "test_ticket_show_resolves_8hex_short_id"
659+
}
660+
test_ticket_show_resolves_8hex_short_id
661+
662+
550663
print_summary

0 commit comments

Comments
 (0)