Scaffold a new T2 (L1-direct) Op file from a single `src/tileops/manifest/` entry by following the 7-step playbook in docs/design/ops-design.md. Emits the 17 scaffold slots (S1-S7, S12-S21); leaves family-specific protocol variables, optional hooks, and kernel implementations to downstream skills.
Scaffold a new T2 (L1-direct) Op file from a single `src/tileops/manifest/` entry by following the 7-step playbook in docs/design/ops-design.md. Emits the 17 scaffold slots (S1-S7, S12-S21); leaves family-specific protocol variables, optional hooks, and kernel implementations to downstream skills.
Arguments
op_name (positional) — manifest key for the op to scaffold, equal to the target cls.__name__ (e.g. CumsumFwdOp).
Contract
Input: op_name must be present in src/tileops/manifest/ with status: spec-only and a non-empty source.kernel_map. source.kernel_map is manifest-level source of truth for Op→Kernel dispatch and cannot be derived by the scaffold (dispatch keys are kernel-internal conventions), so a spec-only entry needs it added before the scaffold can run.
Output: new file at src/ + manifest source.op (e.g., src/tileops/ops/reduction/cumulative.py), containing the 17 scaffold slots; one-line from .<module> import <ClassName> added to the package __init__.py at that file's parent directory (e.g., src/tileops/ops/reduction/__init__.py) with a matching __all__ entry. Note: that parent directory is not always the same as the manifest family field — for example, CumsumFwdOp has family: scan but lives under src/tileops/ops/reduction/. Always key paths off source.op, never off family. Plus a side-artefact at .foundry/plan/<op_name>/plan.json carrying the DRY_RUN self-audit (not tracked in git).
Termination (success): python scripts/validate_manifest.py --check-op <op_name> reports no errors for this op. Warnings are allowed and passed through to the final summary.
Termination (blocked): any validator error for op_name that the scaffold cannot fix by re-reading the playbook's slot rules. Do NOT commit; report with the failing rows from the validator.
Constraints:
MUST NOT emit family-specific protocol variables (_op_kind, _kernel_key, _kernel_cls, _kernel_handles_padding, _op_name, kernel_cls).
MUST NOT emit optional hooks (_pad_value, _validate_dim, _pre_kernel, _post_kernel, _cache_key override).
MUST NOT implement the kernel itself.
MUST NOT modify src/tileops/manifest/, tests, benchmarks, or any existing op file.
MUST NOT extend scope to a T1 (family-base) subclass — the scaffold is T2 only.
DRY_RUN writes plan.json to freeze manifest-sourced facts before codegen. VALIDATE diffs the emitted file against plan.json §1 — any drift is a skill bug, not a manifest issue.
Slot scope
Emit exactly the 17 slots in slot-rules.md: S1–S7, S12–S21. S8–S11 are reserved for T1 thin-wrapper subclasses and skipped.
Out of scope — leave empty:
Item
Reason
Family protocol vars (_op_kind, _kernel_key, _op_name, …)
Recommended under dynamic shapes; depends on kernel math
Kernel implementations
Owned by kernel skill
Tests / benchmarks
Owned by test-op / bench-op
These gaps surface as NotImplementedError or validator warnings; downstream skills fill them.
Steps
1. READ
Load the manifest entry for op_name:
Before running the snippet, substitute <op_name> with the requested manifest key (the skill's positional argument — agent literal substitution, not shell interpolation):
Derive the target file path by prepending src/ to source.op (e.g. src/tileops/ops/reduction/cumulative.py). The filesystem package directory is that file's parent (e.g. src/tileops/ops/reduction/). Do not use the manifest family field to compute paths — it is a semantic label, and some ops have family distinct from their filesystem parent (e.g., CumsumFwdOp has family: scan but lives under reduction/). Module filename is source.op's basename without .py.
2. PRE_CHECK
op_name present in src/tileops/manifest/ → proceed; otherwise BLOCKED ("op not in manifest").
status field explicitly set to spec-only → proceed; status: implemented → BLOCKED ("op already implemented; use implement-op to migrate"); missing status or any other value → BLOCKED ("manifest entry must declare a valid top-level status; the validator treats status as required").
source.kernel_map declared and non-empty → proceed; missing or empty → BLOCKED ("manifest entry needs source.kernel_map before scaffolding — add the dispatch map to the manifest first; the scaffold cannot invent dispatch keys because they are kernel-internal conventions"). Note: per docs/design/manifest.md, source.kernel_map is only required when status: implemented, so many existing spec-only entries lack it — these are the cases that need the map added before scaffolding can run.
Every value in source.kernel_map resolves to an importable symbol → proceed; otherwise BLOCKED ("kernel class not found at expected path").
Target file source.op does NOT exist → proceed; exists → BLOCKED ("target file already present; scaffold would overwrite").
BLOCKED terminations return without writing any file.
3. DRY_RUN
Write .foundry/plan/<op_name>/plan.json with three sections:
Ambiguities tagged needs_doc_fix / needs_manifest_fix / needs_human_decision; surfaced in REPORT, never block
Always proceed to EMIT. Empty open_questions is fine.
Skeleton:
{"locked_facts":{"op_name":"CumsumFwdOp","module_path":"src/tileops/ops/reduction/cumulative.py","kernel_map":{"cumulative_fwd":"CumulativeKernel"},"init_kwargs":[{"name":"dim","source":"signature.params.dim","type":"int","default":-1}],"forward_inputs":["x"],"static_dims":{"N":"x.shape[dim]"},"roofline":{"flops":"M * N","bytes":"2 * M * N * elem_bytes"}},"agent_notes":{"docstring_summary":"...","kernel_ctor_signature_observed":"..."},"open_questions":[{"tag":"needs_human_decision","topic":"...","detail":"..."}]}
If a slot's rule is ambiguous for the given manifest entry (e.g. multi-kernel kernel_map, multiple independent dtype axes, fixed-rank vs arbitrary-rank branching), STOP and surface the ambiguity in the final report instead of guessing. Do not expand scope.
Append alongside existing imports following file's ordering convention; do NOT introduce grouping comments
Add matching <ClassName> to __all__, preserving any existing sectioning.
6. VALIDATE
(a) §1 post-check — diff plan.json.locked_facts against the emitted artefacts:
Parse source.op with ast: extract class name, base, imports, __all__, __init__ kwargs (names / defaults / types in order), forward params, default_kernel_map dict, _static_axes.
Parse dirname(source.op)/__init__.py with both ast (presence + __all__ membership) and raw text (block placement under # --- <KernelClassName> ops --- when the file uses grouping comments; skip placement check on flat-style files).
Any locked_facts field mismatch → BLOCKED §1 drift: <field>. Skill deviated from its own contract; fix the emitted file (or revert and restart DRY_RUN if the plan was wrong). Do NOT edit plan.json to match.
--check-op runs L0–L4 even on status: spec-only. Classify output:
ERROR → BLOCKED. Copy the row to REPORT. For L2 / L3 parity failures, re-check the emitted _infer_output_shapes / _validate_dtypes against manifest shape_rules / dtype_combos first — likely a mis-emit.
WARNING → pass through.
Never edit the manifest to silence an error. No parity_opt_out escape hatch. Demote to status: spec-only only when the implementation genuinely cannot conform.