UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

154 lines (110 loc) • 18.8 kB
# Feature Development Workflow ## Purpose Define the exact stages to follow whenever the user asks to create or change code, so the AI runs the full structured feature-development workflow instead of editing ad hoc. This is the same kind of workflow as `pentest` or `design-test`: it has stages, and every stage is mandatory. ## Trigger Run this workflow whenever the user's request is an **intent to create or change code**, however it is phrased. This is triggered by intent, not by a fixed keyword list. Examples that all trigger it: - "build X", "make X", "create X", "add X", "implement X", "wire up X" - "fix X", "change X", "update X", "refactor X", "rework X", "rename X" - "let's do X", "can you do X", "get X working", and any equivalent If the request would result in an `Edit`, `Write`, or new file in the codebase, it is a code change and this workflow applies. Do **not** improvise. Do **not** jump straight to editing code. Always run the stages below **in order**, and run **every** stage. Skipping a stage (no spec, no review, no checks, no doc-sync) invalidates the workflow and the change cannot be trusted as done. ## Authoritative source The definition of these stages is paqad's own policy in `src/pipeline/feature-development-policy.ts` plus the team's additions in `.paqad/configs/feature-development/` (delivery conventions live in the team's `.paqad/configs/git-workflow/` files). A team changes the workflow in `.paqad/configs/feature-development/`. A stage agent gets its stage's `read:` list and instructions in its brief; `paqad-ai stage brief <stage> --full` prints every one of them, and `paqad-ai config show` prints every setting. The stages below are the mandatory sequence; the YAML carries the authoritative per-stage `strictness`, `escalation`, and gate flags. Where the YAML and this rule differ, the YAML wins. Honor every stage's escalation flag through the Decision Pause Contract: - `stop` → write a Decision Packet and wait for the user before continuing. - `ask` → ask the user and wait for the answer. - `warn` → surface a note and continue. - `defer` → raise the Decision Packet with its change set, keep working, and ask it in the change's one spec question round. Enforcement tiers: an escalation is honoured on one of two tiers, and this contract never claims script enforcement it does not have. SCRIPT-ENFORCED: `checks` `block_on_failure` (`on_failure: block` in `checks.yaml`), because `paqad-ai checks run` exits non-zero on any red command and the completion gate reads its report, and missing mandatory stages, which the stage-evidence completion gate catches. AGENT-RAISED -> DECISION-PAUSE-ENFORCED: `review_findings: stop` in review and `stale_docs: stop` in documentation_sync. These need judgment to raise, so no script detects them; once raised as a `stop`, the Decision-Pause gate holds edits until the packet resolves. ## Announce each stage Make the workflow visible. As you enter each stage, tell the developer where you are, in the paqad voice defined by the narration contract (loaded with the framework). Emit one compact `▸ paqad` status line as you begin each stage, in plain language. Translate the stage into something a person understands; never print the internal stage key. This is required, for two reasons: the developer sees the framework working for them, and the running commentary is the live signal that the workflow is actually being followed, stage by stage. The wording is yours; the cadence is fixed (one line as you start each stage, never a paragraph, never on every action inside a stage). For example: - planning → `▸ paqad · planning this out, checking which module it touches` - specification → `▸ paqad · writing the spec before any code` - development → `▸ paqad · building it to the spec` - review → `▸ paqad · reviewing the change for regressions` - checks → `▸ paqad · running the gates: format, tests, build, rules` - documentation_sync → `▸ paqad · syncing the docs this change touched` - delivery → `▸ paqad · delivering per the branch and PR conventions` Follow the narration contract's plain-English translations and glyph meanings. When a stage hits a `stop` or `ask` escalation, surface it through the Decision Pause Contract (a pause is its own narration moment), not a status line. ## Workflow Stages Run these in order. Depth scales with the change (a trivial change has a one-line spec and a quick review), but no stage is omitted. Announce each stage as you enter it (see "Announce each stage" above). ### Stage 0 — ticket_intake (optional bookend) - Runs only when the request references a tracker ticket (Jira `PROJ-123`, GitHub `#123`). It is an **optional bookend** — a change that did not start from a ticket skips it and is never blocked. - **Fetch the real ticket deterministically**, don't guess from the id: `paqad-ai intake fetch <ref>` pulls the actual title, body, acceptance notes, and labels (GitHub via `gh`; Jira via the Atlassian MCP in-session). Ground the specification in that real text so each acceptance criterion traces back to the ticket. - Detect implicit choices the ticket leaves open and resolve them priors-first, then rules-second, then ask. Auto-resolved decisions must be surfaced for confirmation per `process.intake_decisions.confirm_auto_resolutions`; never bypass the user silently. - Requirement confirmation is a genuine user decision — route it through the Decision Pause Contract (`intake.requirement`), never auto-accept. - Escalations: `missing_ticket: warn`, `unresolved_decisions: stop`. ### Stage 1 — planning - Load the canonical module and instruction docs for the area before planning (`docs/modules/**`, `docs/instructions/**`). - Run the **attribution gate**: identify which module(s) the change belongs to (the `module-attribution-extractor` then `module-attribution-inferencer` skills). When the inferencer is confident (top score 0.5 or more), apply its top choice with no packet and record it as auto-applied, with its score, in the plan's `reuse.consulted`. When it is not confident, never guess silently: keep planning with no-attribution as a provisional label and raise the attribution packet (change set) for the spec question round (`attribution_pending: defer`). - Produce the implementation sequence scoped to the request and the current repository state. - Compile the plan into the feature bundle with `paqad-ai plan compile <plan-template.json>`: it writes the rigid `plan.json` into the active feature's bundle (`.paqad/ledger/feature-evidence/<change>/plan.json`) — the durable planning artifact. **Always include the change's `title` (with its ticket ref, e.g. `fix(PROJ-403): …`) in the plan template**: a change opened by a bare `paqad:stage planning start` is minted as the generic `change-<ULID>`, and the titled compile is what renames the bundle to its descriptive `[<issue>-]<slug>-<ULID>` name. End the planning stage against **that** file (`paqad:stage planning end -- <path-to-plan.json>`, or `paqad-ai stage end planning --artifact <path-to-plan.json>`) — the compile prints the (possibly renamed) path to use. Do **not** hand-write the plan to `.paqad/plans/*` or any other location: only the bundle's `plan.json` is the artifact, and a stage-end pointing anywhere else records inconclusive. - Before compiling the plan, check the Existing surface section and/or run `paqad-ai index query <name>`. The plan must record what you checked (`consulted`), what you will reuse (`reusing`), and must justify anything new (`new_constructs`). A plan template without a `reuse` section does not compile, so a change can never quietly rebuild something the project already has. First-party claims are checked against the code-knowledge index (an unknown symbol fails with the nearest match); when a framework is detected, a new construct must also record the framework check, and a framework-native claim must name the installed version. A framework-native claim is additionally verified against the declarations shipped inside the installed package (`paqad-ai index framework-api build`): a symbol that does not exist at that version fails with the nearest match, and one carrying a static `@deprecated` marker fails citing the deprecation message — so a plan can never adopt an API that was already removed or is already on its way out. A dynamically-provided member warns rather than failing, and a missing index, stack snapshot, or framework-api index downgrades the check to a warning rather than blocking. - The filled plan template looks like this — `summary` is required, and `reuse` is required: ```jsonc { "title": "feat(PROJ-357): short change title with its ticket ref", "summary": "What this change does, in one or two sentences.", "steps": [{ "id": "s1", "description": "wire the router", "module": "pipeline" }], "modules_touched": ["pipeline"], "decisions": ["D-01J…"], // resolved decision-packet ids this plan depends on "risks": [{ "description": "…", "mitigation": "…" }], "reuse": { "consulted": [ // ≥1 entry — what you actually checked { "source": "index-query", "query": "date formatting", "hits": 2 }, ], "reusing": [ // may be empty { "symbol": "formatIsoDate", "file": "src/utils/dates.ts", "how": "call as-is" }, ], "new_constructs": [ // every NEW exported construct, justified { "name": "formatRelativeDate", "justification": "no existing helper handles the relative form; nearest is formatIsoDate (checked)", }, ], }, } ``` `consulted[].source` is one of `existing-surface`, `index-query`, `reuse-catalog`, `module-doc`, `grep`, `framework-api`, `framework-docs`. A framework-native reuse claim adds `package` and the resolved `version` (for example `{ "symbol": "Str::of", "package": "laravel/framework", "version": "10.48.2", "how": "use Str::of()->slug() instead of a hand-rolled slugger" }`); when a framework is detected, a new construct adds `framework_checked: { package, nearest, verdict }` with `verdict` one of `reuse`, `extend`, `insufficient`, `absent`. - Never write anything into a feature bundle directory (`.paqad/ledger/feature-evidence/<change>/`). It holds only its rigid, script-written artifacts (`plan.json`, `specification.json`, `review.json`, the ledgers, `delivery.json`, `receipt.json`, `ai-bom.json`) plus the generated `report.html`. Author your plan template, spec markdown, and review template anywhere else — the compile/freeze/record verbs put the rigid record in the bundle for you and delete the transient input. A stage-end artifact pointing at a non-rigid file inside a bundle directory is rejected. - Escalations: `attribution_pending: defer`, `rule_scripts_stale: ask`, missing docs/design-system: `warn`. ### Stage 2 — specification - Write or refine the feature specification **before** implementation. The spec carries the behavior, acceptance criteria (AC-n, given/when/then, proof type), and confirmed invariants. - On graduated/full lanes the spec must be **frozen and signed off** before development (`require_spec_signoff`, framework-owned, cannot be downgraded by a project override). A mid-build goal change or a work-vs-spec contradiction escalates via the Decision Pause Contract (`spec.change` / `spec.contradiction`). - Freeze it with `paqad-ai spec freeze <spec-file> --signed-off-by <name> --confirm-invariants`: it evaluates the freeze blockers and, on a clean spec, writes the frozen `specification.json` into the active feature's bundle (`.paqad/ledger/feature-evidence/<change>/specification.json`) — the durable record later stages check against. It refuses to freeze over open questions, missing acceptance criteria, or unconfirmed invariants. End the specification stage against **that** `specification.json` (`paqad:stage specification end -- <path-to-specification.json>`, or `paqad-ai stage end specification --artifact <path-to-specification.json>`). Do **not** hand-write the spec to `.paqad/specs/*` or any other location as the durable artifact: only the bundle's `specification.json` is the artifact, and a stage-end pointing anywhere else records inconclusive. - Escalations: `missing_spec: stop`, `missing_spec_signoff: stop`. ### Stage 3 — development - Implement only the requested behavior. Do not refactor, reformat, or rename unrelated code in the same change. - If scope grows beyond the spec, escalate `scope_expansion: ask` before expanding. ### Stage 4 — review - Review the change against correctness, regressions, and rollback risk before treating it as complete. - Before writing findings, run `paqad-ai review digest` and read `.paqad/session/review-digest.md`. It is machine-built from cached evidence in under a second and costs zero model tokens: the changed files and per-stage state, the frozen acceptance criteria, every machine finding (rule scripts, duplication, checks, failing gates), and an honest list of the blind spots no machine covers. - Each finding must either reference a digest row or be explicitly marked judgment-only. Confirm or contest every deterministic row — an unaddressed deterministic finding is itself a review finding. The review gate re-derives the deterministic, file-anchored, high-severity rows and **fails** when the recorded review never cites one by `file:line`, so skipping the digest does not disarm the check. - Record the review with `paqad-ai review record <review-template.json>`: it writes the rigid `review.json` into the active feature's bundle (`.paqad/ledger/feature-evidence/<change>/review.json`) from a filled template (`{ summary, verdict, findings, checked, rollback }`, where `verdict` is one of `safe-to-merge`, `needs-attention`, `inconclusive`). End the review stage against **that** file (`paqad:stage review end -- <path-to-review.json>`, or `paqad-ai stage end review --artifact <path-to-review.json>`). Do **not** hand-write review notes to a `.md` or any other location as the durable artifact: only the bundle's `review.json` is the artifact, and a stage-end pointing anywhere else records inconclusive. - Out-of-scope findings: when you find a real problem the change will not fix (a bug next to the change, a shortcut you had to take, a limit you chose to keep), record it with `paqad-ai debt add` instead of fixing it or leaving it in chat. A review finding you mark as deferred must name its TD id. - Blocking findings escalate `review_findings: stop`. ### Stage 5 — checks - Run the project command checks: `format`, `test`, `build` (use the project profile's mapped commands). `block_on_failure` is true — a failing gate stops forward progress; fix it before continuing. Run them deterministically with `paqad-ai checks run`: it executes the mapped commands, exits non-zero on any red, and persists a structured report the completion gate reads so success is proven, not assumed. - Before `paqad-ai checks run`, run `paqad-ai checks plan`. If it prints `test: sequential (unknown)`, run the test-runner-discovery skill and record its result with `paqad-ai checks record-runner <file>` before running the checks. Never ask the developer to add a parallel flag or install a package; a missing prerequisite is reported and the sequential command is used. - Verify test coverage meets the project bar. - Run the `rule_compliance` gate (registered rule scripts, `mode: strict`, scope `changed-files`); deterministic findings escalate `stop`. The gate needs `rule-script-map.yml`, generated at onboarding and refreshable with `paqad-ai rules compile`; without it enforcement fast-skips. Strictness comes from `enforcement` in the tracked `.paqad/configs/feature-development/rules.yaml` (flat name `rule_compliance`), which a personal or env value may only raise. - Run the `module-health` rollup; rollup-blocked metrics are informational `warn`. ### Stage 6 — documentation_sync - Sync the canonical docs affected by the change after verification passes (module docs, registries, design-system contract as the diff requires). - Out-of-scope findings: when you find a real problem the change will not fix (a bug next to the change, a shortcut you had to take, a limit you chose to keep), record it with `paqad-ai debt add` instead of fixing it or leaving it in chat. A review finding you mark as deferred must name its TD id. - Stale or missing doc updates escalate `stop`. ## Delivery When the change is delivered, follow the team's rules in `.paqad/configs/git-workflow/` (`tickets.yaml`, `branches.yaml`, `commits.yaml`, `pull-requests.yaml`, `enforcement.yaml`) through `paqad-ai deliver`: branch naming, the commit message template and summary style, PR title/body and ticket linkage, and the CI gate (`ci.gate`, `ci.on_red`). Change those files only through the `git-workflow` skill. When the developer did not say whether to commit and `commits.yaml` `ask_when_unspecified` is true, ask once at end-of-change and record the answer (or its absence) with `paqad-ai delivery-link commit-decision <commit|decline|ignored|user-requested>`, which writes the bundle's `delivery.json.commit_decision`. ## End-of-change: a complete evidence bundle A finished feature-development change must leave a **complete** evidence bundle. The `bundle-completeness` gate runs last (after every writer) and, under `bundle_completeness=strict` (the default), makes the change **Needs your attention** when a required bundle file is missing, empty, or invalid — naming the file and the verb that produces it. Required always: `feature.json`, `plan.json`, `specification.json`, `review.json`, `stage-evidence.jsonl`, `delivery.json`; the rest are required when their flag is on (see the manifest in `docs/modules/feature-evidence/index/summary.md`). `feature.json` must carry a real title or a ticket — a bundle left with the placeholder `change` title and no ticket fails. `warn` surfaces the gap without blocking; `off` falls back to the deprecated `evidence_existence_gate`. ## Rules - **Never skip a stage.** Every stage runs, in order, on every code change. A trivial change runs the stages lightly; it does not skip them. - The mandatory safety stages (specification sign-off, checks, documentation_sync) are framework-owned and cannot be downgraded by a project override. - Consult the workflow to decide the **lane** (how deep each stage goes), never to **omit** a stage. - Point at the project's copy of the workflow files and follow them; never inline a frozen copy of the steps that ignores project overrides. - Honor every escalation through the Decision Pause Contract — never resolve a `stop` silently.