From cairn-builder
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).
How this skill is triggered — by the user, by Claude, or both
Slash command
/cairn-builder:spec-updaterThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are amending an existing `app_spec.xml` while the orchestrator is (or has
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.jsonis append-only on the entry level. Existing entries are NEVER modified — neither description, steps, category, nor the order. The only field that ever changes ispasses(false → true) and occasionallyblocked(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.
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).initializer does.feature_list.json entries. Only append.ls app_spec.xml feature_list.json
cat templates/spec-template.xml # to know the section vocabulary
git status # are we mid-uncommitted-changes?
git log --oneline -10
jq 'length' feature_list.json # current feature count
jq '[.[] | select(.passes==true)] | length' feature_list.json # how many already pass
jq '[.[] | select((.blocked//false)==true)] | length' feature_list.json # how many blocked
If app_spec.xml doesn't exist, tell the operator they want /spec-author,
not this skill. Stop.
Ask the operator to be concrete. Useful prompts:
If they want to remove a feature, ask whether they want it:
<out_of_scope> in the spec; existing
feature_list.json entries left as-is (they'll just never pass).
Recommended; preserves history."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.
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.
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")descriptionsteps (3–10 typical, or 10+ for comprehensive)passes: falseGet confirmation. Encourage the operator to add or remove specific bullets before you write.
Edit app_spec.xml in place with the Edit tool. Strategy depends on the
change type:
</core_features>) and insert new content
before it. Preserve indentation.Edit that includes enough surrounding context to be
unambiguous.</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:
# Crude check: every opening tag has a closing tag of the same name.
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.
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 # should be old_count + number_of_additions
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)
"
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.
"blocked": true or move
them to <out_of_scope> in the spec.app_spec.xml as the source of truth; a broken file
cascades into wasted iterations.git log to orient — if your scope change isn't there, they'll be
confused.npx claudepluginhub bholzer/claude-cairn-builderCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.