# v2.27.0 Spec: Derived Surfaces, Phase 1 (M-32: skill manifest + generated AGENTS.md catalog)

**Status:** DRAFT R0 (specced 2026-06-12). Maintainer pre-authorized build start the same day; adversarial review still runs before tag per G1, findings fold back here.
**Covers:** Derived Surfaces Phase 1 (M-32, issue #201; closes #87 on ship). Generates the skill catalog's machine-readable manifest and the AGENTS.md catalog section from frontmatter instead of hand-syncing.
**Companion:** [`plan_v2.27.0.md`](plan_v2.27.0.md) | [`implementation-plan_derived-surfaces.md`](implementation-plan_derived-surfaces.md) | effort brief [`M-32-derived-surfaces.md`](../../efforts/M-32-derived-surfaces.md)

---

## 0. Current state (verified 2026-06-12)

1. AGENTS.md is entirely hand-authored. The auto-sync workflow (`sync-agents-md.yml`) was disabled in the v2.14.x line: its generator expects nested `skills/$phase/` directories, the repo is flat (`skills/<prefix>-<name>/`), so a run would emit an empty catalog. It survives only as a hardened dry-run shell (apply gate + `contents: read`).
2. Enforcing validators on the surface today: `validate-agents-md.sh` (structure), `check-agents-md-command-sync.sh` (commands cross-sync both directions), `check-count-consistency.sh` and `check-landing-page-counts.sh --strict` (numeric claims). None detects a stale or missing SKILL entry's description drift.
3. The generate + committed-output + `--check` staleness pattern is proven: `gen-resource-index.mjs` -> `docs/RESOURCES.md`, with the enforcing "Check resource index is current" CI step; CI has already caught real staleness through it (the v2.26.0 HISTORY-files catch).
4. No machine-readable full-catalog artifact exists. `build-skill-catalog.py` emits a TIER-FILTERED markdown catalog into `skills/foundation-prioritized-action-plan/references/skill-catalog.md` for that skill's own use; per-release `skills-manifest.yaml` files are change logs, not a live catalog.
5. Issue #87 (open since January): "Auto-generated AGENTS.md header from skill inventory ... manual updates are error-prone."

## 1. Scope

**In (Phase 1):**
- `scripts/gen-skill-manifest.mjs` (+ `.test.mjs`): emits `skill-manifest.json` from `skills/*/SKILL.md` frontmatter + filesystem facts; `--check` staleness mode.
- AGENTS.md catalog section generated between HTML-comment markers from the manifest (`--agents` mode of the same script); `--check` staleness mode.
- Two ENFORCING CI steps (manifest current; AGENTS.md catalog current).
- Retire `sync-agents-md.yml`; close #87 on ship.
- Contributor doc note: the regeneration step joins the add-a-skill checklist.

**Out (deferred, recorded as candidates):** landing-page card generation (cards mix prose and counts; counts are already enforced by `check-landing-page-counts --strict`); README At-a-Glance generation (same reason); static Skill Finder; per-skill output schemas; `build-skill-catalog.py` consolidation onto the manifest (follow-on note only); any change to `docs/RESOURCES.md` or route-manifest mechanics.

## 2. Manifest design (normative)

**Location:** `skill-manifest.json` at repo root (decision D-A). Committed.

**Shape (v1):**

```json
{
  "schema": 1,
  "generated_by": "scripts/gen-skill-manifest.mjs",
  "catalog": {
    "skills": 66,
    "phase": 30,
    "foundation": 9,
    "utility": 12,
    "tool": 15,
    "sub_agents": 5,
    "commands": 11,
    "workflows": 12
  },
  "entries": [
    {
      "name": "deliver-prd",
      "description": "<frontmatter description, verbatim>",
      "version": "2.1.0",
      "group": "phase",
      "phase": "deliver",
      "family": null,
      "path": "skills/deliver-prd",
      "references": ["TEMPLATE.md", "EXAMPLE.md"],
      "sample": "library/skill-output-samples/deliver-prd"
    }
  ]
}
```

Field rules: `name` from frontmatter and must match the directory; `description` verbatim (the manifest must never paraphrase the triggering surface); `version` from `metadata.version`; `group` derived from the existing phase-or-classification metadata (`phase` | `foundation` | `utility` | `tool`); `family` carries family/tool metadata where frontmatter declares it (exact key mapping settled in Task 1 from the live shapes, recorded in the script header); `references` and `sample` are filesystem facts (sample omitted when absent). `catalog` counts are derived from `entries` plus the same filesystem enumeration the count validators use, so they agree by construction. Entries sorted by name for stable diffs. Sub-agents, commands, and workflows appear in `catalog` counts only in Phase 1 (entry-level coverage is a later phase).

**No timestamp field** (deliberate): output must be byte-stable for identical inputs or `--check` becomes noise.

## 3. AGENTS.md generated block (normative)

Markers: `<!-- skills-catalog:start (generated by scripts/gen-skill-manifest.mjs --agents; edit frontmatter, not this block) -->` ... `<!-- skills-catalog:end -->`, placed around the existing skill-catalog section. Everything outside the markers stays hand-authored.

Generated content: the catalog listing grouped exactly as the current hand-authored section is (phase / foundation / utility / tool), each entry as `name` + description, rendered to match the CURRENT AGENTS.md formatting as closely as possible so the first-generation diff isolates CONTENT drift (stale descriptions, missing skills) from formatting churn. Where current hand-authored text disagrees with frontmatter, frontmatter wins and the diff line is triaged in review (that disagreement is precisely the drift class this kills).

Constraints: all existing enforcing validators stay green over generated output (`validate-agents-md.sh`, `check-agents-md-command-sync.sh`, count gates, link gates including the alias-form rules from the audit follow-ups). The generator only touches the marker block; idempotence (run twice, no diff) is unit-tested.

## 4. CI wiring

Two steps in validation.yml, both ENFORCING from day one:

- "Check skill manifest is current": `node scripts/gen-skill-manifest.mjs --check`
- "Check AGENTS.md catalog is current": `node scripts/gen-skill-manifest.mjs --agents --check`

Rationale for skipping the advisory rung (Section 1a principle 6 requires a recorded reason): the checks are deterministic, the artifacts are new or generated-in-the-same-PR, so there is no legacy corpus to triage; this mirrors the existing enforcing resource-index staleness step. Node check: CI-only, no shell-parity entries (validation-manifest.yaml remit excludes `.mjs` by design). `sync-agents-md.yml` is deleted in the same PR that lands the AGENTS.md gate.

## 5. Decisions (lettered for review)

| # | Decision | Rationale |
|---|---|---|
| D-A | Manifest at repo root (`skill-manifest.json`) | Machine consumers (tools, future Finder, external integrations) expect a discoverable root artifact; mirrors plugin-manifest placement conventions |
| D-B | Descriptions verbatim from frontmatter, never transformed | The triggering surface must have exactly one source of truth |
| D-C | Enforcing staleness gates immediately (no advisory rung) | Deterministic + no legacy corpus; resource-index precedent; reason recorded per the durable-CI principles |
| D-D | Landing cards and README numbers NOT generated in Phase 1 | Already enforced by count gates; prose-mixed surfaces need a templating decision that should not block the manifest |
| D-E | `sync-agents-md.yml` deleted, not fixed | Marker-based generation supersedes it; keeping a disabled workflow invites confusion (it has been dead weight since v2.14.x) |
| D-F | Byte-stable output, no timestamps, sorted entries | `--check` must mean "content drift", nothing else |

## 6. Acceptance criteria

- 32-1: `gen-skill-manifest.mjs` emits a manifest covering all 66 skills; `catalog` counts match the count validators' filesystem numbers; `.test.mjs` covers shape, sorting, byte-stability, and `--check` in both directions.
- 32-2: AGENTS.md carries the markers; `--agents` regenerates only the block; idempotence unit-tested; first-generation diff reviewed and triaged by the maintainer.
- 32-3: Both `--check` steps wired enforcing in validation.yml; a deliberate description edit without regeneration fails both relevant checks in a test run.
- 32-4: All pre-existing enforcing validators green over generated output.
- 32-5: `sync-agents-md.yml` removed; #87 closed on ship with a pointer to the new mechanism.
- 32-6: Contributor docs updated: add-a-skill flow includes the regeneration command; CHANGELOG `[Unreleased]` entry present.

## 7. Risks

- **First-generation diff size:** AGENTS.md's hand-authored catalog has drifted in unknown ways; the diff may be large. Mitigation: D-B + matching current formatting isolates real drift; maintainer triage is an explicit AC.
- **Frontmatter irregularities:** any skill whose metadata deviates from the dominant shape surfaces immediately (that is the point); the generator fails loudly with the offending path rather than guessing. If WS-A4 (skills-ref metadata normalization, #97) lands in-window, run it first; not a hard dependency.
- **Marker vandalism:** a hand edit inside the markers silently survives until the next regeneration; the enforcing `--check` step catches it on the next push, which is exactly the designed behavior.
