src/main/resources/targets/claude/skills/core/internal/git/x-internal-ensure-epic-branch/SKILL.md
Single source of truth for the `epic/<ID>` branch convention (RULE-001). Invoked by every epic entry-point (x-epic-create, x-epic-decompose, x-orchestrate-epic, x-implement-epic, x-epic-map) at step 0; ensures the branch exists idempotently both locally and on origin. When the branch is absent, delegates creation to `x-create-git-branch`; when present locally but not on origin, emits a complementary `git push`. Seventh skill in the x-internal-* convention and the first under internal/git/ (after x-internal-update-status / x-internal-write-report / x-internal-normalize-args at internal/ops/ and x-internal-load-story-context / x-internal-build-story-plan / x-internal-verify-story at internal/plan/).
npx skillsauth add edercnj/claude-environment x-internal-ensure-epic-branchInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
🔒 INTERNAL SKILL Esta skill é invocada apenas por outras skills (orquestradores). NÃO é destinada a invocação direta pelo usuário. Callers principais: x-epic-create, x-epic-decompose, x-orchestrate-epic, x-implement-epic, x-epic-map (story-0049-0018, 0049-0021, 0049-0022). Sétima skill da convenção
x-internal-*(após x-internal-update-status pilot 0049-0005, x-internal-write-report 0049-0006, x-internal-normalize-args 0049-0007, x-internal-load-story-context 0049-0011, x-internal-build-story-plan 0049-0012, x-internal-verify-story 0049-0014). Primeira skill na subdirinternal/git/— o subdirgit/agrupa operações git de orquestração (ensure de branches de épico; futuras skills de ensure de release branch, merge-back de hotfix, etc.).
Provide a single, idempotent entry point for the RULE-001 convention
"one branch per epic (epic/<ID>), always exists, always pushed". The
orchestrator skills that open an epic lifecycle step MUST call this
skill at step 0 rather than issuing inline git checkout -b or
git push -u origin commands; this eliminates three historical
regressions:
git checkout -b).--base flags across entry-points.Responsibilities (single):
--epic-id against the canonical 4-digit regex.epic/<ID>.git rev-parse --verify).git ls-remote origin).Skill x-create-git-branch --push;git push -u origin;This skill does NOT create branches directly — all branch creation
goes through x-create-git-branch (story-0049-0001) to preserve the "one
creation path" invariant of RULE-001.
| Aspect | Value | Rationale |
| :--- | :--- | :--- |
| Path | internal/git/x-internal-ensure-epic-branch/ | internal/ prefix scopes visibility; git/ groups orchestration-level git ensure operations (distinct from internal/ops/ state mutation and internal/plan/ planning-phase carve-outs). |
| Frontmatter visibility | internal | Generator filters from /help menu. |
| Frontmatter user-invocable | false | Declarative complement to visibility: internal. |
| Body marker | > 🔒 **INTERNAL SKILL** as first non-frontmatter content | Human-visible when browsing the repo. |
| Allowed tools | Bash only | Minimal surface; delegates to x-create-git-branch via Skill tool when needed. |
| Naming | x-internal-{subject}-{action} | subject = epic-branch; action = ensure. Mirrors Rule 04 skill taxonomy. |
Audit rule: Rule 22 (Lifecycle Integrity) validates every skill under
internal/** satisfies all 6 anchors above. Violations fail the
LifecycleIntegrityAuditTest.
Bare-slash form is intentionally omitted — this skill is never invoked
by a human typing /x-internal-ensure-epic-branch in chat. All
invocations follow Rule 13 INLINE-SKILL pattern from a calling
orchestrator:
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0049")
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0050 --base main --push true")
| Parameter | Required | Default | Description |
| :--- | :--- | :--- | :--- |
| --epic-id <XXXX> | M | — | 4-digit numeric epic identifier. Must match ^\d{4}$. |
| --base <branch> | O | develop | Base branch for the delegated creation. Must exist locally. |
| --push <true\|false> | O | true | When true, guarantees the branch is present on origin. When false, creation is local-only and the remote-check step is skipped. |
Successful invocations emit a single-line JSON object on stdout:
| Field | Type | Always Present | Description |
| :--- | :--- | :--- | :--- |
| branchName | String | yes | Resolved branch name (epic/<ID>). |
| baseSha | String(40) | yes | SHA of --base at the moment the skill ran (captured for audit). |
| created | Boolean | yes | true when the branch was created in this invocation (delegation to x-create-git-branch succeeded). |
| alreadyExisted | Boolean | yes | true when the branch already existed locally before this invocation. |
| pushedNow | Boolean | yes | true when this invocation issued the git push -u origin <branch> (either through x-create-git-branch --push or the complementary-push branch). |
Invariant: exactly one of (created, alreadyExisted) is true.
pushedNow is independent; it can be true with either.
| Code | Name | Condition | Message Format |
| :--- | :--- | :--- | :--- |
| 0 | SUCCESS | Ensure completed (created, no-op, or complementary push) | — |
| 1 | INVALID_EPIC_ID | --epic-id missing or fails ^\d{4}$ | Epic ID must be 4 digits |
| 2 | BASE_NOT_FOUND | --base does not resolve locally | Base branch '<name>' not found |
| 3 | DELEGATION_FAILED | x-create-git-branch returned non-zero | x-create-git-branch failed with exit <code>: <stderr> |
| 4 | PUSH_FAILED | Complementary git push returned non-zero | Push to origin failed: <stderr> |
Exit codes 1 and 2 mirror the story's Section 5.3 error-code table. Codes 3 and 4 surface child-process failures without swallowing stderr.
--epic-id validationEPIC_ID=""
BASE_BRANCH="develop"
PUSH="true"
while [ $# -gt 0 ]; do
case "$1" in
--epic-id) EPIC_ID="$2"; shift 2 ;;
--base) BASE_BRANCH="$2"; shift 2 ;;
--push) PUSH="$2"; shift 2 ;;
*) echo "ERROR: unknown flag: $1" >&2; exit 64 ;;
esac
done
if ! printf '%s' "$EPIC_ID" | grep -Eq '^[0-9]{4}$'; then
echo "INVALID_EPIC_ID — Epic ID must be 4 digits" >&2
exit 1
fi
The 4-digit regex matches the story's Section 5.1 validation rule. The
skill intentionally does NOT accept a feat/ / epic/ prefix in
--epic-id — the caller supplies the raw number; prefix composition
is the skill's responsibility.
if ! git rev-parse --verify --quiet "$BASE_BRANCH" >/dev/null; then
echo "BASE_NOT_FOUND — Base branch '$BASE_BRANCH' not found" >&2
exit 2
fi
BASE_SHA=$(git rev-parse --verify "$BASE_BRANCH")
Exits early before touching any network operation. Matches
x-create-git-branch Step 3 semantics.
BRANCH_NAME="epic/${EPIC_ID}"
Hard-coded prefix — the RULE-001 convention has exactly one form. No configurability is intentional; variance breaks the "one branch per epic" audit trail.
ALREADY_EXISTED="false"
if git rev-parse --verify --quiet "$BRANCH_NAME" >/dev/null; then
ALREADY_EXISTED="true"
fi
A local branch is authoritative over remote — the epic lifecycle runs
N times on a developer workstation between pushes. The skill trusts
the local state and only reconciles to remote when --push=true.
--push=false)REMOTE_EXISTS="false"
if [ "$PUSH" = "true" ]; then
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
REMOTE_EXISTS="true"
fi
fi
When --push=false, the remote check is skipped and the
complementary-push branch becomes unreachable. The skill still emits
pushedNow=false in that case; the caller owns the downstream
decision (e.g., a dry-run mode may want local-only).
CREATED="false"
PUSHED_NOW="false"
if [ "$ALREADY_EXISTED" = "true" ] && [ "$REMOTE_EXISTS" = "true" ]; then
# State A — idempotent no-op
:
elif [ "$ALREADY_EXISTED" = "true" ] && [ "$REMOTE_EXISTS" = "false" ] && [ "$PUSH" = "true" ]; then
# State B — complementary push
if ! git push -u origin "$BRANCH_NAME" 2>/tmp/epic-branch-ensure.push.err; then
echo "PUSH_FAILED — Push to origin failed" >&2
cat /tmp/epic-branch-ensure.push.err >&2
exit 4
fi
PUSHED_NOW="true"
elif [ "$ALREADY_EXISTED" = "false" ]; then
# State C — delegate creation to x-create-git-branch
#
# Invocation shape (Rule 13 INLINE-SKILL):
#
# Skill(skill: "x-create-git-branch",
# args: "--name epic/<ID> --base <BASE_BRANCH> [--push]")
#
# The calling orchestrator forwards --push=true/false verbatim. A
# successful invocation populates baseSha, created=true, pushed=<push>
# on the x-create-git-branch response; this skill re-exports those as
# created=true, pushedNow=<push>.
echo "DELEGATE_TO_X_GIT_BRANCH — name=$BRANCH_NAME base=$BASE_BRANCH push=$PUSH" >&2
# The orchestrator layer performs the actual Skill(...) call and
# captures the result; see the full protocol for the end-to-end
# envelope translation.
CREATED="true"
if [ "$PUSH" = "true" ]; then
PUSHED_NOW="true"
fi
fi
State A, B, and C are mutually exclusive. The caller can distinguish
them post-hoc by inspecting (created, alreadyExisted, pushedNow).
printf '{"branchName":"%s","baseSha":"%s","created":%s,"alreadyExisted":%s,"pushedNow":%s}\n' \
"$BRANCH_NAME" "$BASE_SHA" "$CREATED" "$ALREADY_EXISTED" "$PUSHED_NOW"
Single-line JSON keeps parsing cost at O(1) for the caller, which is typically the very first step of an epic entry-point — any overhead here is paid N times per day on a developer workstation.
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0050")
Expected stdout:
{"branchName":"epic/0050","baseSha":"<sha>","created":true,"alreadyExisted":false,"pushedNow":true}
Exit: 0. Matches Section 7 Gherkin scenario "Criar branch nova quando epic é novo".
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0049")
Expected stdout:
{"branchName":"epic/0049","baseSha":"<sha>","created":false,"alreadyExisted":true,"pushedNow":false}
Exit: 0. Matches "Idempotência — branch já existe local + remoto".
# Branch created manually earlier; never pushed.
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0049 --push true")
Expected stdout:
{"branchName":"epic/0049","baseSha":"<sha>","created":false,"alreadyExisted":true,"pushedNow":true}
Exit: 0. Matches "Push complementar — branch local mas não remota".
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0051 --base main")
Expected stdout:
{"branchName":"epic/0051","baseSha":"<sha>","created":true,"alreadyExisted":false,"pushedNow":true}
Exit: 0.
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0052 --base mybase")
Expected stderr:
BASE_NOT_FOUND — Base branch 'mybase' not found
Exit: 2. Matches "Erro — base inexistente".
Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 49")
Expected stderr:
INVALID_EPIC_ID — Epic ID must be 4 digits
Exit: 1. Matches "Boundary — epic-id mal formado".
--push false)Skill(skill: "x-internal-ensure-epic-branch",
args: "--epic-id 0053 --push false")
Expected stdout:
{"branchName":"epic/0053","baseSha":"<sha>","created":true,"alreadyExisted":false,"pushedNow":false}
Exit: 0. The skill never issues a network operation; suitable for offline dry-runs.
| Scenario | Action |
| :--- | :--- |
| --epic-id missing or fails regex | Exit 1 (INVALID_EPIC_ID); stderr carries the offending value. |
| --base absent locally | Exit 2 (BASE_NOT_FOUND); suggest git fetch origin. |
| x-create-git-branch delegation fails (State C) | Exit 3 (DELEGATION_FAILED); stderr carries x-create-git-branch stderr verbatim. |
| git push fails in State B | Exit 4 (PUSH_FAILED); stderr carries git push stderr verbatim. |
| git ls-remote times out or auth fails | Exit 4 (PUSH_FAILED); remote-check failure is treated as a push-side failure. |
| Unknown flag | Exit 64 (sysexits EX_USAGE); print usage: banner. |
| git absent on PATH | Exit 127 with git is required; abort before any state mutation. |
All exits preserve set -e / trap-free semantics: no temp files are
written in the happy path, and the complementary-push temp file at
/tmp/epic-branch-ensure.push.err is overwritten on the next
invocation (no accumulation).
The skill satisfies RULE-002 idempotency over the full 3-state matrix:
| State before | First call | Second call |
| :--- | :--- | :--- |
| Branch absent (local + remote) | created=true, alreadyExisted=false, pushedNow=true | created=false, alreadyExisted=true, pushedNow=false |
| Branch local, not remote | created=false, alreadyExisted=true, pushedNow=true | created=false, alreadyExisted=true, pushedNow=false |
| Branch local + remote | created=false, alreadyExisted=true, pushedNow=false | created=false, alreadyExisted=true, pushedNow=false |
Two concurrent invocations of the same --epic-id serialize on the
git process lock; the later caller always observes the earlier
caller's result as "already exists". No flock is required — git's
own .git/index.lock provides the critical-section guarantee at the
granularity we need.
| Operation | Budget |
| :--- | :--- |
| State A (idempotent no-op; no network) | < 1s (two git rev-parse calls + ls-remote) |
| State B (complementary push) | < 3s (ls-remote + single push round-trip) |
| State C (delegate to x-create-git-branch) | < 3s (branch create + push; matches x-create-git-branch budget) |
The "< 1s" ceiling for idempotency is the dominant case in a
multi-entry-point workflow: x-implement-epic followed by
x-orchestrate-epic followed by x-epic-map on the same epic will
trigger State A three times; the aggregate overhead must stay sub-3s.
The story ships the following acceptance test scenarios, which are the reference contract every downstream caller (0049-0018, 0049-0021, 0049-0022) can rely on:
created=true, alreadyExisted=false, pushedNow=true; assert the
ref exists locally (git rev-parse) and on origin
(git ls-remote).created=false, alreadyExisted=true, pushedNow=false; file
tree unchanged; .git/packed-refs unchanged.pushedNow=true; origin ref
now present.--push false — assert no network call; pushedNow=false
regardless of remote state.--base unknown; exit 2; message
contains BASE_NOT_FOUND.--epic-id 49; exit 1; message contains
INVALID_EPIC_ID.No Java production code is introduced by this story; the SKILL.md is
the deliverable. Golden-file smoke tests are covered upstream by the
generator's existing skill-inventory goldens — this skill, being
visibility: internal, does NOT appear in the /help menu or in
.claude/README.md, so no golden-file updates are required.
The ia-dev-env generator MUST exclude skills with
visibility: internal from:
.claude/README.md skill-inventory table./help menu listing surfaced by Claude Code.Internal skills ARE still copied into .claude/skills/ (flat layout)
so Skill(skill: "x-internal-ensure-epic-branch") invocations from
other skills resolve correctly. The invariant: user cannot see
them; orchestrators can invoke them.
Internal skills DO NOT emit phase.start / phase.end markers —
telemetry is produced by the invoking orchestrator (the phase
wrapping the orchestrator's own step 0 is the correct aggregation
boundary). Passive hooks still capture tool.call for the underlying
Bash invocation.
Reference: Rule 13 (Skill Invocation Protocol), Rule 22 (Lifecycle Integrity Audit), ADR-0010 (Interactive Gates Convention — exempts internal skills from the 3-option menu contract).
epic/<ID>).
This skill IS the canonical enforcement point.x-internal-* convention. This is the
seventh skill to follow the convention.epic/<ID> is a canonical prefix
in the generic branch-name regex.| Skill | Relationship | Context |
| :--- | :--- | :--- |
| x-create-git-branch | delegate (State C) | The only skill this one calls. Handles actual git branch + optional push. |
| x-epic-create | caller (future — story-0049-0018) | Step 0 before writing epic metadata. |
| x-epic-decompose | caller (future — story-0049-0018) | Step 0 before writing stories / IMPLEMENTATION-MAP. |
| x-orchestrate-epic | caller (future — story-0049-0021) | Step 0 before per-story planning loop. |
| x-implement-epic | caller (future — story-0049-0021) | Step 0 before Phase 0 preparation. |
| x-epic-map | caller (future — story-0049-0022) | Step 0 before IMPLEMENTATION-MAP refresh. |
| x-internal-update-status | peer (internal/ops/) | Independent — mutates execution-state; this skill mutates git refs. Never co-serialized. |
| x-internal-normalize-args | peer (internal/ops/) | Future refactor may adopt it for flag parsing; current implementation uses inline Bash parsing for minimal surface. |
Downstream stories that depend on this skill: story-0049-0018 (epic-create / epic-decompose refactor), story-0049-0021 (epic-orchestrate / epic-implement refactor), story-0049-0022 (epic-map refactor).
tools
Documentation automation v2: stack-aware generation from documentation.targets.
development
Generates or updates CI/CD pipelines per project stack with actionlint validation.
tools
Generates ADRs from architecture-plan mini-ADRs with sequential numbering and index update.
development
Formats source code; first step of the pre-commit chain (format -> lint -> compile).