Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .agents/skills/ptf-extract/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ input error.

1. Read the supplied local paper, `specs/schema/ptf-spec.schema.json`, and
`references/extraction-quality-gate.md`.
If the source defines a finite categorical input or a table selected by that
input, also read `references/categorical-lookups.md`.
2. Extract only facts explicitly supported by the paper. Write its standalone
YAML directly to `specs/functions/<apa_article_key>.yaml`, following
`references/spec-template.yaml`.
Expand All @@ -42,6 +44,9 @@ exact YAML path and explicit blockers.
- Do not set `implemented`, run target generation, or edit generated files.
- Do not invent formulas, units, metadata, golden values, applicability, or
API details. Keep uncertainty explicit in the YAML.
- Do not normalize, alias, abbreviate, or otherwise broaden source-defined
categorical values. Keep enum member names, canonical textual values, lookup
rows, and their evidence distinct.
- Give every `type: record` output a PascalCase `name`, whether it is inline or
declared in `$defs`. It names generated structures and classes; `$defs` keys
only resolve local `$ref` targets.
Expand Down
86 changes: 86 additions & 0 deletions .agents/skills/ptf-extract/references/categorical-lookups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Categorical inputs and typed lookups

Use this contract only when the source explicitly defines a finite categorical
input or a complete numeric table selected by such an input. Do not replace a
continuous predictor with representative categories, derive categories from
numeric inputs, or add aliases and normalization that the source does not define.

## Enum type and input binding

Declare the reusable categorical type in `$defs`. Its key is the canonical
PascalCase type name. The required `description` documents the type as a whole.
Each value has a lower-snake-case schema `name`, the exact public textual
`value`, and an optional source-supported `description`.

Bind the type to a function argument with `name` and `$ref`. Add the optional
binding `description` only when the argument's role needs information beyond
the enum type description. Categorical inputs do not have units, symbols, or
numeric domains.

```yaml
$defs:
TextureClass:
type: enum
description: Source-defined texture class.
values:
- name: coarse
value: "Coarse"
description: Source-defined coarse class.
functions:
- inputs:
- name: texture
description: Class used to select the published table row.
$ref: "#/$defs/TextureClass"
```

## Lookup definition and implementation

A lookup definition references an enum input type and a record output type.
Its rows must cover every enum member exactly once. Row `key` values are enum
member schema names; every row `value` must contain exactly the output record's
field names.

Invoke the lookup as an ordered implementation variable. Its `key` names an
in-scope input of the lookup's enum type. Return a compatible record-valued
variable directly, or use `variable.field` in later formula expressions.
Golden-test categorical inputs also use enum member schema names.

```yaml
$defs:
Parameters:
type: record
name: Parameters
fields:
- name: coefficient
symbol: c
unit: "1"
domain: null
description: Published coefficient.
ParametersByTexture:
type: lookup
input:
$ref: "#/$defs/TextureClass"
output:
$ref: "#/$defs/Parameters"
values:
- key: coarse
value: {coefficient: 1.25}
functions:
- implementation:
variables:
- name: parameters
lookup:
table:
$ref: "#/$defs/ParametersByTexture"
key: texture
golden_tests:
- id: coarse_table_row
inputs: {texture: coarse}
expected: {coefficient: 1.25}
rtol: 0.0
atol: 0.0
notes: Direct published table row.
```

Treat a missing category, ambiguous label, incomplete row, unexplained numeric
value, or uncertain category-to-row mapping as a scientific blocker.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ APA-style slug and identifies the generated public module, for example
golden and edge cases, documentation, scope, and semantic `implementation`
fields required by the schema.
- Every record output has an explicit PascalCase `name`.
- When the source uses a finite categorical predictor, represent its reusable
type as an enum in `$defs` and bind it to each function-local argument with
`name` plus `$ref`. The enum owns its type description and admissible values;
the binding description, when present, explains only that argument's role.
- When the source publishes a table selected by a category, model it as a typed
lookup from the enum to a record in `$defs`. Preserve one row per enum member
and one numeric value per output-record field. Use enum member names in lookup
keys and golden inputs, not canonical textual values or target ordinals.
- Use the formula DSL only in `implementation` expressions. In
`scientific_notes`, retain source-supported scientific context, derivations
needed to justify an interpretation, evidence for review decisions, citations,
Expand Down
39 changes: 21 additions & 18 deletions .agents/skills/ptf-generate/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: ptf-generate
description: Generate and verify ptfkit Rust and NumPy targets for one reviewed APA-style source slug. Use after human review of a YAML source file in specs/functions to validate, generate, test, prove idempotence, and atomically mark the source implemented.
description: Generate and verify all retained ptfkit targets for one reviewed APA-style source slug. Use after human review of a YAML source file in specs/functions to validate, generate, run the complete verification suite, and atomically mark the source implemented.
---

# PTF Generate
Expand All @@ -17,27 +17,30 @@ status when input validation fails; report the blocking input error.

## Procedure

1. Treat the argument as the source under review. Read
`specs/schema/ptf-spec.schema.json`, its selected YAML file,
and `references/generation-checklist.md`.
2. Reject unresolved blockers, `TODO` values, schema or semantic failures, and
output-metadata mismatches. Record `outputs.name` is PascalCase and names
generated structures and classes; `$defs` keys only resolve local references.
Do not infer missing science.
3. Validate, generate both retained targets, and run the required verification
gates. Before validation, extract each repeated nontrivial formula
expression within a function into one earlier local implementation variable
and reference it thereafter; retain published numeric lexemes and do not
invent scientific semantics for the calculation intermediate. A generator
capability gap is a blocker, never an invitation to hand-write that
computational target.
4. After every required check passes, change the selected source functions from
`ready-for-implementation` to `implemented`, then revalidate, regenerate,
and prove the second generation pass is idempotent.
1. Read the selected YAML file and require at least one function with status
`ready-for-implementation`. Only those functions participate in the status
transition; leave functions with any other status unchanged.
2. Run `mise run validate`. Treat any structural or semantic validation failure
as a blocker.
3. Run `mise run generate` to update every codegen-owned target.
4. Run `mise run verify`. Report any generator capability or target failure as
a blocker; do not hand-write a generated computational target.
5. Only after all verification passes, change the selected source functions
from `ready-for-implementation` to `implemented`.
6. Run `mise run generate` once more to produce the final implemented state.
Treat the status update and final generation as one transition: do not leave
the selected functions marked `implemented` if final generation fails.

## Hard rules

- Never hand-edit marked generated files.
- Do not rewrite formulas, implementation variables, metadata, or other
scientific content. Human review must complete those changes before this
skill runs.
- `generation.public_python: manual` permits only a hand-written public wrapper
that delegates to the generated native ufunc.
- Do not change status unless all required retained targets pass.
- Do not manually audit generated output or prove regeneration idempotence for
a routine PTF addition. Use those checks when the generator, schema, output
formatting, or generation infrastructure changes, or when explicitly running
`ptf-review`.
45 changes: 0 additions & 45 deletions .agents/skills/ptf-generate/references/generation-checklist.md

This file was deleted.

13 changes: 7 additions & 6 deletions .agents/skills/ptf-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: ptf-review
description: Independently review a generated ptfkit PTF source against its YAML specification, semantic IR, retained Rust and NumPy targets, generation policy, and public API parity. Use for read-only pre-merge review after $ptf-generate.
description: Independently review a generated ptfkit PTF source against its YAML specification, semantic IR, retained targets, generation policy, and public API parity. Use for read-only pre-merge review after $ptf-generate.
---

# PTF Review
Expand All @@ -18,12 +18,13 @@ modifying repository state.
## Procedure

1. Read `specs/schema/ptf-spec.schema.json`, the selected YAML specification,
implementation diff, generated Rust and native NumPy targets, golden tests,
and relevant public wrapper.
implementation diff, generated Rust, C, C++, and native NumPy targets,
golden tests, and relevant public wrapper.
2. Load `references/implementation-review-checklist.md`.
3. Check schema and semantic IR fidelity, both retained targets, deterministic
regeneration, status transition evidence, output order, NumPy broadcasting,
`out`, `NamedTuple` compatibility, docstring fidelity, and public API parity.
3. Check schema and semantic IR fidelity, all retained targets, deterministic
regeneration, status transition evidence, categorical type and lookup
fidelity, output order, NumPy broadcasting, `out`, enum-array typing,
`NamedTuple` compatibility, docstring fidelity, and public API parity.
4. Run or request the project checks appropriate to the changed files.
5. Report findings first, ordered by severity, with file and line references.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
and classes; `$defs` keys only resolve local references.
- [ ] Every public function name, argument, output, unit, and IR expression
matches the YAML specification.
- [ ] Every categorical argument binds its function-local name to the intended
enum type; type and optional binding descriptions retain their separate roles.
- [ ] Enum member names, exact canonical textual values, order, and optional
descriptions match the source-supported specification. Golden inputs use
member names, never textual values or generated ordinals.
- [ ] Every lookup maps the declared enum to the declared record, covers each
member exactly once, and gives each row exactly the record fields. Lookup
invocation keys have the declared enum type, and record-field access resolves
to real fields.
- [ ] No scientific assumption is present only in generated code.

## Formula and units
Expand All @@ -16,8 +25,10 @@

## Retained targets

- [ ] Generated Rust uses `f64` scalar computation from the semantic IR.
- [ ] Generated native NumPy ufuncs use the same IR.
- [ ] Generated Rust, C, and C++ preserve enum types, member identity, typed
lookup conversion, record shape, and numeric computation from the semantic IR.
- [ ] Generated native NumPy ufuncs use the same IR and private ordinal encoding
only as a target implementation detail.
- [ ] Generated target tests cover every structured golden case.
- [ ] Valid IR unsupported by a retained target is reported as a generator
capability blocker, not replaced with hand-written computation.
Expand All @@ -26,6 +37,11 @@

- [ ] Public module and function names, keyword-only inputs, scalar/array
behavior, broadcasting, `out`, and `NamedTuple` output match the contract.
- [ ] Python exposes scalar categorical inputs as the generated enum and array
inputs as its typed `EnumArray`; raw strings, integers, arbitrary arrays, and
normalization aliases are not silently accepted.
- [ ] Generated enum type and member documentation reflects the enum and member
descriptions without conflating them with a function binding description.
- [ ] A manual public module is justified and delegates to generated native
ufuncs without duplicating formulas.

Expand All @@ -40,6 +56,6 @@
## Blocking findings

Classify as blocking: schema or semantic failure; formula, unit, output-order,
or public-API mismatch; missing retained target or golden test; unsupported IR;
nondeterministic generation; unsubstantiated status transition; or exposed
repository-only specification paths.
categorical-type, lookup, or public-API mismatch; missing retained target or
golden test; unsupported IR; nondeterministic generation; unsubstantiated
status transition; or exposed repository-only specification paths.
53 changes: 44 additions & 9 deletions codegen/src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::{Context, Result};

use crate::model::{
CompiledFunction, CompiledGoldenTest, CoreFunction, Entry, Function, Output, Outputs,
CompiledFunction, CompiledGoldenTest, CompiledInput, CoreFunction, Entry, Function,
GoldenInput, Output, Outputs,
};

pub(super) fn functions(entries: Vec<Entry>) -> Result<Vec<CompiledFunction>> {
Expand All @@ -28,12 +29,12 @@ pub(super) fn functions(entries: Vec<Entry>) -> Result<Vec<CompiledFunction>> {
inputs: function
.inputs
.iter()
.map(|input| input.name.clone())
.map(|input| input.name().to_owned())
.collect(),
output,
};
compiled.push(CompiledFunction {
golden_tests: golden_tests(function, &core)?,
golden_tests: golden_tests(function)?,
core,
entry: entry.clone(),
function_index,
Expand All @@ -44,18 +45,52 @@ pub(super) fn functions(entries: Vec<Entry>) -> Result<Vec<CompiledFunction>> {
Ok(compiled)
}

fn golden_tests(function: &Function, core: &CoreFunction) -> Result<Vec<CompiledGoldenTest>> {
fn golden_tests(function: &Function) -> Result<Vec<CompiledGoldenTest>> {
function
.golden_tests
.iter()
.map(|case| {
let inputs = core
let inputs = function
.inputs
.iter()
.map(|name| {
case.inputs.get(name).copied().with_context(|| {
format!("golden test `{}` is missing input `{name}`", case.id)
})
.map(|input| {
let input_name = input.name();
let value = case.inputs.get(input_name).with_context(|| {
format!(
"golden test `{}` is missing input `{}`",
case.id, input_name
)
})?;
match (input.enum_type(), value) {
(None, GoldenInput::Number(value)) => Ok(CompiledInput::Number(*value)),
(Some(enum_type), GoldenInput::Enum(member_name)) => {
enum_type
.values
.iter()
.find(|member| member.name == *member_name)
.with_context(|| {
format!(
"golden test `{}` input `{}` references unknown member `{member_name}` of enum `{}`",
case.id, input_name, enum_type.name
)
})?;
Ok(CompiledInput::Enum {
enum_name: enum_type.name.clone(),
member_name: member_name.clone(),
})
}
(None, GoldenInput::Enum(_)) => anyhow::bail!(
"golden test `{}` input `{}` must be numeric",
case.id,
input_name
),
(Some(enum_type), GoldenInput::Number(_)) => anyhow::bail!(
"golden test `{}` input `{}` must name a member of enum `{}`",
case.id,
input_name,
enum_type.name
),
}
})
.collect::<Result<Vec<_>>>()?;
let expected = function
Expand Down
Loading