| name | spec-updater |
| description | Amend app_spec.xml mid-build for scope changes without breaking the "features only flip false→true" invariant. Use when the operator says "also add X", "drop feature Y", "we need a nightly batch job for Z", or otherwise wants to change scope after the build has started. Edits app_spec.xml in place AND appends new features to feature_list.json (existing entries are never modified). |
| disable-model-invocation | false |
| allowed-tools | Read, Write, Edit, Bash, Glob |
spec-updater — mid-run scope changes
You are amending an existing app_spec.xml while the orchestrator is (or has
been) running. The hard rule of this harness is:
feature_list.json is append-only on the entry level. Existing entries
are NEVER modified — neither description, steps, category, nor the order.
The only field that ever changes is passes (false → true) and
occasionally blocked (added by the stuck-resolver). New features are
appended to the end of the array.
Your job is to enforce that rule while letting the operator change scope.
Operating principles
- Resolve ambiguity before editing. Get the operator to be specific
about what's being added, removed, or changed. Vague asks produce vague
features.
- Preserve XML structure. The spec uses the scaffold from
templates/spec-template.xml. Slot changes into the right top-level
section. If none fits, propose a new top-level section with a
snake_case tag (e.g., <background_work> wasn't in v1 of the spec but
the operator now wants a cron job).
- Confirm before writing. Show a diff of what you'll change before
touching files.
- Translate spec changes into feature-list changes. When core features
change, the test list must reflect it. Generate concrete test entries
the same way the
initializer does.
- Never edit existing
feature_list.json entries. Only append.
- Keep the XML well-formed. Every open tag has a matching close.
Validate after every edit.
Procedure
Step 1 — Get oriented
ls app_spec.xml feature_list.json
cat templates/spec-template.xml
git status
git log --oneline -10
jq 'length' feature_list.json
jq '[.[] | select(.passes==true)] | length' feature_list.json
jq '[.[] | select((.blocked//false)==true)] | length' feature_list.json
If app_spec.xml doesn't exist, tell the operator they want /spec-author,
not this skill. Stop.
Step 2 — Clarify the change
Ask the operator to be concrete. Useful prompts:
- "What's the change in one sentence — adding, removing, or modifying?"
- "Where does it fit? A new core-feature area? A constraint change? An out-of-scope addition?"
- "Are there user-visible behaviors I should add tests for? List them."
If they want to remove a feature, ask whether they want it:
- (a) deferred — added to
<out_of_scope> in the spec; existing
feature_list.json entries left as-is (they'll just never pass).
Recommended; preserves history.
- (b) blocked —
"blocked": true added to the corresponding
feature_list entries so the orchestrator skips them. Equivalent to
stuck-resolver's BLOCKED path but pre-emptive.
Do NOT offer to delete entries from feature_list.json. That violates the
invariant.
Step 3 — Identify the affected section(s)
Map the change to one of the spec's top-level sections:
| Change type | Section |
|---|
| New screen / surface | <ui_layout> |
| New visual treatment | <design_system> |
| New entity or field | <data_model> |
| New API surface | <api_endpoints> |
| Email magic-link login | <auth_and_sessions> |
| Nightly batch job, queue, cron | <background_work> |
| Third-party API or webhook | <external_integrations> |
| New feature area | new child of <core_features> |
| "Don't build social login" | <out_of_scope> |
| Compliance / multi-tenancy / etc. | custom top-level section |
If no existing top-level section fits — for example, the spec was UI-only
and the operator now wants async work — propose adding a new section with
a clear snake_case tag, following the template's pattern. Confirm with the
operator first.
Step 4 — Show a diff
Before writing, show the operator what you'll do. Two diffs:
A. app_spec.xml diff — what's being added / replaced. Show the
exact XML snippet that's being inserted or replaced, with enough
surrounding context to locate it. Get confirmation.
B. feature_list.json additions — list the new entries you'll append.
For each, include:
category ("functional" or "style")
description
steps (3–10 typical, or 10+ for comprehensive)
passes: false
Get confirmation. Encourage the operator to add or remove specific bullets
before you write.
Step 5 — Write the changes
XML edits
Edit app_spec.xml in place with the Edit tool. Strategy depends on the
change type:
- Adding content inside an existing section. Target the closing tag
of that section (e.g.,
</core_features>) and insert new content
before it. Preserve indentation.
- Replacing content inside a section. Read the section first, then
use a targeted
Edit that includes enough surrounding context to be
unambiguous.
- Adding a new top-level section. Insert it just before
</project_specification>. Match the indentation pattern of existing
sections.
After each edit, validate XML well-formedness:
xmllint --noout app_spec.xml 2>/dev/null && echo "VALID" || echo "INVALID — DO NOT COMMIT"
If xmllint is unavailable, fall back to:
python3 -c "import xml.etree.ElementTree as ET; ET.parse('app_spec.xml'); print('VALID')"
If validation fails, revert your edit and try again. Do not commit
invalid XML — the next coder reads this file as the build target.
feature_list.json appends
Use jq to append atomically rather than hand-editing JSON:
jq '. + [{
"category": "functional",
"description": "Nightly cron deletes records older than 90 days",
"steps": [
"Step 1: Insert 5 records with created_at older than 90 days",
"Step 2: Trigger the cleanup job manually",
"Step 3: Verify those records are gone and newer records remain"
],
"passes": false
}]' feature_list.json > feature_list.json.tmp && mv feature_list.json.tmp feature_list.json
For multiple features, build a JSON array of new entries first:
cat > /tmp/new_features.json <<'EOF'
[
{ "category": "functional", "description": "...", "steps": ["..."], "passes": false },
{ "category": "style", "description": "...", "steps": ["..."], "passes": false }
]
EOF
jq -s '.[0] + .[1]' feature_list.json /tmp/new_features.json > feature_list.json.tmp \
&& mv feature_list.json.tmp feature_list.json
Verify the result:
jq . feature_list.json > /dev/null && echo "VALID JSON"
jq 'length' feature_list.json
Step 6 — Commit
git add app_spec.xml feature_list.json
git commit -m "Spec update: <short summary>
- <bullet of what changed in app_spec.xml>
- Appended N new feature entries (indices X..Y)
"
Step 7 — Hand off
Tell the operator:
Spec updated. The orchestrator will pick up the new features on its next
iteration — no need to restart /build-app. The newly appended features
have indices N..M.
If the orchestrator is currently between iterations or paused, that's it.
If the operator has already used .ABORT to stop the loop, remind them to
remove .ABORT and re-invoke /build-app.
Anti-patterns (don't do these)
- Don't edit existing feature_list.json entries — not description, not
steps, not category, not passes (except via the coder/resolver agents),
not order.
- Don't delete features from the list. Use
"blocked": true or move
them to <out_of_scope> in the spec.
- Don't restructure unrelated parts of the spec. Make minimum-diff
edits — preserving the template's section order and any existing
whitespace patterns.
- Don't commit invalid XML. Always validate after editing. The next
coder reads
app_spec.xml as the source of truth; a broken file
cascades into wasted iterations.
- Don't reformat the whole file to "normalize" it. Keep diffs small
and targeted.
- Don't add features without asking what tests would prove they work.
An untested feature is a feature the orchestrator can't make progress on.
- Don't bypass git. Every spec change is a commit. The next coder reads
git log to orient — if your scope change isn't there, they'll be
confused.