# Align

> Scan the user's codebase, ask up to five clarifying questions, and write a fenced Alignment block into AGENTS.md that the Hydrant lifecycle skills (`/go`, `/prep`, `/preflight`, `/yeet`) read for stack-specific behavior. Use when the user asks for `/align`, says "align hydrant-skills", wants the lifecycle skills tuned to this codebase's conventions, or runs the install for the first time.

- Skill: `background-craft/align` (Agent Skill)
- Install (CLI): `npx skillmds@latest add background-craft/align`
- Raw SKILL.md: https://api.skillmd.com/api/skills/background-craft/align/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Background-Craft (https://skillmd.com/u/background-craft)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/background-craft/align

---


# Align

`/align` is a one-time (re-runnable) tune. It records the conventions of *this* codebase — branch strategy, single-file test command, PR flow, merge policy, branch naming, package manager, lint/typecheck commands — into a fenced Alignment block in `AGENTS.md`. Lifecycle skills read that block to adapt their behavior. Without it they fall back to safe defaults.

## What this skill produces

A single fenced block in `AGENTS.md`:

```markdown
<!-- BEGIN: hydrant alignment -->
## Hydrant alignment

<!-- Generated by /align. Re-run /align to update. Manual edits inside this fence are preserved on re-run only when /align prompts before overwriting. -->

- package-manager: pnpm
- single-file-test-cmd: pnpm test {file}
- lint-cmd: pnpm lint
- typecheck-cmd: pnpm tsc --noEmit
- branch-strategy: feature-branches
- branch-naming: hyd-{number}-{slug}
- pr-flow: same-repo
- merge-policy: review-required
<!-- END: hydrant alignment -->
```

Stable keys (do not invent new ones without coordinating with the lifecycle skills):

- `package-manager` — `npm` | `pnpm` | `yarn` | `bun` | `pip` | `uv` | `poetry` | `cargo` | `go` | `none`
- `single-file-test-cmd` — runner-specific command with the literal token `{file}` where the path is substituted (e.g. `pnpm test {file}`, `pytest {file}`, `cargo test --test {file}`)
- `lint-cmd` — full command, no substitution (e.g. `pnpm lint`, `ruff check`)
- `typecheck-cmd` — full command, no substitution (e.g. `pnpm tsc --noEmit`, `mypy .`); use `none` if the language isn't statically typed
- `branch-strategy` — `trunk-based` | `feature-branches` | `gitflow`
- `branch-naming` — pattern with `{number}` and `{slug}` placeholders (e.g. `hyd-{number}-{slug}`, `feat/{slug}`); use `none` if there's no convention
- `pr-flow` — `same-repo` | `fork-and-pr`
- `merge-policy` — `review-required` | `self-merge`

Lifecycle skills look up by key. If a key is missing, the consumer falls back to its own default and prints a one-line nudge to re-run `/align`.

## Workflow

Run these phases in order. Skip later phases only when justified by what an earlier phase found.

### Phase 1: Detect

Walk the repo root with whatever filesystem tools the harness exposes. Do not run network calls. Do not modify any file in this phase.

Read each of these if it exists, in this order. Stop reading a category as soon as you have a confident signal — don't keep digging.

1. **Stack and package manager**
   - `package.json` → Node project. Check `packageManager` field, `engines`, then look for `pnpm-lock.yaml` / `yarn.lock` / `bun.lockb` / `package-lock.json` to disambiguate.
   - `pyproject.toml` → Python. Check `[tool.poetry]` vs `[tool.uv]` vs `[tool.pdm]`; check for `uv.lock` / `poetry.lock`.
   - `Cargo.toml` → Rust + cargo.
   - `go.mod` → Go.
   - `Gemfile` → Ruby + bundler. `composer.json` → PHP. `pubspec.yaml` → Dart. Be willing to record `package-manager: none` if nothing matches.
2. **Test, lint, typecheck commands**
   - From `package.json` `scripts`: a `test` script implies the runner; look for `lint`, `format`, `typecheck`, `tsc` script aliases.
   - For Python: `pytest.ini` / `pyproject.toml [tool.pytest]` / a `tests/` directory → pytest. `mypy.ini` / `[tool.mypy]` → mypy. `ruff.toml` / `.ruff.toml` / `[tool.ruff]` → ruff.
   - For Rust/Go: `cargo test` / `go test ./...` are the defaults; do not ask.
   - For typed Node: presence of `tsconfig.json` → `tsc --noEmit` is the typecheck.
3. **CI configuration**
   - `.github/workflows/*.yml` for required-status-check signals (helps infer `merge-policy`).
   - `.github/PULL_REQUEST_TEMPLATE.md` and `CODEOWNERS` → presence of either is a strong signal for `merge-policy: review-required`.
4. **Branching**
   - `git branch -a` (or whatever the harness exposes for listing branches) — look at the last 10–20 branches. If most match a pattern like `hyd-123-foo` or `feat/some-thing`, propose that as `branch-naming`.
   - If only `main`/`master` and short-lived feature branches exist that get merged quickly → suggest `trunk-based`.
   - Long-running `develop`/`release/*` branches → suggest `gitflow`.
5. **Repo topology**
   - Existing remote(s) from the git config. If `origin` points at a personal fork while another remote points at the upstream, that's a strong signal for `pr-flow: fork-and-pr`.
6. **Polyglot monorepo check**
   - If you found *more than one* primary stack manifest at the repo root or in obvious workspace dirs (`packages/*/package.json`, `services/*/Cargo.toml`, etc.), do not silently pick one. Treat this as a polyglot case and follow the polyglot path in Phase 2.

Record what you found as a structured intermediate (in working memory; do not write to disk yet):

```
detected:
  package-manager: pnpm  (confident — pnpm-lock.yaml present)
  single-file-test-cmd: ?  (npm scripts have `test: vitest`, but single-file form unclear)
  lint-cmd: pnpm lint  (confident — scripts.lint present)
  typecheck-cmd: pnpm tsc --noEmit  (confident — tsconfig.json present)
  branch-strategy: feature-branches  (likely — many short-lived `feat/*` branches)
  branch-naming: feat/{slug}  (likely — 8/10 recent branches match)
  pr-flow: same-repo  (confident — single origin remote)
  merge-policy: review-required  (likely — CODEOWNERS present)
```

Distinguish *confident* (a manifest or lockfile says so) from *likely* (a heuristic). Only confident values bypass the interview.

### Phase 2: Interview

Ask **at most five** questions, only for fields that aren't `confident`. Skip fields that are confident. Skip the interview entirely if everything is confident.

Each question should:

1. State your *recommendation* up front — derived from the detection step.
2. Show a short rationale.
3. Offer the user a chance to override.

Use whatever your harness's structured-question affordance is. If it doesn't have one, ask in a numbered list and accept either short numeric answers or freeform overrides.

The five canonical questions, in priority order:

1. **Single-file test command** (skip if confident)
   - Recommendation: `<runner> {file}` filled in from the detected runner (`vitest {file}`, `pytest {file}`, `go test ./{file}`, etc.).
   - Why: lifecycle `/preflight` runs this on the changed file when it can.
2. **Branch strategy** (skip if confident)
   - Recommendation: based on how recent branches look.
   - Options: trunk-based, feature-branches, gitflow.
3. **Branch naming** (skip if confident)
   - Recommendation: most-common pattern observed, with `{number}` / `{slug}` placeholders.
   - Allow `none` if the user has no convention.
4. **PR flow** (skip if confident)
   - Recommendation: `same-repo` if there's one origin; `fork-and-pr` if you saw an upstream remote distinct from origin.
5. **Merge policy** (skip if confident)
   - Recommendation: `review-required` if CODEOWNERS or required checks exist; `self-merge` otherwise.

After answers come back, also resolve `lint-cmd`, `typecheck-cmd`, and `package-manager` from detection. If any of those are still unknown after detection, fall back to recording `none` rather than asking — they aren't worth a question slot.

### Phase 2-bis: Polyglot monorepo path (only if Phase 1 flagged it)

You have two options. Pick one and confirm with a single yes/no question. Do not write per-workspace blocks unless the user opts into them.

- **Option A — primary workspace**: tell the user "I see Node + Python here. I'll record alignment for the Node workspace and skip Python — that's the simplest path. Want me to do that?" If yes, return to the normal Phase 2 with the chosen stack.
- **Option B — per-workspace blocks**: write *multiple* fenced blocks in `AGENTS.md`, each labeled with a relative path:

  ```markdown
  <!-- BEGIN: hydrant alignment workspace=apps/web -->
  ...
  <!-- END: hydrant alignment workspace=apps/web -->

  <!-- BEGIN: hydrant alignment workspace=services/ingest -->
  ...
  <!-- END: hydrant alignment workspace=services/ingest -->
  ```

  Each block uses the same stable keys. Lifecycle skills resolve which block applies by walking up from the file under edit until they find a block whose `workspace=` prefix matches.

When in doubt, pick Option A. Per-workspace blocks add real complexity for the consumer; only use them when the user explicitly wants them.

### Phase 3: Write

Now write to `AGENTS.md` at the repo root.

1. **If `AGENTS.md` doesn't exist**: create it with a top-level `# Agents` heading and the Hydrant alignment block as the first section. Do not pull in any other content.
2. **If `AGENTS.md` exists and contains a `<!-- BEGIN: hydrant alignment -->` fence**: replace *only* the fenced region (inclusive of both sentinels). Do not touch any line outside it. Do not reorder or reflow surrounding content.
3. **If `AGENTS.md` exists but has no fence**: append the fenced block at the end of the file, separated from prior content by a single blank line.
4. **If the existing fence is a polyglot multi-block setup**: only update the block whose `workspace=` matches the workspace you ran against. Leave the others untouched.

**Manual-edit safety**. If you detect that the existing fence has a comment line like `# manually edited` *or* the body is no longer key-value formatted (free-form text), pause and ask the user before overwriting:

> "The existing alignment block has been hand-edited. Overwriting would lose those edits. Do you want to overwrite, merge (keep edited values where present), or abort?"

Only proceed once the user picks one.

The block format inside the fences:

```markdown
## Hydrant alignment

<!-- Generated by /align. Re-run /align to update. -->

- package-manager: <value>
- single-file-test-cmd: <value with {file} placeholder>
- lint-cmd: <value>
- typecheck-cmd: <value>
- branch-strategy: <value>
- branch-naming: <value with {number}/{slug} placeholders>
- pr-flow: <value>
- merge-policy: <value>
```

Always emit the keys in this order. Always include all eight keys, even if a value is `none` — consumers parse by key, missing keys force them to fall back blindly.

### Phase 4: Confirm

Print a short summary back to the user:

- Where the block was written (`AGENTS.md`, plus workspace label for polyglot).
- The eight key/value pairs.
- A one-line "next steps" pointer: `Lifecycle skills (/go, /prep, /preflight, /yeet) will pick these up automatically. Re-run /align any time the conventions change.`

Do not run any test, lint, or typecheck command as part of `/align`. Writing the block is the only side effect this skill is allowed to have.

## Edge cases

- **No package manifest at all (raw shell scripts repo)**. Phase 1 records `package-manager: none`. Phase 2 still runs but recommends `none` for `single-file-test-cmd`, `lint-cmd`, and `typecheck-cmd`; user can override.
- **Empty repo (no commits, no files beyond `.git`)**. Detection finds nothing. Tell the user "this repo doesn't have enough signal yet — come back after you've added a manifest or a couple of files," and exit without writing.
- **AGENTS.md is huge or owned by a different convention**. Don't reformat. Only the fenced region belongs to `/align`. If there's no fence, append; never insert mid-document.
- **User runs `/align` from a subdirectory of a monorepo**. Resolve the repo root (walk up to the nearest `.git`); always write to `AGENTS.md` at the repo root, not the subdir.
- **`AGENTS.md` is a symlink or read-only**. Stop, tell the user, do not silently fail.

## What this skill never does

- Modify any file other than `AGENTS.md`.
- Run tests, lint, build, or typecheck as part of `/align`.
- Make network calls.
- Call any `mcp__hydrant__*` tool — `/align` is purely a local-codebase scan.
- Overwrite a hand-edited fence without prompting.
- Lock the user in. The block is plain markdown; the user can edit any value any time.

## Why this matters

The lifecycle skills (`/go`, `/prep`, `/preflight`, `/yeet`) are stack-agnostic by design. Without an Alignment block they ask the user the same questions every invocation or fall back to the safest-but-clunkiest defaults. `/align` records those answers once so they don't have to be asked again.

Drift is the user's friend. The block is markdown — they edit it, we read it, no schema enforcement beyond the consumer's tolerance for missing keys.

