Skip to content

v1.31.0 ·

Two rules nobody could obey

The skill that writes roadmap items told its agent one thing: do not add any other field.

It is the one artifact AIDLC produces that is meant to leave the repository. A contributor with no checkout and no interest in YAML loads it into whatever agent they use, describes what they want, and gets a correctly formatted file back. Nobody has to learn the format.

Then the first real external project, fieldmodel-apps, added a field of its own. It keeps requirements traceability as data: every roadmap item lists the requirement IDs it delivers in a requirements key, and a check computes coverage as a set difference. The check fails any item missing the key, even when the right value is empty, because a set difference over an optional key does not fail loudly. It under-reports quietly.

So the two rules were each correct and together impossible. An agent obeying the skill produced an item that failed the project's build. An agent satisfying the build disobeyed an instruction in a file stamped framework-owned. Neither side could fix it. The project could not patch the template, because the next update overwrites it, and that overwriting is what keeps the portable skill current. The framework could not anticipate a field it had no way to hear about.

It also recurred. Each time someone wrote an item, a person had to remember the local exception and say it in the prompt. That kind of papercut does not get fixed, it gets avoided, and people go back to hand-writing the file.

One uncomfortable note. Traceability as data is already in the done column here, and evidently it never reached item frontmatter. The first project to want traceability per item built it by hand and hit this seam within days.

What changed

A project declares its fields where the framework already looks:

roadmap:
  item_fields:
    - name: requirements
      required: true
      default: []

The roadmap.sync section lives there already, so there is no new file. required defaults to true, since the motivating case needs the key even when empty, and default defaults to null. Four things follow. The emitted skill carries each declared field with its required-ness and default, and its rule becomes "add no field beyond the template above". That matters because the agent loading it usually cannot read your config. aidlc roadmap import and the roadmap-layout doctor migration write the declared default, so imported items are not born failing your gate. aidlc doctor reports items missing a required field and backfills the default. A wrong-shaped value is reported and never rewritten, because wrapping a scalar in a list is a confident wrong fix and only the author knows what they meant.

Triage moves and the roadmap-done action needed no change. They already kept extension fields byte for byte, by luck of an earlier decision. A test now asserts it.

Where I drew the line

The framework checks that a field is present and the right shape. It never looks inside. There is no key to express a rule about contents, and that is the feature. requirements: [not-a-real-id] is your own script's business, and a test says so, so anyone who adds content checks fails a test that names the reason. Past that line the framework becomes the general schema engine I was asked not to build.

Shape comes from the default: a list default means a list. I rejected a type: key because it is a second representation of one fact. The cost is that a field with a null default gets no shape check. I refused adding the field to the stock template, which hard-codes one project's concept and moves the collision to the next. status is refused by name, because status is the directory.

I also rejected report-only for existing items. A project with fifty items would see fifty problems and no way to act, and a permanently red check trains people to ignore it.