# `/ba-develop` — Commit checkpoints

> Loaded on demand when a phase passes and the orchestrator emits a commit.

## Commit command — via the SANCTIONED path (never raw `git commit`)

After every phase whose gate passes, commit through the **`gitflow commit` CLI**,
NOT a raw `git add -A && git commit`. This is mandatory, not stylistic:

> A raw `git commit` typed at the Bash shell is **blocked by the `git-guard`
> PreToolUse hook** (it requires the user's manual approval — the anti-"commit
> n'importe comment" safety net). The `gitflow commit` CLI runs `git` via
> `execFile` **inside its tsx process**, so a PreToolUse-Bash hook never sees it
> → it passes BY DESIGN (exactly how `scaffold-migration` / `/efcore` bypass
> `ef-guard`). So the orchestrator checkpoints autonomously without anyone
> flipping `GIT_GUARD_OFF`, and the guard still blocks ad-hoc raw commits.

Run from anywhere; pass `projectPath` as `--workdir`:

```bash
npx --prefer-offline tsx skills/gitflow/cli/commit/index.ts \
  --spec '{"message":"<per-phase message>","noEfcore":true}' \
  --workdir "<projectPath>" --json
```

- `noEfcore:true` — `/ba-develop` does its OWN destructive-migration review and
  records the `migration.destructive` blocker (below); don't double-gate it via
  the CLI's EF policy.
- The CLI **refuses `main`/`master`** and **never pushes** (no `"push":true` in
  the spec — pushing is ALWAYS a manual user decision). The feature branch stays
  local until the user runs `/gitflow pr` or `/gitflow sync`.
- If a phase produced an EF migration, review its `Up()` for destructive ops
  (DROP COLUMN/TABLE) **BEFORE any apply** — the apply policy has no destructive
  axis, so this scan is the only destructive gate:
  - **Destructive** → commit it anyway (the CLI will, since `noEfcore`), record a
    `migration.destructive` blocker (critical), **never auto-apply** it.
  - **Additive** → commit it AND apply it through the sanctioned CLI:
    `npx --prefer-offline tsx skills/efcore/cli/apply/index.ts --spec '{"cwd":"<projectPath>"}'`
    (spec **cwd-only** — never `connectionString`). Parse the JSON envelope, not
    the exit code: `success:true` → applied (record in the phase summary);
    `blocked:true` (remote/unknown DB) → `migration.not-applied` blocker (high)
    with the CLI's `reason`, continue — the app may still auto-migrate at boot.
- Conventional-commits scope = the module code (e.g. `hr.leave`).
- Never push from this orchestrator; never raw `git commit`.

## When NOT to commit

- Gate failed AND auto-healing exhausted — the item is **deferred** (blocker +
  best-effort). Commit whatever DID pass in the phase; the deferred item is
  recorded in `blockers[]`, never committed as if it were done.
- `nothing to commit` — re-running a phase that produced no diff. Log
  `nothing to commit`, continue.

**Exception — Phase 3 sub-phase 3.5 (audit-apply) MUST always run** even when
3.0-3c reported no file changes. Drift can exist in files no generator touched
this run. Skipping audit-apply is the silent re-run failure mode users report
as "I re-ran and nothing was fixed".

## Per-phase message templates

| After phase | Message |
|-------------|---------|
| Core | `feat({appCode}): add core foundation seed (nav + roles + permissions)` |
| Entities | `feat({moduleCode}): add domain entities + migrations` |
| API (2a) | `feat({moduleCode}): add api controllers + business handlers` |
| API (2b) | `feat({moduleCode}): add screen-driven controllers + screen DTOs` |
| Frontend | `feat({moduleCode}): add frontend pages + routes` |
| Acceptance | `feat({moduleCode}): add acceptance tests from AC (N facts, 0 TODO)` |

## Auto-healed commit body

When the auto-healing loop ran one or more retries before the gate passed,
append a body line summarising what was fixed:

```
feat(hr.leave): add api controllers + business handlers

Auto-healed in 3 iterations:
  attempt 1 → business.todo-br (BR-005 implemented from règles-métier.md L42)
  attempt 2 → audit-dev-api.applicable (DEV-API-008 auto-applied, 4 findings)
  attempt 3 → gate passed
```

The body lines come straight from the run's `heal.log.json` (path defined ONCE
in `auto-healing.md` § Heal log) — no hand-editing.

## Failure handling

Parse the CLI's `--json` envelope (`{ success, error?, branch, commit?, filesChanged }`):

- **`success: true`** → record `commit.hash` (short) in the phase summary, continue.
- **`error: "Cannot commit directly to main branch"`** → **skip commits**, record a
  `git.cannot-commit` blocker (medium, `userAction: run /gitflow start
  feature/{moduleCode} then commit`), and keep generating — the code still lands
  on disk. Do NOT halt.
- **`error: "No changes to commit"`** → no-op (re-run with no diff), log, continue.
- **any other `error`** (not a git repo, git failure, …) → record a
  `git.cannot-commit` blocker with the message, continue. A commit failure never
  aborts the run.
- **destructive EF migration detected** in the phase review (orchestrator-side,
  independent of the commit CLI) → still commit, record a `migration.destructive`
  blocker (critical), never auto-apply. Continue.
- **`excludedFiles` non-empty** in the CLI envelope (incomplete migration changeset
  committed WITHOUT its migration files) → re-run `scaffold-migration` to produce a
  complete changeset, or record a `migration.incomplete-changeset` blocker (high).
  Never leave it as a silent warning. Continue.

> Exit codes mirror this: `0` = committed, `1` = a handled non-fatal case above
> (main branch / nothing to commit / other), `2` = bad `--spec` (fix the JSON).
