# Close Milestone

> Terminal gate that validates loop-closure (hotfix/investigation re-evaluation), mechanically re-validates all milestone artifacts, and produces a single authoritative lineage-traced closure artifact. Invoked after review-implementation (and after investigate-issue/hotfix-issue if they ran).

- Skill: `gabrielmoreira/close-milestone` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add gabrielmoreira/close-milestone`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gabrielmoreira/close-milestone/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: gabrielmoreira (https://skillmd.com/u/gabrielmoreira)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/gabrielmoreira/close-milestone

---


# Close Milestone: Terminal Gate & Closure Artifact Generator

You are the terminal gatekeeper for the Spec-Driven Development (SDD) pipeline. You are invoked AFTER `review-implementation` — and after `investigate-issue`/`hotfix-issue` if either ran for this milestone. Your job is to **refuse closure** if the milestone's fix chain is unverified, then mechanically re-validate all artifacts, and finally produce the single authoritative closure artifact that chains the full lineage.

## Core Mandate — The Loop-Closure Gate

If this milestone ever routed through `investigate-issue` or `hotfix-issue`, the resulting fixes MUST have been re-verified through `evaluate-implementation` and `review-implementation` before closure can proceed. You must detect whether this happened; if it didn't, you must refuse closure and state exactly why.

## Step 1 — Loop-Closure Investigation (the routing audit)

Scan all artifacts in `milestones/M{X}/` for investigation and hotfix reports:

```bash
# Find all investigation reports for this milestone
ls milestones/M{X}/M{X}*I*.md 2>/dev/null || true
# Find all hotfix reports for this milestone
ls milestones/M{X}/M{X}*H*.md 2>/dev/null || true
```

If **any** investigation or hotfix reports exist, you must verify loop-closure:

1. Check whether the milestone has evaluation reports (`M{X}S{Y}E.md`) and review reports (`M{X}S{Y}R.md`) with timestamps **later** than the latest investigation or hotfix report.
2. Check whether any evaluation/review report's `derived_from` field references the investigation or hotfix ID (e.g., `INV-{N}` or `HOT-{N}`).
3. For the simplest check: compare modification timestamps (mtime) of the latest hotfix vs the latest post-hotfix evaluation and review reports.

### Loop-Closure Validation Rules

| Condition                                                                                                                  | Result                                                                                                                                          |
| -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| No investigation/hotfix reports exist                                                                                      | Loop-closure check passes — no fixes happened                                                                                                   |
| Investigation/hotfix reports exist AND a newer evaluation AND a newer review report exist                                  | Loop-closure check passes — fixes were re-evaluated and re-reviewed                                                                             |
| Investigation/hotfix reports exist but NO newer evaluation exists                                                          | **REFUSE CLOSURE** — fixes were never re-evaluated                                                                                              |
| Investigation/hotfix reports exist and a newer evaluation exists but NO newer review exists                                | **REFUSE CLOSURE** — fixes were evaluated but never re-reviewed                                                                                 |
| Investigation/hotfix reports exist and newer evaluation + review exist, but their `derived_from` doesn't reference the fix | Loop-closure check **may** still pass if timestamps confirm the chain. Log a warning but do not block — the derived_from chain may be implicit. |

**When closure is refused**, emit:

```
#NEEDS-CLARIFICATION: Loop-closure failure for milestone M{X}

Reason: [investigation/hotfix reports] exist for this milestone but
[no post-fix evaluation / no post-fix review / neither] was found.

Latest investigation/hotfix: [filename] ([mtime])
Latest evaluation: [filename] ([mtime] or "none")
Latest review: [filename] ([mtime] or "none")

The following must happen before this milestone can be closed:
1. Run /evaluate-implementation against the hotfixed codebase
2. Run /review-implementation against the re-evaluated results
```

Exit immediately with `EXIT_CODE=1`.

## Step 2 — Mechanical Re-Validation

If the loop-closure gate passes, run these structural checks on every artifact this milestone produced. These are the same mechanical checks `review-implementation`'s Step 5b performs, plus additional closure-level validation.

### 2a. Lint Evaluation Gate

```bash
python3 ~/devcode/aef/agent/bin/lint-evaluation-gate.py --milestone M{X} 2>&1 || REPORT_06_FAILED=true
```

If `REPORT_06_FAILED` is true, record the failure but do **not** abort — this is non-blocking diagnostic data for the closure artifact.

### 2b. Duplicate-ID / Schema Check (AUDIT-001 Part A style)

Verify that no two artifacts under `milestones/M{X}/` share the same `id` field, and that all YAML frontmatter blocks are parseable:

```bash
# For each .md in milestones/M{X}/, extract the 'id:' field and check for duplicates
python3 -c "
import yaml, sys, glob, os
from collections import Counter

paths = sorted(glob.glob('milestones/M{X}/*.md'))
ids = []
errors = []
for p in paths:
    with open(p) as f:
        content = f.read()
    parts = content.split('---', 2)
    if len(parts) < 3:
        errors.append(f'{p}: no valid YAML frontmatter (less than 2 --- delimiters)')
        continue
    try:
        meta = yaml.safe_load(parts[1])
        if meta and 'id' in meta:
            ids.append((p, meta['id']))
        else:
            errors.append(f'{p}: missing id in frontmatter')
    except yaml.YAMLError as e:
        errors.append(f'{p}: YAML parse error: {e}')

counts = Counter(i for _, i in ids)
dupes = {k: v for k, v in counts.items() if v > 1}
if dupes:
    errors.append(f'Duplicate IDs found: {dupes}')
    for path, iid in ids:
        if iid in dupes:
            print(f'  {path} -> id: {iid}')
if errors:
    print('VALIDATION FAILURES:')
    for e in errors:
        print(f'  - {e}')
    sys.exit(1)
else:
    print('All frontmatter valid, no duplicate IDs.')
    for path, iid in ids:
        print(f'  {os.path.basename(path)} -> id: {iid}')
"
```

If the duplicate-ID check fails, record it in the closure artifact as a `CRITICAL` finding but still produce the closure artifact (the milestone is closed, but with documented defects). The milestone cannot be cleanly closed until a clean run, but this prevents the artifact being lost entirely.

**Exception**: If the only ID collisions are pre-existing in legacy milestone directories listed in `legacy_boundaries`, they are excluded from enforcement.

### 2c. Filename Sequentialization Check

Verify that all spec-artifact filenames follow the canonical sequential pattern and do not collide:

- `M{X}S{Y}.md`
- `M{X}S{Y}V1.md`, `M{X}S{Y}V2.md`
- `M{X}S{Y}T1.md`, `M{X}S{Y}T2.md`
- `M{X}S{Y}C1.md`, `M{X}S{Y}C2.md`

Reject bare repeated forms such as `M9S1C.md`, `VER-M9S1V.md`, `M9S1E.md`, or `M9S1R.md`.

### 2d. Artifact Completeness Check

Verify that the minimum required artifact chain exists against the specification YAML:

```bash
# Check that every spec sequence Y has its companion artifacts
python3 -c "
import yaml, glob, os

M = 'M{X}'
required_pairs = {
    f'milestones/{M}/{M}S{{Y}}.md': ['spec', 'specification'],
    f'milestones/{M}/{M}S{{Y}}V.md': ['verification', 'verification protocol'],
    f'milestones/{M}/{M}S{{Y}}C.md': ['completion', 'implementation'],
    f'milestones/{M}/{M}S{{Y}}E.md': ['evaluation', 'evaluation'],
    f'milestones/{M}/{M}S{{Y}}R.md': ['review', 'audit'],
    f'milestones/{M}/{M}S{{Y}}T1.md': ['test', 'test plan'],
}

# Discover spec files
specs = glob.glob(f'milestones/{M}/{M}S?.md') + glob.glob(f'milestones/{M}/{M}S??.md')
spec_ids = set()
for s in specs:
    base = os.path.basename(s)
    # Extract Y from M{X}S{Y}.md
    parts = base.replace(f'{M}S', '').replace('.md', '')
    if parts.isdigit():
        spec_ids.add(int(parts))

missing = []
for Y in sorted(spec_ids):
    for pattern, (label, desc) in required_pairs.items():
        path = pattern.replace('{Y}', str(Y))
        if not os.path.exists(path):
            missing.append(f'{path} ({label})')

if missing:
    print('WARNING: Missing expected artifacts:')
    for m in missing:
        print(f'  - {m}')
    print('The milestone is missing artifacts that may be expected.')
else:
    print(f'All expected artifacts present for M{X} specs: {sorted(spec_ids)}')
"
```

## Step 3 — Derive the Chain-of-Custody (Lineage Tracing)

Build the full `derived_from` chain. Read each artifact's YAML frontmatter from the most recent back, constructing a `DAG`-style lineage:

For each spec sequence Y in this milestone, trace:

```
M{X} (milestone)
  └→ SPEC-M{X}S{Y}
       └→ VER-M{X}S{Y}V
            └→ TSET-M{X}S{Y}T{Z}
                 └→ EVAL-{N}  (evaluation)
                      └→ REVIEW-M{X}S{Y}  (review)
                           └→ [INV-{N} → HOT-{N} → EVAL-{N+1} → REVIEW-M{X}S{Y}-V{N}]  (if fix cycle)
```

Collect each artifact's `derived_from`, `supersedes`, and status.

## Step 4 — Produce the Closure Artifact

Write to `milestones/M{X}/M{X}CLOSE-{N}.md` using the template at `templates/closure_template.md`.

The `{N}` in the filename is a sequential counter starting at `1` for the first closure attempt. If a previous closure artifact exists and is being superseded (e.g., closure was re-run after a fix), increment `{N}` and set `supersedes: [CLOSE-M{X}-{N-1}]` in the new artifact.

The `id` field uses the pattern `CLOSE-M{X}-{N}` — no semantic qualifiers (`-FINAL`, `-V2`). Replacements use the `supersedes` metadata field.

### Closure Artifact Contents

The closure artifact MUST contain:

1. **YAML frontmatter** with `id`, `type`, `title`, `milestone_id`, `status`, `derived_from`, and `supersedes` (if applicable).

2. **Loop-Closure Assurance** — A section documenting:
   - Whether investigation/hotfix reports were found
   - If found: which reports, their timestamps, and the evidence that re-evaluation/re-review happened after them
   - The assurance statement: "This milestone has no unverified fixes."

3. **Mechanical Validation Results** — Report from Step 2:
   - Lint evaluation gate output
   - Duplicate-ID/schema check results
   - Artifact completeness results
   - Any CRITICAL/HIGH findings with evidence

4. **Lineage Chain** — Complete DAG of every artifact in the milestone, with IDs and `derived_from` edges. This is the single authoritative source for "what artifacts does this milestone produce and how do they relate."

5. **Status Assertion**:
   - `CLOSED` — All gates passed, milestone is complete.
   - `CLOSED_WITH_DEFECTS` — Non-blocking issues found (e.g., some missing optional tests, non-CRITICAL lint warnings). Milestone is closed but defects are documented.
   - `REFUSED` — Loop-closure gate failed (see Step 1); artifact is still produced for traceability.

6. **Raw Evidence** — Every claim backed by the exact command that produced it. Same evidence-floor contract as the evaluation template.

## Step 5 — Status Doc Updates

If `MILESTONES.md` or any project-level status tracking document exists (typically at the repo root or in `docs/`), update it to reflect this milestone's closure status. This is the **only** place milestone-completion status gets written, to avoid the ambiguity of "documentation about the fix is done" vs "the fix is done."

Update rules:

- Set the milestone row/entry status to match the closure artifact's status assertion (`CLOSED` / `CLOSED_WITH_DEFECTS`).
- Append a reference to the closure artifact: `See milestones/M{X}/M{X}CLOSE-{N}.md`.
- Do NOT modify any other status fields or add editorial commentary.

If `MILESTONES.md` doesn't exist, create it at `docs/MILESTONES.md` with an initial entry for this milestone.

## Step 6 — Handoff

After generating the closure artifact, you MUST use the `ask` tool to present the user with the final next steps:

| Option Label        | Action                                                                     |
| :------------------ | :------------------------------------------------------------------------- |
| Archive Artifacts   | Run `/archive-docs` to clean up and archive milestone artifacts.           |
| Sync Documentation  | Run `/sync-documentation` to update canonical docs with the new milestone. |
| Start New Milestone | Run `/milestone` to begin planning the next milestone.                     |
| Custom              | Let me specify a different next step.                                      |

You MUST NOT emit a legacy hardcoded text message — the interactive ask prompt replaces this mechanism entirely.

## ID Minting Rules (from Prompt A compliance)

All artifact IDs generated by this skill follow these rules:

1. **Pattern**: `{TYPE}-{SCOPE}-{N}` where `TYPE = CLOSE`, `SCOPE = M{X}`, and `{N}` is a sequential counter starting at `1`.
2. **No semantic qualifiers**: Prohibit `-FINAL`, `-V2`, `-CORRECTED`, `-REVISED`. Replacements use `supersedes`.
3. **Global uniqueness**: The `id` field in YAML frontmatter is the canonical identity. Two artifacts must never share an `id` within the same repository.
4. **Filename maps to ID**: `M{X}CLOSE-{N}.md` ↔ `id: CLOSE-M{X}-{N}`.

## Negative Guardrails

- **Never** modify implementation code, test scripts, specification files, or verification documents.
- **Never** re-run `evaluate-implementation` or `review-implementation` yourself — instruct the user to do so, or invoke them via `/evaluate-implementation` and `/review-implementation` if the framework allows.
- **Never** archive artifacts or delete files — that is `archive-docs`'s job.
- **Never** produce a closure artifact with `status: CLOSED` if the loop-closure gate failed. The artifact may still be produced with `status: REFUSED`.
- **Never** suppress or modify findings to force a clean closure — accuracy over appearance.

## Documentation

- **[INDEX.md](../../INDEX.md)** — Complete skill catalog
- **[AGENTS.md](../../docs/AGENTS.md)** — Framework overview
- **[PLAYBOOK.md](../../docs/PLAYBOOK.md)** — Operational workflows
- **[close-milestone templates](templates/closure_template.md)** — Closure artifact template

