Two files: the template you write and the record paqad keeps
A plan exists in two forms. A schema here means the exact list of fields a file must have and the values each field may take.
- The plan template is a JSON file the AI writes anywhere outside the evidence bundle. It holds only the content of the plan.
- The plan record,
plan.json, is written bypaqad-ai plan compile <template.json>into the change's bundle at.paqad/ledger/feature-evidence/<change>/plan.json. paqad adds identity fields and a content hash, rejects any field it does not know, and then deletes the template.
Nobody should edit plan.json by hand. The content hash makes a hand edit detectable, and the AI is instructed never to write into a bundle folder.
Template fields
| Field | Required | Contents |
|---|---|---|
summary | Yes | A non-empty description of the approach. |
title | No | A readable title. If the bundle still has a generic name, paqad renames it from the title. |
steps | No | Ordered steps, each with id and description, and optionally module, classification and files. |
modules_touched | No | Module names the change affects. |
decisions | No | IDs of decision pauses (D-…) the plan depends on. |
risks | No | Each with a description and a mitigation. |
reuse | Yes, in practice | What was checked before building. plan compile refuses a template without it. |
Step fields in more detail:
classificationis the verdict of thediff-minimizerskill:ac-satisfying(meets an acceptance criterion),necessary-setup,scaffoldingorover-build.fileslists the files the step expects to touch, as project-relative paths. paqad uses it to tell a frontend change apart before any code exists.
The reuse section
The reuse object has three lists.
| List | Each entry |
|---|---|
consulted (at least one entry) | source, query, hits, and optionally target (the package@version checked). source is one of existing-surface, index-query, reuse-catalog, module-doc, grep, framework-api, framework-docs. |
reusing (may be empty) | symbol and how, optionally file. A library symbol also sets package and version. paqad records a provenance value (asserted, unknown-dynamic or doc-derived) from its own index where it can. |
new_constructs (may be empty) | name and justification, optionally framework_checked with package, nearest and a verdict of reuse, extend, insufficient or absent. |
An example template
{
"title": "Relative dates on the invoice list",
"summary": "Show 'due in 3 days' style dates using the existing date helpers.",
"steps": [
{ "id": "S1", "description": "Extend formatIsoDate callers in the invoice list",
"module": "invoices", "files": ["src/invoices/list.tsx"] },
{ "id": "S2", "description": "Add a new helper for relative dates",
"module": "shared", "files": ["src/utils/dates.ts"] }
],
"modules_touched": ["invoices", "shared"],
"decisions": [],
"risks": [
{ "description": "Time zones shift the day boundary",
"mitigation": "Compute in the user's zone, cover with a test" }
],
"reuse": {
"consulted": [{ "source": "index-query", "query": "date format", "hits": 2 }],
"reusing": [{ "symbol": "formatIsoDate", "file": "src/utils/dates.ts", "how": "call as-is" }],
"new_constructs": [{ "name": "formatRelativeDate",
"justification": "No existing helper handles the relative form" }]
}
}
Step S2 says "add a new helper", so the template must declare a new construct. Leaving new_constructs empty would make plan compile refuse it.
What plan compile adds to the record
The stored plan.json contains everything above plus schema_version (1), doc_type (paqad.plan), issue, slug and ulid (taken from the bundle folder name, never from the AI), created_at, updated_at and content_hash.
What blocks a compile and what only warns
| Result | Cause |
|---|---|
| Refused | No summary; no reuse section; empty consulted; a reused symbol not in the code index; a library claim without version or for a package not in your stack snapshot; a library symbol that is missing or deprecated at that version; a create step with no new construct; a new construct without a framework check when a framework is detected; any field outside the schema; no active change (run paqad-ai stage start planning first). |
| Warning only | The code index, stack snapshot or framework index has not been built, so a claim could not be verified. |
All of these are machine checks. Whether the plan is a good plan remains the AI's judgement, and the reuse-or-create choices it cannot settle come to you as decision pauses (see Planning a change).
Where acceptance criteria and invariants live now
The plan says how the work will be approached. What the change must do lives in the frozen spec, specification.json, in the same bundle. Each acceptance criterion has an ID such as AC-1, given, when and then statements, and a proof_type of automated, manual or visual. Each invariant, a rule that must stay true, has an ID such as INV-1 and a confirmed flag. See From plan to frozen spec.
Earlier documentation described a YAML planning manifest with requirement_graph, execution_slices, verification_matrix, decision_log, doc_targets and regression_watch. Those types still exist inside the package, but in 1.91.1 no command or hook writes such a manifest.