feat: enforce issue-linked branches + document versioning/tagging policy (#48)

Formalize the branch↔issue relationship and add a release/version-tagging policy.

Branch/issue linkage:
- scripts/worktree-start now validates branch names: implementation branches
  must match (fix|feat|docs|chore)/issue-<number>-<slug>; review branches
  review/pr-<number>-<slug>. Untraceable names are rejected with a clear error
  (exit 2). New --allow-unlinked override for genuine exceptions. --dry-run
  preserved.
- Documented issue → branch → worktree → PR → cleanup traceability in the
  runbook and the portable SKILL, including the claim-comment convention and
  Closes #n / Refs #n PR-body usage.
- Noted that Gitea exposes no native issue→branch API field (only a PR head
  branch), so linkage is enforced via branch name + claim comment + PR body +
  cleanup.

Versioning / tagging policy (docs only; no release automation yet):
- SemVer vMAJOR.MINOR.PATCH (v0.x.y while unstable) with PATCH/MINOR/MAJOR bump
  rules.
- Annotated tags only, from the exact commit on remote master, only after the
  full suite passes, with release notes referencing merged PRs/issues. Never tag
  feature branches, dirty worktrees, unreviewed/self-authored work, or commits
  not on remote master.
- Release runbook in the runbook + SKILL, plus a new
  skills/llm-project-workflow/templates/release-tag.md prompt template.

Tests: worktree-start branch validation — accepts fix/feat/docs/chore/issue-*
and review/pr-*, rejects fix/random-name / my-branch / non-numeric issue,
honors --allow-unlinked, preserves --dry-run. Full suite 291 passed / 0 failures;
bash -n clean; git diff --check clean; no secrets.

Release-tag automation (a scripts/release-tag helper) intentionally deferred to a
later issue to keep this diff narrow and testable.

Closes #48. Refs #38, #39, #46.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 03:41:59 -04:00
parent 00ec883014
commit f18cecc998
5 changed files with 214 additions and 9 deletions
+37 -6
View File
@@ -3,21 +3,32 @@ set -euo pipefail
usage() {
cat <<'EOF'
usage: scripts/worktree-start [--dry-run] <branch-name> [start-ref]
usage: scripts/worktree-start [--dry-run] [--allow-unlinked] <branch-name> [start-ref]
Create an issue-specific git worktree under branches/<branch-name-with-slashes-replaced>.
Create an issue-linked git worktree under branches/<branch-name-with-slashes-replaced>.
Branch names must be traceable to an issue (or a PR, for review branches):
implementation: (fix|feat|docs|chore)/issue-<number>-<short-description>
review: review/pr-<number>-<short-description>
Use --allow-unlinked to bypass the check (discouraged).
Examples:
scripts/worktree-start fix/issue-123-example
scripts/worktree-start --dry-run review/pr-123-scope-check prgs/master
scripts/worktree-start --dry-run review/pr-456-scope-check prgs/master
EOF
}
dry_run=0
if [[ "${1:-}" == "--dry-run" ]]; then
dry_run=1
allow_unlinked=0
while [[ "${1:-}" == --* ]]; do
case "$1" in
--dry-run) dry_run=1 ;;
--allow-unlinked) allow_unlinked=1 ;;
--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
shift
fi
done
if [[ $# -lt 1 || $# -gt 2 ]]; then
usage >&2
@@ -27,6 +38,26 @@ fi
branch="$1"
start_ref="${2:-prgs/master}"
# Enforce issue-linked, traceable branch names (issue → branch → worktree → PR).
if [[ "$allow_unlinked" -eq 0 ]]; then
if [[ "$branch" =~ ^(fix|feat|docs|chore)/issue-[0-9]+-.+ ]] \
|| [[ "$branch" =~ ^review/pr-[0-9]+-.+ ]]; then
:
else
cat >&2 <<EOF
Untraceable branch name: $branch
Implementation branches must be issue-linked:
(fix|feat|docs|chore)/issue-<number>-<short-description>
Review branches:
review/pr-<number>-<short-description>
Fix the branch name, or pass --allow-unlinked to override (discouraged).
EOF
exit 2
fi
fi
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
worktree_name="${branch//\//-}"