---
id: OBPI-0.16.0-04-template-engine
parent: ADR-0.16.0-cms-architecture-formalization
item: 4
lane: Lite
status: Completed
---

<!-- markdownlint-disable-file MD013 MD022 MD036 MD040 MD041 -->

# OBPI-0.16.0-04 — template-engine

## ADR ITEM (Lite) — Level 1 WBS Reference

- Source ADR: `docs/design/adr/pre-release/ADR-0.16.0-cms-architecture-formalization/ADR-0.16.0-cms-architecture-formalization.md`
- OBPI Entry: `OBPI-0.16.0-04 — "Make gz agent sync vendor-aware; render canonical content to enabled vendor shapes"`

## OBJECTIVE (Lite)

Transform `gz agent sync control-surfaces` from a skill-mirroring tool into a proper
template engine that renders ALL canonical content (skills AND rules) into vendor-specific
shapes for ONLY enabled vendors. The engine reads `.gzkit/manifest.json` for vendor
enablement, reads canonical content from `.gzkit/skills/` and `.gzkit/rules/`, and
generates vendor surfaces with appropriate format transformations:

- Claude: `.claude/rules/*.md` with `paths:` YAML frontmatter
- Copilot: `.github/instructions/*.instructions.md` with `applyTo:` frontmatter
- Codex: `.agents/skills/` mirrors
- Gemini: `GEMINI.md` content sections
- OpenCode: `opencode.json` instruction references

## LANE (Lite)

Lite — ADR note + stdlib unittest + smoke (≤60s).

## ALLOWED PATHS (Lite)

- `src/gzkit/agent_sync.py` (major refactor — vendor-aware rendering)
- `src/gzkit/renderers/` (new — per-vendor rendering modules)
- `.claude/rules/` (generated output)
- `.claude/skills/` (generated output — already works)
- `.github/instructions/` (generated output — replaces hand-authored)
- `.github/skills/` (generated output — already works)
- `.agents/skills/` (generated output — already works)
- `tests/test_agent_sync.py`

## DENIED PATHS (Lite)

- `.gzkit/` canonical content (read-only for rendering)
- CI files, lockfiles

## REQUIREMENTS (FAIL-CLOSED — Lite)

1. `gz agent sync control-surfaces` reads `manifest.vendors` to determine which surfaces to generate
1. Disabled vendors produce no output (no stale mirrors left behind)
1. Enabled vendors get BOTH skill mirrors AND rule mirrors
1. Rule rendering transforms vendor-neutral frontmatter (`paths:`) into vendor-specific format (`applyTo:` for Copilot)
1. Generated files include a header comment: `<!-- Generated by gz agent sync — do not edit -->`
1. `gz agent sync --dry-run` shows what would be generated without writing
1. Existing skill mirroring behavior preserved exactly (regression proof)

## Acceptance Criteria

<!--
Specific, testable criteria for completion.
Each checkbox carries a deterministic REQ ID: REQ-<semver>-<obpi_item>-<criterion_index>.
Backfilled 2026-04-15 under GHI #160 Phase 3 from REQUIREMENTS prose above.
-->

- [x] REQ-0.16.0-04-01: `gz agent sync control-surfaces` reads `manifest.vendors` to determine which surfaces to generate
- [x] REQ-0.16.0-04-02: Disabled vendors produce no output (no stale mirrors left behind)
- [x] REQ-0.16.0-04-03: Enabled vendors get BOTH skill mirrors AND rule mirrors
- [x] REQ-0.16.0-04-04: Rule rendering transforms vendor-neutral frontmatter (`paths:`) into vendor-specific format (`applyTo:` for Copilot)
- [x] REQ-0.16.0-04-05: Generated files include a header comment: `<!-- Generated by gz agent sync — do not edit -->`
- [x] REQ-0.16.0-04-06: `gz agent sync --dry-run` shows what would be generated without writing
- [x] REQ-0.16.0-04-07: Existing skill mirroring behavior preserved exactly (regression proof)


## QUALITY GATES (Lite)

- [x] Gate 1 (ADR): Intent recorded in this brief
- [x] Gate 2 (TDD): `uv run gz test` — 655 tests pass, 11 vendor-specific
- [x] Code Quality: `uv run gz lint` + `uv run gz typecheck` clean

## Evidence

### Gate 1 (ADR)

- [x] Intent and scope recorded

### Gate 2 (TDD)

```text
uv run -m unittest tests.test_agent_sync -v
Ran 11 tests in 0.054s — OK
uv run gz test — 655 tests, OK
```

### Code Quality

```text
uv run gz lint — All checks passed
uv run gz typecheck — All checks passed
```

### Implementation Summary

- Orchestration: refactored `sync_all()` in `src/gzkit/sync.py` to read `manifest.vendors` and gate per vendor
- Renderers: `render_rule_for_claude()` and `render_rule_for_copilot()` in `src/gzkit/rules.py`
- Batch output: `render_rules_to_dir()` with stale file cleanup and generated-file header
- Claude format: `paths:` YAML frontmatter (global rules omit frontmatter)
- Copilot format: `applyTo:` frontmatter
- Vendor gating: disabled vendors produce no output; no stale artifacts

### Key Proof

```text
$ uv run -m unittest tests.test_agent_sync.TestSyncAllVendorAware.test_disabled_vendor_skipped -v
test_disabled_vendor_skipped ... ok
```

Disabled vendors are skipped entirely — no stale artifacts generated.

## Human Attestation

- Attestor: `self-close-exception (Lite lane)`
- Attestation: `Self-closed per AGENTS.md Lane Inheritance Rule`
- Date: `2026-03-18`

---

**Brief Status:** Completed

**Date Completed:** 2026-03-18

**Evidence Hash:** -
