Skip to content
Back to skills

Phase Contract

ASecurity

Internal contract: the single owner of the eight phase-lint rules, the two accepted phase grammars, the fixed PASS/BLOCKED result, and the normalized phase fingerprint. Consumed by `execute-phase` and the lane's `plan` step. Not a menu entry.

  • 21 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 2, 2026
ai-agentsgorailsgitapi

Works with

  • api

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add gtrabanco/agentic-workflow --skill phase-contract --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Phase Contract?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Phase Contract
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gtrabanco-phase-contract-agentic-workflow/badge)](https://www.skillsdirectory.com/skills/gtrabanco-phase-contract-agentic-workflow)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: phase-contract
user-invocable: false
version: 1.1.0
author: "Gabriel Trabanco <1969593+gtrabanco@users.noreply.github.com>"
license: MIT
description: >
  Internal contract: the single owner of the eight phase-lint rules, the two
  accepted phase grammars, the fixed PASS/BLOCKED result, and the normalized
  phase fingerprint. Consumed by `execute-phase` and the lane's `plan` step.
  Not a menu entry.
---

# Phase Contract (internal)

The one authoritative owner of phase shape for this workflow. Planners lint
every phase they emit against the eight rules below; `execute-phase` re-checks
the same rules before any edit. Nothing else may define what a valid phase is —
including which grammar a phase is written in.

## When to use

- The lane's `plan` step — lint every phase before emission.
- `execute-phase` — phase-lint pre-flight guard before editing a phase.
- Templates store the contract version + fingerprint + lint result, never the
  eight rules (they point here).

## The eight phase-lint rules

Every implementation phase must pass all 8 boxes before it is emitted (planner
skills) or executed (`execute-phase` pre-flight). Fail-closed: any unticked box
blocks emission/execution until the phase is re-cut or split.

1. **Title names ONE deliverable** — FAIL if it joins nouns with `+`, `,`, `&`,
   `and`/`y`, or `/`. Sole authorized exception: the templates' literal closing
   title `Hardening & PR` (`docs/features/_TEMPLATE/SPEC.md`,
   `docs/fix/_TEMPLATE/SPEC.md`) is kept verbatim — its `&` is a *normalization
   separator*, not a deliverable joiner, and its title-deliverable normalizes to
   `hardening-pr`. Any other `&`-joined title still FAILs.
2. **One declared layer** — each phase declares exactly one of the fixed enum
   `schema/db | domain | api | ui | config/infra | docs | hardening | close-out`;
   FAIL if any task's target file belongs to another. Tests for the phase's own
   layer belong to the phase; a test-only phase declares `hardening`.
3. **≥ 1 task, ≤ 8 tasks** — every phase carries at least one checkbox task; a phase with zero tasks is BLOCKED (fail-closed). Final hardening/close-out phase: 1–10, only the literal close-out chain.
4. **One checkbox = one deliverable** — FAIL if a task contains a `→` chain of
   implementation steps, enumerates > 3 cases/scenarios, or creates > 1 file of
   distinct concerns.
5. **Zero decision words** — FAIL on `Decide`, `choose`, `OR` between
   alternatives, `If … then <change scope>`.
6. **No conditional scope mutation** — a task may not move work between phases
   at runtime.
7. **No external/manual gates inside implementation phases** —
   human/out-of-repo verifications live in the hardening/close-out phase,
   marked `manual`.
8. **Machine-checkable done-when** — every phase ends with one verifiable
   invariant (a command + expected outcome).

**The executed-phase exemption (boxes 3 and 7 only).** A fully-ticked phase is historical:
those two boxes are positional — box 3's budget and box 7's `gh pr`
placement both key off the plan's last phase — and a replan appends its work
*after* an executed hardening (`replan-findings`' `PHASE_APPEND.md`), which would
otherwise retro-block work that already passed this gate when it was emitted.
The exemption is scoped to those two boxes: boxes 1, 2, 4, 5, 6 and 8 stay armed for an
executed phase, and for an unemitted phase every box stays armed, so pre-ticking
a phase to dodge a check is a defect, never a pass.

## The two grammars (fix #272)

One contract, two shapes. `scripts/phase-lint.mjs` mechanizes exactly this
split and never carries a second copy of the semantics:

| grammar | where | shape |
|---|---|---|
| **plan** | a plan file, or any `##/###/#### P<n> — <Title>` heading | heading + `Layer: <enum>` + `- [ ]` tasks + `Done-when:` |
| **unit doc** | the unit doc's `## Tasks` section | one `P<n> — <task> (validator: <command> <outcome>)` bullet per phase, wrapped lines joining the bullet, plus an optional nested `- Relevant files: <path, path…>` line |

Precedence is fail-closed and one-way: **if a document carries any plan-style
phase heading, the plan grammar governs it**; only when it carries none does
the unit-doc grammar apply; a document with no phase in either answers
`BLOCKED: no-phases`. A `P<n>` bullet outside `## Tasks` (a progress-log line,
for instance) is prose, never a phase.

### Mapping the eight boxes to a bullet phase

A bullet has no separate title line and declares no layer, so two boxes have no
input and are **skipped** (not "passed"); the rest run unchanged over the
bullet's joined text:

| box | unit-doc status |
|---|---|
| 1 title names one deliverable | **n/a** — there is no title line; the bullet is the task, and box 4 carries atomicity |
| 2 one declared layer | **n/a** — a unit doc declares no layer (layers belong to plans) |
| 3 ≥ 1 task, ≤ 8 tasks | applies — a bullet is exactly one task; a repeated `P<n>` number FAILs as `duplicate unit-doc phase P<n>` |
| 4 one deliverable | applies — same arrow-chain, >3-case and created-file checks |
| 5 zero decision words | applies, unchanged |
| 6 no conditional scope mutation | applies, unchanged |
| 7 no external/manual gates | applies with the **final-bullet exemption**: the last bullet is the unit doc's verification/close-out line, so `manual` / `ask the user` / `gh pr` are allowed there only |
| 8 machine-checkable done-when | applies — the bullet's `(validator: …)` span **is** the done-when: it must carry a backticked command *and* an expected outcome; a bullet without the span FAILs as `bullet carries no \`(validator: …)\`` |

### The `Relevant files:` sub-section is metadata

A nested `- Relevant files: <path, path…>` line under a bullet declares that
phase's read set (the lane's plan step writes it from its own discovery). It is
**never** a task: it does not count toward box 3, never feeds a box-2 target and
is scanned by no rule. Every entry must be a path or a glob; an empty list, an
empty entry or a token that is neither fails the phase closed under its own
`relevant-files:` finding id — a malformed read set is reported, never dropped.
**Never-authority (same rule as every selector):** a file that is *not* listed
is never out of scope — an executor may always add one, logging it in the unit
doc's Progress log.

## Result — fixed PASS/BLOCKED output

```text
Phase-lint: PASS (8/8)
```

A unit-doc bullet phase reports the boxes it actually runs:

```text
P<n> Phase-lint: PASS (6/6) · fingerprint P<n>:unit-doc:<n-tasks>:<title-deliverable>
```

or, on the first failing box:

```text
Phase-lint: BLOCKED — box <n>: <one-line reason>
```

A malformed `Relevant files:` sub-section reports under its own id:

```text
P<n> relevant-files: <one-line reason>
```

A BLOCKED phase is re-cut or split; it is never emitted, committed, or executed
as-is (no `--force` bypass exists for a non-atomic phase — that decision belongs
to the user).

## Normalized phase fingerprint

Each phase's fingerprint is a deterministic string over the phase's shape:
`P<n>:<layer>:<n-tasks>:<title-deliverable>`. It binds the lint result to the
exact phase version a plan committed to — templates and plans record
`Phase-lint: PASS (8/8) · fingerprint <fingerprint>` so later re-lints and
`execute-phase` can confirm nothing in the phase shape drifted since planning.
A unit-doc bullet phase has no layer, so its slot is the literal `unit-doc`
(`P<n>:unit-doc:<n-tasks>:<title-deliverable>`, title = the bullet text up to
its first `:`, with the `(validator: …)` span removed); both grammars hash the
same way, so a re-lint of either is comparable with its own earlier run.

## Guardrails

- Sole owner of the eight rules — planners and executor point here, never carry
  their own copy.
- Never relax a rule to let a phase pass; re-cut the phase instead.
- Docs only — no code, no branch.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…