git-flow-master
End-to-end Git operator for any branching strategy. Auto-detects the project's strategy (solo-main, main+integration, enterprise multi-branch, trunk-based, GitFlow, GitHub Flow, GitLab Flow, SDET integration-trunk for chained test-automation suites) from .git config, branches, an
Install
npx skills add https://github.com/upex-galaxy/agentic-qa-boilerplate/tree/main/.agents/skills/git-flow-master
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install upex-galaxy-agentic-qa-boilerplate@llmmart
git clone https://github.com/upex-galaxy/agentic-qa-boilerplate.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole upex-galaxy/agentic-qa-boilerplate collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Git Flow Master — One Skill for Branches, Commits, Pushes, PRs, and Conflicts
This skill is the project's single entry point for everything that happens on the version-control layer: creating branches, writing commits, pushing safely, opening pull requests, resolving conflicts, and planning chained / stacked PRs when a change outgrows the review budget.
It does not assume one branching model. The project may run on main only, on main + staging, on a multi-branch enterprise layout, or on any of the well-known flows (trunk-based, GitFlow, GitHub Flow, GitLab Flow). The skill detects which one is active and adapts every command accordingly. The detection is sticky: once resolved, the strategy is recorded in the git_strategy: block of .agents/project.yaml so future invocations skip the prompt.
Compact Rules
- DO: read the repo state (status, branches, diff, log, fetch, upstream, remotes) at the start of EVERY invocation and report it before acting. Never assume repo state.
- DO: resolve the branching strategy from the
git_strategy:block in.agents/project.yamlfirst, then layout heuristics, then by asking — never pick one silently. Persist the resolution back into that block, never into a separate file and never as policy prose inAGENTS.md. - WHEN
git_strategy.strategyis set butproject.project_nameis null, ormeta.strategy_sourceis stillinheritedon a named project: the strategy was INHERITED from the template, not chosen. Treat it as unconfirmed and OFFER Strategy Setup once per session — never auto-run it, and proceed under the inherited strategy on a "no". - DO: consult
git_strategy.policy.direct_push_to_protectedbefore any direct push to a protected branch —allowedis standing authorization (asking anyway collapses it intoconfirm),confirmasks every time,forbiddenrefuses and routes through a PR. A missing or null block behaves asconfirm. - DO NOT: force-push,
--force-with-lease,--no-verify, amend or rebase a pushed commit, or otherwise rewrite pushed history, unless the user explicitly authorizes it AND the branch is unshared. - DO NOT: run a repo-wide discard (
git restore .,git checkout -- .,git reset --hard, untargetedgit stash,git clean -f) — concurrent sessions may share this working tree. Discard only explicit paths this session modified; unclear ownership means stop and ask. - DO NOT:
git add -Aorgit add .. List explicit paths, so a secret or another session's work cannot ride along. - DO: keep one commit to one responsibility, in conventional format (
{type}({ISSUE-KEY}): {description}). Commit messages, branch names and PR bodies are English and carry NO AI attribution. - DO: close EVERY commit message, in every strategy, with the two forensic trailers
Worktree: <name|primary>thenSession: <label>, copied from theAGENT IDENTITY:line in session context (unknownwhen a value cannot be resolved). They are forensics, not attribution — a harness-branded trailer (Claude-Session:, an AICo-Authored-By:) stays forbidden. - WHEN a pre-commit hook rejects a commit: stop, fix the underlying issue, and create a NEW commit. Never
--amendthe rejected one. - DO: propose every branch name, commit set, and PR body and wait for an explicit OK before executing.
- DO: stop at PR creation — merging is the user's next step, never automatic. If the
ghtransport is missing or unauthenticated, surface the blocker instead of implying a PR was opened. - WHEN reconciling declared policy against the host: run the policy-verify tool once at the first push / PR / merge intent, never hand-query protection endpoints. A
404on the classic protection endpoint does not mean unprotected, and a push that succeeded may have been a documented bypass, not permission. Report drift; never auto-correct it. - WHEN a planned change exceeds ~400 changed lines: run the chained-PR decision (single-pr / stacked-to-main / feature-branch-chain / size-exception) before coding, and re-run it if the real diff outgrows the estimate rather than silently up-budgeting.
- WHEN a conflict fires: diagnose and classify it first, present options ranked by safety, and prefer a safe abort over a guess. Never pick a destructive option silently.
Read full SKILL.md when: running Strategy Setup, resolving a specific conflict type, picking a base branch or branch prefix for an unfamiliar strategy, or setting up an isolated worktree.
When to use
Trigger on any of these intents — even without literal keywords:
- "I want to start work on UPEX-123" → branch creation
- "commit and push", "subir cambios", "push to main" → commit + push flow
- "abrí un PR contra staging" → PR creation
- "tengo conflictos al hacer pull" → conflict resolution
- "este PR va a quedar enorme" → chained-PR planning hand-off
- "qué estrategia de git usamos en este repo" → strategy detection / persistence
- "el push fue rechazado" → diagnostic + recovery flow
If the user is asking about testing a ticket, authoring test cases, writing automated tests, or running regression suites — that is not this skill. Hand back to /sprint-testing, /test-documentation, /test-automation, or /regression-testing.
The six operations
Every git-flow-master invocation maps to one (or a sequence) of these six operations. Operation choice is driven by the user's request; strategy resolution shapes how each operation runs.
| Op | Trigger phrases (examples) | Skill behaviour |
|---|---|---|
| Branch | "create branch", "new feature branch", "start UPEX-123" | Resolve strategy → propose name with prefix + issue key → wait for OK → checkout |
| Commit | "commit this", "commit and push", "make atomic commits" | Group by responsibility → propose conventional commits → wait for OK → execute one-by-one |
| Push | "push", "push to main", "subir cambios" | Diagnose upstream → confirm if pushing to a protected branch → never --force without explicit user opt-in |
| PR | "create PR", "abrir PR", "gh pr create" | Pick base branch from strategy → render body inline → ask labels/reviewers → call gh pr create |
| Conflict | "fix conflict", "rebase failed", "push rejected" | Diagnose first (see references/conflict-resolution.md) → present options → guide resolution → verify clean state |
| Strategy Setup | "set up our git strategy", "bootstrap branching", "configura el flujo de git", "materialize the flow" | Resolve strategy → run decision questionnaire (Q1-Q4) → conditionally create/ff-sync long-lived branches (never force) → write the git_strategy: block in .agents/project.yaml. Skips questions already answered by non-n/a git_strategy.decisions.* fields. See references/strategy-setup.md. |
When the operation is ambiguous (user just says "git-flow-master" or "let's do the git stuff"), report the current repo state (Step 1 below) and ask what they need.
Step 1 — Always: read the repo state
Run these silently every invocation. Do not act until the picture is clear:
git status
git branch --show-current
git branch -a
git diff --stat
git log --oneline -5
git fetch origin
git status -sb
git remote -v
Summarise to the user:
- Current branch.
- Dirty / clean working tree (staged / unstaged / untracked counts).
- Unpushed / unpulled commits (ahead / behind upstream).
- Upstream status (no upstream, up-to-date, diverged).
- Remote name(s) — most repos have one (
origin); some have a fork + upstream.
This summary is cheap, prevents 90% of mistakes, and is the input to every subsequent decision.
Step 1b — Reconcile the declared policy against the host (once per session)
git_strategy.policy.* in .agents/project.yaml records what the team DECIDED. The hosting platform records what is actually ENFORCED. These drift, and the drift only surfaces at the worst moment: a merge that stalls on an approval nobody expected, or a "protected" branch that was never protected.
Run this ONCE per session, at the first push / PR / merge intent (not on read-only operations), and cache the result for the rest of the session.
Run the tool; do not perform the queries by hand:
bun run git:policy verify # read-only; exit 1 on drift
bun run git:policy verify --stamp # same, and records the reconciliation when clean
It queries BOTH GitHub protection mechanisms for every branch in git_strategy.branches / protected, compares the union against the declared policy, and prints each divergence as declared vs enforced. The strategy-to-ruleset mapping and what the tool deliberately does not manage: references/ruleset-parity.md.
Why a tool rather than a checklist. This reconciliation existed only as prose in the sibling boilerplate and kept not happening — that repo shipped require_pr_reviews: 0 against a host demanding one approval plus a code-owner review, and it surfaced months later as a refused merge. This repo had the identical divergence. A script performs every query on every run; a procedure performs the ones the reader remembered.
Facts that still bind you when reading its output:
- A
404frombranches/{b}/protectiondoes NOT mean the branch is unprotected. A repo governed by rulesets returns404there while enforcing PR requirements, approvals, signed commits and non-fast-forward bans throughrules/branches/{b}. Stopping at the classic endpoint produces a confident "unprotected" reading on a branch that requires a reviewed pull request. - A push that succeeds is not evidence of an absent rule. Org owners and anyone on the ruleset bypass list push through while the rule still binds everyone else. When a push prints
Changes must be made through a pull request, that was a BYPASS: report it as one, never as permission. Withgit_strategy.policy.admin_bypass: true(or the divergence listed ingit_strategy.policy.accepted_divergences), theBypassed rule violationsremote line is the DOCUMENTED norm — mention it in the report as expected, do NOT treat it as an anomaly, do NOT stall asking for confirmation, and NEVER open a PR to "satisfy" the rule. require_code_owner_review: truewith noCODEOWNERSfile is unsatisfiable, not strict. Nobody outside the bypass list can clear it, so every merge becomes a bypass.
On drift, report — never auto-correct. Three legitimate resolutions: update .agents/project.yaml to match the host, change the host (bun run git:policy apply, dry run until --yes), or accept the divergence and record WHY in this project's own AGENTS.md. Editing either side needs the user's choice.
Step 2 — Resolve the branching strategy
The skill supports eight strategies (see references/branching-strategies.md for the full catalogue, detection signals, and trade-offs):
| Strategy | One-line description |
|---|---|
solo-main |
Single long-lived branch (main). All work lands directly. Best for solo projects, scratch repos, prototypes. |
main-integration |
main (production) + a single integration branch (staging / dev / develop). Features merge to integration, release-promote to main. |
enterprise |
main + integration + many short-lived feature/*, fix/*, release/*, hotfix/* branches. Adds environment branches when needed. |
trunk-based |
Trunk (main) is the only long-lived branch. Short-lived feature branches (<1 day) merge fast, behind feature flags. CI gate is non-negotiable. |
gitflow |
Vincent Driessen's classic. main (releases) + develop (integration) + feature/* + release/* + hotfix/*. Heavyweight; mostly legacy. |
github-flow |
main always deployable. feature/* branches → PR → merge → deploy. No staging/develop branch. |
gitlab-flow |
GitHub Flow + environment branches (pre-production, production) to model deployment promotion. |
sdet |
SDET Gitflow. main (confirmed tests) + ephemeral per-suite integration trunk test/<module>-suite. Test tickets chain through the trunk (--no-ff); one final PR → main. For chained test-automation suites. Opt-in; see references/sdet-integration-trunk.md. |
Detection algorithm
Apply in order; stop at the first definitive answer:
git_strategy:block in.agents/project.yaml— read it. Ifgit_strategy.strategyis non-null (one of the eight slugs), it +git_strategy.branches(production / integration / ephemeral_pattern) +git_strategy.decisions(promote_method / feature_merge / hotfix_policy) ARE the persisted decision — use them. Eachgit_strategy.decisions.*field whose value is NOTn/a/empty means Strategy Setup SKIPS that question on re-run (idempotent — idempotency is keyed off thegit_strategy.decisions.*fields, not markers). Inherited-template guard: the boilerplate ships the block FILLED (strategy: solo-main) and a scaffolded project INHERITS it verbatim (the scaffolder only patchesproject.project_name/project.project_key). So a non-nullgit_strategy.strategyis only authoritative when the project is actually onboarded. Readproject.project_namein the SAME file: ifgit_strategy.strategyis non-null BUTproject.project_nameisnull, the block was INHERITED from the template (not chosen for THIS project) — treat the strategy as UNCONFIRMED and route to the Bootstrap trigger's inherited case (it still operates under the inherited strategy if the offer is declined). Ifproject.project_nameis set, the block is confirmed → use it normally, no nudge.- Single-branch heuristic —
git branch -ashows onlymain(ormaster) and no integration branch in the remote →solo-main. - Two-branch heuristic — exactly
main(ormaster) + one of{staging, dev, develop, integration}exists upstream →main-integration(record the integration branch name). - Multi-branch heuristic —
main+ integration + activefeature/*orrelease/*branches ingit branch -a→enterprise. - Project hints — look for
.gitlab-ci.yml(suggestsgitlab-flow),release/*andhotfix/*long-lived branches (suggestsgitflow). - Fallback — ask the user. Show the options with one-line descriptions; mirror their language. Do NOT pick silently. On a test-automation repo (KATA / Playwright /
/test-automation), surfacesdetas the recommended option.sdetis opt-in only — never inferred silently from layout; a livetest/<module>-suitetrunk withtest/{KEY}-*PRs targeting it confirms an already-activesdetsuite.
Persist the decision
Once resolved (whether by detection or by asking), write/update the git_strategy: block in place inside .agents/project.yaml (preserve the rest of the file — it holds project identity, env config, etc.; create the block if it is missing). NEVER write a separate file. It is the single source of truth. At minimum the first five operations need git_strategy.strategy + git_strategy.branches; the full schema (with git_strategy.decisions, git_strategy.protected, git_strategy.policy, git_strategy.branch_prefixes, git_strategy.meta) is populated by Strategy Setup (3.6).
# .agents/project.yaml — git_strategy block (only the fields that apply shown)
git_strategy:
strategy: main-integration
branches:
production: main
integration: staging
ephemeral_pattern: null
The block is the source of truth; its git_strategy.description field is the one-paragraph human summary. The user can edit it; the next invocation re-reads it.
AGENTS.md's ## Git Strategy section is just a pointer to .agents/project.yaml (git_strategy: block) — NEVER write strategy policy or branch decisions into AGENTS.md.
If the strategy uses an integration branch with a non-default name (anything other than staging), record it under git_strategy.branches.integration so commits don't have to re-detect.
Block fields and idempotent setup. git_strategy.strategy + git_strategy.branches are the minimum the first five operations need. Strategy Setup (3.6) additionally populates the three git_strategy.decisions.* fields (promote_method, feature_merge, hotfix_policy) plus the git_strategy.policy.* fields (Q4); these gate questionnaire skips. On any later invocation, detection reads the block and treats each git_strategy.decisions.* field that is NOT n/a/empty as an already-answered questionnaire question — Strategy Setup re-run only asks the questions whose git_strategy.decisions.* fields are still n/a, and never recreates a branch that already exists.
Bootstrap trigger — offer setup on a fresh repo (never auto-run)
At the top of any git intent, after Step 1 (repo state) and Step 2 detection have run, evaluate the gate — it fires on EITHER of two conditions:
(a) Unset —
git_strategy.strategyin.agents/project.yamlis null (or thegit_strategy:block is absent) AND the repo looks fresh — any of: onlymain/masterexists locally and on the remote; fewer than ~3 commits; or a boilerplate sentinel file is present (e.g..agents/project.yaml).(b) Inherited —
git_strategy.strategyis non-null BUTproject.project_name(same file) isnull. The block was INHERITED from the boilerplate template (this project has not been onboarded yet) — it was NOT chosen for THIS project. Treat it as UNCONFIRMED.
If EITHER condition is true, OFFER (do not auto-execute, do not silently pick a strategy), using the matching prompt:
(unset case (a)) "No git strategy is set up yet. Want me to run Strategy Setup — pick the flow, create the branches it needs, and write the
git_strategy:block in.agents/project.yaml? (Y/N)"
(inherited case (b)) "This project's
git_strategylooks inherited from the boilerplate (project not onboarded yet —project.project_nameis null). Want to run Strategy Setup to define this project's own flow? (Y/N)"
Rules:
- Offer once per session, then cache the answer. Do not re-prompt every git intent in the same session.
- Never auto-run. A
Noproceeds with the requested operation under the detected (case a) or inherited (case b) strategy without writing the block. - A
Yesenters Strategy Setup (3.6) before continuing with the original git intent. - The boilerplate ships
.agents/project.yamlwith thegit_strategy:block FILLED (strategy: solo-main); a scaffolded project INHERITS it verbatim (the scaffolder patches onlyproject.project_name/project.project_key, and the updater freezes the file viabootstrapOnlyPaths). So the unset case (a) and the inherited case (b) are the two ways a project reaches a real git intent without having confirmed its own flow → the offer fires on first real use — by design (template-trap guard). Ifproject.project_nameis set, the strategy is confirmed and NEITHER case fires.
Step 3 — Operation-specific runbooks
3.1 Branch creation
Decide the prefix from the dominant change. Use this fixed vocabulary (mixed-changes precedence: feat > fix > refactor > test > docs > chore):
| Prefix | When the dominant change is… |
|---|---|
feat/ |
new feature or capability |
fix/ |
bug fix |
test/ |
adding or updating automated tests (no product code) |
docs/ |
docs only |
refactor/ |
code change without behaviour change |
chore/ |
tooling, deps, housekeeping |
For enterprise and gitflow strategies, also consider release/X.Y.Z and hotfix/X.Y.Z when appropriate.
In a QA repo most work lands as test/, fix/, or chore/ branches. Feature branches (feat/) are rare here — a feat/ in this repo usually means a change to the test framework itself (new fixture, new Page component layer, new reporter).
Issue key extraction (in order):
- Current branch name regex:
(?:feat|feature|fix|test|docs|refactor|chore)/([A-Z]+-\d+)-. $ARGUMENTSfor[A-Z]+-\d+.- Ask the user once: "Is there an issue key for this work?" — accept "no" gracefully.
Branch name format — read git_strategy.branch_prefixes in .agents/project.yaml (naming_with_key / naming_without_key / precedence); the patterns below are the shipped defaults, used verbatim when the block is absent:
- With key:
{prefix}/{ISSUE-KEY}-{kebab-slug}(e.g.test/UPEX-123-bulk-assign-coverage). - Without key:
{prefix}/{kebab-slug}(e.g.refactor/split-kata-fixtures). - Keep slugs lowercase, hyphen-separated, ≤50 chars.
Strategy-specific source branch:
solo-main,github-flow,trunk-based→ branch offmain.main-integration,gitlab-flow→ branch off the integration branch (staging/dev/ equivalent).enterprise→ branch off the integration branch unless it is ahotfix/*, which branches offmain.gitflow→feature/*branches offdevelop;hotfix/*offmain;release/*offdevelop.sdet→test/{KEY}-*ticket branches + Plus Branches (docs/*/chore/*/fix/*) branch off the ephemeral integration trunktest/<module>-suite; the trunk itself is cut frommainon demand when a suite begins. Never stack a ticket on the previous ticket branch. Seereferences/sdet-integration-trunk.md.
Always propose the name and ask for OK before git checkout -b. Never create silently.
3.2 Commits
Group changes by responsibility, not by file type:
| Group | Typical paths |
|---|---|
| Test code | tests/, tests/components/, tests/e2e/, tests/integration/ |
| API schemas | api/schemas/, codegen output, OpenAPI types |
| Test data | tests/data/ |
| Skills/Docs | .agents/skills/, .agents/, AGENTS.md, docs/, README.md |
| Config | package.json, tsconfig.json, playwright.config.ts, lint/format configs |
Test data and fixtures stay with the tests they support. If a test commit ships its own fixture, they belong in the same commit, not in a separate chore: commit.
Conventional commit format:
- With issue key:
{type}({ISSUE-KEY}): {description}(e.g.test(UPEX-123): cover bulk-assign empty states). - Without key:
{type}: {description}. - Breaking changes: append
!after type/scope and addBREAKING CHANGE:footer.
Vocabulary: feat, fix, docs, style, refactor, perf, test, chore, build, ci, revert (full list in references/conventional-commits.md).
Hard rules (apply on every commit):
- One commit = one responsibility. Never bundle unrelated changes.
- Never
git add -Aorgit add .— list explicit paths to avoid leaking secrets (.env, credentials) or unrelated work. - PBI ladder guard (repos running the
.context/PBI/cache,AGENTS.md§9). After staging, rungit diff --cached --name-only | grep '^\.context/'. Anything staged there must be one of the three[COMMIT]-tier paths (.context/PBI/README.md,.context/PBI/templates/**,.context/PBI/epics/*/test-specs/**); every other match is[SYNC]cache that leaked past the ignore ladder — a directory likestories/reads as untracked ingit statusand an explicit-pathgit adddescends straight past the exclusion. Unstage it (git restore --staged <path>) before the commit proceeds. A commit that touches no.context/path skips this check. - No AI attribution. No
Generated with Claude Code, noCo-Authored-By: Claude, no equivalent line. Commits look human-authored. (Critical Reminder #3 inAGENTS.md.) - If a pre-commit hook fails, stop, fix the underlying issue, create a NEW commit. Never
--amenda commit the hook rejected —--amendoperates on the previous commit, which destroys context.
Forensic trailers (mandatory, every commit, every strategy). The last two lines of every commit message are:
Worktree: <name|primary>
Session: <label>
- Both values come from the
AGENT IDENTITY:line the prompt hook injects into this session's context (worktree=…,session=…). Copy them; do not re-derive them per commit. The session label may contain spaces and parentheses (my-session (c0ffee12)): take everything aftersession=up to the literalharness=token, never split the line on whitespace. primaryis the correct worktree value when the session is not running in a linked worktree. When a value could not be resolved at all, writeunknown— never guess a name, never drop the key. A missing trailer is less recoverable than an honestunknown.- Nothing goes below them, and nothing is added beside them.
- These are forensics, not attribution. They record WHICH working tree and WHICH session produced the commit, so a bisect, an incident review, or a parallel-session post-mortem can find the right transcript. They are deliberately harness-agnostic: no tool, vendor, or model is named. The prohibition in Critical Rule #3 is untouched — never
Claude-Session:, never aCo-Authored-By:for an AI, never a "Generated with …" line, never any other harness-branded key.
Present all proposed commits as one block. Wait for OK / modify / reject before executing.
3.3 Push
Push command depends on Step 1 output:
- No upstream →
git push -u origin {branch}. - Upstream behind →
git push. - Upstream diverged → stop. Do not force. Hand to conflict resolution (3.5).
Protected branches per strategy — the branches the policy gate below guards (union with git_strategy.protected):
solo-main→mainis protected.main-integration→ bothmainand the integration branch are protected.gitflow→mainanddevelopare protected.github-flow/trunk-based→mainis protected.enterprise→main, integration, and anyrelease/*are protected.sdet→mainis protected (only the final suite PR lands, reviewed + green CI). The integration trunk is protected-by-convention but its ticket/Plus PRs are self-merged by the maintainer with no ruleset friction.
Policy gate (consult git_strategy.policy.direct_push_to_protected before any direct push to a protected branch):
git_strategy.policy.direct_push_to_protected |
Behaviour |
|---|---|
allowed |
Standing authorization — push WITHOUT a per-push confirm. The recorded value IS the authorization (stamped by Strategy Setup with the user); asking anyway collapses allowed into confirm and empties the third state. |
confirm (default) |
Always ask. Wait for explicit yes. |
forbidden |
Refuse the direct push. Redirect to the PR flow (3.4): propose a work branch + gh pr create. |
For confirm, ask: "You are about to push directly to the protected branch {branch} in a {strategy} flow. Confirm?" Wait for explicit yes. Missing or null git_strategy block (fresh scaffold, project never onboarded) → behave as confirm: the safe default is to ask, never to assume standing authorization.
Admin bypass (only when contemplating skipping PR/protection for an urgent change): only when git_strategy.policy.admin_bypass: true may the skill OFFER a bypass — and it MUST re-confirm at runtime BOTH: (a) the operator actually holds admin rights on the repo (ASK — the skill cannot know the GitHub role; admin_bypass is a team POLICY intent, not a capability check), AND (b) the irreversible action itself. If git_strategy.policy.admin_bypass: false, NEVER offer a bypass regardless of the operator's role.
Never pass --force, --force-with-lease, --no-verify, or any history-rewriting flag unless the user explicitly requests it AND the branch is unshared. Document the request in the conversation. (Critical Reminder #6 in AGENTS.md: never rewrite pushed history.)
3.4 Pull request
Base branch picks itself from the strategy:
| Strategy | Default PR base |
|---|---|
solo-main, github-flow, trunk-based |
main |
main-integration, gitlab-flow |
integration branch (e.g. staging) |
enterprise |
integration branch; hotfix/* → main |
gitflow |
feature/* → develop; hotfix/* → main; release/* → main (and back-merge to develop) |
sdet |
test/{KEY}-* ticket branches + Plus Branches → the integration trunk test/<module>-suite; the single final suite PR → main (after the sync gate). See references/sdet-integration-trunk.md. |
The user can override with --base X in arguments. If overridden, surface it in the confirmation: "PR will target {base} instead of the strategy default {default}."
Title format: {type}({ISSUE-KEY}): {description} — under 70 chars. Without a key: {type}: {description}.
Body — render inline (no template file to read) using the structure in references/pr-templating.md. Substitute placeholders the skill can fill (<<ISSUE_KEY>>, <<SUMMARY>>, <<CHANGES>>, <<TEST_PLAN>>, <<RISK>>). Leave any unfilled placeholder visible so the author can edit it before posting — do not silently drop sections.
For test/* branches in this repo, the PR body should use the structure in references/pr-test-automation.md (project-local template tuned for KATA test-automation PRs). For non-test/* branches use the generic structure in references/pr-templating.md.
Write the rendered body to a tempfile (e.g. $(mktemp)) and pass it via gh pr create --body-file to avoid escaping issues.
Reviewers, labels, draft — see references/pr-templating.md. Never hardcode labels the repo may not have configured; verify with gh label list if uncertain.
Final command shape:
gh pr create \
--title "{title}" \
--body-file {tmpfile} \
--base {base} \
[--reviewer {users}] \
[--label {labels}] \
[--draft]
Stop at PR creation. Merging is the user's explicit next step. Never auto-merge. Surface: "Review the PR. Once approved, merge via the GitHub UI or run gh pr merge {number} --squash --delete-branch." One carve-out: when a supervised worker's dispatch or brief NAMES the merge as a step, that instruction IS the explicit next step and the worker merges. The rule exists so a PR is never merged by an agent acting on its own judgement; a dispatch that says "open the PR and merge it" is not the agent's judgement, it is the owner's, delivered through the channel the fleet uses for every other instruction. Say in the report that the merge was pre-authorized and by which line of the brief.
Optional pre-PR adversarial gate — when the diff exceeds the 400-line cognitive review budget OR touches shared scaffolding (KATA base classes, fixtures, OpenAPI schemas), surface /judgment-day as an optional pre-PR review: "Diff is large / touches shared scaffolding. Want to run /judgment-day before opening the PR?". Two blind judges review the diff in parallel; only approves when both agree. See .agents/skills/judgment-day/SKILL.md. Never invoked automatically — user opts in.
3.5 Conflict resolution
Conflicts are diagnosed before they are resolved. The user is rarely in a hurry; a wrong fix here costs hours.
Run git status, git diff --check, and inspect .git/MERGE_HEAD / REBASE_HEAD to classify the situation, then follow the matching playbook in references/conflict-resolution.md:
- Merge conflict (content)
- Merge conflict (rename / delete)
- Rebase conflict
- Push rejected (diverged)
- Detached HEAD
- Stash apply conflict
- Unrelated histories
- Pre-commit hook rejected the commit
For every type, the playbook follows the same shape:
- Explain what happened (root cause, in the user's language).
- Present options ranked by safety. Never pick destructive options (force push, hard reset,
--abortof an unfinished merge with uncommitted work) silently. - Guide the resolution step by step.
- Verify (
git status,git log --oneline -3). - Teach prevention (one short note on how to avoid this next time).
When in doubt, abort safely (git merge --abort, git rebase --abort, git cherry-pick --abort) rather than push forward. Aborting always wins over guessing.
3.6 Strategy Setup
The first five operations adapt to a strategy that already exists. Strategy Setup is the operation that establishes one: it resolves (or asks) the strategy, captures the merge + hotfix + protection-policy decisions the other operations depend on, materializes the long-lived branches the strategy needs, and writes the git_strategy: block in .agents/project.yaml. It is the only operation that creates branches and writes the strategy definition.
When it runs
- Explicit: the user asks — "set up our git strategy", "bootstrap branching", "configura el flujo de git", "materialize the flow".
- Bootstrap offer (see "Bootstrap trigger" below): a git intent arrives and EITHER
git_strategy.strategyis null (or the block is absent) with a fresh-looking repo, ORgit_strategy.strategyis non-null butproject.project_nameis null (inherited template — not onboarded). The skill OFFERS to run setup. It never auto-runs.
Six-step flow (mechanics live in references/strategy-setup.md — do not inline them here):
- Read repo state — Step 1 (already always runs).
- Resolve strategy — reuse Step 2 detection. If still undetermined, ask the 7-option question (one slug out).
- Decision questionnaire — run Q1/Q2/Q3/Q4 below, capturing merge methods + hotfix policy + protection policy. SKIP any question that does not apply to the resolved strategy, and SKIP any question whose decision field is already populated (idempotent re-run — see Step 2 extension). Q4 applies to ALL strategies.
- Materialize — conditional on the resolved strategy: create an integration branch ONLY if the strategy needs one and it is missing; ff-sync the integration/production pair if one is a pure ancestor of the other (NEVER
--force); set up local tracking. Full materialization table + sync mechanics inreferences/strategy-setup.md. - Persist — write the
git_strategy:block in.agents/project.yaml(the structured source of truth) with the fields that apply to the resolved strategy, preserving the rest of the file. NEVER write a separate file. Do NOT render a prose runbook anywhere — the operational HOW lives in this skill's references (branching-strategies.mdcatalogue +sdet-integration-trunk.md), read on demand. Per-strategy field values inreferences/branching-strategies.md→ "git_strategy field rules (per strategy)". - Report — branches created/synced, decisions captured, block location.
Decision questionnaire (defaults first; each gated on the resolved strategy)
| Q | Question | Applies to | Options (default first) | Drives |
|---|---|---|---|---|
| Q1 | Promotion method, integration → production | strategies with an integration branch (main-integration, gitlab-flow, enterprise; gitflow = develop → main) |
Fast-forward only / Merge commit (--no-ff) / Squash |
release runbook + whether branches stay byte-identical |
| Q2 | Merge method, work-branch → integration (or → trunk) | all multi-branch strategies | Merge commit (--no-ff) / Squash / Rebase + merge |
how integration history accrues |
| Q3 | Hotfix policy | strategies with a production branch distinct from where work lands | Branch off production → PR to production → back-merge to integration same day / Always via integration / No policy | hotfix runbook + invariant maintenance |
| Q4 | Protected-branch bypass policy | ALL strategies | direct_push_to_protected: forbidden / confirm / allowed (default per strategy — solo-main=allowed, multi-branch=forbidden, sdet=confirm) · admin_bypass: may a repo admin bypass PR/protection for urgent changes? (default false) · require_pr_reviews: min approvals (default null / strategy-appropriate) |
how strictly protected branches are guarded (Push op 3.3) → sets git_strategy.policy.* |
Q1/Q2/Q3 defaults are what the main-integration worked example chose; they are DEFAULTS, not hardcoded. The user can override any of them. Single-branch strategies (solo-main, github-flow, trunk-based) answer NONE of Q1/Q2/Q3 — they have no integration branch and no distinct production branch. Q4 applies to every strategy (even single-branch ones — a solo main is still protected) and writes git_strategy.policy.*; per-strategy Q4 defaults live in references/branching-strategies.md → "git_strategy field rules (per strategy)".
The git_strategy block fields (write only the ones that apply; leave decisions.* at n/a for any decision the strategy doesn't use):
git_strategy:
strategy: VALUE # one of the eight slugs
branches:
integration: NAME # or null
decisions:
promote_method: ff-only|merge-commit|squash|n/a
feature_merge: merge-commit|squash|rebase-merge|n/a
hotfix_policy: branch-off-prod-backmerge|via-integration|none|n/a
policy: # Q4 — applies to ALL strategies
direct_push_to_protected: forbidden|confirm|allowed
admin_bypass: true|false # team POLICY intent, not enforcement
require_pr_reviews: null|0|N
Non-negotiables
- Never
--force(not--force-with-leaseeither) during a setup sync. Sync only on a true fast-forward; if the integration/production pair has diverged both ways → STOP and hand to conflict resolution (3.5). - Confirm before any push to a protected branch. A setup ff-sync push is still a push to a protected branch — ask first.
- Propose, don't auto-execute branch creation. Show the plan (which branch, off what, why) and wait for OK before
git checkout -b/git branch. - No AI attribution in any commit the setup makes (see this skill's "Critical rules" section and the project
AGENTS.md).
Pointers (do not inline mechanics here)
references/strategy-setup.md— full questionnaire detail (Q1-Q4), the per-strategy materialization table, sync mechanics, persist sequence, report format.references/branching-strategies.md→ "git_strategy field rules (per strategy)" — the per-strategy field values written into thegit_strategy:block of.agents/project.yaml(strategy / branches / decisions / policy).
Step 4 — Chained / stacked PRs (when a change outgrows the budget)
When a planned change estimates > 400 changed lines (additions + deletions), the work should be split. The 400-line cognitive review budget is borrowed from industry research (SmartBear, Cisco code-review studies); above it, defect detection drops sharply.
There are three options:
stacked-to-main— 2 to 4 small PRs, each branched off the strategy's default base. PRs depend on previous merges. The base always works between merges. Best for linearly decomposable work.feature-branch-chain— one long-lived integration branch; child PRs merge into it; one final PR merges it to the strategy's default base. Best for changes with shared scaffolding (new types, new schemas) that would break partial merges.size-exception— for mechanical diffs (mass renames, formatter sweeps, generated code, vendor updates). Requires explicit user override and aWhy size-exception:line in the PR body.
Walk the chained-PR decision tree inline (see references/branching-strategies.md § Chained-PR decision tree). The decision picks one of: single-pr, stacked-to-main, feature-branch-chain, size-exception. Once decided, execute the resulting branch plan from this skill.
The branch plan that comes out of the decision is the contract for execution. If the implementation diverges (the actual diff is larger than the estimate), re-invoke the decision — do not silently up-budget the existing strategy.
Variables consumed
{{PROJECT_KEY}}— issue prefix for branch naming (e.g.UPEX-123). Resolves from.agents/project.yaml.{{ATLASSIAN_URL}}— base URL for the Traceability section in PR bodies. Resolves from.agents/project.yaml:atlassian_url.- Any project missing
.agents/project.yamlwill lack these. Fall back to a generic{prefix}/{slug}and surface a one-line warning: clone the full boilerplate (the foundation files ship with the repo).
Hand-offs to other skills
| Situation | Hand off to |
|---|---|
| Strategic split of a large change | Step 4 (inline decision tree in this skill) |
| Pre-sprint AC refinement on backlog Stories | /shift-left-testing |
| In-sprint manual QA per ticket | /sprint-testing |
| Test case authoring + ROI in TMS | /test-documentation |
| KATA-compliant automated test authoring | /test-automation |
| Regression suite execution + GO/NO-GO | /regression-testing |
| Atlassian (Jira) operations triggered by a commit / PR | /acli |
| First-time orientation | /agentic-qa-onboard |
Critical rules — apply every invocation
- Diagnose before acting. Step 1 always runs. Never assume repo state.
1b.
policy:records INTENT, not enforcement. Reconcile it by RUNNINGbun run git:policy verify(Step 1b) at the first push / PR / merge intent, then--stampwhen clean. Never perform the protection queries by hand, and never state what the remote requires from adeclaredreading.git:policy applyis a dry run until--yes, and refuses to remove a guard, lower the approval bar, turn off code-owner review, or widen the merge methods unless--allow-looseningis passed for that specific give-up. 1c.strategy: solo-mainis the shipped DEFAULT, not evidence of a decision.meta.strategy_sourcetells them apart:inheritedmeans nobody chose. On a repo whoseproject.project_nameis set and whosestrategy_sourceis stillinherited, OFFER Strategy Setup and say what the default costs (no integration branch, no promotion path, no review gate). Strategy Setup stampschosen; nothing else may. - One commit = one responsibility. Never bundle unrelated changes.
- No AI attribution in commits or PR bodies. Commits look human-authored. (Critical Reminder #3 in
AGENTS.md.) The two forensic trailers of 3.2 (Worktree:/Session:) are the one thing that always closes a commit message — they name a working tree and a session, never a tool, so they are not attribution and not optional. - Confirm before pushing to any protected branch. Strategy-driven; see Step 3.3. (Critical Reminder #5 in
AGENTS.md.) - Never force-push, never rewrite pushed history, never
--no-verifyunless the user explicitly authorises it AND the branch is unshared. (Critical Reminder #6 inAGENTS.md.) - No
git add -A/git add .— always list explicit paths. - Show proposed commits / branches / PR body and wait for OK before executing. The user can accept, modify, or reject any item.
ghCLI is the PR transport. Ifghis missing or unauthenticated (gh auth statusfails), stop and surface the blocker. Do not pretend a PR was opened.- PRs stop at creation. Merging is the user's explicit next step — with the one carve-out in 3.4: a supervised worker whose dispatch or brief NAMES the merge is executing the owner's instruction, not its own judgement, and merges.
- Strategy is sticky. Once resolved, persist in the
git_strategy:block of.agents/project.yaml. The next invocation re-reads the block rather than asking again. - Language: artifacts (commits, branches, PR bodies, AGENTS.md sections) in English. Mirror the user's language only in conversation.
- No global discards. Never
git restore .,git checkout -- .,git reset --hard, untargetedgit stash, orgit clean -f— concurrent agent sessions may share this working tree without worktrees. Discard only explicit paths this session modified; if file ownership is unclear, stop and ask the user. (Critical Rule #15 inAGENTS.md; see alsoreferences/worktrees.mdfor true isolation.)
Anti-patterns — NEVER do these
- G1. NEVER force-push to
mainor any shared branch — destroys teammates' history and is unrecoverable once others have pulled. - G2. NEVER amend or rebase a pushed commit — creates orphan commits in others' clones and rewrites history that was already replicated.
- G3. NEVER commit secrets, credentials,
.envcontents, or auth tokens — git history is forever; a single commit leaks the secret permanently. - G4. NEVER include "Generated with Claude Code", "Co-Authored-By: Claude", or any AI-attribution line in commit messages or PR bodies (Critical Rule #3). Commits look human-authored.
- G5. NEVER push to
mainwithout explicit user confirmation (Critical Rule #5). Strategy-driven protection applies to every protected branch, not justmain. - G6. NEVER bypass pre-commit / pre-push hooks with
--no-verifyto "ship faster" — hooks exist to catch the bug you didn't notice. Fix the hook failure and create a new commit. - G7. NEVER mix concerns in a single commit (feat + refactor + lint fix bundled together) — atomic commits enable surgical revert and clean blame.
- G8. NEVER stack PRs without naming the dependency chain in the PR body — reviewers can't tell which PR to read first or what each one depends on.
- G9. NEVER discard working-tree changes globally (
git restore .,git checkout -- .,git reset --hard, untargetedgit stash,git clean -f) — when multiple agent sessions share one working tree without worktrees, a global discard destroys another session's uncommitted work with no recovery. Target only the explicit paths this session modified; unclear ownership → stop and ask the user (Critical Rule #15).
Isolated worktrees (parallel / risky work)
When work needs to be isolated from in-progress changes on the current branch — a second
AI session running in parallel, a hotfix while a feature is open, or unrelated WIP you do
not want to mix — use a git worktree (a second working directory on its own branch,
sharing one .git). Three paths:
- Manual git (portable, any tool):
git worktree add ../dir -b feat/x main→ work →git worktree remove/prune. - Claude Code harness (this agent only):
EnterWorktreemoves the session into a fresh worktree under.claude/worktrees/;ExitWorktree(keep/remove) leaves it. Other coding agents lack this — they use the manual path. - Orchestrated (a coordinated fleet of worker sessions): the orchestration layer creates,
provisions and removes one worktree per worker, outside the repo and visible to the owner.
Git mechanics are identical; the lifecycle is owned by
orca-orchestration/SKILL.md.
Key gotchas: a fresh worktree contains only the tracked files of its base — untracked
WIP does not teleport (mv it in, or commit first) and every gitignored file is
missing too (bun run worktree:provision). Keep the primary tree's git status clean, and
run the orphan audit before removing a worktree — gitignored evidence dies with it. Full
lifecycle, multi-session safety rules, and the decision guide: references/worktrees.md.
Pre-flight checklist (run before exiting any operation)
- Step 1 ran and the repo state was reported.
- Strategy resolved (detected from the
git_strategy:block in.agents/project.yaml, inferred from layout, or asked) and persisted to that block if newly chosen. - Branch / commit / push / PR / conflict operation followed the runbook for that strategy.
- Each commit is atomic, conventional, and free of AI attribution.
- Each commit message ends with the two forensic trailers (
Worktree:thenSession:), values taken from theAGENT IDENTITY:context line or written asunknown. - No
git add -A/--force/--no-verifyused unless explicitly authorised. - No global discard ran (
git restore ./git checkout -- ./git reset --hard/ untargetedgit stash/git clean); any discard targeted explicit session-owned paths only. - PR (if created) has Title <70 chars, body with Summary / Changes / Test Plan / Traceability / Risk, base branch matches strategy.
- PR URL returned to the user; no merge attempted.
- Conflicts (if any) are fully resolved AND verified (
git statusclean,git logsensible). - If Strategy Setup ran: branches were proposed (not auto-created), ff-syncs used a true fast-forward only (no
--force), and a diverged pair was handed to conflict resolution rather than forced. - If Strategy Setup ran: the
git_strategy:block in.agents/project.yamlwas written in place with the fields that apply to the resolved strategy (strategy / branches / decisions / policy / protected / branch_prefixes / description / meta), preserving the rest of the file.
Reference files
| File | When to read |
|---|---|
references/branching-strategies.md |
Full catalogue of the 8 strategies + detection signals + trade-offs + chained-PR decision tree + per-strategy git_strategy field rules (the block in .agents/project.yaml). Read when resolving strategy or planning a chain. |
references/strategy-setup.md |
Strategy Setup (3.6) mechanics: decision questionnaire detail, per-strategy materialization table, ff-sync mechanics (never force), persist sequence, report format. Read when running or re-running Strategy Setup. |
references/sdet-integration-trunk.md |
sdet strategy runbook: per-ticket loop, ephemeral integration trunk, local double-env gate, Sanity CI + CI-fallback clause, Plus Branches, sync gate, final PR, TC lifecycle. Read when running an sdet test-automation suite. |
references/conventional-commits.md |
Full type vocabulary, scope rules, breaking-change syntax, mixed-changes precedence. Read when proposing commits. |
references/pr-templating.md |
PR body template, placeholder rules, label / reviewer / draft conventions, multi-strategy base-branch table. Read when opening a PR. |
references/conflict-resolution.md |
Per-conflict-type playbooks (merge / rebase / push-rejected / detached-HEAD / stash / unrelated histories / hook rejection). Read when Step 3.5 fires. |
references/worktrees.md |
Git worktrees for isolated/parallel work — manual git, Claude Code EnterWorktree/ExitWorktree, orchestrated worktrees, the untracked-files gotcha, gitignored-file provisioning, multi-session safety, the orphan audit before removal, cleanup, decision guide. Read when isolating work or running parallel sessions. |
Read references on demand — do not load them all upfront. Each file is self-contained.
Files (agentic-qa-boilerplate)
-
evals
-
evals.json 11.2 KB
{ "skill_name": "git-flow-master", "evals": [ { "id": 1, "prompt": "I just finished work on UPEX-277 (empty states for the reservations module). Need to commit the 4 changed files and push the branch — there's no PR yet. Repo uses main + staging.", "expected_output": "Skill detects main-integration strategy from layout (main+staging), persists strategy + branches into the git_strategy block of .agents/project.yaml (in place, never a separate file) if git_strategy.strategy is null, groups the 4 files into atomic conventional commits with scope UPEX-277, asks before pushing to current feature branch, and offers to open PR against staging next. No AI attribution. No git add -A.", "should_trigger": true, "files": [] }, { "id": 2, "prompt": "abrime un PR para esta branch contra staging, label feature, ya está todo pusheado", "expected_output": "Skill detects branch already pushed, renders inline PR body. For test/* branches it uses references/pr-test-automation.md as the structure; otherwise references/pr-templating.md. Picks staging as base (matches main-integration strategy), proposes feature + ready-for-review labels, runs gh pr create --body-file with tempfile, returns PR URL. Does not auto-merge.", "should_trigger": true, "files": [] }, { "id": 3, "prompt": "git status shows 'both modified' on tests/components/api/UsersApi.ts and 'deleted by them' on tests/components/api/LegacyUsersApi.ts after I tried to merge staging into my feature branch. Help me fix this.", "expected_output": "Skill identifies a content conflict + a rename/delete conflict, follows the merge-conflict playbook from references/conflict-resolution.md (diagnose first, present options ranked by safety, never silently force/abort), guides resolution per file, verifies clean state with git status before exiting.", "should_trigger": true, "files": [] }, { "id": 4, "prompt": "We're starting a brand new repo for a side project. Just me, no team. What's the simplest git setup? I'll mostly push directly to main.", "expected_output": "Skill recognises the strategy-selection intent, presents the 7 supported strategies with one-line descriptions, recommends solo-main given the context (single contributor, simplicity), and writes git_strategy.strategy: solo-main into the git_strategy block of .agents/project.yaml (in place) so future invocations skip the question.", "should_trigger": true, "files": [] }, { "id": 5, "prompt": "Test ticket UPEX-310 — verify the bulk-assign action works for the users table. Need ATP, exploration, ATR, and a bug report if anything breaks.", "expected_output": "Skill should NOT trigger as the primary handler — this is an in-sprint manual QA request, owned by /sprint-testing. git-flow-master only enters the picture later when /sprint-testing needs branch/commit/PR operations (e.g. to commit the PBI folder). If git-flow-master responds at all, it should redirect to /sprint-testing.", "should_trigger": false, "files": [] }, { "id": 6, "prompt": "Write a Playwright KATA test for the calculateDiscount API endpoint covering the edge case when the input is zero.", "expected_output": "Skill should NOT trigger — this is a KATA test-authoring request, owned by /test-automation. git-flow-master is purely a version-control-layer skill.", "should_trigger": false, "files": [] }, { "id": 7, "prompt": "the push got rejected with '! [rejected] test/UPEX-200 -> test/UPEX-200 (fetch first)'. what now?", "expected_output": "Skill recognises push-rejected diagnostic, follows the push-rejected playbook (fetch + diff to show what's missing both ways, present pull-merge vs pull-rebase vs force-push-with-lease ranked by safety), warns that force-push is destructive and only acceptable on unshared branches with explicit opt-in, never executes force silently.", "should_trigger": true, "files": [] }, { "id": 8, "prompt": "Este cambio va a tocar 800 líneas entre nuevos Page components, ATCs, fixtures y data. Cómo lo trozeo en PRs más chicos?", "expected_output": "Skill recognises chained-PR planning intent. Walks the inline decision tree from references/branching-strategies.md (Q1 mechanical? Q2 linearly decomposable? Q3 shared scaffolding?). Returns a strategy (stacked-to-main, feature-branch-chain, or size-exception) plus a concrete branch plan grounded in KATA layers (fixtures + components first, then ATCs, then test data).", "should_trigger": true, "files": [] }, { "id": 9, "prompt": "Brand new automation repo, just me writing Playwright specs. Run our git strategy setup — I'll only ever push to main, keep it dead simple.", "expected_output": "Strategy Setup (3.6) runs. Resolves solo-main (single contributor, only main, simplicity). Creates NO integration branch (materialization table: solo-main = main only). Asks NONE of Q1/Q2/Q3 (single-branch strategy gates them all out) but DOES ask Q4 (protection policy, universal) with solo-main defaults. Writes a MINIMAL git_strategy block in .agents/project.yaml (in place, preserving the rest of the file): git_strategy.strategy:solo-main, branches.integration null, branches.ephemeral_pattern null, protected:[main], all decisions.* n/a, policy.direct_push_to_protected:allowed + policy.admin_bypass:false + policy.require_pr_reviews:0, description. NEVER a separate git-strategy.yaml file. No prose runbook rendered into AGENTS.md. No --force, no AI attribution.", "should_trigger": true, "files": [] }, { "id": 10, "prompt": "Set up our git strategy. Repo has main + staging already; staging is behind main. Use fast-forward promotion, merge commits for features, hotfix off prod with same-day back-merge.", "expected_output": "Strategy Setup runs. Resolves main-integration (main + staging). Materializes: staging already exists so it is NOT recreated; ancestry check (git log main..staging empty) shows main is ahead and staging is a pure ancestor, so ff-syncs staging up to main via 'git push origin origin/main:refs/heads/staging' — NEVER --force; confirms before the protected-branch push. Writes the git_strategy block in .agents/project.yaml (in place, preserving the rest of the file; never a separate git-strategy.yaml) with strategy:main-integration, branches.production main + branches.integration staging, protected:[main,staging], decisions.promote_method ff-only + decisions.feature_merge merge-commit + decisions.hotfix_policy branch-off-prod-backmerge, policy.direct_push_to_protected forbidden + policy.admin_bypass false + policy.require_pr_reviews 1 (Q4 defaults), and a description capturing the main-is-ancestor-of-staging invariant. No prose runbook rendered into AGENTS.md. No AI attribution.", "should_trigger": true, "files": [] }, { "id": 11, "prompt": "We use gitflow here — develop plus release branches. Configure the git strategy.", "expected_output": "Strategy Setup runs. Resolves gitflow (develop present). Ensures develop exists (off main if missing); does NOT create release/* or hotfix/* at setup. Sets work-branch base to develop. Writes the git_strategy block in .agents/project.yaml (in place; never a separate git-strategy.yaml) with strategy:gitflow, branches.production main + branches.integration develop, protected:[main,develop], decisions.promote_method merge-commit (release/* -> main is inherently a merge commit, NOT normalized to ff-only) + decisions.feature_merge merge-commit + decisions.hotfix_policy branch-off-prod-backmerge, policy.direct_push_to_protected forbidden + policy.admin_bypass false + policy.require_pr_reviews 1 (Q4), and a description capturing the develop/main back-merge discipline (NOT 'production is ancestor of integration'). The release/hotfix command shapes stay in the catalogue, not the block. No --force, no AI attribution.", "should_trigger": true, "files": [] }, { "id": 12, "prompt": "Bootstrap branching for this repo. It has main and staging but they've each got commits the other doesn't.", "expected_output": "Strategy Setup runs and resolves main-integration. During materialization the ancestry check (git log main..staging AND git log staging..main) is non-empty in BOTH directions => branches diverged both ways => Strategy Setup STOPS the sync, does NOT --force, and hands off to conflict resolution (SKILL.md 3.5). The report states the divergence and points to conflict resolution rather than claiming a successful ff-sync. No history rewritten.", "should_trigger": true, "files": [] }, { "id": 13, "prompt": "Re-run git strategy setup. The git_strategy block in .agents/project.yaml already has strategy:main-integration, branches.integration staging, decisions.promote_method ff-only and decisions.feature_merge merge-commit, but decisions.hotfix_policy is still n/a.", "expected_output": "Detection (Step 2 extension) reads the git_strategy block of .agents/project.yaml and treats Q1 (git_strategy.decisions.promote_method) and Q2 (git_strategy.decisions.feature_merge) as already answered (non-n/a) => SKIPS them. Q3 (git_strategy.decisions.hotfix_policy, still n/a) is asked; Q4 (git_strategy.policy.*) is asked only if its policy fields are still at defaults/unset. Existing branches are not recreated. Idempotent: the only new write is git_strategy.decisions.hotfix_policy (plus policy.* if Q4 ran) in the same block, in place. No re-asking of answered questions, no separate file, no --force.", "should_trigger": true, "files": [] }, { "id": 14, "prompt": "configura el flujo de git para este repo de automatización nuevo", "expected_output": "Strategy Setup runs (Spanish trigger 'configura el flujo de git'). Conversation mirrors Spanish; artifacts (the git_strategy block fields + description in .agents/project.yaml) stay English. Step 1 repo state -> Step 2 detection -> if undetermined, present the 7 strategies and ask for one slug (detect-then-questionnaire), then run the applicable Q1/Q2/Q3 gated on the resolved strategy plus the universal Q4 (protection policy). Persists in place to the git_strategy block (never a separate git-strategy.yaml). Proposes branch creation (never auto-executes), never --force, no AI attribution.", "should_trigger": true, "files": [] }, { "id": 15, "prompt": "We run github-flow, everything goes through PRs to main. But push directly to main for me real quick, I'm in a hurry.", "expected_output": "Skill reads git_strategy.policy.direct_push_to_protected from the git_strategy block in .agents/project.yaml. For github-flow the default is 'forbidden', so the skill REFUSES the direct push to the protected branch main and redirects to the PR flow (3.4) — propose a work branch + gh pr create. Because git_strategy.policy.admin_bypass is false, it does NOT offer an admin bypass regardless of role. No --force, no --no-verify, no AI attribution. (Had policy been allowed it would push after the normal confirm; had it been confirm it would always ask first.)", "should_trigger": true, "files": [] } ] }
-
-
references
-
branching-strategies.md 32.6 KB
# Branching Strategies — Catalogue, Detection, Trade-offs Eight strategies are supported. Each one tells the skill where new branches start, where PRs target, what counts as "protected", and how releases promote. --- ## Table of contents 1. [`solo-main`](#solo-main) 2. [`main-integration`](#main-integration) 3. [`enterprise`](#enterprise) 4. [`trunk-based`](#trunk-based) 5. [`gitflow`](#gitflow) 6. [`github-flow`](#github-flow) 7. [`gitlab-flow`](#gitlab-flow) 8. [`sdet`](#sdet) 9. [Detection algorithm — combined view](#detection-algorithm) 10. [Chained-PR decision tree](#chained-pr-decision-tree) 11. [Strategy comparison matrix](#strategy-comparison-matrix) --- ## `solo-main` **Shape**: one long-lived branch (`main`). All work lands directly. Optional ephemeral branches when the user wants a PR for documentation or CI gating. **Best for**: solo projects, prototypes, scratch repos, personal websites, throwaway demos. **Detection signals**: - `git branch -a` returns only `main` (or `master`) and no other long-lived remote branches. - Single contributor in `git log --format='%ae' | sort -u`. - No `staging` / `dev` / `develop` branch upstream. **Source branch for new work**: `main`. **PR base**: `main` (when PRs are used at all — solo-main often skips PRs entirely). **Protected branches**: `main`. Confirm before any push. **Release model**: continuous; every push is a release. **Trade-offs**: - Pros: zero branching overhead, fast feedback loop. - Cons: no review gate; `main` may break between commits; no isolation for risky work. --- ## `main-integration` **Shape**: `main` (production) + one integration branch (`staging` / `dev` / `develop`). Features merge to integration; integration promotes to `main` only on release. **Best for**: small teams (2-10 people), one-product repos, CD pipelines that promote `staging → main` on a cadence. **Detection signals**: - `git branch -a` shows `main` (or `master`) AND exactly one of `{staging, dev, develop, integration}` upstream. - Branch protection rules on both branches (if visible via `gh api`). - `AGENTS.md` mentions both branches in a "Git Workflow" section. **Source branch for new work**: integration branch (e.g. `staging`). **PR base**: integration branch by default. Promotion PRs (`staging → main`) target `main`. **Protected branches**: `main` AND integration branch. Confirm before any direct push to either. **Release model**: integration branch is always deployable to a staging environment; `main` deploys to production on a release event. **Trade-offs**: - Pros: single review gate; staging environment matches production; rollbacks are straightforward (revert the promotion PR). - Cons: integration branch can drift if releases are rare; double-merge cost when promoting (cherry-pick / merge / rebase / re-PR). **Persisted in the `git_strategy:` block of `.agents/project.yaml`**: ```yaml git_strategy: strategy: main-integration branches: production: main integration: staging ``` --- ## `enterprise` **Shape**: `main` + integration + many short-lived `feature/*`, `fix/*`, plus `release/*` and `hotfix/*` for production fixes. May add environment branches (`pre-production`, regional branches) when the deployment topology demands it. **Best for**: 10+ contributors, multiple parallel features, regulated environments (compliance / audit), products with explicit release cycles. **Detection signals**: - `main` + integration + active `feature/*` or `release/*` branches in `git branch -a`. - `.github/CODEOWNERS` exists and is non-trivial. - `gh api repos/.../branches/main/protection` returns rules with required reviewers + status checks. - `release/*` or `hotfix/*` long-lived branches. **Source branch for new work**: - `feature/*`, `fix/*` → integration branch. - `hotfix/*` → `main` (cherry-pick back to integration after merge). - `release/X.Y.Z` → integration branch (cut for stabilisation). **PR base**: integration; `hotfix/*` → `main`; `release/*` → `main` (with back-merge to integration). **Protected branches**: `main`, integration, `release/*`. Confirm before any direct push. **Release model**: explicit release branches stabilise; release PR merges to `main` and triggers production deploy. **Trade-offs**: - Pros: parallel work isolated; release-branch stabilisation prevents "feature freeze" on integration; hotfix path independent of feature work. - Cons: branching overhead; back-merges easy to forget; release-branch coordination required. --- ## `trunk-based` **Shape**: trunk (`main`) is the only long-lived branch. Short-lived branches (<1 day, often <1 hour) merge fast. Incomplete features hide behind feature flags. CI gate on every commit is non-negotiable. **Best for**: high-velocity teams with strong CI/CD, feature-flag infrastructure, mature test pyramid (DORA "elite performer" pattern). **Detection signals**: - `git branch -a` shows `main` plus only ephemeral feature branches (most ≤1 day old). - High commit frequency to `main` (`git log --since='7 days ago' --pretty=oneline | wc -l` > 20 in a small team). - Feature flag system in `package.json` / config (LaunchDarkly, Unleash, custom). - `.github/workflows/` enforces CI on every PR. **Source branch for new work**: `main`. **PR base**: `main`. Direct commits to `main` for tiny changes are also acceptable in pure trunk-based. **Protected branches**: `main`. CI gate is the protection — required status checks before merge. **Release model**: continuous deployment from `main`. Feature flags decouple deploy from release. **Trade-offs**: - Pros: minimal branching overhead; conflicts rare (short-lived branches); enables CD. - Cons: requires feature flags + strong CI; no obvious place for long-running spike work. --- ## `gitflow` **Shape**: Vincent Driessen's classic (2010). `main` (releases only) + `develop` (integration) + `feature/*` (off `develop`) + `release/*` (off `develop`, merge to `main`) + `hotfix/*` (off `main`). **Best for**: products with explicit, infrequent versioned releases (desktop apps, libraries with semver, embedded software). Mostly **legacy** today; Driessen himself notes most teams should prefer trunk-based or GitHub Flow. **Detection signals**: - `develop` branch exists upstream (this is the unique signal). - `release/*` and / or `hotfix/*` long-lived branches. - `.gitflow` config file (rare). - Commit history shows merge commits with `Merge branch 'release/X.Y.Z'`. **Source branch for new work**: - `feature/*` → `develop`. - `release/*` → `develop`. - `hotfix/*` → `main`. **PR base**: - `feature/*` → `develop`. - `release/*` → `main` (then back-merge to `develop`). - `hotfix/*` → `main` (then back-merge to `develop`). **Protected branches**: `main`, `develop`, all `release/*`. **Release model**: cut `release/X.Y.Z` from `develop`; stabilise; merge to `main` AND `develop`; tag. **Trade-offs**: - Pros: explicit release stabilisation; hotfix path independent; well-documented. - Cons: heavy; merge complexity; back-merge errors common; ill-suited to CD. --- ## `github-flow` **Shape**: `main` always deployable. `feature/*` branches → PR → review → merge → deploy. No staging / develop branch. **Best for**: web apps with continuous deployment, GitHub-native teams, projects with one production environment. **Detection signals**: - `git branch -a` shows `main` + `feature/*` (or unprefixed feature branches) only. - No `staging` / `dev` / `develop` upstream. - `.github/workflows/` deploys on push to `main`. - `CONTRIBUTING.md` mentions "branch off main, open PR". **Source branch for new work**: `main`. **PR base**: `main`. **Protected branches**: `main`. Required status checks + at least one review. **Release model**: every merge to `main` deploys. Tags are optional, used for marketing versions. **Trade-offs**: - Pros: simple; matches CD; clear single source of truth. - Cons: no staging environment without extra effort; rollback = revert PR. --- ## `gitlab-flow` **Shape**: GitHub Flow + environment branches (`pre-production`, `production`, regional `production-eu`). Code flows in one direction: `main → pre-production → production`. **Best for**: teams that need a deployment pipeline with promotion gates but want to avoid GitFlow's release-branch complexity. Common in GitLab-hosted projects. **Detection signals**: - `.gitlab-ci.yml` exists and references multiple environments. - `git branch -a` shows `main` + `pre-production` (or `staging`) + `production`. - Linear merge history (no back-merges). - GitLab repo (vs GitHub) — but the pattern is portable. **Source branch for new work**: `main`. **PR (MR) base**: `main` for feature work. Promotion MRs: `main → pre-production`, `pre-production → production`. **Protected branches**: all environment branches (`main`, `pre-production`, `production`). **Release model**: cherry-pick or fast-forward from `main` through environment branches. **Trade-offs**: - Pros: explicit promotion path; matches deployment pipeline; no back-merge complexity. - Cons: extra branches to maintain; promotion MRs add ceremony. --- ## `sdet` **SDET Gitflow — integration-trunk for chained test-automation suites.** Full runbook: `references/sdet-integration-trunk.md`. **Shape**: `main` (permanent — holds confirmed, regression-ready tests) + an **ephemeral per-suite integration trunk** named `test/<module>-suite`. The trunk is the local surrogate-`main` for one test suite: it is created off `main` when the suite starts and deleted after the suite's final PR merges. Each ticket is a `test/{KEY}-{slug}` branch cut from the trunk, PR'd **into the trunk** (not `main`), and merged `--no-ff`. Adjacent non-test work rides **Plus Branches** (`docs/*`, `chore/*`, `fix/*`) that also PR into the trunk. One final reviewed PR promotes `trunk → main`. **Best for**: test-automation repos where a single maintainer (or AI agent) automates multi-ticket suites (a whole module, or a chained ticket-driven scope from `/test-automation`). The defining problem it solves: avoid one giant unreviewable PR, avoid one tiny PR per ticket paying `main`'s ruleset tax, and keep the final diff clean. **Detection signals**: - `git_strategy.strategy: sdet` in `.agents/project.yaml` (primary — this strategy is opt-in, never silently auto-detected). - A long-lived-for-the-suite `test/<module>-suite` trunk + multiple `test/{KEY}-*` ticket PRs targeting it (not `main`). - A QA/test-automation boilerplate repo (KATA, Playwright, `/test-automation` skill present). **Source branch for new work**: - `test/{KEY}-{slug}` ticket branches → cut from the **integration trunk** (never from the previous ticket branch). - The trunk `test/<module>-suite` itself → cut from `main` on demand when a suite begins (NOT at Strategy Setup time). - Plus Branches (`docs/*`, `chore/*`, `fix/*`) → cut from the trunk, carry adjacent non-test work. **PR base**: - Ticket branches + Plus Branches → the **integration trunk**. - Final suite PR → `main` (the only PR that faces `main`'s rulesets). **Protected branches**: `main` (final PR requires review + green CI — no CI-fallback here). The trunk is protected-by-convention while alive but intermediate ticket/Plus PRs are self-merged by the maintainer with no ruleset friction. **Merge methods** (fixed, not a questionnaire choice): ticket/Plus → trunk is **always `--no-ff`** (preserve per-ticket history; never squash). `trunk → main` follows the repo's allowed merge method — prefer **merge-commit** to preserve the multi-branch look; squash collapses the suite to one commit on `main` (the chain still lives on the pushed trunk for traceability). **Release model**: per suite. Tickets accumulate on the trunk behind Sanity-CI + review; a **sync gate** (`git merge origin/main`) runs before the final PR so the `trunk → main` diff shows only this suite's test work. "Automated" = running in CI on `main` (after the final merge), never at trunk-merge time. **CI-fallback clause**: when a Sanity-CI red is purely infra / known-flake (proven by a local pass on both `local` and `staging` AND the red being present independent of the change), the local double-pass authorizes merging **into the trunk** only — never the final `trunk → main` PR. This is NOT a skip of the tests→types→lint rule (local gate still fully passes); it only governs whether a remote infra-red blocks a trunk-internal merge. ENVIRONMENT-class classification is owned by `/regression-testing`. **Persisted in the `git_strategy:` block of `.agents/project.yaml`**: ```yaml git_strategy: strategy: sdet branches: production: main integration: null ephemeral_pattern: "test/<module>-suite" decisions: feature_merge: merge-commit ``` `git_strategy.decisions.feature_merge` is fixed at `merge-commit` (`--no-ff`) — it is a defining property, not a questionnaire answer. `git_strategy.branches.integration` stays `null` (the trunk is ephemeral per-suite, captured in `git_strategy.branches.ephemeral_pattern`). `git_strategy.decisions.promote_method` / `.hotfix_policy` stay `n/a` (no production deploy; `main` is the confirmed-tests branch). **Trade-offs**: - Pros: per-ticket scoped diffs (review is actually useful); per-ticket Sanity CI; zero ruleset friction on intermediate merges; one clean consolidated PR to `main`; `--no-ff` preserves the suite's commit topology. - Cons: extra branch layer; sync-gate ceremony before the final PR; the CI-fallback clause requires human judgment; assumes a single maintainer per suite (intermediate PRs are self-merged). --- ## Detection algorithm The combined detection runs in this order. Stop at the first definitive answer. ``` 1. Read the `git_strategy:` block of `.agents/project.yaml`. If `git_strategy.strategy` is non-null, use it + `git_strategy.branches` + `git_strategy.decisions` (and `git_strategy.policy`) fields. (Sticky decision wins.) 2. Inspect `git branch -a`: - Only `main` (or `master`) → solo-main. - `main` + exactly one of {staging, dev, develop, integration} → main-integration. Record the integration branch name in the second marker. - `main` + `develop` (Driessen-style) → check for `release/*` or `hotfix/*`. If present → gitflow. If only `develop` and `feature/*` → main-integration with develop. - `main` + `pre-production` and/or `production` → gitlab-flow. 3. Inspect `git log` and `git branch -a` together: - Many short-lived ephemeral branches (most <1 day) + high `main` commit frequency + feature-flag config detected → trunk-based. - Many `feature/*` + `release/*` long-lived → enterprise. 4. Inspect repo metadata: - `.gitlab-ci.yml` with environment stages → gitlab-flow. - `.github/CODEOWNERS` non-trivial + protection rules visible → enterprise. - `.github/workflows/deploy.yml` triggered on push to main, no other long-lived branches → github-flow. 5. Fallback: ask the user. Show the eight options as a numbered list with one-line descriptions. Mirror their language. Do not pick silently. Note on `sdet`: it is **opt-in only** — never inferred silently from layout. It is resolved from the `git_strategy:` block in `.agents/project.yaml` (step 1) or chosen explicitly in the fallback (step 5). On a test-automation repo (KATA / Playwright / `/test-automation`), surface it as the recommended option in the fallback list. A live `test/<module>-suite` trunk with `test/{KEY}-*` PRs targeting it confirms an already-active `sdet` suite. ``` After resolution, persist to the `git_strategy:` block in `.agents/project.yaml` (in place, preserving the rest of the file): ```yaml git_strategy: strategy: VALUE branches: production: main integration: NAME # null when the strategy has none ephemeral_pattern: null description: > This project uses the `VALUE` flow: <one-paragraph description for humans>. ``` --- ## Chained-PR decision tree > **`sdet` short-circuit**: when the active strategy is `sdet`, the integration-trunk model IS the standing chained mode for every test suite — do not walk this tree for test-automation work. `sdet` is `feature-branch-chain` promoted from a large-change exception to the permanent operational mode, with extra gates (`--no-ff` ticket merges, local double-env validation, Sanity CI, sync gate, single final PR). See `references/sdet-integration-trunk.md`. This tree still applies to non-test changes on an `sdet` repo (a one-off large refactor of the framework itself). When a planned change estimates `> 400 changed lines` (additions + deletions), apply this decision tree before opening PRs. ``` Q1: Is the change mostly mechanical (rename, formatter, generated code, vendor update)? ├─ Yes → size-exception (requires explicit user override + Why size-exception: rationale) └─ No → continue to Q2 Q2: Is the change linearly decomposable into 2–4 independent slices, each <400 lines, where the strategy's default base safely contains slice N without slices N+1..M? ├─ Yes → stacked-to-main └─ No → continue to Q3 Q3: Does the change have shared scaffolding (new types, new base classes, new schemas) that multiple later slices depend on, where partial merges to base would break things? ├─ Yes → feature-branch-chain └─ No → re-decompose. Send the planner back to story breakdown. A monolithic non-mechanical change without shared scaffolding is a planning smell. ``` **Strategy outputs**: - `stacked-to-main` — 2 to 4 PRs, each branched off the strategy's default base. Each PR is self-contained; base always works after each merge. - `feature-branch-chain` — one long-lived integration branch cut from the strategy's default base; child PRs merge into it; final PR merges integration into base. - `size-exception` — single PR with explicit `Why size-exception:` line. Reviewer told upfront not to read line-by-line. The chosen plan is a **contract** for execution. If the actual diff exceeds the estimate, re-invoke the decision — do not silently up-budget. --- ## Strategy comparison matrix | Aspect | solo-main | main-integration | enterprise | trunk-based | gitflow | github-flow | gitlab-flow | sdet | | ---------------------------- | ---------- | ---------------- | ----------- | ------------ | ----------- | ----------- | ----------- | ------------------ | | Long-lived branches | 1 | 2 | 3+ | 1 | 3+ | 1 | 2-4 | 1 (+ephemeral trunk) | | PR review required | Optional | Yes | Yes | Yes | Yes | Yes | Yes | Final PR only | | CI gate | Optional | Yes | Yes | **Required** | Yes | Yes | Yes | Yes (Sanity) | | Feature flags | No | Optional | Optional | **Required** | Optional | Optional | Optional | No | | Release-branch stabilisation | No | No | Yes | No | Yes | No | No | No (per-suite trunk) | | Hotfix path | Direct | Promotion | Dedicated | Direct | Dedicated | Direct | Promotion | n/a | | Best team size | 1 | 2-10 | 10+ | 5+ | 5-50 | 1-20 | 5-30 | 1 maintainer/suite | | Deployment frequency | Continuous | Per-release | Per-release | Continuous | Per-release | Continuous | Continuous | Per-suite | | Complexity | Low | Low-medium | High | Medium | High | Low | Medium | Medium | --- ## git_strategy field rules (per strategy) Strategy Setup (SKILL.md 3.6) no longer renders a prose runbook into `AGENTS.md` — it **populates the `git_strategy:` block in `.agents/project.yaml`** (in place, preserving the rest of the file), the single source of truth. This section is the authoritative reference for WHAT field VALUES each strategy writes into that block. The detailed operational HOW (release commands, hotfix commands, invariant prose) is NOT persisted anywhere — it lives in this catalogue (the per-strategy sections above) and in `references/sdet-integration-trunk.md`, read on demand. (The yaml snippets below show only the `git_strategy` block; everything nests under that key inside `.agents/project.yaml`.) The conceptual blocks that the old runbook rendered now map to `git_strategy` fields: - **(a) Markers → fields** — `git_strategy.strategy` + `git_strategy.branches.integration` (or `ephemeral_pattern`) + the applicable `git_strategy.decisions.*`. Decisions a strategy doesn't use stay `n/a`. - **(b) Invariant** — NOT persisted. It is implied by `git_strategy.decisions.promote_method: ff-only` (the "production is an ancestor of integration" invariant holds only for `ff-only`). The prose explaining it lives in this catalogue's per-strategy section, read on demand. - **(c) Branch-role table → `branches` + `protected`** — `git_strategy.branches.production` / `.integration` / `.ephemeral_pattern` capture the long-lived/ephemeral branches; `git_strategy.protected` lists the branches needing confirm-before-push. Work-branch prefixes live in `git_strategy.branch_prefixes`. - **(d) Merge methods + promotion + hotfix → `decisions`** — `feature_merge` (work-branch → integration/trunk), `promote_method` (integration → production), `hotfix_policy`. The actual command shapes are read from this catalogue, not stored in the block. - **(e) Protection policy → `policy`** (Q4, applies to ALL strategies) — `direct_push_to_protected` (`forbidden` | `confirm` | `allowed`), `admin_bypass` (team POLICY intent, not enforcement), `require_pr_reviews`. Consumed by the Push operation (SKILL.md 3.3). Per-strategy defaults are listed in each field-rule block below. > `git_strategy.decisions.promote_method: ff-only` is the marker for the fast-forward release model + the ancestor invariant. `merge-commit`/`squash` means the invariant does NOT hold — the per-strategy catalogue section explains the alternative command shape. ### `solo-main` — field rule (MINIMAL) Single long-lived branch. No integration, no promotion, no hotfix — all `decisions.*` stay `n/a`. ```yaml git_strategy: strategy: solo-main description: > This project uses the `solo-main` flow. One long-lived branch; every push to `main` is a release. branches: production: main integration: null ephemeral_pattern: null protected: - main decisions: promote_method: n/a feature_merge: n/a hotfix_policy: n/a policy: direct_push_to_protected: allowed # Q4 — solo dev pushes straight to main admin_bypass: false # n/a in practice (single operator); kept false require_pr_reviews: 0 ``` Work lands on `main` directly, or via an optional PR → `main` when a review/CI gate is wanted. No promotion or hotfix ceremony — there is one branch. Q4 policy: direct push to `main` is `allowed` (standing authorization — no per-push confirm), no admin bypass, zero required reviews. ### `github-flow` — field rule (MINIMAL) `main` always deployable; feature branches → PR → merge → deploy. No integration, no promotion, no hotfix. ```yaml git_strategy: strategy: github-flow description: > This project uses the `github-flow` flow. `main` is always deployable; every change is a short-lived branch merged via PR. Merge = deploy; rollback = revert the PR. branches: production: main integration: null ephemeral_pattern: null protected: - main decisions: promote_method: n/a feature_merge: n/a hotfix_policy: n/a policy: direct_push_to_protected: forbidden # Q4 — everything lands via PR admin_bypass: false require_pr_reviews: 1 ``` Every change is a short-lived `feature/*` / `fix/*` branch off `main` → PR → `main`. Q4 policy: direct push to `main` is `forbidden` (PR-only), no admin bypass, 1 required review. ### `trunk-based` — field rule (MINIMAL) Trunk (`main`) is the only long-lived branch; short-lived branches merge fast behind flags. CI gate non-negotiable. ```yaml git_strategy: strategy: trunk-based description: > This project uses the `trunk-based` flow. `main` is the only long-lived branch; short-lived branches merge fast, incomplete work hides behind feature flags. The CI gate is non-negotiable. branches: production: main integration: null ephemeral_pattern: null protected: - main decisions: promote_method: n/a feature_merge: merge-commit # or n/a if Q2 was not asked hotfix_policy: n/a policy: direct_push_to_protected: forbidden # Q4 — CI-gated PRs only admin_bypass: false require_pr_reviews: 1 ``` Short-lived branch (off `main`, <1 day) → fast, CI-gated merge to trunk. `feature_merge` is recorded only if Q2 was asked; otherwise leave `n/a`. Q4 policy: direct push to `main` is `forbidden` (the CI gate runs on PRs), no admin bypass, 1 required review. ### `main-integration` — field rule `main` (production) + one integration branch. Populates `branches.integration` + all three `decisions.*`. This is the GOLD shape. ```yaml git_strategy: strategy: main-integration description: > This project uses the `main-integration` flow. One environment per branch: localhost (dev) → staging (integration) → main (production). Core invariant (ff-only promotion): `main` MUST always be an ancestor of `staging`. branches: production: main integration: staging ephemeral_pattern: null protected: - main - staging decisions: promote_method: ff-only # Q1 feature_merge: merge-commit # Q2 hotfix_policy: branch-off-prod-backmerge # Q3 policy: direct_push_to_protected: forbidden # Q4 — main + staging are PR-only admin_bypass: false require_pr_reviews: 1 ``` - `decisions.promote_method: ff-only` → the "main is an ancestor of staging" invariant holds; release is `git merge --ff-only staging`. For `merge-commit`/`squash` the invariant does NOT hold and the release command is `git merge --no-ff staging` (or `--squash`). - `decisions.feature_merge` → how `feature/fix → staging` accrues history. - `decisions.hotfix_policy: branch-off-prod-backmerge` → hotfix branches off `main`, PRs to `main`, back-merges to `staging` same day. Command shapes live in the `main-integration` catalogue section above. ### `gitflow` — field rule `main` + `develop`; `release/*` cut off `develop`, merged to `main` AND back-merged to `develop`; `hotfix/*` off `main`. ```yaml git_strategy: strategy: gitflow description: > This project uses the `gitflow` flow. `develop` is integration; `main` holds releases only. Invariant: `develop` and `main` diverge between releases by design; every release/hotfix that lands on `main` is back-merged into `develop` the same day (back-merge discipline). branches: production: main integration: develop ephemeral_pattern: null protected: - main - develop decisions: promote_method: merge-commit # release/* → main is inherently a merge commit, never ff feature_merge: merge-commit # Q2: feature/* → develop hotfix_policy: branch-off-prod-backmerge # Q3: hotfix off main, back-merge to develop policy: direct_push_to_protected: forbidden # Q4 — main + develop are PR-only admin_bypass: false require_pr_reviews: 1 ``` > Field note: gitflow's `promote_method` is `merge-commit` BY NATURE — a `release/* → main` merge is inherently a merge commit, never a fast-forward. Do NOT normalize it to the Q1 `ff-only` default. gitflow's invariant is the same-day back-merge to `develop`, not an ancestor relation. `release/*` / `hotfix/*` are on-demand branches (not stored in `branches:`); their roles + command shapes live in the `gitflow` catalogue section above. ### `gitlab-flow` — field rule `main` + environment branches; code flows one direction `main → pre-production → production`. `production` is the production branch. ```yaml git_strategy: strategy: gitlab-flow description: > This project uses the `gitlab-flow` flow. Work merges to `main`; code is promoted one direction through environment branches: main → pre-production → production. Invariant (ff-only promotion): each env branch is a pure ancestor of the one upstream. branches: production: production integration: main # feature base + first env ephemeral_pattern: null protected: - main - pre-production - production decisions: promote_method: ff-only # Q1: promotion through env branches feature_merge: merge-commit # Q2: feature/fix → main hotfix_policy: branch-off-prod-backmerge # Q3: branch off production, forward-port up the chain policy: direct_push_to_protected: forbidden # Q4 — env branches are promotion-only (ff), work via PR to main admin_bypass: false require_pr_reviews: 1 ``` - `branches.production` is `production` (NOT `main` — work integrates at `main`, production is the last env). The env branches `pre-production` / `production` carry the promotion chain; their roles + ff-promotion commands live in the `gitlab-flow` catalogue section above. - `decisions.hotfix_policy` for one-direction flows means branch off `production`, then forward-port / cherry-pick up the chain (no literal back-merge). ### `enterprise` — field rule `main` + integration + on-demand `feature/*`, `fix/*`, `release/*`, `hotfix/*`. Promotion is integration → `main` AND `release/*` → `main`. ```yaml git_strategy: strategy: enterprise description: > This project uses the `enterprise` flow. `main` (production) + integration, with on-demand release/* stabilisation branches and hotfix/* off main. Invariant (ff-only promotion): `main` is a pure ancestor of the integration branch. branches: production: main integration: staging ephemeral_pattern: null protected: - main - staging # release/* are protected-when-alive (on-demand, not stored here) decisions: promote_method: ff-only # Q1 feature_merge: merge-commit # Q2: feature/fix → integration hotfix_policy: branch-off-prod-backmerge # Q3 policy: direct_push_to_protected: forbidden # Q4 — main + integration (+ release/*) are PR-only admin_bypass: false require_pr_reviews: 1 ``` `release/*` / `hotfix/*` / `feature/*` are on-demand branches (created by the Branch operation, not at setup); their roles, the `release/* → main` promotion, and back-merge command shapes live in the `enterprise` catalogue section above. ### `sdet` — field rule `main` (permanent) + an ephemeral per-suite integration trunk. No production deploy → no invariant, no promotion, no hotfix. The trunk is NOT a fixed branch name — it is a per-suite pattern captured in `branches.ephemeral_pattern`. ```yaml git_strategy: strategy: sdet description: > This project uses the `sdet` (SDET Gitflow) flow. `main` holds confirmed, regression-ready tests. Each test suite runs on an ephemeral integration trunk `test/<module>-suite` (created off `main`, deleted after the suite merges). Tickets chain through the trunk; one final reviewed PR promotes the suite to `main`. Full runbook: .agents/skills/git-flow-master/references/sdet-integration-trunk.md branches: production: main integration: null # the trunk is ephemeral, not a fixed long-lived branch ephemeral_pattern: "test/<module>-suite" protected: - main decisions: promote_method: n/a # no production deploy feature_merge: merge-commit # FIXED — ticket/Plus → trunk is always --no-ff, never squash hotfix_policy: n/a policy: direct_push_to_protected: confirm # Q4 — maintainer self-merges the trunk; main confirms admin_bypass: false require_pr_reviews: 0 # 0 on the trunk (self-merge); 1 on the final trunk → main PR ``` - `git_strategy.branches.ephemeral_pattern` holds the per-suite trunk pattern (`test/<module>-suite`); `git_strategy.branches.integration` stays `null` because no fixed integration branch persists. - `git_strategy.decisions.feature_merge: merge-commit` is FIXED (a defining property, never a questionnaire choice). `promote_method` / `hotfix_policy` stay `n/a`. - `git_strategy.policy`: `direct_push_to_protected: confirm` (the maintainer self-merges the trunk; pushes to `main` are confirmed), `admin_bypass: false`, `require_pr_reviews: 0` on the trunk / `1` on the final `trunk → main` PR. - The per-ticket loop, `--no-ff` ticket merges, Plus Branches, the sync gate, the single final `trunk → main` PR, and the CI-fallback clause are NOT in the yaml — they live in `references/sdet-integration-trunk.md` and the `sdet` catalogue section above. -
conflict-resolution.md 14.9 KB
# Conflict Resolution — Per-Type Playbooks Conflicts are diagnosed before they are resolved. The user is rarely in a hurry; a wrong fix here costs hours of recovery. The shape of every playbook is the same: 1. **Explain** what happened (root cause, in the user's language). 2. **Present options** ranked by safety. Never pick destructive options silently. 3. **Guide** the resolution step by step. 4. **Verify** (`git status`, `git log --oneline -3`, working tree clean). 5. **Teach prevention** (one short note on how to avoid it next time). When in doubt, **abort** (`git merge --abort`, `git rebase --abort`, `git cherry-pick --abort`) rather than push forward. Aborting always wins over guessing. --- ## Table of contents 1. [Diagnosis — gather information first](#diagnosis) 2. [Merge conflict (content)](#merge-conflict-content) 3. [Merge conflict (rename / delete)](#merge-conflict-rename--delete) 4. [Rebase conflict](#rebase-conflict) 5. [Push rejected (diverged)](#push-rejected) 6. [Detached HEAD](#detached-head) 7. [Stash apply conflict](#stash-apply-conflict) 8. [Unrelated histories](#unrelated-histories) 9. [Pre-commit hook rejected the commit](#pre-commit-hook-rejected) 10. [Emergency commands (last resort)](#emergency-commands) --- ## Diagnosis Run silently and read the output before deciding which playbook applies: ```bash git status git branch -vv git log --oneline -5 git stash list git diff --check ls -la .git | grep -E 'MERGE_HEAD|REBASE|CHERRY_PICK|BISECT' ``` Classify by `git status` output and any state files in `.git/`: | Symptom | State file | Probable problem | | -------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------ | | `both modified:` | `MERGE_HEAD` | Merge conflict (content) | | `deleted by us:` / `deleted by them:` | `MERGE_HEAD` | Merge conflict (rename/delete) | | `interactive rebase in progress` / `REBASE_HEAD` | `REBASE_HEAD`, `rebase-apply/`, `rebase-merge/` | Rebase conflict | | `MERGING` (no `both modified`) | `MERGE_HEAD` | Incomplete merge — finish or abort | | `HEAD detached at <sha>` | none | Detached HEAD | | `Your branch and 'origin/X' have diverged` | none | Branch divergence | | `! [rejected]` from push | none | Push rejected | | `error: Your local changes to the following files would be overwritten by merge` | none | Stash needed before pull/merge | | `CONFLICT (cherry-pick)` | `CHERRY_PICK_HEAD` | Cherry-pick conflict (use rebase playbook) | If the user provided context, use it. If not, ask: > _"What were you trying to do when this happened? — pull/fetch, push, merge, rebase, checkout, stash apply, or something else?"_ --- ## Merge conflict (content) **Explanation** (use the user's language): > _Two branches edited the same lines of the same file. Git can't decide which version is right, so it asks you to choose. The conflicting file has markers like `<<<<<<<`, `=======`, `>>>>>>>`._ **Steps**: 1. List the conflicted files: ```bash git diff --name-only --diff-filter=U ``` 2. For each file, show the conflict block(s) and ask which side to keep: ``` 📄 src/auth/login.ts (lines 45-52) <<<<<<< HEAD (your branch) const timeout = 5000; ======= const timeout = 10000; >>>>>>> feature/performance Choose: [1] keep yours (5000) [2] use theirs (10000) [3] combine [4] view more context ``` 3. Apply the resolution. For "keep ours" / "keep theirs" Git has shortcuts: ```bash git checkout --ours <file> # keep your branch's version git checkout --theirs <file> # keep the incoming branch's version # then: git add <file> ``` For "combine", edit the file manually, remove all conflict markers, then `git add <file>`. 4. After all files are resolved: ```bash git status # should show "all conflicts fixed" git commit # uses the prepared merge message # OR explicit: git commit -m "merge: integrate {branch} into {current}" ``` 5. Verify clean state and a sensible log: ```bash git status git log --oneline -5 ``` **Prevention**: pull the upstream branch frequently; keep branches short-lived; coordinate on long-running shared files. --- ## Merge conflict (rename / delete) **Explanation**: > _One branch deleted (or renamed) a file the other branch modified. Git doesn't know if the modification should follow the rename, be reapplied to a new path, or be discarded._ **Steps**: 1. Identify the file(s): ```bash git status # look for "deleted by us/them" git log --diff-filter=D --name-only -1 # find the deletion commit ``` 2. Decide: - **Keep the deletion** (the file is gone, modifications discarded): ```bash git rm <file> ``` - **Restore the file** with the modifications: ```bash git checkout HEAD -- <file> # if deleted by them git checkout MERGE_HEAD -- <file> # if deleted by us git add <file> ``` - **Migrate modifications** to the renamed path (if a rename was the cause): manually copy the changes to the new path, `git add` it, `git rm` the old one. 3. Commit and verify (same as content conflict). **Prevention**: when renaming files, do it in its own commit (no behaviour change in the same commit). Reviewers catch rename conflicts early. --- ## Rebase conflict **Explanation**: > _Rebase reapplies your commits one by one on top of a different base. If a commit doesn't apply cleanly, the rebase pauses and asks you to fix the conflict before continuing._ **Steps**: 1. See which commit is being applied: ```bash git status # shows current commit being rebased git rebase --show-current-patch # full diff of the failing commit ``` 2. Present options: - **Resolve and continue**: edit the conflicted files, `git add`, then `git rebase --continue`. - **Skip this commit**: `git rebase --skip` (the commit is dropped — confirm with user; this is destructive). - **Abort the rebase**: `git rebase --abort` (returns to pre-rebase state; safe). 3. If resolving: same content-conflict flow as above (`git checkout --ours/--theirs` or manual edit, then `git add`, then `git rebase --continue`). 4. After all commits land, verify: ```bash git log --oneline -10 git status ``` **Prevention**: rebase early and often (rebase a 1-day branch, not a 3-week branch); never rebase a branch others are working on. **Critical rule**: never rebase a branch that has been pushed AND is shared. Rewriting public history is forbidden. --- ## Push rejected **Explanation**: > _The remote has commits you don't have locally. Pushing now would overwrite those commits and lose work. Git refuses to do that._ **Steps**: 1. Diagnose: ```bash git fetch origin git log HEAD..origin/{branch} --oneline # commits on remote you don't have git log origin/{branch}..HEAD --oneline # commits you have that aren't on remote ``` 2. Present options ranked by safety: **[1] Pull with merge** (safest, creates a merge commit): ```bash git pull origin {branch} git push ``` History shows the divergence as a merge commit. Recommended when the branch is shared. **[2] Pull with rebase** (linear history, more conflict risk): ```bash git pull --rebase origin {branch} git push ``` Reapplies your commits on top of the remote. Linear history. Safe **only** if the branch is yours alone (the rebase rewrites your local commits — fine if no one else has them). **[3] Force push** (DANGEROUS — only with explicit user opt-in AND only on unshared branches): ```bash # Never run silently. Confirm the branch is unshared. git push --force-with-lease ``` `--force-with-lease` is safer than `--force`; it refuses if the remote moved since your last fetch. Still destructive — never on `main` or shared branches. 3. After pushing, verify: ```bash git status git log --oneline -5 ``` **Prevention**: `git fetch` before starting work; pull frequently on long-running branches. --- ## Detached HEAD **Explanation**: > _Normally HEAD points to a branch. "Detached" means HEAD points directly at a commit (no branch). Any commits you make here are easy to lose because no branch tracks them._ **Steps**: 1. Diagnose: ```bash git log --oneline -1 # what commit you're on git branch -a # branches available ``` 2. Decide: **[1] Just looking — go back**: ```bash git checkout - # go back to the previous branch ``` **[2] Made changes I want to keep — create a branch from here**: ```bash git checkout -b {prefix}/{slug} ``` (Use the branch-creation runbook in `SKILL.md` § 3.1 for naming.) **[3] Discard any changes and switch to a known branch**: ```bash git checkout main # or whichever branch you want ``` 3. Verify: ```bash git status git branch --show-current ``` **Prevention**: when checking out a tag or specific commit for inspection, use `git switch --detach <ref>` (Git 2.23+) so the detachment is intentional. Create a branch immediately if you plan to commit. --- ## Stash apply conflict **Explanation**: > _A stash is uncommitted work saved temporarily. Applying (or popping) the stash conflicts with files that have changed since you saved it._ **Steps**: 1. Identify which files conflict: ```bash git status # shows "both modified" entries git stash list # list of stashes ``` 2. Options: **[1] Resolve manually**: edit conflicting files (same content-conflict flow), then: ```bash git add <files> git stash drop # remove the stash if you used `git stash apply` ``` `git stash pop` removes the stash automatically after a clean apply; if the apply conflicts, the stash is **kept** so you can retry. Drop it explicitly when done. **[2] Abort and keep the stash**: ```bash git stash show --name-only # list ONLY the paths the stash touches git checkout -- <paths-from-stash> # discard ONLY those paths, never `.` git reset HEAD <paths-from-stash> # unstage anything that got staged ``` > ⚠️ Never `git checkout -- .` here — other agent sessions may hold uncommitted work in the same working tree (Anti-pattern G9, Critical Rule #15). Discard only the paths the stash apply touched. The stash is still in `git stash list`. Apply again later when the working tree is in a different state. 3. Verify: ```bash git status git stash list ``` **Prevention**: prefer commits over stashes for anything you might leave for more than a few hours. Stashes are easy to forget and easy to lose. --- ## Unrelated histories **Explanation**: > _Git refuses to merge two branches that don't share any common commit. This usually happens when you initialised separate repos and tried to merge them, or cloned with `--depth` and history is missing._ **Steps**: 1. Confirm the situation: ```bash git log --oneline -5 git fetch --unshallow # if it was a shallow clone ``` 2. If you intentionally want to merge unrelated histories: ```bash git pull origin main --allow-unrelated-histories # or: git merge --allow-unrelated-histories <branch> ``` Then resolve any content conflicts that arise (same content-conflict flow). 3. If you did NOT intend to merge unrelated histories, **stop** and figure out why the histories are unrelated. The flag suppresses a safety check; using it on the wrong repo bonds two unrelated codebases together. **Prevention**: clone with full history (`git clone <url>`, no `--depth`) when you plan to merge. --- ## Pre-commit hook rejected **Explanation**: > _A pre-commit hook (lint, format, test) returned non-zero, so the commit was NOT created. The hook is the gatekeeper; respecting it keeps the repo healthy._ **Steps**: 1. Read the hook output. It almost always says exactly what failed. 2. Fix the underlying issue: - Lint failures: run the auto-fixer (`bun run lint:fix` / `pnpm lint --fix` / `eslint --fix`). - Format failures: run the formatter (`bun run format:fix` / `prettier --write`). - Test failures: actually fix the test or the code under test. - Type failures: fix the types. 3. Re-stage the fixed files and create a **NEW** commit: ```bash git add <fixed-files> git commit -m "{type}({key}): {description}" ``` 4. **Never** `git commit --amend` here. The hook rejected the commit — it does not exist. `--amend` would mutate the _previous_ commit, which destroys context. Forward commits only. (Critical Reminder #7 in `AGENTS.md`.) 5. **Never** `git commit --no-verify` to bypass the hook unless the user explicitly authorises it. Hooks exist for a reason. **Prevention**: run the hooks' commands locally before committing (`bun run lint:check`, `tsc --noEmit`, etc.). Faster than discovering the failure at commit time. --- ## Emergency commands For when everything is broken and you need to recover. These commands can lose work — use them only with explicit user consent and only when the alternatives have been tried. ```bash # Show every reference HEAD has pointed to (including "lost" commits) git reflog # Recover a "lost" commit by sha git checkout -b recovery <sha> # Reset to a specific reflog entry (DESTRUCTIVE — current changes lost). # Requires explicit user OK AND a working tree with no other session's # uncommitted work (Anti-pattern G9, Critical Rule #15). git reset --hard HEAD@{N} # Clean up an aborted operation safely git merge --abort git rebase --abort git cherry-pick --abort # Last resort: clone fresh cd .. git clone {url} {dir}-fresh # Then move uncommitted work over manually ``` **Reflog is the safety net.** A commit only truly disappears after `git gc` runs (default: 90 days for unreferenced commits). Until then, reflog can recover anything that was once committed. -
conventional-commits.md 6.3 KB
# Conventional Commits — Reference This is the commit-message contract for git-flow-master. The grammar is the standard [Conventional Commits](https://www.conventionalcommits.org/) spec, narrowed to the type vocabulary the project uses and extended with project-specific rules (issue keys, mixed-changes precedence, no-AI-attribution). --- ## Grammar ``` <type>(<optional-scope>)!: <description> [optional body] [optional BREAKING CHANGE: footer] [optional Refs / Closes / Co-authored-by footers — but NEVER Claude] ``` Regex the message must match: ``` ^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._-]+\))?!?: .+ ``` Description rules: - Imperative mood ("add", not "added" / "adds"). - Lowercase first word, no trailing period. - Under 72 characters for the subject line. - Body wraps at 72 characters; blank line between subject and body. --- ## Type vocabulary | Type | When to use | PR label hint | | ---------- | -------------------------------------------------------------- | --------------------- | | `feat` | New feature or user-visible capability | `feature` | | `fix` | Bug fix | `bugfix` | | `docs` | Documentation only (README, AGENTS.md, code comments) | `docs` | | `style` | Formatting / whitespace / lint fixes — no behaviour change | `chore` | | `refactor` | Code change without behaviour change | `chore` | | `perf` | Performance improvement (with measurable rationale in body) | `feature` or `perf` | | `test` | Adding or updating automated tests with no product code change | `chore` | | `chore` | Tooling, deps, config, housekeeping | `chore` | | `build` | Build system / external deps (Webpack, Vite, Bundler, Docker) | `chore` | | `ci` | CI configuration (GitHub Actions, GitLab CI) | `chore` | | `revert` | Reverts a previous commit (Git auto-generates the format) | matches reverted type | --- ## Issue keys When the work is tied to a tracker (Jira, Linear, GitHub Issues), include the key in the **scope** position: ``` feat(UPEX-123): add bulk-assign action fix({{PROJECT_KEY}}-45): handle empty response in reservation list docs(UPEX-200): clarify branch prefix vocabulary ``` Without an issue key: ``` feat: add bulk-assign action docs: clarify branch prefix vocabulary ``` **Extraction order** (the skill applies this automatically): 1. Current branch name regex: `(?:feat|feature|fix|test|docs|refactor|chore)/([A-Z]+-\d+)-`. 2. `$ARGUMENTS` for `[A-Z]+-\d+`. 3. Ask the user once. Accept "no key" gracefully. --- ## Scope (without an issue key) Scope is optional. Use it when it sharpens the message: ``` feat(auth): add JWT token refresh fix(api): handle 429 from upstream chore(deps): bump bubbletea to v0.26 refactor(parser): extract tokenizer ``` Lowercase. Hyphen / underscore / dot allowed. No spaces. Match this regex: `^[a-z0-9._-]+$`. --- ## Mixed changes — precedence rule A single commit may span multiple "types" (e.g. a feature ships its own tests). Pick the **dominant** type by this precedence: ``` feat > fix > refactor > test > docs > chore ``` Examples: - Feature code + its tests → `feat:` (tests are part of the feature). - Bug fix + its regression test → `fix:` (the test is part of the fix). - Refactor + updated docs explaining the new internals → `refactor:` (docs are derivative). - Pure docs (README updated, no code) → `docs:`. - Pure test additions for existing untested code → `test:`. This is also the **branch-prefix** precedence rule (a `feat/UPEX-123-foo` branch is fine even if it ships tests). --- ## Breaking changes Append `!` after the type/scope and add a `BREAKING CHANGE:` footer with migration notes: ``` feat(api)!: rename POST /users/create to POST /users BREAKING CHANGE: the legacy endpoint POST /users/create now returns 410. Migrate clients to POST /users which keeps the same payload contract. ``` The `!` flags the breaking change to changelog tooling. The footer documents the migration path. PR label hint: `breaking-change`. --- ## Atomic commit checklist (run before staging) - [ ] One commit = one responsibility. If you can describe the commit with the word "and", split it. - [ ] The repo still makes sense after applying only this commit (no half-implementations). - [ ] Tests / docs for this unit are included in the same commit. - [ ] Rollback is reasonable without reverting unrelated work. - [ ] Commit message explains the **outcome**, not the file list. - [ ] Subject line ≤ 72 chars. Body wraps at 72. - [ ] No `git add -A` / `git add .`. Each file path is listed explicitly. --- ## Hard rules 1. **No AI attribution.** Never include `Generated with Claude Code`, `Co-Authored-By: Claude <…>`, or any equivalent line. Commits look human-authored. (Critical Reminder #4 in `AGENTS.md`.) 2. **No `git add -A` / `git add .`.** Always list the exact paths to avoid leaking secrets (`.env`, credentials) or unrelated work. 3. **Never `--amend` a commit a hook rejected.** The hook rejected the commit, so it does not exist; `--amend` would mutate the previous commit instead. Fix the underlying issue and create a new commit. 4. **Never `--amend` a published commit.** Once pushed, a commit is part of shared history. Add a forward commit (`fix:`, `revert:`) instead. 5. **Never `--no-verify`** unless the user explicitly authorises bypassing hooks. --- ## Examples — full set ``` feat(UPEX-123): add bulk-assign action fix(UPEX-200): handle empty response in reservation list docs: clarify branch prefix vocabulary refactor(parser): extract tokenizer chore(deps): bump zod to v4 perf(api): cache OS detection result test(installer): add coverage for catalog step execution build: update goreleaser config for arm64 ci: split unit and e2e test jobs revert: undo model picker redesign style: fix linter warnings in catalog package feat(cli)!: change default config path BREAKING CHANGE: --config is now --config-file. The legacy flag prints a warning and continues to work for one minor release; it will be removed in the next major. ``` -
pr-templating.md 9.8 KB
# PR Templating — Body, Labels, Reviewers, Base Branch This file is the contract for opening pull requests. The body is **rendered inline** — there is no template file in the repo to read. Placeholders the skill can fill from session context are filled; placeholders the skill cannot fill are left **visible** so the author can complete them before posting (do not silently drop sections). --- ## Title Format: `{type}({ISSUE-KEY}): {description}` — under 70 characters. Without an issue key: `{type}: {description}`. Examples: - `feat(UPEX-123): add bulk-assign action` - `fix({{PROJECT_KEY}}-45): handle empty response in reservation list` - `docs: clarify branch prefix vocabulary` - `chore(deps): bump zod to v4` The title is the first thing reviewers see in the PR list — keep it scannable. --- ## Body — inline template Render this verbatim, substituting the placeholders: ```markdown ## Summary <<SUMMARY>> ## Changes <<CHANGES>> ## Test Plan <<TEST_PLAN>> ## Traceability - Issue: [<<ISSUE_KEY>>](<<ISSUE_URL>>) - Branch: `{branch}` - Base: `{base}` - Strategy: `{strategy}` ## Evidence <<EVIDENCE>> ## Risk <<RISK>> ``` ### Placeholder rules | Placeholder | What goes here | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `<<SUMMARY>>` | One-paragraph summary derived from the commits: what changed and why. | | `<<CHANGES>>` | Bulleted list — one bullet per commit, format `- {type}({ISSUE-KEY}): {description}`. | | `<<TEST_PLAN>>` | Bulleted steps to verify: test commands run, manual checks done, environments hit. Mark each as `[x]` if already verified locally. | | `<<ISSUE_KEY>>` | Key extracted in the branch step. If absent, **drop the entire `- Issue:` line** rather than leave a dangling link. | | `<<ISSUE_URL>>` | `{{ATLASSIAN_URL}}/browse/<<ISSUE_KEY>>` when both available. Otherwise drop. | | `{branch}` | Current branch name (computed by the skill). | | `{base}` | PR base branch (resolved from the strategy — see table below). | | `{strategy}` | Active strategy slug (`solo-main`, `main-integration`, etc.). Helps reviewers understand the merge target. | | `<<EVIDENCE>>` | Pointer to `.context/PBI/epics/EPIC-<KEY>-<slug>/stories/STORY-<KEY>-<slug>/evidence/` when applicable (screenshots, traces, logs). For backend/CLI PRs without visual evidence, leave the placeholder so the author can fill it in or delete it. | | `<<RISK>>` | Short risk assessment: blast radius, affected modules, rollback plan. One paragraph. | Do not pad sections. Empty sections invite skim-reads. --- ## Base branch — resolved from strategy | Strategy | Default PR base | Notes | | ------------------ | ---------------------------------------------------------------- | --------------------------------------------------------------- | | `solo-main` | `main` | PRs are optional in this flow; if used, target `main`. | | `main-integration` | integration branch (e.g. `staging`) | Promotion PRs (`staging → main`) are explicit, separate events. | | `enterprise` | integration branch | Exception: `hotfix/*` → `main` (and back-merge to integration). | | `trunk-based` | `main` | PRs are short-lived; merges are fast. | | `gitflow` | `develop` for `feature/*`; `main` for `release/*` and `hotfix/*` | Release PRs back-merge to `develop` after merging to `main`. | | `github-flow` | `main` | Always. | | `gitlab-flow` | `main` | Promotion MRs target `pre-production`, then `production`. | | `sdet` | integration trunk `test/<module>-suite` for `test/{KEY}-*` + Plus Branches | The single final suite PR targets `main` (after the sync gate). Use the `pr-test-automation.md` body for the final PR. See `sdet-integration-trunk.md`. | The user can override with `--base X` in arguments. When overridden, surface it in the confirmation: > _"PR will target `{base}` instead of the strategy default `{default}`. Confirm?"_ --- ## Labels Suggest labels based on the dominant commit type: | Type | Label | | -------------------------------------------------------------- | -------------------------------------------- | | `feat:` | `feature` | | `fix:` | `bugfix` | | `docs:` | `docs` | | `perf:` | `feature` (or `perf` if the repo defines it) | | `chore:` / `refactor:` / `style:` / `test:` / `build:` / `ci:` | `chore` | | Breaking change (`!`) | `breaking-change` | Always combine the type label with `ready-for-review`, OR propose `--draft` if the user wants a draft PR. **Verify before applying.** Repos differ. Run `gh label list` if uncertain — never hardcode labels the repo may not have configured. If a label is missing, ask the user whether to create it or skip. --- ## Reviewers - If `.github/CODEOWNERS` exists and matches the modified paths, suggest the matched owners. - Otherwise ask the user. **Never hardcode usernames** — repos and teams change. For solo-main / personal projects, reviewers are usually skipped. --- ## Draft vs ready-for-review Open as `--draft` when: - The implementation is intentionally incomplete (early feedback wanted). - CI is expected to fail until follow-up commits land. - The user explicitly says "draft". Otherwise open as ready-for-review with the appropriate label. --- ## Rendering and submission Write the rendered body to a tempfile to avoid shell-escaping issues: ```bash TMP=$(mktemp) cat > "$TMP" <<'EOF' ## Summary …rendered body… EOF gh pr create \ --title "{title}" \ --body-file "$TMP" \ --base "{base}" \ [--reviewer {users}] \ [--label {labels}] \ [--draft] rm "$TMP" ``` Always show the rendered body and base branch to the user **before** running `gh pr create`. The user can edit, accept, or reject. --- ## After creation - Return the PR URL. - Surface the next step: _"Review the PR. Once approved, merge via the GitHub UI or run `gh pr merge {number} --squash --delete-branch`."_ - **Do not auto-merge.** Merging is an explicit, separate action by the user. --- ## Worked example Branch: `feat/UPEX-123-bulk-assign` Strategy: `main-integration` (integration = `staging`) Issue key: `UPEX-123` Two commits: 1. `feat(UPEX-123): add bulk-assign domain model and tests` 2. `feat(UPEX-123): wire bulk-assign into users table UI` Rendered body: ```markdown ## Summary Adds bulk-assign action to the users table so admins can transfer ownership of multiple records in one operation. Backed by a new domain model with unit tests; UI uses the existing table-action slot. ## Changes - feat(UPEX-123): add bulk-assign domain model and tests - feat(UPEX-123): wire bulk-assign into users table UI ## Test Plan - [x] Unit tests pass (`bun run test domain/bulk-assign`) - [x] Lint green (`bun run lint:check`) - [x] Types green (`tsc --noEmit`) - [ ] Manual smoke test on staging after merge ## Traceability - Issue: [UPEX-123](https://upex.atlassian.net/browse/UPEX-123) - Branch: `feat/UPEX-123-bulk-assign` - Base: `staging` - Strategy: `main-integration` ## Evidence See `.context/PBI/epics/EPIC-UPEX-100-<epic-slug>/stories/STORY-UPEX-123-bulk-assign/evidence/` for the design walkthrough screenshots. ## Risk Low blast radius — new code path behind a feature flag (`bulkAssign`). Rollback: disable the flag and revert this PR. ``` Title: `feat(UPEX-123): add bulk-assign action` Labels: `feature, ready-for-review` Base: `staging` -
pr-test-automation.md 804 B
# Pull Request — Test Automation > Template consumed by `/git-flow-master` when the active branch matches `test/*`. Placeholders in `<<ANGLE_BRACKETS>>` are session variables filled at PR creation time; any that remain after substitution are left visible for the author to complete before posting. ## Summary <<SUMMARY>> ## Changes <<CHANGES>> ## Test Plan <<TEST_PLAN>> ## Traceability - **Issue**: <<ISSUE_KEY>> (<<ISSUE_URL>>) - **ATP**: <<ATP_LINK>> - **ATR**: <<ATR_LINK>> - **TCs covered**: <<TC_LIST>> ## Screenshots / Evidence <<EVIDENCE>> ## Risk Assessment <<RISK>> ## Checklist - [ ] Tests pass locally (`bun run test`) - [ ] Type-check clean (`bun run types:check`) - [ ] Lint clean (`bun run lint:check`) - [ ] No AI attribution in commits - [ ] Traceability links verified -
ruleset-parity.md 9.6 KB
# Ruleset Parity — the declared strategy and the enforced host, kept in one shape `git_strategy` in `.agents/project.yaml` says what the team decided. The host says what is actually enforced. This file owns the mapping between them and the tool that reconciles it: `bun run git:policy`. > **Why a tool and not a procedure.** SKILL.md Step 1b has always specified this reconciliation in prose, for an agent to carry out by hand. It kept not happening. The boilerplate itself shipped `require_pr_reviews: 0` against a host demanding one approval plus a code-owner review, and nobody noticed until a merge was refused with `the base branch policy prohibits the merge`. A script performs every query on every run, which is the one property prose cannot guarantee. --- ## 1. Commands | Command | Reads host | Writes host | Writes yaml | Exit | | --- | --- | --- | --- | --- | | `bun run git:policy plan` | no | no | no | 0 | | `bun run git:policy verify` | yes | no | no | **1 on unaccepted drift**; 0 in parity, when every drift is ACCEPTED (§2b), or when the host is unreachable (warned, not verified) | | `bun run git:policy verify --stamp` | yes | no | `meta.policy_verified` + `policy_source` (`verified`, or `accepted` when accepted divergences exist) when clean | same | | `bun run git:policy apply` | yes | no (dry run) | no | 0 / 1 (unreachable host is fatal — an empty reading would derive a payload that drops every host rule) | | `bun run git:policy apply --yes` | yes | **yes** | no | 0 / 1 | `apply` is a dry run by default. `--yes` is what writes. `--allow-loosening` is additionally required for any change that removes a guard or lowers the bar (see §4). `verify` is **hook-safe by design** — it runs in the pre-push hook and inside `bun run repo:check`. An unreachable host (offline, no `gh`, no auth) warns and exits 0: absence of data is not drift, and a stale shell must not block pushing an unrelated change. --- ## 2. The mapping — `git_strategy` to ruleset ### Which branches the ruleset covers Derived from the strategy shape, then **unioned** with whatever `protected:` already lists. An operator may protect more than the strategy implies; narrowing that silently would itself be a loosening. | Strategy | Branches covered | | --- | --- | | `solo-main`, `github-flow`, `trunk-based` | `branches.production` | | `main-integration`, `enterprise` | `branches.production` + `branches.integration` | | `gitflow` | `branches.production` + `branches.integration` (default `develop`) | | `gitlab-flow` | `branches.production` + `branches.integration` + `pre-production` | | `sdet` | `branches.production` only — the per-suite trunk is EPHEMERAL (`branches.ephemeral_pattern`) and `integration` is `null`. Protecting it would put ruleset friction on exactly the intermediate merges the strategy exists to keep frictionless. | ### Which rules get written | Rule | Source | Notes | | --- | --- | --- | | `deletion` | always | every strategy protects against branch deletion | | `non_fast_forward` | always | blocks force-push | | `creation` | always | | | `pull_request` | **omitted** when `policy.direct_push_to_protected: allowed` | the `pull_request` rule IS what blocks a direct push. Declaring `allowed` while shipping the rule is the exact contradiction this tool exists to catch — unless that exact divergence is listed in `policy.accepted_divergences` (§2b), in which case the host's rule is **carried forward as-is**. | | `pull_request.required_approving_review_count` | `policy.require_pr_reviews` (`null` → `0`) | | | `pull_request.require_code_owner_review` | **derived** from whether a `CODEOWNERS` file exists | never declared — see §3 | | `pull_request.allowed_merge_methods` | `decisions.feature_merge` | `merge-commit`→`[merge]`, `squash`→`[squash]`, `rebase-merge`→`[rebase]`. On `n/a` the host's current value is **preserved**, because a non-decision must not widen what the repo permits. | | `required_signatures`, `required_status_checks`, anything else | **preserved from the existing ruleset** | not derivable from `git_strategy`; dropping a guard the tool has no opinion about is a silent loosening | ### 2b. Accepted divergences — intended disagreement, formally declared Sometimes BOTH sides are right on purpose: this boilerplate declares `direct_push_to_protected: allowed` (how work actually lands — the admin credential is on the bypass list) while the host keeps a `pull_request` rule (protecting every non-bypass contributor). That is not drift to fix; it is a decision to record. Declare it in `.agents/project.yaml`: ```yaml git_strategy: policy: accepted_divergences: - field: main.direct_push_to_protected # the verify finding's field, verbatim enforced: blocked (pull_request rule) # what the host does (informational) accepted: 2026-08-21 # when it was signed off reason: > Why both sides are intentionally different. ``` Effects: - **`verify`** reports the matching finding under `ACCEPTED (n)` with its reason, exits 0, and `--stamp` records `policy_source: accepted` (a distinct value — `verified` still means "host matches the yaml exactly"). - **`verify` flags stale entries** — an accepted divergence that no longer matches any drift is reported as a NOTE so the list cannot accumulate dead exceptions. - **`apply`** preserves the host's side of the accepted field instead of deriving its own (for `direct_push_to_protected`: the host's `pull_request` rule is carried forward verbatim), so applying never bulldozes an accepted divergence. Acceptance is per-field and needs a reason. It is the yaml-native replacement for burying the sign-off in prose only this repo's `AGENTS.md` could hold. --- ## 3. What this deliberately does not manage **`bypass_actors`.** Real bypass entries need GitHub actor IDs — an org-admin role, specific user IDs. That is organisation identity, not project configuration, and it must not live in a versioned per-project file that gets copied between repos. `verify` reports the bypass list; `apply` omits the field on update so the host keeps whatever is configured, and seeds an org-admin entry only when creating a ruleset from scratch on a project that declared `admin_bypass: true`. **`CODEOWNERS`.** The tool derives `require_code_owner_review` from whether the file exists rather than reading it from yaml. Turning that flag on without the file produces a requirement **nobody outside the bypass list can ever satisfy** — the merge is refused, and the only way through is a bypass, which is strictly worse than no rule. `verify` reports that combination as drift with a named remedy. **Organisation-level rulesets.** `GET /orgs/{org}/rulesets` returns `403 Upgrade to GitHub Team` on a Free plan, so the unit of configuration here is the repository. A team that later gets org rulesets should treat this tool as the per-repo layer beneath them. **Classic branch protection.** `verify` READS it, because a `404` on `branches/{b}/protection` means "not configured through that mechanism", never "unprotected". `apply` never writes it: mixing both mechanisms on one branch produces a union nobody can reason about. --- ## 4. The loosening guard `apply` refuses, unless `--allow-loosening` is passed, any change that: - removes a `deletion`, `non_fast_forward`, or `required_signatures` rule; - removes the `pull_request` rule entirely (direct pushes become possible); - lowers `required_approving_review_count`; - turns off `require_code_owner_review`; - permits a merge method the host currently forbids. A tool that can silently open `main` is a worse problem than the drift it fixes. The flag exists because some of these are legitimate and intended — turning off an unsatisfiable code-owner requirement, for instance — but each one has to be asked for. --- ## 5. When to run which **`verify` on the first push / PR / merge intent of a session.** This is Step 1b, now executable. It is read-only and cheap. `--stamp` records the reconciliation so later operations know how far the yaml can be trusted. **`apply` right after Strategy Setup**, and after any deliberate change to `git_strategy.policy`. Always read the dry run before passing `--yes`. **`verify` also runs automatically**: the pre-push hook and `bun run repo:check` both invoke it, so unaccepted drift blocks a push instead of escaping out the back door. Unreachable host = warn + exit 0 there (see §1). **Never `apply` to fix a `verify` failure you have not read.** Drift has three legitimate resolutions and only one of them is "change the host": the yaml may be the wrong side, or the divergence may be intended — in which case record it in `git_strategy.policy.accepted_divergences` (§2b) with a reason, and summarize the WHY in the project's `AGENTS.md` → `## Git Strategy` if it needs prose context. --- ## 6. Worked example — the drift this tool was built from The boilerplate declared, and the host enforced: ``` require_pr_reviews: declared 0 enforced 1 direct_push_to_protected: declared allowed enforced blocked (pull_request rule) require_code_owner_review: n/a enforced true, with no CODEOWNERS anywhere ``` The first was fixed in the yaml (the host was right). The second is INTENDED on both sides and was later formalized as an accepted divergence (§2b) — `allowed` describes how work lands via the bypass list, the host rule protects everyone else. The third was fixed on the host: with no `CODEOWNERS` file the requirement was unsatisfiable, so every merge had to bypass the ruleset. `apply` refused the change until `--allow-loosening` was passed, printed the payload first, preserved `required_signatures` and the `[merge]`-only method list, and left the bypass list untouched. -
sdet-integration-trunk.md 20.4 KB
# SDET Integration-Trunk — Chained Test-Automation Suites This file is the heavy runbook behind the `sdet` strategy (catalogue entry in `branching-strategies.md`, materialization row in `strategy-setup.md`). The strategy catalogue holds the WHAT; this file holds the per-suite, per-ticket HOW. `sdet` is the strategy for automating a multi-ticket test suite (an entire module, or a chained ticket-driven scope from `/test-automation`) **without** one giant unreviewable PR, **without** one tiny PR per ticket fighting `main`'s rulesets, and **without** a polluted final diff. It is the standing operational mode for test-automation suites — not a large-change exception. --- ## TL;DR - One **integration trunk** acts as the local surrogate-`main` for the whole suite (e.g. `test/monthly-statement-suite`). It is **ephemeral per suite**: created when the suite starts, deleted after the final PR merges. - Each ticket is cut **from the trunk** (never stacked on the previous ticket branch), worked, validated locally on **both `local` and `staging`**, pushed, Sanity-CI'd, PR'd **into the trunk** (not `main`), reviewed, fixed, and merged with **`--no-ff`** (never squash). - The next ticket is cut from the **updated trunk** after each merge. - Adjacent non-test work (docs / tooling / fixes) is parked in **Plus Branches** inserted between tickets; they PR into the trunk like ticket branches. - Before the final PR, a **sync gate** (`git merge origin/main`) runs on the trunk so the `trunk → main` diff shows only this suite's test work. - A single, pre-reviewed PR goes **trunk → main** — the only PR that faces `main`'s rulesets. The core insight: the integration trunk is a "`main` surrogate" for the suite. "Never go back to `main`" really means "return to the trunk instead, until the very end." --- ## Trunk naming convention `<module>`, `<KEY>`, `<slug>` in this document are **placeholders** — substitute the real values. Do NOT copy the literal examples (`monthly-statement`, `BK-742`); they only illustrate the shape. The trunk name derives from the **suite's scope** (the `/test-automation` planning scope that opened it): | Scope (from `/test-automation`) | Trunk name pattern | Example | | --- | --- | --- | | **Module-driven** (Macro) — a whole module | `test/<module-slug>-suite` | `test/monthly-statement-suite` | | **Ticket-driven** (Medium) — one user story split across several PRs | `test/<STORY-KEY>-<slug>-suite` | `test/BK-742-checkout-suite` | | **Regression-driven** (Micro) — a single TC | usually **no trunk** — one `test/{KEY}-{slug}` branch straight to a normal PR; only spin up a trunk if the single TC genuinely fans into multiple chained PRs | Rule of thumb: the trunk is named after **whatever the suite is about** — a module slug when automating a module, the story key+slug when chaining PRs under one story. The `-suite` suffix is deliberate and scope-neutral: a single trunk collects whatever the chained PRs are — E2E specs, integration specs, or a mix of UI and API ATCs — so `-suite` covers all of them without implying the work is only end-to-end. Keep it lowercase, hyphen-separated, ≤50 chars, and unique per live suite. --- ## The diagram ``` main (protected: rulesets + required checks) │ │ ① integration trunk = local surrogate-main for the suite ▼ ┌────────────────────────────────────────────────────────────────────────┐ │ test/monthly-statement-suite ← INTEGRATION TRUNK (ephemeral) │ │ created off main + pushed immediately so ticket PRs have a target │ └────────────────────────────────────────────────────────────────────────┘ │ │ ② cut ─► test/{KEY}-{slug} (one ticket) │ • Plan → Code → Review (KATA, /test-automation) │ • LOCAL PASS on `local` AND `staging` ← gate before push │ • push → Sanity CI on the branch (workflow_dispatch) │ • PR ➜ trunk (NOT main) │ • review loop → fix → re-run Sanity │ merge ◄──┘ --no-ff (preserves the ticket's commits + a merge commit) │ │ ③ cut from the UPDATED trunk (not from the previous ticket) │ ─► test/{KEY}-{slug} …same loop… merge ◄──┘ --no-ff │ │ ◇ (optional) PLUS BRANCH between tickets — docs/chore/fix → PR ➜ trunk │ ▼ ┌────────────────────────────────────────────────────────────────────────┐ │ test/monthly-statement-suite (now carries every ticket + plus history) │ └────────────────────────────────────────────────────────────────────────┘ │ │ ④ SYNC GATE: git merge origin/main (after every upstream PR is on main) │ cancels duplicated/squashed upstream content → clean final diff │ │ ⑤ ONE final PR ➜ main ← the only PR that faces rulesets ▼ main → trunk deleted after merge ``` --- ## The per-ticket loop (the unit of work) For each ticket `{KEY}` (the issue key from the TMS, e.g. `{{PROJECT_KEY}}-757`): ```bash # 0. Start from a clean, up-to-date trunk git checkout test/<module>-suite git pull --ff-only # the trunk moves forward only # 1. Cut the ticket branch FROM the trunk (never from the previous ticket branch) git checkout -b test/{KEY}-{slug} # 2. Plan → Code → Review (/test-automation: Phase 0..3) # 3. LOCAL VALIDATION GATE — must PASS on both environments before push # (catches env-coupled flakiness: local build/SPA divergence + staging data assumptions) bun run test <path/to/new.spec.ts> # against `local` (active_env = local) bun run test <path/to/new.spec.ts> # against `staging` (active_env = staging) bun run types:check bun run lint:check # 4. Push + Sanity CI on the branch git push -u origin test/{KEY}-{slug} # trigger the Sanity/smoke suite via GitHub Actions (/regression-testing, workflow_dispatch) # 5. PR INTO the trunk (NOT main) gh pr create --base test/<module>-suite --head test/{KEY}-{slug} # 6. Review loop # - address Critical + High; triage Low; re-run Sanity after fixes # - optional adversarial gate: /judgment-day on a large or scaffolding-touching diff # - iterate until review is quiet (max 2 revision rounds per /test-automation rule) # 7. Merge into the trunk preserving history gh pr merge --merge # merge commit (--no-ff equivalent); NEVER squash here # 8. Next ticket → back to step 0 (cut from the now-updated trunk) ``` ### Hard gates (do not skip) | Gate | Why | | --- | --- | | Cut each ticket from the trunk, **after** the prior merge | Stacking ticket B on ticket A before A merges leaks A's diff into B's PR and breaks per-ticket review scoping. | | Local PASS on **`local` AND `staging`** before push | Consistency across environments; `local` catches build/SPA divergence, `staging` catches data assumptions. This local double-pass IS Critical Rule #7 (tests → types → lint) — fully satisfied, never skipped. | | **`--no-ff`** (merge commit), **never squash**, into the trunk | Preserves each ticket's commit detail so the consolidated history "reads like multiple branches merged." Squash here flattens the very history the suite wants to keep. | | **Sync gate** (`git merge origin/main`) before the final PR | The only thing that guarantees a clean `trunk → main` diff. See below. | ### Several workers on one suite The loop above is written for one session walking the chain, and that remains the default. When a batch is worked by a fleet (`.agents/skills/test-automation/references/batch-fleet.md`), the shape changes in exactly three places and nowhere else: - **Ticket branches are cut from the trunk in parallel** — each worker in its own worktree, each branch off the trunk's then-current tip. Still never off another ticket branch. - **The trunk stays single-writer.** Only the conductor merges into it, one PR at a time, in an order it decides; a worker opens its PR and stops. Step 0's "start from a clean, up-to-date trunk" becomes the conductor's job between merges, and a worker whose branch is now behind re-merges the trunk into its branch rather than waiting. - **After each merge the conductor regenerates `kata-manifest.json`** and re-runs the suite on the merged state. Two green branches can be red together. The sync gate, the final PR, `--no-ff`, and the local double-env gate are unchanged — the double-env gate runs in each worker's own worktree. --- ## Reading the Sanity-CI gate (infra failures vs real test failures) A red Sanity-CI job does **not** always mean the change is bad — the job goes red for any failed step, including infra ones unrelated to the tests. Before reacting, read the test **step**, not the job conclusion. Failure classification is owned by `/regression-testing` (REGRESSION / FLAKY / ENVIRONMENT / KNOWN / NEW-TEST). - **Real test failure** — a `TC-xxx` FAIL / assertion error inside the test step → fix it (the fix → re-run loop). This is exactly what the gate exists for; a local pass that fails here is the gate earning its keep. - **Infra / ENVIRONMENT failure** — auth/secrets drift, artifact-quota upload errors, missing browser, runner issues → not a code defect. ### CI-fallback clause (infra-red / known-flake) — integration merges ONLY When the CI red is purely infra or a known pre-existing flake, **proven by both** of: 1. the change passing locally on **both** `local` and `staging`, AND 2. the same red being present independent of the change (nightly suite already red, or the failing line is in shared pre-existing code), then: - the local double-pass is the authoritative signal for merging **into the trunk** (never for the final `trunk → main` PR), and - a separate infra/flake ticket is filed and referenced in the ticket PR. This keeps the chain moving without lowering the bar for `main`. **The CI-fallback clause is NOT a skip of Critical Rule #7** — local tests/types/lint still all pass; it only governs whether a *remote* infra-red blocks a *trunk-internal* merge. The final `trunk → main` PR still requires a genuinely green test step (or the infra fixed first). See `/regression-testing` for the ENVIRONMENT-class classification this clause depends on. --- ## Plus Branches (adjacent non-test work) While automating a suite, unrelated local changes accumulate — tooling tweaks, `.gitignore`, sprint docs, PBI folders for other tickets, scratch SQL. These must be committed somehow but must **not** ride inside a `test/*` ticket branch (they pollute the ticket's diff and the final suite diff). A **Plus Branch** is inserted between the end of one ticket and the start of the next, carrying only that adjacent work. It flows through the chain exactly like a ticket branch — PR into the trunk, `--no-ff` merge. ```bash # After finishing a ticket, before cutting the next: git checkout test/<module>-suite git pull --ff-only git checkout -b chore/<batch-descriptor> # or docs/<...>, fix/<...> git add <only the adjacent files> # be surgical — never `git add .` git commit -m "chore: <what>" git push -u origin chore/<batch-descriptor> gh pr create --base test/<module>-suite ... # PR into the trunk like the rest gh pr merge --merge ``` **Rules for Plus Branches:** - Prefix by content type per the repo branch convention: `docs/` (markdown, context, skills), `chore/` (tooling, config, deps), `fix/` (bugfixes). **Never `test/`** — that prefix is reserved for automation ticket branches. - One Plus Branch may batch several unrelated adjacent changes as long as they share a content type by predominance. If two content types are large, split into two Plus Branches. - **Working-tree carry-along caveat**: uncommitted changes follow you across `git checkout -b`. If you leave adjacent changes uncommitted while cutting a ticket branch, `git add .` discipline is required to avoid sweeping them into the ticket commit. When in doubt, `git stash` before cutting a ticket branch and pop onto a Plus Branch later. > Owner-direct-to-`main` interaction: a project's standing "owner pushes docs directly to `main`" exception (if it has one) applies to adjacent work done **outside** an active suite. While a suite is in flight, adjacent work rides a Plus Branch into the trunk so the final `trunk → main` diff stays coherent. --- ## The sync gate (why the final diff stays clean) GitHub computes a PR's "Files changed" as a three-dot diff from the merge base: `git diff $(git merge-base main <trunk>) <trunk>`. It shows everything the trunk added since the fork point and does **not** subtract what `main` did independently. Consequences: - Upstream PRs merged to `main` via **squash** get a new SHA. The trunk still carries the original commits → they reappear in the final diff. - An upstream PR merged via **merge-commit / rebase** (preserving SHAs) enters `main`'s ancestry and cancels from the trunk diff. **Mitigation (always applies, regardless of how upstream merged)** — right before opening the final PR, once every upstream PR is on `main`, run inside the trunk: ```bash git checkout test/<module>-suite git fetch origin git merge origin/main # merge, NOT rebase (forward-only; no force-push on a pushed branch) ``` Duplicated content is identical, so the 3-way merge auto-resolves with no net change. After this, `main` is fully contained in the trunk's ancestry, the merge base becomes `main`'s tip, and the diff collapses to only this suite's test work. Verify before the PR: ```bash git diff origin/main...HEAD --stat # should list ONLY tests/** + this suite's specs ``` --- ## Final PR to main This is the **only** PR that must satisfy `main`'s branch protection (required checks + rulesets). History preservation depends on `main`'s allowed merge method, configured on the repo: - If `main` allows **merge-commit** → the per-ticket `--no-ff` structure survives; the merge "looks like multiple branches merged" — the intended outcome. - If `main` forces **squash** → the whole suite collapses to one commit on `main`. The detailed chain still lives on the (pushed) trunk and its merged ticket PRs for traceability, but `main`'s first-parent history shows a single commit. **Confirm the repo's merge setting before relying on the multi-branch look on `main`.** Title the final PR after the **suite**, not a single ticket (e.g. `test(monthly-statement): automate E2E suite {KEY1}…{KEYn}`), and list the contained tickets + their TC IDs in the body for TMS traceability. Use the `references/pr-test-automation.md` body structure. After the final PR merges and CI is green on `main`, delete the trunk (`git push origin --delete test/<module>-suite`); the next suite starts a fresh trunk. --- ## TC / backlog lifecycle interaction The TMS lifecycle runs **per ticket**, anchored to the ticket-branch PR — not to the final `trunk → main` PR. Status transitions are executed via `/test-documentation` + `[ISSUE_TRACKER_TOOL]` (`/acli`); never by this skill. Cross-check the exact status names against `.agents/jira-workflows.json` before transitioning. | Moment | Action | | --- | --- | | Start of a ticket (`/test-automation` Phase 0) | Ticket → In Progress; TCs → In Automation (or reuse Candidate). | | Ticket PR opened into the trunk (Phase 3) | TCs → In Review. | | Ticket PR **merged into the trunk** | Do NOT flip to Automated. The code is not on `main` yet. Leave at In Review. | | Final `trunk → main` PR merged + CI green on `main` | TCs → Automated; tickets → Done. | > "Automated" means running in CI on `main`. Merging into the trunk is not that. The Automated / Done flips for every ticket happen after the final merge, in one post-merge pass. Test status (PASSED/FAILED) is never set manually — the CI reporter owns it. --- ## Resume — the Git Ledger in `progress.md` A suite spans many ticket branches and multiple `/test-automation` invocations. A resuming session (different agent, new context, or after a compaction) must know **how the trunk was left** without re-deriving it from `git log`. That state is persisted append-only in the suite's `progress.md` `git:` field — the **Git Ledger** (schema in `agentic-qa-core/references/session-management.md` §7). **Who writes it**: the orchestrator, at every branch action in the per-ticket loop — never a subagent, never by rewriting. Append a new phase entry with a fresh `git:` line; the latest one in the tail is the current truth. **When to append a `git:` line** (each is one snapshot): - Trunk created off `main` (suite start) → `trunk test/<module>-suite@<sha> | pending: <KEY..KEY> | sync-gate: no | final-PR: none` - Ticket merged `--no-ff` into the trunk → `trunk …@<new-sha> | merged test/{KEY} --no-ff | pending: <remaining KEYs> | …` - Plus Branch merged → `… | merged chore/<desc> --no-ff | …` - Sync gate run (`git merge origin/main`) → `… | sync-gate: done | …` - Final PR opened → `… | final-PR: #NN (open)` - Final PR merged + trunk deleted → `… | final-PR: #NN merged | trunk deleted` **Recommended line shape** (append-only — never edit a prior one): ``` - git: trunk test/monthly-statement-suite@a1b2c3d | merged test/BK-757 --no-ff | pending: BK-758..BK-761 | sync-gate: no | final-PR: none ``` **On resume (Phase 0)**: read the tail of `progress.md`, take the **last** `git:` line. It tells the resuming session: the trunk name + tip SHA, which tickets are already merged, which remain, whether the sync gate has run, and whether the final PR is open. From there, continue the per-ticket loop — cut the next pending ticket from the (updated) trunk. This is the reinforcement that keeps the SDET flow on track even if a later session has lost the strategy context. --- ## Setup checklist (one-time, per suite) 1. Pick the trunk name: `test/<module>-suite` (e.g. `test/monthly-statement-suite`). 2. Cut it off `main` and push it so ticket PRs have a target. (The Branch operation does this on demand — the trunk is NOT created by Strategy Setup.) 3. Confirm any prerequisite upstream PRs the trunk base already contains are queued to merge to `main` so the sync gate can later cancel them. 4. Park current adjacent uncommitted work for a later Plus Branch (or `git stash` it) — keep it out of ticket branches. ## Per-ticket checklist - [ ] Pre-flight: Playwright browser installed (`bunx playwright install chromium`); API tokens fresh; CI secrets mirror `.env` (quotes stripped); local servers up if the `local` gate will run. - [ ] Cut from the up-to-date trunk. - [ ] Plan → Code → Review (KATA, `/test-automation`). - [ ] Local PASS on `local` AND `staging`; `types:check`; `lint:check`. - [ ] Push; Sanity CI — read the test step, not the job conclusion (apply the CI-fallback clause only if the red is infra/known-flake). - [ ] PR into the trunk; review loop until quiet. - [ ] Merge `--no-ff` into the trunk; cut the next ticket from the updated trunk. ## Suite-close checklist - [ ] All tickets merged into the trunk. - [ ] Sync gate: `git merge origin/main`; `git diff origin/main...HEAD --stat` shows only test work. - [ ] Final PR `trunk → main`; satisfy rulesets / required checks (genuinely green — no CI-fallback here). - [ ] Post-merge: TCs → Automated, parent tickets → Done, CI green on `main`; delete the trunk. --- ## Anti-patterns - ❌ Stacking ticket B on ticket A's branch before A merges (leaks A's diff into B's PR). - ❌ Squash-merging ticket branches into the trunk (flattens the history the suite wants to keep). - ❌ Opening the final PR without the sync gate (noisy diff full of already-merged upstream content). - ❌ Committing adjacent non-test work onto a `test/*` ticket branch instead of a Plus Branch. - ❌ Flipping TCs to Automated when a ticket merges into the trunk (it is not on `main` yet). - ❌ Using the CI-fallback clause for the final `trunk → main` PR (it is integration-only). - ❌ `rebase` / force-push on the pushed trunk (use forward-only merges). -
strategy-setup.md 17.2 KB
# Strategy Setup — Mechanics (questionnaire, materialization, sync, persist, report) This file is the heavy reference behind operation **3.6 Strategy Setup** in `SKILL.md`. The SKILL.md section holds WHEN and the six-step flow; this file holds HOW. Per-strategy `git_strategy` field values live in `references/branching-strategies.md` → "git_strategy field rules (per strategy)" — this file does not duplicate them. Strategy Setup is **detection + questionnaire → conditional materialization → write the `git_strategy:` block in `.agents/project.yaml`** (in place, preserving the rest of the file — NEVER a separate file). Nothing is baked in. A single-branch strategy creates no branches and writes a minimal block (integration null, all decisions `n/a`); a strategy with an integration branch creates/syncs exactly the branches its row in the materialization table requires and records them in the block. --- ## 1. Decision questionnaire — full detail Run after the strategy slug is resolved (Step 2). Ask the questions in order. For each question: if its `git_strategy.decisions.*` field (or, for Q4, its `git_strategy.policy.*` field) in `.agents/project.yaml` is already set (not `n/a`/empty/null-by-default), SKIP it (idempotent). If the question does not apply to the resolved strategy (gating column below), SKIP it. Present the default first and let the user override. Q1/Q2/Q3 are strategy-gated; **Q4 applies to ALL strategies**. ### Q1 — Promotion method, integration → production - **Applies to**: strategies that have an integration branch separate from production — `main-integration`, `gitlab-flow`, `enterprise`, and `gitflow` (where the `develop → main` release is the promotion). - **Skipped for**: `solo-main`, `github-flow`, `trunk-based` (no integration branch). - **Options (default first)**: 1. **Fast-forward only** — production is always a pure ancestor of integration; promotion is `git merge --ff-only`. Keeps the two branches byte-identical at release. This is the default. 2. **Merge commit (`--no-ff`)** — promotion creates a merge commit on production. Branches are NOT byte-identical; the ancestor invariant does not hold. 3. **Squash** — promotion squashes integration into one commit on production. Rewrites SHAs; invariant does not hold. - **Persisted as**: `git_strategy.decisions.promote_method: ff-only|merge-commit|squash` - **Drives**: the release runbook block, and whether the "production is an ancestor of integration" invariant is rendered (only for `ff-only`). ### Q2 — Merge method, work-branch → integration (or → trunk) - **Applies to**: all multi-branch strategies (any strategy where work branches off something and merges back). For `trunk-based` this is work-branch → trunk. - **Skipped for**: `solo-main` (work lands directly; PRs optional). - **Options (default first)**: 1. **Merge commit (`--no-ff`)** — preserves the branch topology in history. Default. 2. **Squash** — one commit per work-branch on integration; linear history, loses intermediate commits. 3. **Rebase + merge** — replays work-branch commits onto integration; linear history, preserves individual commits. - **Persisted as**: `git_strategy.decisions.feature_merge: merge-commit|squash|rebase-merge` - **Drives**: how integration history accrues; referenced by the merge-methods table in the runbook. ### Q3 — Hotfix policy - **Applies to**: strategies with a production branch distinct from where day-to-day work lands — `main-integration`, `gitlab-flow`, `enterprise`, `gitflow`. - **Skipped for**: `solo-main`, `github-flow`, `trunk-based` (production IS where work lands; a hotfix is just another change). - **Options (default first)**: 1. **Branch off production → PR to production → back-merge to integration same day** — keeps the ff-only invariant intact. Default. 2. **Always via integration** — hotfix flows through integration like any change; slower but no back-merge to forget. 3. **No policy** — decide per incident (records intent to NOT standardize). - **Persisted as**: `git_strategy.decisions.hotfix_policy: branch-off-prod-backmerge|via-integration|none` - **Drives**: the hotfix runbook block + the invariant-maintenance note. - **Note (one-direction flows)**: for `gitlab-flow` the "back-merge to integration" is realized as a forward-port / cherry-pick up the environment chain (`production` → `pre-production` → `main`), not a literal merge back — gitlab-flow has no back-merges. > The defaults (ff-only / merge-commit / branch-off-prod-backmerge) are the `main-integration` worked-example choices. They are DEFAULTS. Always present them as overridable, never auto-select without showing the alternatives. ### Q4 — Protected-branch bypass policy - **Applies to**: **ALL strategies** (single-branch ones too — a solo `main` is still a protected branch). Q4 is never strategy-gated out. - **Drives**: the Push operation (SKILL.md 3.3) — how strictly a direct push to a protected branch is guarded, and whether an admin bypass may even be offered. - **Sub-questions (defaults are per-strategy — see `references/branching-strategies.md` → "git_strategy field rules (per strategy)")**: 1. **`direct_push_to_protected`** — `forbidden` | `confirm` | `allowed`. How a direct push to a protected branch is treated. `allowed` = standing authorization, push without a per-push confirm (the recorded value IS the authorization); `confirm` = always ask; `forbidden` = refuse the direct push, redirect to the PR flow. Default per strategy: `solo-main` = `allowed`; multi-branch (`main-integration` / `enterprise` / `gitflow` / `github-flow` / `trunk-based` / `gitlab-flow`) = `forbidden`; `sdet` = `confirm` (trunk self-merge). 2. **`admin_bypass`** — `true` | `false`. May a repo admin bypass PR/protection for an urgent change? Default `false`. **This is a team POLICY intent, NOT an enforcement check** — the real capability depends on the GitHub user's role. When `true`, the Push op may OFFER a bypass but MUST re-confirm at runtime (a) that the operator actually holds admin rights (ASK — the skill cannot know the role) and (b) the irreversible action. When `false`, never offer a bypass regardless of role. 3. **`require_pr_reviews`** — `null` | `0` | `N`. Minimum approvals before merge to a protected branch (informational; the skill does not enforce GitHub rulesets, it records intent). Default `null` for solo-main (`0`); strategy-appropriate otherwise (typically `1` for multi-branch flows; `0` on the sdet trunk / `1` on the final sdet PR). - **Persisted as**: `git_strategy.policy.direct_push_to_protected` + `git_strategy.policy.admin_bypass` + `git_strategy.policy.require_pr_reviews`. > Q4 has no single global default — each sub-field's default is keyed off the resolved strategy (per the branching-strategies.md field rules). Present each default first and let the user override. ### `sdet` — questionnaire is mostly pre-answered `sdet` does not run the open Q1/Q2/Q3 questionnaire. Those answers are fixed by the strategy's definition (Q4 still runs — see below): - **Q1 (promotion)** — n/a. There is no production deploy; `main` holds confirmed tests. The final `trunk → main` PR follows the **repo's allowed merge method** (prefer merge-commit to preserve the multi-branch look; squash collapses the suite). Leave `git_strategy.decisions.promote_method: n/a`. - **Q2 (work-branch merge)** — **fixed at `merge-commit` (`--no-ff`)**, set `git_strategy.decisions.feature_merge: merge-commit`. Never offer squash/rebase: preserving per-ticket history is a defining property of `sdet`. - **Q3 (hotfix)** — n/a. Leave `git_strategy.decisions.hotfix_policy: n/a`. - **Q4 (protection policy)** — STILL ASKED (Q4 is universal). `sdet` defaults: `git_strategy.policy.direct_push_to_protected: confirm` (the trunk is self-merged by the maintainer), `git_strategy.policy.admin_bypass: false`, `git_strategy.policy.require_pr_reviews: 0` on the trunk / `1` on the final `trunk → main` PR. So Strategy Setup for `sdet` sets `git_strategy.strategy: sdet` + `git_strategy.decisions.feature_merge: merge-commit` + the `sdet` Q4 `policy` defaults (the other `decisions.*` stay `n/a`, `branches.integration` stays null, `branches.ephemeral_pattern: "test/<module>-suite"`), materializes nothing extra at setup (the trunk is per-suite, created on demand by the Branch operation). The full per-suite operational detail lives in `references/sdet-integration-trunk.md`. --- ## 2. Materialization table — what to ensure per strategy For the resolved strategy, ensure the long-lived branches in this table exist. **If a branch already exists, never recreate it — only ff-sync it if it is behind its pair (Section 3).** Work-branch and on-demand branches (`feature/*`, `release/*`, `hotfix/*`) are NOT created at setup; they are created later by the Branch operation when work starts. | Strategy | Long-lived branches to ensure | Work-branch base | Promotion path | Production branch | | ------------------ | ------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------- | ----------------- | | `solo-main` | `main` only | `main` | n/a (direct) | `main` | | `main-integration` | `main` + integration (`staging` / `dev` / `develop` — ask name, default `staging`) | integration | integration → `main` | `main` | | `enterprise` | `main` + integration (`feature/*`, `release/*` created on demand, NOT at setup) | integration (hotfix off `main`) | integration → `main`; `release/*` → `main` | `main` | | `trunk-based` | `main` (trunk) only | `main` (short-lived) | n/a (fast merge to trunk) | `main` | | `gitflow` | `main` + `develop` | `develop` (hotfix off `main`, release off `develop`) | `release/*` → `main` (+ back-merge to `develop`) | `main` | | `github-flow` | `main` only | `main` | n/a (PR → `main`) | `main` | | `gitlab-flow` | `main` + env branches (`pre-production`, `production`) | `main` | `main` → `pre-production` → `production` | `production` | | `sdet` | `main` only (the integration trunk `test/<module>-suite` is ephemeral per-suite — created on demand by the Branch operation, NOT at setup) | integration trunk (per suite) | trunk → `main` (single final PR, per suite) | `main` | **Branch-creation rules**: - Single-branch strategies (`solo-main`, `github-flow`, `trunk-based`) ensure `main` only — there is nothing to create on a normal repo. No integration branch, ever. - `main-integration`: if the integration branch is missing, ASK its name (default `staging`), then propose creating it off `main` and ff-syncing (Section 3). - `gitflow`: ensure `develop` (create off `main` if missing); do not create `release/*` / `hotfix/*` at setup. - `gitlab-flow`: ensure `pre-production` and `production` (create off `main` in pipeline order if missing). - `enterprise`: ensure integration off `main`; `release/*` / `feature/*` are on-demand only. - `sdet`: ensure `main` only at setup — create NO integration branch. The per-suite trunk `test/<module>-suite` is created off `main` by the Branch operation when a suite starts and deleted after the final PR; it is never materialized at Strategy Setup time and is never ff-synced (it carries `--no-ff` merge commits by design). Always **propose** each branch creation (which branch, off what, why) and wait for OK. Never `git checkout -b` / `git branch` silently. --- ## 3. Sync mechanics — fast-forward ONLY, never `--force` A sync runs only when a long-lived pair exists (integration + production, or two env branches) and one is behind the other as a **pure ancestor**. The goal is to bring the behind branch up to the ahead branch without rewriting history. ### 3.1 Ancestry check (decide direction) ```bash git fetch origin git log --oneline <base>..<ahead> # commits in <ahead> not in <base>; must be EMPTY for a ff in this direction ``` - `git log <production>..<integration>` empty → `integration` has nothing `production` lacks; `integration` is a pure ancestor of `production` and is behind, while `production` is ahead (the common `main-integration` case where integration is behind a freshly-promoted `main` — e.g. `git log main..staging` empty when `staging` is behind `main`). The behind branch (`integration`) gets fast-forwarded up to `production`. - `git log <integration>..<production>` empty → production is a pure ancestor of integration; integration is ahead (integration has accumulated unreleased work ahead of production). The behind branch (`production`) gets fast-forwarded up to `integration`. - **Both non-empty** → the branches have **diverged both ways** → STOP. Do NOT sync. Hand to conflict resolution (SKILL.md 3.5). Never force. ### 3.2 Fast-forward push (the only sync we perform) When exactly one direction is empty, the behind branch can be fast-forwarded to the ahead ref: ```bash # bring <behind-branch> up to <ahead-ref> by a fast-forward push — NO --force git push origin <ahead-ref>:refs/heads/<behind-branch> ``` (For the local checkout of the behind branch, `git merge --ff-only <ahead-ref>` is equivalent and fails loudly if it is not a true fast-forward — use it rather than a plain `git merge` so a non-ff is rejected instead of silently creating a merge commit.) ### 3.3 Hard rules - **NEVER `--force`, never `--force-with-lease`** in a setup sync. A sync is a fast-forward or it is not a sync. - A push to a protected branch (integration or production) requires explicit confirmation first — a setup ff-sync push is still a push. - Diverged-both-ways → STOP → conflict resolution. Setup does not resolve conflicts itself. --- ## 4. Persist sequence Once branches are materialized and decisions captured, persist in this order: 1. **Write the `git_strategy:` block in `.agents/project.yaml`** (in place — create the block if absent; overwrite the relevant fields if it exists; preserve the rest of the file, which holds project identity + env config). NEVER write a separate file. Populate the fields that apply to the resolved strategy (all nested under `git_strategy`): - `strategy` — the resolved slug. - `branches` — `production` (release/default branch), `integration` (long-lived integration branch name or `null`), `ephemeral_pattern` (strategy-specific on-demand trunk pattern or `null`). - `protected` — branches requiring explicit confirm before a direct push. - `decisions` — `promote_method` / `feature_merge` / `hotfix_policy`, each captured from Q1/Q2/Q3 or left `n/a` when the question does not apply. - `policy` — `direct_push_to_protected` / `admin_bypass` / `require_pr_reviews`, captured from Q4 (applies to every strategy; defaults are per-strategy). - `branch_prefixes` — `precedence` + naming patterns (carry the defaults unless the user overrides). - `description` — the one-paragraph human summary of the flow for this repo. - `meta.created` — today's date; bump `meta.setup_version` on a re-run that changes the schema. Per-strategy field values: `references/branching-strategies.md` → "git_strategy field rules (per strategy)". 2. **Set up local tracking** for any newly-ensured branch (`git branch --set-upstream-to=origin/<branch> <branch>` or `git checkout -b <branch> origin/<branch>`), so later operations don't re-detect. AGENTS.md's `## Git Strategy` section is a shipped pointer to `.agents/project.yaml` (`git_strategy:` block) — NEVER write strategy policy there. The block is the source of truth; its `git_strategy.description` field is the human summary. A later Strategy Setup re-run re-reads the block and only fills the `git_strategy.decisions.*` / `git_strategy.policy.*` fields still unset. --- ## 5. Report format Close Strategy Setup with a compact report: ``` Strategy Setup complete — <strategy-slug> Branches: - <branch>: created off <base> | already existed | ff-synced to <ahead-ref> (no force) | skipped (n/a for this strategy) Decisions captured: - promote_method: <value | n/a> - feature_merge: <value | n/a> - hotfix_policy: <value | n/a> Policy captured (Q4): - direct_push_to_protected: <forbidden | confirm | allowed> - admin_bypass: <true | false> - require_pr_reviews: <null | 0 | N> Definition: .agents/project.yaml (git_strategy block, <N> fields populated) Next: branch off <work-branch-base> to start work; the Branch operation will use this strategy automatically. ``` If a diverged pair stopped the sync, the report instead states the divergence and points to conflict resolution (3.5) — it does NOT claim a successful sync. -
worktrees.md 14.1 KB
# Git Worktrees — Isolated Parallel Work (manual + Claude Code harness) A **worktree** is a second working directory wired to the **same `.git`**. Git normally gives you one working tree; with worktrees you get several — each with its **own checked-out branch, its own files, and its own index** — while they all share one object database (commits, blobs, refs). ``` ~/proj/ <- primary worktree (branch A — e.g. feature-in-progress) .git/ <------ one shared object store ------+ src/ ... | | ~/proj-hotfix/ <- linked worktree (branch B — e.g. hotfix) ---+ src/ ... | -+ ``` Committing in one worktree never touches another worktree's files. A branch can be checked out in **only one** worktree at a time (git enforces this), which is exactly what makes worktrees safe for **parallel sessions** — including multiple AI agents working locally at once. --- ## When to use a worktree - **Parallel AI sessions** — two agents (or an agent + a human) working the repo at once, each on its own branch, without stepping on each other's files. - **Isolate risky / unrelated WIP** — you have important uncommitted work on branch A and want to build something unrelated (branch B) without polluting A's working tree or risking an accidental `git add` mixing the two. - **Hotfix while a feature is open** — patch `main` in a clean tree without stashing or disturbing the half-done feature. - **Review a PR branch** — check out someone's branch in a separate tree without disrupting your own. ### When NOT to bother - A simple branch switch on a clean tree → just `git switch`/`git checkout -b`. - One short linear task → a normal branch is enough; a worktree is overhead. --- ## Approach A — Manual git (portable, works with any tool or agent) This is plain git. It works the same in any terminal, any editor, any coding agent. ```bash # inspect git worktree list # show every worktree + its branch # create: new directory + NEW branch, based on a ref git worktree add ../proj-feature -b feat/x main # branch feat/x from main, in ../proj-feature git worktree add ../proj-hotfix hotfix/y # check out an EXISTING branch hotfix/y # work — both directories live simultaneously cd ../proj-feature # ...edit / commit normally... git add -p && git commit -m "feat: x" git push -u origin feat/x cd ../proj # hop back to the primary tree any time # clean up after the branch is merged git worktree remove ../proj-feature # delete the dir (refuses if uncommitted; --force overrides) git branch -d feat/x # delete the branch once merged git worktree prune # drop stale registrations (if a dir was rm'd by hand) # extras git worktree lock ../proj-feature "reason" # protect from prune (e.g. dir on external/removable disk) git worktree unlock ../proj-feature git worktree move ../proj-feature ../proj-feature2 # relocate a worktree ``` **Golden rules** - Same branch in two worktrees → **git blocks it**. Give every worktree its own branch. - `git worktree remove` **refuses** when there are uncommitted changes → commit (or `--force` to discard). - Deleting a worktree directory with `rm -rf` leaves a stale registration → run `git worktree prune` afterward. - A worktree's `HEAD`, index, and stash-vs-tree are independent; **the stash list and config are shared** (see Multi-session safety). --- ## Approach B — Claude Code harness (`EnterWorktree` / `ExitWorktree`) > **Claude-Code-specific.** `EnterWorktree`/`ExitWorktree` are native **Claude Code** > tools that orchestrate `git worktree` *and move the agent's session into it*. Other > coding agents (Cursor, Copilot, Codex, Aider, …) do **not** have these — there, use > **Approach A** (manual git) or that tool's own equivalent. The underlying git mechanics > are identical regardless. **`EnterWorktree`** — creates a worktree under `.claude/worktrees/<name>/` on a new branch and switches the session's working directory into it. - Base ref is governed by the `worktree.baseRef` setting: - `fresh` (default) → branch from `origin/<default-branch>` (clean, independent of local WIP). - `head` → branch from your current local `HEAD` (carries your current branch's commits). - Params: `name` (create a new worktree) **or** `path` (enter an existing one already made with `git worktree add`). **`ExitWorktree`** — returns the session to the original directory. - `action: "keep"` — leave the worktree + branch on disk (come back later / preserve work). - `action: "remove"` — delete the worktree dir **and** its branch. With uncommitted files or unmerged commits it **refuses** unless `discard_changes: true`. - Only operates on worktrees **this session** created via `EnterWorktree` — it will not touch one you made by hand (`git worktree add`). **Subagents** — the `Agent` tool (and workflow agents) accept `isolation: "worktree"`, which runs each subagent in its own temporary, auto-cleaned worktree. Use that only when parallel subagents mutate files and would otherwise collide — not to isolate a whole session. ## Approach C — Orchestrated worktree (managed by the orchestration layer) When several agent sessions are being coordinated — a conductor plus N workers — the worktrees are created and destroyed by the orchestration layer instead of by hand, one per worker, outside the repo. The mechanics (create, provision, launch a session into it, remove) belong to `orca-orchestration/SKILL.md`; from git's point of view it is still an ordinary linked worktree, so everything else in this file applies unchanged. Write the calls as `[ORCHESTRATION_TOOL] <verb>: …` pseudocode and load that skill for the HOW. Topology choice per activity: `orca-orchestration/references/topologies.md`. The distinction that matters here: an orchestrated worktree is **visible to the owner** (board card, managed terminal, phone) and outlives the session that made it, while a harness worktree lives inside the repo and is invisible outside the session that created it. Launching a session into that worktree can go through the native path (supervised — the orchestrator recognizes the session and can address it directly) or the custom-argv path (never supervised, the default fallback); from git's point of view the worktree itself is identical either way. ### Manual vs harness vs orchestrated at a glance | | `git worktree` (manual) | `EnterWorktree` (Claude Code) | Orchestrated (Approach C) | | --- | --- | --- | --- | | Portability | any tool / agent | Claude Code only | any agent, but needs the orchestration app + binary on the machine | | Directory location | anywhere you choose (`../dir`) | fixed under `.claude/worktrees/` | the orchestrator's own workspace dir, outside the repo | | Base ref | whatever you pass | setting: `fresh`=origin/default or `head` | the base you pass at create time — **verify the new HEAD against `origin/<base>`**, it resolves local refs | | Moves the agent's session | no (you `cd`) | yes, automatically | no — it creates the tree, then a session is launched INTO it | | Cleanup | manual (`remove`/`prune`) | `ExitWorktree remove` | orchestrated removal + `git worktree prune`, always after the orphan audit below | | Branch naming | you choose | derived from the name (rename with `git branch -m`) | you choose at create time | | Owner can see it (board / phone) | no | no | yes | --- ## The untracked-files gotcha (applies to BOTH approaches) A brand-new worktree starts with **only the tracked files of its base ref**. Files that are **untracked** in your current tree (new, never `git add`ed) live physically in the *current* directory — they **do not teleport** into the new worktree. To bring untracked WIP into a fresh worktree, **move it**: ```bash mv ./cli/new-feature ../proj-feature/cli/new-feature # untracked files: just move them # or: commit them on a branch first, then create the worktree from that branch ``` **Do not move a tracked path by accident.** If you `mv` a directory that contains tracked files, git sees them as deleted in the source tree. Restore with: ```bash git checkout -- path/to/tracked-file # bring a tracked file back into the source tree ``` --- ## Provisioning: what a fresh worktree does NOT have (all approaches) Untracked files are only half of it. Everything **gitignored** is missing too, and that half fails in ways that point at the wrong cause: no `.env` means the MCP servers do not parse (they reference `${VAR}`) and any login script has no credentials; no `node_modules/` reports `Cannot find module`; a missing `.claude/skills` alias makes every Claude Code skill invocation an `Unknown skill`; a missing `.context/PBI/` cache fails **silently** — the session simply cannot see the synced ticket. ```bash bun run worktree:provision # in the new worktree: .env, deps, the skills alias, community skills, .auth/ bun run context:hydrate # rebuild the Jira cache (needs credentials, so run it after the above) ``` `.session/` is deliberately NOT provisioned: a plan, brief, or roster written inside a worktree dies with it. Keep those in the primary checkout and cite them by **absolute** path. Full gap table and how to wire provisioning as an orchestration setup hook: `orca-orchestration/references/provisioning.md`. --- ## Multi-session safety (no collisions between parallel agents) Rule of thumb: **one session = one worktree = one branch.** | Shared across worktrees (safe) | Isolated per worktree | | --- | --- | | `.git/objects` (commits/blobs — append-only, no overwrite) | working directory (files) | | refs, config, hooks, **stash list** | index / staging area | | | checked-out branch (duplicate checkout blocked by git) | - Two sessions never edit the same physical file or the same branch → they cannot clobber each other's work. - **Stash is global to the repo.** Do not rely on `git stash` to hand work between sessions — commit to your branch instead. - **Runtime, not git:** if both sessions run a local server / dev process, give each a **distinct port** (or rely on port auto-fallback). Git isolation does not isolate network ports, temp files, or databases. - **Worktree nested inside the repo** (e.g. Claude Code's `.claude/worktrees/`): the parent repo may show it as untracked. Hide it **locally** without a tracked commit by adding the path to the shared exclude file: ```bash echo '.claude/worktrees/' >> "$(git rev-parse --git-common-dir)/info/exclude" ``` `info/exclude` lives in the shared git-common dir (one copy for all worktrees) and is never committed — so it cannot leak into another branch's history. --- ## Orphan audit — run BEFORE removing any worktree Removing a worktree deletes its directory, and **gitignored files are not in git**: `.env`, `.auth/`, captured evidence and screenshots, local reports, anything under `.session/`. A clean `git status` says nothing about them — it is exactly the state in which they look safe to delete. ```bash git -C <worktree> status --porcelain # tracked work: must be committed AND pushed git -C <worktree> log --oneline origin/<base>.. # commits that exist only here git -C <worktree> status --porcelain --ignored # THE audit: every ignored/untracked file about to die ``` For each survivor in that last list, decide once: **copy it out** to the primary checkout (evidence, reports, anything a Jira comment or an ATR already references), or accept the loss deliberately (`node_modules/`, caches, a `.env` that is just a copy). A durable document belongs in the primary checkout or in the tracker, never only in a worktree. Only then remove the worktree. --- ## Cleanup checklist - [ ] Orphan audit ran (`--ignored`) and every file worth keeping was copied to the primary checkout. - [ ] Branch's work is committed and pushed (or deliberately discarded). - [ ] `git worktree remove <path>` (or `ExitWorktree remove`) — succeeds only when clean. - [ ] `git branch -d <branch>` once the branch is merged. - [ ] `git worktree prune` if any directory was removed by hand. - [ ] Local `info/exclude` entries cleaned up if the worktree path is gone for good. --- ## Decision guide | Situation | Do this | | --- | --- | | Clean tree, one linear task | Just a branch (`git switch -c`) — no worktree | | Risky WIP on current branch, need to build something unrelated | Worktree on a new branch | | Two AI sessions in parallel | One worktree + one branch **each** | | Claude Code, want the session moved for you | `EnterWorktree` (base `fresh` for independence) | | Any other agent / portable script | `git worktree add … -b …` (Approach A) | | Parallel subagents mutating files | `Agent`/workflow `isolation: "worktree"` | | A coordinated fleet of worker sessions the owner wants to watch and steer | Orchestrated worktree per worker (Approach C) — `orca-orchestration/SKILL.md` | | Several sessions that only read code and write to the tracker (manual QA, AC refinement) | **No worktree** — same checkout. The isolation they need is a browser profile and a session dir, not a second tree | --- ## AI working pattern (this repo) When an AI session needs isolation from in-progress work on another branch: 1. Prefer `EnterWorktree` (Claude Code) with base `fresh` so the new branch is independent of the current branch's local WIP; rename the branch to convention (`git branch -m feat/<slug>`). 2. **Move** any untracked WIP into the worktree (it will not be there automatically). 3. Keep the primary repo's `git status` **clean** — verify with `git -C <primary> status`. 4. Hide the nested worktree from the primary tree via local `info/exclude`. 5. Do all further work (edits, verifies, commits) in the worktree; the other branch stays untouched. 6. On completion, commit on the worktree's branch → open its own PR → `ExitWorktree` (`keep` to preserve, `remove` when merged/abandoned).
-
-
SKILL.md 56.5 KB
--- name: git-flow-master description: "End-to-end Git operator for any branching strategy. Auto-detects the project's strategy (solo-main, main+integration, enterprise multi-branch, trunk-based, GitFlow, GitHub Flow, GitLab Flow, SDET integration-trunk for chained test-automation suites) from .git config, branches, and the `git_strategy:` block in `.agents/project.yaml`, then adapts every commit, branch, push, PR, conflict-fix, and chained-PR action to that strategy. Use this skill whenever the user wants to: create a branch (`crear branch`, `new feature branch`, `start work on UPEX-123`), commit changes (`commit this`, `commitear esto`, `make a commit`, `commit and push`), push code (`push`, `push to main`, `push to staging`, `subir cambios`), open a pull request (`create PR`, `open PR`, `abrir PR`, `crear pull request`, `gh pr create`), fix merge conflicts (`fix conflict`, `resolver conflicto`, `merge conflict`, `rebase conflict`, `push rejected`), plan stacked or chained PRs (`stack of PRs`, `chained PRs`, `split this PR`, `PR demasiado grande`), set up an isolated git worktree (`worktree`, `work in a worktree`, `isolate this work`, `parallel session`, `aislar el trabajo`, `trabajar aislado`), set up or bootstrap a branching strategy on a fresh repo (`set up our git strategy`, `bootstrap branching`, `configura el flujo de git`, `git strategy setup`, `materialize the git flow`, `create the staging branch and write the runbook`), or pick / change / set up a branching strategy (`git flow`, `git strategy`, `branching strategy`, `which git flow do we use`, `set up our git strategy`, `bootstrap branching`, `configura el flujo de git`). Trigger even when the user does not say `git-flow-master` literally — if the work is git-or-PR-shaped, this is the right tool. Do NOT use for: testing tickets (use /sprint-testing), authoring test cases in TMS (use /test-documentation), writing automated tests (use /test-automation), running regression suites (use /regression-testing), or general code editing — git-flow-master operates strictly on the version-control layer." license: MIT compatibility: [claude-code, opencode] phase: implementation complementary_categories: [] --- <!-- Model preferences (advisory; dispatchers may use to route) --> <!-- model_preferences: foundation: opus # high-leverage architectural work planning: sonnet # structured writing implementation: sonnet # default for code work review: opus # critical analysis archive: haiku # mechanical close-out --> # Git Flow Master — One Skill for Branches, Commits, Pushes, PRs, and Conflicts This skill is the project's single entry point for everything that happens on the version-control layer: creating branches, writing commits, pushing safely, opening pull requests, resolving conflicts, and planning chained / stacked PRs when a change outgrows the review budget. It does not assume one branching model. The project may run on `main` only, on `main + staging`, on a multi-branch enterprise layout, or on any of the well-known flows (trunk-based, GitFlow, GitHub Flow, GitLab Flow). The skill **detects** which one is active and adapts every command accordingly. The detection is sticky: once resolved, the strategy is recorded in the `git_strategy:` block of `.agents/project.yaml` so future invocations skip the prompt. --- ## Compact Rules - DO: read the repo state (status, branches, diff, log, fetch, upstream, remotes) at the start of EVERY invocation and report it before acting. Never assume repo state. - DO: resolve the branching strategy from the `git_strategy:` block in `.agents/project.yaml` first, then layout heuristics, then by asking — never pick one silently. Persist the resolution back into that block, never into a separate file and never as policy prose in `AGENTS.md`. - WHEN `git_strategy.strategy` is set but `project.project_name` is null, or `meta.strategy_source` is still `inherited` on a named project: the strategy was INHERITED from the template, not chosen. Treat it as unconfirmed and OFFER Strategy Setup once per session — never auto-run it, and proceed under the inherited strategy on a "no". - DO: consult `git_strategy.policy.direct_push_to_protected` before any direct push to a protected branch — `allowed` is standing authorization (asking anyway collapses it into `confirm`), `confirm` asks every time, `forbidden` refuses and routes through a PR. A missing or null block behaves as `confirm`. - DO NOT: force-push, `--force-with-lease`, `--no-verify`, amend or rebase a pushed commit, or otherwise rewrite pushed history, unless the user explicitly authorizes it AND the branch is unshared. - DO NOT: run a repo-wide discard (`git restore .`, `git checkout -- .`, `git reset --hard`, untargeted `git stash`, `git clean -f`) — concurrent sessions may share this working tree. Discard only explicit paths this session modified; unclear ownership means stop and ask. - DO NOT: `git add -A` or `git add .`. List explicit paths, so a secret or another session's work cannot ride along. - DO: keep one commit to one responsibility, in conventional format (`{type}({ISSUE-KEY}): {description}`). Commit messages, branch names and PR bodies are English and carry NO AI attribution. - DO: close EVERY commit message, in every strategy, with the two forensic trailers `Worktree: <name|primary>` then `Session: <label>`, copied from the `AGENT IDENTITY:` line in session context (`unknown` when a value cannot be resolved). They are forensics, not attribution — a harness-branded trailer (`Claude-Session:`, an AI `Co-Authored-By:`) stays forbidden. - WHEN a pre-commit hook rejects a commit: stop, fix the underlying issue, and create a NEW commit. Never `--amend` the rejected one. - DO: propose every branch name, commit set, and PR body and wait for an explicit OK before executing. - DO: stop at PR creation — merging is the user's next step, never automatic. If the `gh` transport is missing or unauthenticated, surface the blocker instead of implying a PR was opened. - WHEN reconciling declared policy against the host: run the policy-verify tool once at the first push / PR / merge intent, never hand-query protection endpoints. A `404` on the classic protection endpoint does not mean unprotected, and a push that succeeded may have been a documented bypass, not permission. Report drift; never auto-correct it. - WHEN a planned change exceeds ~400 changed lines: run the chained-PR decision (single-pr / stacked-to-main / feature-branch-chain / size-exception) before coding, and re-run it if the real diff outgrows the estimate rather than silently up-budgeting. - WHEN a conflict fires: diagnose and classify it first, present options ranked by safety, and prefer a safe abort over a guess. Never pick a destructive option silently. **Read full SKILL.md when**: running Strategy Setup, resolving a specific conflict type, picking a base branch or branch prefix for an unfamiliar strategy, or setting up an isolated worktree. --- ## When to use Trigger on any of these intents — even without literal keywords: - "I want to start work on UPEX-123" → branch creation - "commit and push", "subir cambios", "push to main" → commit + push flow - "abrí un PR contra staging" → PR creation - "tengo conflictos al hacer pull" → conflict resolution - "este PR va a quedar enorme" → chained-PR planning hand-off - "qué estrategia de git usamos en este repo" → strategy detection / persistence - "el push fue rechazado" → diagnostic + recovery flow If the user is asking about testing a ticket, authoring test cases, writing automated tests, or running regression suites — that is **not** this skill. Hand back to `/sprint-testing`, `/test-documentation`, `/test-automation`, or `/regression-testing`. --- ## The six operations Every git-flow-master invocation maps to one (or a sequence) of these six operations. Operation choice is driven by the user's request; strategy resolution shapes how each operation runs. | Op | Trigger phrases (examples) | Skill behaviour | | ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Branch** | "create branch", "new feature branch", "start UPEX-123" | Resolve strategy → propose name with prefix + issue key → wait for OK → checkout | | **Commit** | "commit this", "commit and push", "make atomic commits" | Group by responsibility → propose conventional commits → wait for OK → execute one-by-one | | **Push** | "push", "push to main", "subir cambios" | Diagnose upstream → confirm if pushing to a protected branch → never `--force` without explicit user opt-in | | **PR** | "create PR", "abrir PR", "gh pr create" | Pick base branch from strategy → render body inline → ask labels/reviewers → call `gh pr create` | | **Conflict** | "fix conflict", "rebase failed", "push rejected" | Diagnose first (see `references/conflict-resolution.md`) → present options → guide resolution → verify clean state | | **Strategy Setup** | "set up our git strategy", "bootstrap branching", "configura el flujo de git", "materialize the flow" | Resolve strategy → run decision questionnaire (Q1-Q4) → conditionally create/ff-sync long-lived branches (never force) → write the `git_strategy:` block in `.agents/project.yaml`. Skips questions already answered by non-`n/a` `git_strategy.decisions.*` fields. See `references/strategy-setup.md`. | When the operation is ambiguous (user just says "git-flow-master" or "let's do the git stuff"), report the current repo state (Step 1 below) and ask what they need. --- ## Step 1 — Always: read the repo state Run these silently every invocation. Do not act until the picture is clear: ```bash git status git branch --show-current git branch -a git diff --stat git log --oneline -5 git fetch origin git status -sb git remote -v ``` Summarise to the user: - Current branch. - Dirty / clean working tree (staged / unstaged / untracked counts). - Unpushed / unpulled commits (ahead / behind upstream). - Upstream status (no upstream, up-to-date, diverged). - Remote name(s) — most repos have one (`origin`); some have a fork + upstream. This summary is cheap, prevents 90% of mistakes, and is the input to every subsequent decision. --- ## Step 1b — Reconcile the declared policy against the host (once per session) `git_strategy.policy.*` in `.agents/project.yaml` records what the team DECIDED. The hosting platform records what is actually ENFORCED. These drift, and the drift only surfaces at the worst moment: a merge that stalls on an approval nobody expected, or a "protected" branch that was never protected. Run this ONCE per session, at the first push / PR / merge intent (not on read-only operations), and cache the result for the rest of the session. **Run the tool; do not perform the queries by hand:** ```bash bun run git:policy verify # read-only; exit 1 on drift bun run git:policy verify --stamp # same, and records the reconciliation when clean ``` It queries BOTH GitHub protection mechanisms for every branch in `git_strategy.branches` / `protected`, compares the union against the declared policy, and prints each divergence as `declared` vs `enforced`. The strategy-to-ruleset mapping and what the tool deliberately does not manage: `references/ruleset-parity.md`. **Why a tool rather than a checklist.** This reconciliation existed only as prose in the sibling boilerplate and kept not happening — that repo shipped `require_pr_reviews: 0` against a host demanding one approval plus a code-owner review, and it surfaced months later as a refused merge. This repo had the identical divergence. A script performs every query on every run; a procedure performs the ones the reader remembered. **Facts that still bind you when reading its output:** - **A `404` from `branches/{b}/protection` does NOT mean the branch is unprotected.** A repo governed by rulesets returns `404` there while enforcing PR requirements, approvals, signed commits and non-fast-forward bans through `rules/branches/{b}`. Stopping at the classic endpoint produces a confident "unprotected" reading on a branch that requires a reviewed pull request. - **A push that succeeds is not evidence of an absent rule.** Org owners and anyone on the ruleset bypass list push through while the rule still binds everyone else. When a push prints `Changes must be made through a pull request`, that was a BYPASS: report it as one, never as permission. With `git_strategy.policy.admin_bypass: true` (or the divergence listed in `git_strategy.policy.accepted_divergences`), the `Bypassed rule violations` remote line is the DOCUMENTED norm — mention it in the report as expected, do NOT treat it as an anomaly, do NOT stall asking for confirmation, and NEVER open a PR to "satisfy" the rule. - **`require_code_owner_review: true` with no `CODEOWNERS` file is unsatisfiable, not strict.** Nobody outside the bypass list can clear it, so every merge becomes a bypass. **On drift, report — never auto-correct.** Three legitimate resolutions: update `.agents/project.yaml` to match the host, change the host (`bun run git:policy apply`, dry run until `--yes`), or accept the divergence and record WHY in this project's own `AGENTS.md`. Editing either side needs the user's choice. --- ## Step 2 — Resolve the branching strategy The skill supports eight strategies (see `references/branching-strategies.md` for the full catalogue, detection signals, and trade-offs): | Strategy | One-line description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `solo-main` | Single long-lived branch (`main`). All work lands directly. Best for solo projects, scratch repos, prototypes. | | `main-integration` | `main` (production) + a single integration branch (`staging` / `dev` / `develop`). Features merge to integration, release-promote to `main`. | | `enterprise` | `main` + integration + many short-lived `feature/*`, `fix/*`, `release/*`, `hotfix/*` branches. Adds environment branches when needed. | | `trunk-based` | Trunk (`main`) is the only long-lived branch. Short-lived feature branches (<1 day) merge fast, behind feature flags. CI gate is non-negotiable. | | `gitflow` | Vincent Driessen's classic. `main` (releases) + `develop` (integration) + `feature/*` + `release/*` + `hotfix/*`. Heavyweight; mostly legacy. | | `github-flow` | `main` always deployable. `feature/*` branches → PR → merge → deploy. No staging/develop branch. | | `gitlab-flow` | GitHub Flow + environment branches (`pre-production`, `production`) to model deployment promotion. | | `sdet` | SDET Gitflow. `main` (confirmed tests) + ephemeral per-suite integration trunk `test/<module>-suite`. Test tickets chain through the trunk (`--no-ff`); one final PR → `main`. For chained test-automation suites. Opt-in; see `references/sdet-integration-trunk.md`. | ### Detection algorithm Apply in order; stop at the first definitive answer: 1. **`git_strategy:` block in `.agents/project.yaml`** — read it. If `git_strategy.strategy` is non-null (one of the eight slugs), it + `git_strategy.branches` (production / integration / ephemeral_pattern) + `git_strategy.decisions` (promote_method / feature_merge / hotfix_policy) ARE the persisted decision — use them. Each `git_strategy.decisions.*` field whose value is NOT `n/a`/empty means Strategy Setup SKIPS that question on re-run (idempotent — idempotency is keyed off the `git_strategy.decisions.*` fields, not markers). **Inherited-template guard:** the boilerplate ships the block FILLED (`strategy: solo-main`) and a scaffolded project INHERITS it verbatim (the scaffolder only patches `project.project_name` / `project.project_key`). So a non-null `git_strategy.strategy` is only authoritative when the project is actually onboarded. Read `project.project_name` in the SAME file: if `git_strategy.strategy` is non-null BUT `project.project_name` is `null`, the block was INHERITED from the template (not chosen for THIS project) — treat the strategy as UNCONFIRMED and route to the Bootstrap trigger's inherited case (it still operates under the inherited strategy if the offer is declined). If `project.project_name` is set, the block is confirmed → use it normally, no nudge. 2. **Single-branch heuristic** — `git branch -a` shows only `main` (or `master`) and no integration branch in the remote → `solo-main`. 3. **Two-branch heuristic** — exactly `main` (or `master`) + one of `{staging, dev, develop, integration}` exists upstream → `main-integration` (record the integration branch name). 4. **Multi-branch heuristic** — `main` + integration + active `feature/*` or `release/*` branches in `git branch -a` → `enterprise`. 5. **Project hints** — look for `.gitlab-ci.yml` (suggests `gitlab-flow`), `release/*` and `hotfix/*` long-lived branches (suggests `gitflow`). 6. **Fallback** — ask the user. Show the options with one-line descriptions; mirror their language. Do NOT pick silently. On a test-automation repo (KATA / Playwright / `/test-automation`), surface `sdet` as the recommended option. `sdet` is opt-in only — never inferred silently from layout; a live `test/<module>-suite` trunk with `test/{KEY}-*` PRs targeting it confirms an already-active `sdet` suite. ### Persist the decision Once resolved (whether by detection or by asking), write/update the `git_strategy:` block **in place** inside `.agents/project.yaml` (preserve the rest of the file — it holds project identity, env config, etc.; create the block if it is missing). NEVER write a separate file. It is the single source of truth. At minimum the first five operations need `git_strategy.strategy` + `git_strategy.branches`; the full schema (with `git_strategy.decisions`, `git_strategy.protected`, `git_strategy.policy`, `git_strategy.branch_prefixes`, `git_strategy.meta`) is populated by Strategy Setup (3.6). ```yaml # .agents/project.yaml — git_strategy block (only the fields that apply shown) git_strategy: strategy: main-integration branches: production: main integration: staging ephemeral_pattern: null ``` The block is the source of truth; its `git_strategy.description` field is the one-paragraph human summary. The user can edit it; the next invocation re-reads it. AGENTS.md's `## Git Strategy` section is **just a pointer** to `.agents/project.yaml` (`git_strategy:` block) — NEVER write strategy policy or branch decisions into AGENTS.md. If the strategy uses an integration branch with a non-default name (anything other than `staging`), record it under `git_strategy.branches.integration` so commits don't have to re-detect. **Block fields and idempotent setup.** `git_strategy.strategy` + `git_strategy.branches` are the minimum the first five operations need. Strategy Setup (3.6) additionally populates the three `git_strategy.decisions.*` fields (`promote_method`, `feature_merge`, `hotfix_policy`) plus the `git_strategy.policy.*` fields (Q4); these gate questionnaire skips. On any later invocation, detection reads the block and treats each `git_strategy.decisions.*` field that is NOT `n/a`/empty as an already-answered questionnaire question — Strategy Setup re-run only asks the questions whose `git_strategy.decisions.*` fields are still `n/a`, and never recreates a branch that already exists. ### Bootstrap trigger — offer setup on a fresh repo (never auto-run) At the top of any git intent, after Step 1 (repo state) and Step 2 detection have run, evaluate the gate — it fires on EITHER of two conditions: > **(a) Unset** — `git_strategy.strategy` in `.agents/project.yaml` is null (or the `git_strategy:` block is absent) AND the repo **looks fresh** — any of: only `main`/`master` exists locally and on the remote; fewer than ~3 commits; or a boilerplate sentinel file is present (e.g. `.agents/project.yaml`). > > **(b) Inherited** — `git_strategy.strategy` is non-null BUT `project.project_name` (same file) is `null`. The block was INHERITED from the boilerplate template (this project has not been onboarded yet) — it was NOT chosen for THIS project. Treat it as UNCONFIRMED. If EITHER condition is true, **OFFER** (do not auto-execute, do not silently pick a strategy), using the matching prompt: > _(unset case (a))_ "No git strategy is set up yet. Want me to run Strategy Setup — pick the flow, create the branches it needs, and write the `git_strategy:` block in `.agents/project.yaml`? (Y/N)" > _(inherited case (b))_ "This project's `git_strategy` looks inherited from the boilerplate (project not onboarded yet — `project.project_name` is null). Want to run Strategy Setup to define this project's own flow? (Y/N)" Rules: - **Offer once per session**, then cache the answer. Do not re-prompt every git intent in the same session. - **Never auto-run.** A `No` proceeds with the requested operation under the detected (case a) or inherited (case b) strategy without writing the block. - A `Yes` enters Strategy Setup (3.6) before continuing with the original git intent. - The boilerplate ships `.agents/project.yaml` with the `git_strategy:` block FILLED (`strategy: solo-main`); a scaffolded project INHERITS it verbatim (the scaffolder patches only `project.project_name` / `project.project_key`, and the updater freezes the file via `bootstrapOnlyPaths`). So the unset case (a) and the inherited case (b) are the two ways a project reaches a real git intent without having confirmed its own flow → the offer fires on first real use — by design (template-trap guard). If `project.project_name` is set, the strategy is confirmed and NEITHER case fires. --- ## Step 3 — Operation-specific runbooks ### 3.1 Branch creation Decide the **prefix** from the dominant change. Use this fixed vocabulary (mixed-changes precedence: `feat > fix > refactor > test > docs > chore`): | Prefix | When the dominant change is… | | ----------- | ---------------------------------------------------- | | `feat/` | new feature or capability | | `fix/` | bug fix | | `test/` | adding or updating automated tests (no product code) | | `docs/` | docs only | | `refactor/` | code change without behaviour change | | `chore/` | tooling, deps, housekeeping | For `enterprise` and `gitflow` strategies, also consider `release/X.Y.Z` and `hotfix/X.Y.Z` when appropriate. In a QA repo most work lands as `test/`, `fix/`, or `chore/` branches. Feature branches (`feat/`) are rare here — a `feat/` in this repo usually means a change to the test framework itself (new fixture, new Page component layer, new reporter). **Issue key extraction** (in order): 1. Current branch name regex: `(?:feat|feature|fix|test|docs|refactor|chore)/([A-Z]+-\d+)-`. 2. `$ARGUMENTS` for `[A-Z]+-\d+`. 3. Ask the user once: "Is there an issue key for this work?" — accept "no" gracefully. **Branch name format** — read `git_strategy.branch_prefixes` in `.agents/project.yaml` (`naming_with_key` / `naming_without_key` / `precedence`); the patterns below are the shipped defaults, used verbatim when the block is absent: - With key: `{prefix}/{ISSUE-KEY}-{kebab-slug}` (e.g. `test/UPEX-123-bulk-assign-coverage`). - Without key: `{prefix}/{kebab-slug}` (e.g. `refactor/split-kata-fixtures`). - Keep slugs lowercase, hyphen-separated, ≤50 chars. **Strategy-specific source branch**: - `solo-main`, `github-flow`, `trunk-based` → branch off `main`. - `main-integration`, `gitlab-flow` → branch off the integration branch (`staging` / `dev` / equivalent). - `enterprise` → branch off the integration branch unless it is a `hotfix/*`, which branches off `main`. - `gitflow` → `feature/*` branches off `develop`; `hotfix/*` off `main`; `release/*` off `develop`. - `sdet` → `test/{KEY}-*` ticket branches + Plus Branches (`docs/*`/`chore/*`/`fix/*`) branch off the **ephemeral integration trunk** `test/<module>-suite`; the trunk itself is cut from `main` on demand when a suite begins. Never stack a ticket on the previous ticket branch. See `references/sdet-integration-trunk.md`. Always **propose** the name and ask for OK before `git checkout -b`. Never create silently. ### 3.2 Commits Group changes by responsibility, not by file type: | Group | Typical paths | | ----------- | ----------------------------------------------------------------------------- | | Test code | `tests/`, `tests/components/`, `tests/e2e/`, `tests/integration/` | | API schemas | `api/schemas/`, codegen output, OpenAPI types | | Test data | `tests/data/` | | Skills/Docs | `.agents/skills/`, `.agents/`, `AGENTS.md`, `docs/`, `README.md` | | Config | `package.json`, `tsconfig.json`, `playwright.config.ts`, lint/format configs | **Test data and fixtures stay with the tests they support.** If a test commit ships its own fixture, they belong in the same commit, not in a separate `chore:` commit. **Conventional commit format**: - With issue key: `{type}({ISSUE-KEY}): {description}` (e.g. `test(UPEX-123): cover bulk-assign empty states`). - Without key: `{type}: {description}`. - Breaking changes: append `!` after type/scope and add `BREAKING CHANGE:` footer. **Vocabulary**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `build`, `ci`, `revert` (full list in `references/conventional-commits.md`). **Hard rules** (apply on every commit): - One commit = one responsibility. Never bundle unrelated changes. - Never `git add -A` or `git add .` — list explicit paths to avoid leaking secrets (`.env`, credentials) or unrelated work. - **PBI ladder guard (repos running the `.context/PBI/` cache, `AGENTS.md` §9).** After staging, run `git diff --cached --name-only | grep '^\.context/'`. Anything staged there must be one of the three `[COMMIT]`-tier paths (`.context/PBI/README.md`, `.context/PBI/templates/**`, `.context/PBI/epics/*/test-specs/**`); every other match is `[SYNC]` cache that leaked past the ignore ladder — a directory like `stories/` reads as untracked in `git status` and an explicit-path `git add` descends straight past the exclusion. Unstage it (`git restore --staged <path>`) before the commit proceeds. A commit that touches no `.context/` path skips this check. - **No AI attribution.** No `Generated with Claude Code`, no `Co-Authored-By: Claude`, no equivalent line. Commits look human-authored. (Critical Reminder #3 in `AGENTS.md`.) - If a pre-commit hook fails, **stop, fix the underlying issue, create a NEW commit**. Never `--amend` a commit the hook rejected — `--amend` operates on the previous commit, which destroys context. **Forensic trailers (mandatory, every commit, every strategy).** The last two lines of every commit message are: ``` Worktree: <name|primary> Session: <label> ``` - Both values come from the `AGENT IDENTITY:` line the prompt hook injects into this session's context (`worktree=…`, `session=…`). Copy them; do not re-derive them per commit. The session label may contain spaces and parentheses (`my-session (c0ffee12)`): take everything after `session=` up to the literal ` harness=` token, never split the line on whitespace. - `primary` is the correct worktree value when the session is not running in a linked worktree. When a value could not be resolved at all, write `unknown` — never guess a name, never drop the key. A missing trailer is less recoverable than an honest `unknown`. - Nothing goes below them, and nothing is added beside them. - **These are forensics, not attribution.** They record WHICH working tree and WHICH session produced the commit, so a bisect, an incident review, or a parallel-session post-mortem can find the right transcript. They are deliberately harness-agnostic: no tool, vendor, or model is named. The prohibition in Critical Rule #3 is untouched — never `Claude-Session:`, never a `Co-Authored-By:` for an AI, never a "Generated with …" line, never any other harness-branded key. Present all proposed commits as one block. Wait for OK / modify / reject before executing. ### 3.3 Push Push command depends on Step 1 output: - No upstream → `git push -u origin {branch}`. - Upstream behind → `git push`. - Upstream diverged → **stop**. Do not force. Hand to conflict resolution (3.5). **Protected branches per strategy** — the branches the policy gate below guards (union with `git_strategy.protected`): - `solo-main` → `main` is protected. - `main-integration` → both `main` and the integration branch are protected. - `gitflow` → `main` and `develop` are protected. - `github-flow` / `trunk-based` → `main` is protected. - `enterprise` → `main`, integration, and any `release/*` are protected. - `sdet` → `main` is protected (only the final suite PR lands, reviewed + green CI). The integration trunk is protected-by-convention but its ticket/Plus PRs are self-merged by the maintainer with no ruleset friction. **Policy gate (consult `git_strategy.policy.direct_push_to_protected` before any direct push to a protected branch):** | `git_strategy.policy.direct_push_to_protected` | Behaviour | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `allowed` | **Standing authorization** — push WITHOUT a per-push confirm. The recorded value IS the authorization (stamped by Strategy Setup with the user); asking anyway collapses `allowed` into `confirm` and empties the third state. | | `confirm` (default) | Always ask. Wait for explicit yes. | | `forbidden` | **Refuse** the direct push. Redirect to the PR flow (3.4): propose a work branch + `gh pr create`. | For `confirm`, ask: _"You are about to push directly to the protected branch `{branch}` in a `{strategy}` flow. Confirm?"_ Wait for explicit yes. **Missing or null `git_strategy` block** (fresh scaffold, project never onboarded) → behave as `confirm`: the safe default is to ask, never to assume standing authorization. **Admin bypass (only when contemplating skipping PR/protection for an urgent change):** only when `git_strategy.policy.admin_bypass: true` may the skill OFFER a bypass — and it MUST re-confirm at runtime BOTH: (a) the operator actually holds admin rights on the repo (ASK — the skill cannot know the GitHub role; `admin_bypass` is a team POLICY intent, not a capability check), AND (b) the irreversible action itself. If `git_strategy.policy.admin_bypass: false`, NEVER offer a bypass regardless of the operator's role. **Never** pass `--force`, `--force-with-lease`, `--no-verify`, or any history-rewriting flag unless the user explicitly requests it AND the branch is unshared. Document the request in the conversation. (Critical Reminder #6 in `AGENTS.md`: never rewrite pushed history.) ### 3.4 Pull request **Base branch** picks itself from the strategy: | Strategy | Default PR base | | ----------------------------------------- | ------------------------------------------------------------------------------------------------ | | `solo-main`, `github-flow`, `trunk-based` | `main` | | `main-integration`, `gitlab-flow` | integration branch (e.g. `staging`) | | `enterprise` | integration branch; `hotfix/*` → `main` | | `gitflow` | `feature/*` → `develop`; `hotfix/*` → `main`; `release/*` → `main` (and back-merge to `develop`) | | `sdet` | `test/{KEY}-*` ticket branches + Plus Branches → the integration trunk `test/<module>-suite`; the single final suite PR → `main` (after the sync gate). See `references/sdet-integration-trunk.md`. | The user can override with `--base X` in arguments. If overridden, surface it in the confirmation: _"PR will target `{base}` instead of the strategy default `{default}`."_ **Title format**: `{type}({ISSUE-KEY}): {description}` — under 70 chars. Without a key: `{type}: {description}`. **Body** — render inline (no template file to read) using the structure in `references/pr-templating.md`. Substitute placeholders the skill can fill (`<<ISSUE_KEY>>`, `<<SUMMARY>>`, `<<CHANGES>>`, `<<TEST_PLAN>>`, `<<RISK>>`). Leave any unfilled placeholder visible so the author can edit it before posting — do not silently drop sections. For `test/*` branches in this repo, the PR body should use the structure in `references/pr-test-automation.md` (project-local template tuned for KATA test-automation PRs). For non-`test/*` branches use the generic structure in `references/pr-templating.md`. Write the rendered body to a tempfile (e.g. `$(mktemp)`) and pass it via `gh pr create --body-file` to avoid escaping issues. **Reviewers, labels, draft** — see `references/pr-templating.md`. Never hardcode labels the repo may not have configured; verify with `gh label list` if uncertain. **Final command shape**: ```bash gh pr create \ --title "{title}" \ --body-file {tmpfile} \ --base {base} \ [--reviewer {users}] \ [--label {labels}] \ [--draft] ``` **Stop at PR creation.** Merging is the user's explicit next step. Never auto-merge. Surface: _"Review the PR. Once approved, merge via the GitHub UI or run `gh pr merge {number} --squash --delete-branch`."_ **One carve-out**: when a supervised worker's dispatch or brief NAMES the merge as a step, that instruction IS the explicit next step and the worker merges. The rule exists so a PR is never merged by an agent acting on its own judgement; a dispatch that says "open the PR and merge it" is not the agent's judgement, it is the owner's, delivered through the channel the fleet uses for every other instruction. Say in the report that the merge was pre-authorized and by which line of the brief. **Optional pre-PR adversarial gate** — when the diff exceeds the 400-line cognitive review budget OR touches shared scaffolding (KATA base classes, fixtures, OpenAPI schemas), surface `/judgment-day` as an optional pre-PR review: _"Diff is large / touches shared scaffolding. Want to run `/judgment-day` before opening the PR?"_. Two blind judges review the diff in parallel; only approves when both agree. See `.agents/skills/judgment-day/SKILL.md`. Never invoked automatically — user opts in. ### 3.5 Conflict resolution Conflicts are diagnosed before they are resolved. The user is rarely in a hurry; a wrong fix here costs hours. Run `git status`, `git diff --check`, and inspect `.git/MERGE_HEAD` / `REBASE_HEAD` to classify the situation, then follow the matching playbook in `references/conflict-resolution.md`: - Merge conflict (content) - Merge conflict (rename / delete) - Rebase conflict - Push rejected (diverged) - Detached HEAD - Stash apply conflict - Unrelated histories - Pre-commit hook rejected the commit For every type, the playbook follows the same shape: 1. Explain what happened (root cause, in the user's language). 2. Present options ranked by safety. **Never** pick destructive options (force push, hard reset, `--abort` of an unfinished merge with uncommitted work) silently. 3. Guide the resolution step by step. 4. Verify (`git status`, `git log --oneline -3`). 5. Teach prevention (one short note on how to avoid this next time). When in doubt, **abort safely** (`git merge --abort`, `git rebase --abort`, `git cherry-pick --abort`) rather than push forward. Aborting always wins over guessing. ### 3.6 Strategy Setup The first five operations *adapt to* a strategy that already exists. Strategy Setup is the operation that **establishes** one: it resolves (or asks) the strategy, captures the merge + hotfix + protection-policy decisions the other operations depend on, materializes the long-lived branches the strategy needs, and writes the `git_strategy:` block in `.agents/project.yaml`. It is the only operation that creates branches and writes the strategy definition. **When it runs** - **Explicit**: the user asks — "set up our git strategy", "bootstrap branching", "configura el flujo de git", "materialize the flow". - **Bootstrap offer** (see "Bootstrap trigger" below): a git intent arrives and EITHER `git_strategy.strategy` is null (or the block is absent) with a fresh-looking repo, OR `git_strategy.strategy` is non-null but `project.project_name` is null (inherited template — not onboarded). The skill OFFERS to run setup. It never auto-runs. **Six-step flow** (mechanics live in `references/strategy-setup.md` — do not inline them here): 1. **Read repo state** — Step 1 (already always runs). 2. **Resolve strategy** — reuse Step 2 detection. If still undetermined, ask the 7-option question (one slug out). 3. **Decision questionnaire** — run Q1/Q2/Q3/Q4 below, capturing merge methods + hotfix policy + protection policy. SKIP any question that does not apply to the resolved strategy, and SKIP any question whose decision field is already populated (idempotent re-run — see Step 2 extension). Q4 applies to ALL strategies. 4. **Materialize** — conditional on the resolved strategy: create an integration branch ONLY if the strategy needs one and it is missing; ff-sync the integration/production pair if one is a pure ancestor of the other (NEVER `--force`); set up local tracking. Full materialization table + sync mechanics in `references/strategy-setup.md`. 5. **Persist** — write the `git_strategy:` block in `.agents/project.yaml` (the structured source of truth) with the fields that apply to the resolved strategy, preserving the rest of the file. NEVER write a separate file. Do NOT render a prose runbook anywhere — the operational HOW lives in this skill's references (`branching-strategies.md` catalogue + `sdet-integration-trunk.md`), read on demand. Per-strategy field values in `references/branching-strategies.md` → "git_strategy field rules (per strategy)". 6. **Report** — branches created/synced, decisions captured, block location. **Decision questionnaire (defaults first; each gated on the resolved strategy)** | Q | Question | Applies to | Options (default first) | Drives | | -- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | Q1 | Promotion method, integration → production | strategies with an integration branch (`main-integration`, `gitlab-flow`, `enterprise`; `gitflow` = `develop → main`) | **Fast-forward only** / Merge commit (`--no-ff`) / Squash | release runbook + whether branches stay byte-identical | | Q2 | Merge method, work-branch → integration (or → trunk) | all multi-branch strategies | **Merge commit (`--no-ff`)** / Squash / Rebase + merge | how integration history accrues | | Q3 | Hotfix policy | strategies with a production branch distinct from where work lands | **Branch off production → PR to production → back-merge to integration same day** / Always via integration / No policy | hotfix runbook + invariant maintenance | | Q4 | Protected-branch bypass policy | **ALL strategies** | `direct_push_to_protected`: forbidden / confirm / allowed (default per strategy — solo-main=`allowed`, multi-branch=`forbidden`, sdet=`confirm`) · `admin_bypass`: may a repo admin bypass PR/protection for urgent changes? (default `false`) · `require_pr_reviews`: min approvals (default `null` / strategy-appropriate) | how strictly protected branches are guarded (Push op 3.3) → sets `git_strategy.policy.*` | Q1/Q2/Q3 defaults are what the `main-integration` worked example chose; they are DEFAULTS, not hardcoded. The user can override any of them. Single-branch strategies (`solo-main`, `github-flow`, `trunk-based`) answer NONE of Q1/Q2/Q3 — they have no integration branch and no distinct production branch. **Q4 applies to every strategy** (even single-branch ones — a solo `main` is still protected) and writes `git_strategy.policy.*`; per-strategy Q4 defaults live in `references/branching-strategies.md` → "git_strategy field rules (per strategy)". **The git_strategy block fields** (write only the ones that apply; leave `decisions.*` at `n/a` for any decision the strategy doesn't use): ```yaml git_strategy: strategy: VALUE # one of the eight slugs branches: integration: NAME # or null decisions: promote_method: ff-only|merge-commit|squash|n/a feature_merge: merge-commit|squash|rebase-merge|n/a hotfix_policy: branch-off-prod-backmerge|via-integration|none|n/a policy: # Q4 — applies to ALL strategies direct_push_to_protected: forbidden|confirm|allowed admin_bypass: true|false # team POLICY intent, not enforcement require_pr_reviews: null|0|N ``` **Non-negotiables** - **Never `--force`** (not `--force-with-lease` either) during a setup sync. Sync only on a true fast-forward; if the integration/production pair has diverged both ways → STOP and hand to conflict resolution (3.5). - **Confirm before any push to a protected branch.** A setup ff-sync push is still a push to a protected branch — ask first. - **Propose, don't auto-execute** branch creation. Show the plan (which branch, off what, why) and wait for OK before `git checkout -b` / `git branch`. - **No AI attribution** in any commit the setup makes (see this skill's "Critical rules" section and the project `AGENTS.md`). **Pointers (do not inline mechanics here)** - `references/strategy-setup.md` — full questionnaire detail (Q1-Q4), the per-strategy materialization table, sync mechanics, persist sequence, report format. - `references/branching-strategies.md` → "git_strategy field rules (per strategy)" — the per-strategy field values written into the `git_strategy:` block of `.agents/project.yaml` (strategy / branches / decisions / policy). --- ## Step 4 — Chained / stacked PRs (when a change outgrows the budget) When a planned change estimates `> 400 changed lines` (additions + deletions), the work should be split. The 400-line cognitive review budget is borrowed from industry research (SmartBear, Cisco code-review studies); above it, defect detection drops sharply. There are three options: 1. **`stacked-to-main`** — 2 to 4 small PRs, each branched off the strategy's default base. PRs depend on previous merges. The base always works between merges. Best for linearly decomposable work. 2. **`feature-branch-chain`** — one long-lived integration branch; child PRs merge into it; one final PR merges it to the strategy's default base. Best for changes with shared scaffolding (new types, new schemas) that would break partial merges. 3. **`size-exception`** — for mechanical diffs (mass renames, formatter sweeps, generated code, vendor updates). Requires explicit user override and a `Why size-exception:` line in the PR body. Walk the chained-PR decision tree inline (see `references/branching-strategies.md` § Chained-PR decision tree). The decision picks one of: `single-pr`, `stacked-to-main`, `feature-branch-chain`, `size-exception`. Once decided, execute the resulting branch plan from this skill. The branch plan that comes out of the decision is the **contract** for execution. If the implementation diverges (the actual diff is larger than the estimate), re-invoke the decision — do not silently up-budget the existing strategy. --- ## Variables consumed - `{{PROJECT_KEY}}` — issue prefix for branch naming (e.g. `UPEX-123`). Resolves from `.agents/project.yaml`. - `{{ATLASSIAN_URL}}` — base URL for the Traceability section in PR bodies. Resolves from `.agents/project.yaml:atlassian_url`. - Any project missing `.agents/project.yaml` will lack these. Fall back to a generic `{prefix}/{slug}` and surface a one-line warning: clone the full boilerplate (the foundation files ship with the repo). --- ## Hand-offs to other skills | Situation | Hand off to | | ------------------------------------------------------ | ---------------------------------------------------- | | Strategic split of a large change | Step 4 (inline decision tree in this skill) | | Pre-sprint AC refinement on backlog Stories | `/shift-left-testing` | | In-sprint manual QA per ticket | `/sprint-testing` | | Test case authoring + ROI in TMS | `/test-documentation` | | KATA-compliant automated test authoring | `/test-automation` | | Regression suite execution + GO/NO-GO | `/regression-testing` | | Atlassian (Jira) operations triggered by a commit / PR | `/acli` | | First-time orientation | `/agentic-qa-onboard` | --- ## Critical rules — apply every invocation 1. **Diagnose before acting.** Step 1 always runs. Never assume repo state. 1b. **`policy:` records INTENT, not enforcement.** Reconcile it by RUNNING `bun run git:policy verify` (Step 1b) at the first push / PR / merge intent, then `--stamp` when clean. Never perform the protection queries by hand, and never state what the remote requires from a `declared` reading. `git:policy apply` is a dry run until `--yes`, and refuses to remove a guard, lower the approval bar, turn off code-owner review, or widen the merge methods unless `--allow-loosening` is passed for that specific give-up. 1c. **`strategy: solo-main` is the shipped DEFAULT, not evidence of a decision.** `meta.strategy_source` tells them apart: `inherited` means nobody chose. On a repo whose `project.project_name` is set and whose `strategy_source` is still `inherited`, OFFER Strategy Setup and say what the default costs (no integration branch, no promotion path, no review gate). Strategy Setup stamps `chosen`; nothing else may. 2. **One commit = one responsibility.** Never bundle unrelated changes. 3. **No AI attribution** in commits or PR bodies. Commits look human-authored. (Critical Reminder #3 in `AGENTS.md`.) The two forensic trailers of 3.2 (`Worktree:` / `Session:`) are the one thing that always closes a commit message — they name a working tree and a session, never a tool, so they are not attribution and not optional. 4. **Confirm before pushing to any protected branch.** Strategy-driven; see Step 3.3. (Critical Reminder #5 in `AGENTS.md`.) 5. **Never force-push, never rewrite pushed history, never `--no-verify`** unless the user explicitly authorises it AND the branch is unshared. (Critical Reminder #6 in `AGENTS.md`.) 6. **No `git add -A` / `git add .`** — always list explicit paths. 7. **Show proposed commits / branches / PR body and wait for OK** before executing. The user can accept, modify, or reject any item. 8. **`gh` CLI is the PR transport.** If `gh` is missing or unauthenticated (`gh auth status` fails), stop and surface the blocker. Do not pretend a PR was opened. 9. **PRs stop at creation.** Merging is the user's explicit next step — with the one carve-out in 3.4: a supervised worker whose dispatch or brief NAMES the merge is executing the owner's instruction, not its own judgement, and merges. 10. **Strategy is sticky.** Once resolved, persist in the `git_strategy:` block of `.agents/project.yaml`. The next invocation re-reads the block rather than asking again. 11. **Language**: artifacts (commits, branches, PR bodies, AGENTS.md sections) in English. Mirror the user's language only in conversation. 12. **No global discards.** Never `git restore .`, `git checkout -- .`, `git reset --hard`, untargeted `git stash`, or `git clean -f` — concurrent agent sessions may share this working tree without worktrees. Discard only explicit paths this session modified; if file ownership is unclear, stop and ask the user. (Critical Rule #15 in `AGENTS.md`; see also `references/worktrees.md` for true isolation.) --- ## Anti-patterns — NEVER do these - **G1.** NEVER force-push to `main` or any shared branch — destroys teammates' history and is unrecoverable once others have pulled. - **G2.** NEVER amend or rebase a pushed commit — creates orphan commits in others' clones and rewrites history that was already replicated. - **G3.** NEVER commit secrets, credentials, `.env` contents, or auth tokens — git history is forever; a single commit leaks the secret permanently. - **G4.** NEVER include "Generated with Claude Code", "Co-Authored-By: Claude", or any AI-attribution line in commit messages or PR bodies (Critical Rule #3). Commits look human-authored. - **G5.** NEVER push to `main` without explicit user confirmation (Critical Rule #5). Strategy-driven protection applies to every protected branch, not just `main`. - **G6.** NEVER bypass pre-commit / pre-push hooks with `--no-verify` to "ship faster" — hooks exist to catch the bug you didn't notice. Fix the hook failure and create a new commit. - **G7.** NEVER mix concerns in a single commit (feat + refactor + lint fix bundled together) — atomic commits enable surgical revert and clean blame. - **G8.** NEVER stack PRs without naming the dependency chain in the PR body — reviewers can't tell which PR to read first or what each one depends on. - **G9.** NEVER discard working-tree changes globally (`git restore .`, `git checkout -- .`, `git reset --hard`, untargeted `git stash`, `git clean -f`) — when multiple agent sessions share one working tree without worktrees, a global discard destroys another session's uncommitted work with no recovery. Target only the explicit paths this session modified; unclear ownership → stop and ask the user (Critical Rule #15). --- ## Isolated worktrees (parallel / risky work) When work needs to be isolated from in-progress changes on the current branch — a second AI session running in parallel, a hotfix while a feature is open, or unrelated WIP you do not want to mix — use a **git worktree** (a second working directory on its own branch, sharing one `.git`). Three paths: - **Manual git** (portable, any tool): `git worktree add ../dir -b feat/x main` → work → `git worktree remove` / `prune`. - **Claude Code harness** (this agent only): `EnterWorktree` moves the session into a fresh worktree under `.claude/worktrees/`; `ExitWorktree` (`keep`/`remove`) leaves it. Other coding agents lack this — they use the manual path. - **Orchestrated** (a coordinated fleet of worker sessions): the orchestration layer creates, provisions and removes one worktree per worker, outside the repo and visible to the owner. Git mechanics are identical; the lifecycle is owned by `orca-orchestration/SKILL.md`. Key gotchas: a fresh worktree contains only the **tracked** files of its base — **untracked WIP does not teleport** (`mv` it in, or commit first) and every **gitignored** file is missing too (`bun run worktree:provision`). Keep the primary tree's `git status` clean, and run the **orphan audit** before removing a worktree — gitignored evidence dies with it. Full lifecycle, multi-session safety rules, and the decision guide: `references/worktrees.md`. --- ## Pre-flight checklist (run before exiting any operation) - [ ] Step 1 ran and the repo state was reported. - [ ] Strategy resolved (detected from the `git_strategy:` block in `.agents/project.yaml`, inferred from layout, or asked) and persisted to that block if newly chosen. - [ ] Branch / commit / push / PR / conflict operation followed the runbook for that strategy. - [ ] Each commit is atomic, conventional, and free of AI attribution. - [ ] Each commit message ends with the two forensic trailers (`Worktree:` then `Session:`), values taken from the `AGENT IDENTITY:` context line or written as `unknown`. - [ ] No `git add -A` / `--force` / `--no-verify` used unless explicitly authorised. - [ ] No global discard ran (`git restore .` / `git checkout -- .` / `git reset --hard` / untargeted `git stash` / `git clean`); any discard targeted explicit session-owned paths only. - [ ] PR (if created) has Title <70 chars, body with Summary / Changes / Test Plan / Traceability / Risk, base branch matches strategy. - [ ] PR URL returned to the user; no merge attempted. - [ ] Conflicts (if any) are fully resolved AND verified (`git status` clean, `git log` sensible). - [ ] If Strategy Setup ran: branches were proposed (not auto-created), ff-syncs used a true fast-forward only (no `--force`), and a diverged pair was handed to conflict resolution rather than forced. - [ ] If Strategy Setup ran: the `git_strategy:` block in `.agents/project.yaml` was written in place with the fields that apply to the resolved strategy (strategy / branches / decisions / policy / protected / branch_prefixes / description / meta), preserving the rest of the file. --- ## Reference files | File | When to read | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `references/branching-strategies.md` | Full catalogue of the 8 strategies + detection signals + trade-offs + chained-PR decision tree + per-strategy `git_strategy` field rules (the block in `.agents/project.yaml`). Read when resolving strategy or planning a chain. | | `references/strategy-setup.md` | Strategy Setup (3.6) mechanics: decision questionnaire detail, per-strategy materialization table, ff-sync mechanics (never force), persist sequence, report format. Read when running or re-running Strategy Setup. | | `references/sdet-integration-trunk.md` | `sdet` strategy runbook: per-ticket loop, ephemeral integration trunk, local double-env gate, Sanity CI + CI-fallback clause, Plus Branches, sync gate, final PR, TC lifecycle. Read when running an `sdet` test-automation suite. | | `references/conventional-commits.md` | Full type vocabulary, scope rules, breaking-change syntax, mixed-changes precedence. Read when proposing commits. | | `references/pr-templating.md` | PR body template, placeholder rules, label / reviewer / draft conventions, multi-strategy base-branch table. Read when opening a PR. | | `references/conflict-resolution.md` | Per-conflict-type playbooks (merge / rebase / push-rejected / detached-HEAD / stash / unrelated histories / hook rejection). Read when Step 3.5 fires. | | `references/worktrees.md` | Git worktrees for isolated/parallel work — manual git, Claude Code `EnterWorktree`/`ExitWorktree`, orchestrated worktrees, the untracked-files gotcha, gitignored-file provisioning, multi-session safety, the orphan audit before removal, cleanup, decision guide. Read when isolating work or running parallel sessions. | Read references on demand — do not load them all upfront. Each file is self-contained.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.