aw-spec-cli 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/assets/.appweaver/ORCH-DIALECT.md +17 -5
  2. package/assets/.appweaver/orchestrator/README.md +1 -1
  3. package/assets/.appweaver/orchestrator/adapters/claude_agent_sdk.py +2 -2
  4. package/assets/.appweaver/orchestrator/adapters/google_antigravity.py +28 -6
  5. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +13 -1
  6. package/assets/.appweaver/orchestrator/orchestrator.py +126 -39
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +5 -0
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +1 -0
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +1 -0
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +888 -141
  11. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +826 -16
  12. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +780 -12
  13. package/assets/.appweaver/stack-profiles/java-spring.json +3 -3
  14. package/assets/.appweaver/templates/spec-template.md +73 -62
  15. package/assets/.appweaver/templates/story-template.md +107 -0
  16. package/assets/guardrails/technology/java-spring/ai-rules.md +505 -0
  17. package/assets/guardrails/technology/java-spring/api-versioning.md +782 -0
  18. package/assets/guardrails/technology/java-spring/authorization.md +618 -0
  19. package/assets/guardrails/technology/java-spring/clean-architecture.md +960 -0
  20. package/assets/guardrails/technology/java-spring/coding-standards.md +821 -0
  21. package/assets/guardrails/technology/java-spring/dependency-policy.md +91 -0
  22. package/assets/guardrails/technology/java-spring/domain-driven-design.md +467 -0
  23. package/assets/guardrails/technology/java-spring/domain_abstraction.md +424 -0
  24. package/assets/guardrails/technology/java-spring/folder-structure.md +1537 -0
  25. package/assets/guardrails/technology/java-spring/global_exception_handler.md +247 -0
  26. package/assets/guardrails/technology/java-spring/logging.md +215 -0
  27. package/assets/guardrails/technology/java-spring/modular-monolith.md +77 -0
  28. package/assets/guardrails/technology/java-spring/persistence.md +316 -0
  29. package/assets/guardrails/technology/java-spring/problem_details.md +98 -0
  30. package/assets/guardrails/technology/java-spring/result_mapping.md +48 -0
  31. package/assets/guardrails/technology/java-spring/technology-constitution.md +168 -0
  32. package/assets/guardrails/technology/java-spring/testing.md +601 -0
  33. package/assets/guardrails/technology/java-spring/zero-trust.md +210 -0
  34. package/dist/index.js +0 -0
  35. package/dist/utils/config.d.ts +22 -0
  36. package/dist/utils/config.d.ts.map +1 -0
  37. package/dist/utils/config.js +76 -0
  38. package/dist/utils/config.js.map +1 -0
  39. package/dist/utils/copy-assets.d.ts.map +1 -1
  40. package/dist/utils/copy-assets.js +4 -11
  41. package/dist/utils/copy-assets.js.map +1 -1
  42. package/dist/utils/paths.d.ts.map +1 -1
  43. package/dist/utils/paths.js +11 -7
  44. package/dist/utils/paths.js.map +1 -1
  45. package/package.json +2 -1
  46. package/assets/guardrails/technology/java-spring/README.md +0 -62
  47. package/dist/commands/update.d.ts +0 -3
  48. package/dist/commands/update.d.ts.map +0 -1
  49. package/dist/commands/update.js +0 -142
  50. package/dist/commands/update.js.map +0 -1
  51. package/dist/utils/manifest.d.ts +0 -31
  52. package/dist/utils/manifest.d.ts.map +0 -1
  53. package/dist/utils/manifest.js +0 -91
  54. package/dist/utils/manifest.js.map +0 -1
  55. package/dist/utils/update-engine.d.ts +0 -52
  56. package/dist/utils/update-engine.d.ts.map +0 -1
  57. package/dist/utils/update-engine.js +0 -203
  58. package/dist/utils/update-engine.js.map +0 -1
@@ -6,47 +6,54 @@
6
6
  # NOT guess or create the feature — it CONSUMES the active one, and is phase-pure
7
7
  # (spec side only; the single design.md is the `design` phase).
8
8
  #
9
+ # specify writes ONE artifact: spec.md, plus the Change History row that records what
10
+ # it wrote. It does not touch stories/. The spec is the CONTRACT; a story is an
11
+ # execution SLICE over it, and the two directions between them each have their own
12
+ # workflow — /appweaver update-stories refreshes the backlog after the spec moves,
13
+ # /appweaver update-spec folds a hand-edited story back into the contract. Keeping
14
+ # specify story-blind is what stops the spec->story rule having two implementations.
15
+ #
9
16
  # Auto-detects first-vs-subsequent from spec.md:
10
- # • BOOTSTRAP (no spec.md) → author the MASTER spec (US-1) + init active_version.json.
11
- # • DELTA (spec.md exists) → delegate to the delta-spec module: open a change (cr-setup)
12
- # and author the delta-spec ONLY. The master spec.md is left UNTOUCHED — the change
13
- # stays OPEN; finalize-delta merges the delta into spec.md + design.md, and the
14
- # change is not closed until close-delta at the end of the chain.
15
- # (The former standalone `cr` capture's spec half now lives here.)
17
+ # • BOOTSTRAP (no spec.md) → author the spec from the template + init active_version.
18
+ # • EXTEND (spec.md exists) → edit spec.md IN PLACE: add the requirements the input
19
+ # describes, and amend the ones it changes. No delta document, no open change, no
20
+ # merge — the spec is the living truth and this is a write to it.
21
+ #
22
+ # Both paths then run ONE converge block — checklist + clarify + validate — over
23
+ # spec.md, and both finish by re-baselining the ledger hash.
16
24
  #
17
- # Both paths then run ONE converge block — checklist + clarify + validate — over the
18
- # document THIS invocation authored, never the other one:
19
- # • BOOTSTRAP → the master spec, graded by the spec checklist.
20
- # • DELTA → the delta-spec, graded by a DELTA-SHAPED checklist written inside
21
- # changes/<cr-id>/. The master is not read, not graded, NOT written.
22
- # The pairing is bound from create-checklist's own output (checklist_file + target →
23
- # quality_target), so the checklist and the document it grades can never diverge.
24
- # Why it matters: the delta is authored relative to the master, so a "quality" rewrite
25
- # of the master mid-change would leave the delta computed against a stale baseline —
26
- # and in brownfield it would silently edit an adopted, code-derived spec that nothing
27
- # downstream re-checks for fidelity. Narrow rewrites run include_standards:false.
28
- # Accuracy of the delta AGAINST the master (id continuity, conflicts) stays where it
29
- # belongs: /appweaver analyze-delta-design.
25
+ # THE LEDGER. spec.md's `## Change History` is the sync anchor between the contract and
26
+ # its backlog: each row names the requirement ids that version touched, and each story
27
+ # records the version it last synced at, so "is this story stale" is one integer
28
+ # comparison. Three properties of how this workflow writes it are load-bearing:
30
29
  #
31
- # Forward paths:
32
- # NEW : specify → design → analyze-design → implement
33
- # CHANGE : specify (delta-spec) → design (delta-design) → analyze-delta-design →
34
- # implement-delta → build → finalize-delta → analyze-slice → close-delta
30
+ # 1. The row is COMPUTED, not declared, on BOTH paths. An extend snapshots spec.md before
31
+ # authoring and diffs requirement ids after the converge loop has finished with it; a
32
+ # bootstrap diffs the finished document against nothing, which yields every id it
33
+ # carries and is exactly the v1.0 row's content. A row the model declared could omit a
34
+ # requirement the quality loop rewrote on its way past, and an omitted id is silent —
35
+ # update-stories would never learn it moved.
36
+ # 2. The row and the hash are written by ONE call (ledger-append), so a row can never
37
+ # exist without the hash that proves the body it describes.
38
+ # 3. The ledger is checked on ENTRY, not only stamped on exit. A spec hand-edited with
39
+ # no row is the ledger's one silent failure, and running specify over the top would
40
+ # bury that edit under a version bump that never named it.
35
41
  #
36
- # Launch: /appweaver specify [strategy] <what the feature/change does>
42
+ # Launch: /appweaver specify [strategy] <what the feature needs, or what changes>
37
43
  # e.g. /appweaver specify Customers place an order for one or more items
38
44
  # Prereq: /appweaver select-feature (sets the active aggregate)
45
+ # Next: /appweaver update-stories (turns the new/changed requirements into stories)
39
46
  # =============================================================================
40
47
 
41
48
  name: aw-spec-specify
42
- description: Author the specification of the ACTIVE feature (aggregate). First call writes the master spec (US-1); a later call (spec.md exists) delegates to the delta-spec module to author a delta-spec ONLY (master spec.md untouched — finalize-delta merges it) and leaves the change OPEN. The checklist/clarify/validate loops grade whichever document the call authored, using a delta-shaped checklist on the change path. Spec phase only — design is separate.
43
- version: 2.3.0
49
+ description: Author or extend the specification of the ACTIVE feature (aggregate). The first call writes spec.md from the template; a later call edits spec.md in place, adding the requirements the input describes and amending the ones it changes. Both paths run the checklist/clarify/validate converge loop over spec.md. An extend appends a Change History row whose requirement ids are COMPUTED from a before/after diff, and re-baselines the ledger hash. Writes no stories — /appweaver update-stories owns the backlog. Spec phase only; design is separate.
50
+ version: 3.4.0
44
51
  agent_agnostic: true
45
52
  dialect: aw-spec.orchestrator/v1
46
53
 
47
54
  inputs:
48
55
  user_input:
49
- description: What the feature (first call) or the change (later calls) should do, in natural language.
56
+ description: What the feature should do (first call), or what is being added or changed (later calls), in natural language.
50
57
  required: true
51
58
  bind: USER_INPUT
52
59
 
@@ -74,14 +81,44 @@ vars:
74
81
  spec_all_pass: "false" # validation-loop guard, flipped by the validate query
75
82
  checklist_total: "0" # bound by checklist-read
76
83
  checklist_passed: "0" # stays 0 when the checklist has no items to score
77
- cr_id: "" # set on the DELTA path only
78
- cr_dir: "" # set on the DELTA path only — changes/<cr-id>/
79
- quality_target: "" # the document the converge loops grade AND rewrite:
80
- # spec.md (bootstrap) | delta-spec.md (delta). Bound from
81
- # create-checklist's own output so it always agrees with
82
- # the checklist. NEVER hand-set to spec_file on the delta
83
- # path — that is what silently drifts an adopted spec.
84
84
  version: "" # current active_version.json version (for the report)
85
+ ledger_ok: "true" # entry tripwire result; the bootstrap path has no ledger to check
86
+ ledger_state: "bootstrap" # clean | unstamped | ledgered_edit | unledgered_edit | rollback | delta_open
87
+ ledger_error: ""
88
+ open_change: "" # a delta change open on this feature — reported, and only
89
+ # blocking once its delta-spec has actually been authored
90
+ snapshot: "" # pre-authoring copy of spec.md; the diff's "before"
91
+ changed_csv: "" # the ids this run actually touched — what the ledger row records
92
+ story_ids_csv: "" # changed, narrowed to FR-/BR- — the only classes a story can cite
93
+ removed_csv: "" # ids this run DELETED — every story citing one is orphaned (E1)
94
+ modified_csv: "" # ids amended in place, as distinct from added or removed
95
+ disturbing_csv: "" # modified ∪ removed — the ids an existing story could feel
96
+ change_count: "0" # 0 on an extend that changed no requirement: no row is written
97
+ ledger_version: "" # the version the row was written at
98
+ covered_csv: "" # ids with OPEN stories, read before authoring as a preference
99
+ covered_note: "" # that preference, WORDED for the case at hand — see ext_covered_*
100
+ disturbed_any: "false" # did this run move a requirement someone is building against
101
+ disturbed_csv: "" # which of them
102
+ disturbed_ids: "" # the same, as a list — the options of the per-requirement ask
103
+ disturbed_stories: "" # the open stories citing those ids
104
+ disturbed_flying: "" # the subset already past story-spec — a follow-on, not an edit
105
+ may_change: "" # the requirements the author approved for amendment
106
+ approved_csv: "" # the same, normalised
107
+ preserve_csv: "" # disturbed minus approved — restored and re-expressed additively
108
+ preserve_count: "0" # 0 = everything was approved, so nothing is re-authored
109
+ marker_count: "0" # [NEEDS CLARIFICATION] markers, counted before the model is asked
110
+ clarifications: [] # what the model made of them; empty when there were none to make
111
+ reissued_csv: "" # ids this run re-used after the ledger had already spent them
112
+ held_csv: "" # the preserved ids the restore actually put back
113
+ violated_csv: "" # the preserved ids it did not — amended without approval
114
+ quality_accept: "" # the author's answer when the spec ships still failing
115
+ readdit_pass: "false" # guard for the restore's own quality loop
116
+ readdit_failing: "" # what that loop found wanting in the requirements it minted
117
+ readdit_verdict: "true" # that loop's verdict, or "nothing was re-authored" when it never ran
118
+ readdit_accept: "" # the author's answer when the re-authored spec ships still failing
119
+ boot_row_version: "" # non-empty once the v1.0 row has actually been written
120
+ stamp_hold: "false" # true = do not re-baseline the hash; the document is unfinished
121
+ uncovered_csv: "" # requirements with no story at all — nobody is building these
85
122
 
86
123
  workflow:
87
124
 
@@ -95,6 +132,11 @@ workflow:
95
132
  - { name: feature_dir, from: "$.feature_dir" }
96
133
  - { name: spec_file, from: "$.spec_file" }
97
134
  - { name: spec_exists, from: "$.spec_exists" }
135
+ # needs_bootstrap = no spec.md, OR a first run that never finished. version-init raises
136
+ # a marker and only ledger-ids lowers it, so a bootstrap that died after writing spec.md
137
+ # is RE-RUN rather than mistaken for a spec to extend (which re-applied the same input
138
+ # over an unfinished v1.0 and bumped it to v1.1).
139
+ - { name: needs_bootstrap, from: "$.needs_bootstrap" }
98
140
 
99
141
  - id: require_feature
100
142
  action: verify_context
@@ -134,23 +176,30 @@ workflow:
134
176
  hooks_from_variable: before_hooks
135
177
 
136
178
  # ═══════════════════════ BOOTSTRAP — no spec.md yet ═══════════════════════
137
- # Copy the spec template into the (already-existing) feature dir + init version.
138
- - id: boot_scaffold
139
- condition: "{{spec_exists}} == false"
179
+ # Init the version state, THEN copy the spec template into the (already-existing)
180
+ # feature dir. That order is the point: --bootstrap raises the unfinished-first-draft
181
+ # marker, and raising it BEFORE spec.md exists means no crash can leave a document on
182
+ # disk that the next fresh run mistakes for a spec to extend. Scaffolding first left a
183
+ # window of exactly two calls wide in which a bare template was on disk with no marker
184
+ # beside it — and a bare template is `unstamped`, which the entry tripwire passes.
185
+ # --bootstrap is specify's, not the primitive's: adopt inits the same file after
186
+ # reversing a real spec out of code, and must not be marked unfinished.
187
+ - id: boot_version_init
188
+ condition: "{{needs_bootstrap}} == true"
140
189
  action: execute_script
141
190
  script: { ref: actions }
142
- subcommand: scaffold
143
- args: ["--feature-dir", "{{feature_dir}}", "--allow-existing"]
191
+ subcommand: version-init
192
+ args: ["--feature-dir", "{{feature_dir}}", "--bootstrap"]
144
193
 
145
- - id: boot_version_init
146
- condition: "{{spec_exists}} == false"
194
+ - id: boot_scaffold
195
+ condition: "{{needs_bootstrap}} == true"
147
196
  action: execute_script
148
197
  script: { ref: actions }
149
- subcommand: version-init
150
- args: ["--feature-dir", "{{feature_dir}}"]
198
+ subcommand: scaffold
199
+ args: ["--feature-dir", "{{feature_dir}}", "--allow-existing"]
151
200
 
152
201
  - id: boot_ctx
153
- condition: "{{spec_exists}} == false"
202
+ condition: "{{needs_bootstrap}} == true"
154
203
  action: build_context
155
204
  context:
156
205
  - "{{spec_file}}" # the copied template = section structure
@@ -173,18 +222,25 @@ workflow:
173
222
  # no file and no ux/ rules mapped, the section resolves to
174
223
  # "None" and the spec is unchanged from a backend-only one.
175
224
  - id: boot_generate
176
- condition: "{{spec_exists}} == false"
225
+ condition: "{{needs_bootstrap}} == true"
177
226
  action: agent_author
178
227
  output_target: { type: file, path: "{{spec_file}}" }
179
228
  task: |
180
- Author the MASTER specification for this aggregate by filling out the
181
- template in context. This is the first user story, US-1. Base everything on:
229
+ Author the specification for this aggregate by filling out the template in
230
+ context. Base everything on:
182
231
 
183
232
  {{USER_INPUT}}
184
233
 
185
234
  Rules for the content you generate:
186
- - The feature IS the aggregate; set the Aggregate field and keep US-1's id.
235
+ - The feature IS the aggregate; set the Aggregate field.
187
236
  - Preserve the template's section order and headings.
237
+ - THIS DOCUMENT HOLDS NO USER STORIES. Every operation on the aggregate
238
+ (CreateOrder, UpdateOrder, …) is a REQUIREMENT (FR-n) here — never a user
239
+ story, never an "As a … I want …" block, never a Given/When/Then acceptance
240
+ scenario, and never a section for any of those. A story is an execution slice
241
+ that lives beside this file in stories/ and cites the ids below; it is written
242
+ by /appweaver update-stories, not here. Design and code trace to the ids in
243
+ this document, which is what lets the backlog churn without disturbing them.
188
244
  - SPECIFY IS THE ONLY PHASE THAT READS THE DOMAIN RULES. Design and implement
189
245
  work from the spec/design, never the domain docs — so this spec is the SOLE
190
246
  carrier of the domain into the pipeline. Model the domain COMPLETELY here, or
@@ -205,7 +261,10 @@ workflow:
205
261
  but never how the adapter is built.
206
262
  - Write for non-technical business stakeholders.
207
263
  - Write System Requirements in EARS as the template shows (THE SYSTEM SHALL
208
- / WHEN…/ IF…THEN / WHILE…/ WHERE…) — each atomic and testable.
264
+ / WHEN…/ IF…THEN / WHILE…/ WHERE…) — each atomic and testable. An EARS
265
+ requirement IS its own acceptance criterion; do not add a separate one.
266
+ - Give every requirement a STABLE id, numbered from 001 in its own class. Ids are
267
+ permanent: they are what stories cite and what design traces to.
209
268
  - Capture always-true BUSINESS invariants as Business Rules (BR-n), and state
210
269
  the aggregate boundary (what it owns vs references by id, and out of scope).
211
270
  Record reasonable defaults in Assumptions.
@@ -238,91 +297,253 @@ workflow:
238
297
  standard that governs every such behaviour is a UX-n.
239
298
  Where neither a UI constitution nor ux/ rules are in context there is nothing to
240
299
  lift: write "None (as of v1.0)" in both — never delete either section.
241
- - This is v1.0: fill the initial Change History row's date and keep US-1's
242
- "Added: v1.0" marker.
300
+ - This is v1.0: leave the initial Change History row's version, date and
301
+ "Requirements changed" cell as the template has them. The kit rewrites that row
302
+ from the finished document, so ids listed here are discarded — the same rule that
303
+ makes every later version row an observation rather than a claim. Leave its Stories cell as "—";
304
+ update-stories fills that in when it raises the backlog.
305
+ - LEAVE THE "## Story Coverage" BLOCK ALONE, placeholder row and all. Everything
306
+ between the AW:COVERAGE markers is generated from the stories/ directory after
307
+ this step; anything written there by hand is discarded at the next write.
243
308
  - ALWAYS include a "## Success Criteria" section with 2-4 measurable,
244
- technology-agnostic, verifiable SC-nnn outcomes derived from the acceptance
245
- criteria. It is MANDATORY — never drop it; when a metric must be inferred,
246
- state a reasonable, testable target rather than omitting the section.
309
+ technology-agnostic, verifiable SC-nnn outcomes derived from the requirements.
310
+ It is MANDATORY — never drop it; when a metric must be inferred, state a
311
+ reasonable, testable target rather than omitting the section.
247
312
  - Add at most 3 "[NEEDS CLARIFICATION: <question>]" markers, and only for
248
313
  choices that materially affect scope/UX and have no reasonable default.
249
314
  - Every section the template marks *(mandatory)* — at minimum System
250
- Requirements and Success Criteria — plus Business Need, User Stories, and
251
- the Change History row MUST be present and populated. The four lift sections
252
- (Constraints & NFRs, Data Classification, Integration Contracts, Experience
253
- Standards) and Experience Scope MUST also be present — populated when their rules
254
- apply, else "None (as of v1.0)". You may
255
- omit only a genuinely inapplicable OPTIONAL item (e.g. the WHERE optional-feature
256
- requirement, or an empty Assumptions list) — never a mandatory section, and
257
- never write "N/A".
315
+ Requirements and Success Criteria — plus Business Need and the Change History
316
+ row MUST be present and populated. The four lift sections (Constraints & NFRs,
317
+ Data Classification, Integration Contracts, Experience Standards) and Experience
318
+ Scope MUST also be present — populated when their rules apply, else
319
+ "None (as of v1.0)". You may omit only a genuinely inapplicable OPTIONAL item
320
+ (e.g. the WHERE optional-feature requirement, or an empty Assumptions list) —
321
+ never a mandatory section, and never write "N/A".
258
322
  - Do NOT embed any checklist and NO design/implementation.
259
323
 
260
- # ═════════════════════════ DELTA — spec.md exists ═════════════════════════
261
- # A later requirement = a new user story on the SAME aggregate. Delegate to the
262
- # delta-spec module: it opens the change (cr-setup, single-active-change guarded) and
263
- # authors the delta-spec ONLY — the master spec.md is left UNTOUCHED. The change
264
- # stays OPEN; finalize-delta merges the delta into spec.md later in the chain. Runs in
265
- # this context (shares vars + the DDD + domain sticky standards). The former standalone
266
- # `cr` capture's spec half now lives here. Downstream: design (delta-design) →
267
- # analyze-delta-design → implement-delta → build → finalize-delta → analyze-slice →
268
- # close-delta (close-delta is what finally archives the change and bumps the version).
269
- - id: delta_spec_module
270
- condition: "{{spec_exists}} == true"
271
- action: include
272
- module: .appweaver/orchestrator/modules/delta-spec.orch.yaml
273
-
274
- # ══════ CONVERGE — checklist + clarify + validate, ON THE DOCUMENT JUST WRITTEN ══════
275
- # These loops run on WHICHEVER document this invocation authored — the master spec on
276
- # the bootstrap path, the delta-spec on the change path. They must NEVER write the
277
- # master while a change is open: the delta is authored relative to the master, so
278
- # mutating the master here would leave the delta computed against a stale baseline
279
- # AND (brownfield) silently edit an adopted, code-derived spec that no downstream
280
- # stage re-checks for fidelity. Hence quality_target — bound from the SAME script call
281
- # that creates the checklist, so the checklist and the graded document cannot diverge.
324
+ # ═══════════════════════ EXTEND — spec.md already exists ═══════════════════
325
+ # 5. THE ENTRY TRIPWIRE. Before writing a word, establish that the document on disk is
326
+ # the one the ledger describes. Two states stop the run, and both are states where
327
+ # writing would destroy information rather than add it:
282
328
  #
283
- # 5. Quality checklist (shared script — static content, NO model). Idempotent in
284
- # content; note it rewrites the file, so it is deliberately scoped to the path that
285
- # authored the document (a delta run no longer resets the master's scored boxes).
286
- - id: create_checklist
287
- condition: "{{spec_exists}} == false"
329
+ # unledgered_edit spec.md was hand-edited and no Change History row was added.
330
+ # Appending this run's row would bump the version past that edit
331
+ # without ever naming its ids, so update-stories could not
332
+ # reconcile it and every story citing it stays silently stale.
333
+ # delta_open a change is open AND its delta-spec has been authored, so a delta
334
+ # is measured against this master. Temporary — the guard retires with
335
+ # the delta family — but while both exist it is the one silent way
336
+ # the two ways of changing a spec collide. Narrowed to an AUTHORED
337
+ # delta because cr-setup scaffolds a delta-spec for every change it
338
+ # opens: gating on the open flag alone refused every extend for the
339
+ # whole life of a DESIGN-only change, which strands nothing, and the
340
+ # refusal named a chain the operator may be retiring. The message now
341
+ # also names the way OUT (cr-archive) rather than only the way through.
342
+ #
343
+ # `unstamped` and `ledgered_edit` both pass: the first is a spec that predates the
344
+ # ledger, the second is a hand edit that did write its row. Both are re-baselined by
345
+ # the spec-stamp at the end of the run.
346
+ # No `on_error: continue`: spec-check reports every ledger STATE through its JSON with a
347
+ # zero exit, so a non-zero exit is the script itself failing. Continuing past that bound
348
+ # the gate's vars to nothing and produced the refusal message with its reason blanked out
349
+ # ("cannot write spec.md: (State: .)"), which is the right decision told unreadably.
350
+ - id: ext_ledger_check
351
+ condition: "{{needs_bootstrap}} == false"
288
352
  action: execute_script
289
353
  script: { ref: actions }
290
- subcommand: create-checklist
354
+ subcommand: spec-check
355
+ args: ["--feature-dir", "{{feature_dir}}"]
356
+ outputs:
357
+ - { name: ledger_ok, from: "$.ok" }
358
+ - { name: ledger_state, from: "$.state" }
359
+ - { name: ledger_error, from: "$.error" }
360
+ - { name: open_change, from: "$.open_change" }
361
+ - { name: version, from: "$.version" }
362
+
363
+ - id: ext_ledger_gate
364
+ condition: "{{needs_bootstrap}} == false"
365
+ action: verify_context
366
+ require_true: ledger_ok
367
+ on_false: stop
368
+ on_false_message: >
369
+ /appweaver specify cannot write spec.md: {{ledger_error}}
370
+ The Change History is how a story learns that a requirement it cites has moved —
371
+ a version that does not name the ids it changed leaves every affected story
372
+ stale with nothing to detect it. Resolve the state above, then re-run specify.
373
+ (State: {{ledger_state}}.)
374
+
375
+ # 6. Snapshot the document as it stands. This is the diff's "before", and it is what
376
+ # lets the Change History row be computed from what the run actually did rather than
377
+ # from what it claimed. Taken BEFORE authoring and consumed AFTER the converge loop,
378
+ # so a requirement the quality pass rewrote in passing is still recorded.
379
+ - id: ext_snapshot
380
+ condition: "{{needs_bootstrap}} == false"
381
+ action: execute_script
382
+ script: { ref: actions }
383
+ subcommand: spec-snapshot
384
+ args: ["--feature-dir", "{{feature_dir}}"]
385
+ outputs:
386
+ - { name: snapshot, from: "$.snapshot" }
387
+
388
+ # 6b. WHO IS BUILDING WHAT. A read-only scan of stories/ for the requirements that have
389
+ # open stories against them. specify writes no stories, but it is about to change a
390
+ # contract other people may already be building against, and knowing which ids those
391
+ # are is the difference between an amendment that costs a re-sync and one that costs
392
+ # nothing. Fed to the author as a preference, then checked against the diff below —
393
+ # the preference is a nudge, the check after the fact is the detector.
394
+ # Empty until the backlog exists, which degrades to "no preference, no ask".
395
+ - id: ext_story_context
396
+ condition: "{{needs_bootstrap}} == false"
397
+ action: execute_script
398
+ on_error: continue
399
+ script: { ref: actions }
400
+ subcommand: story-refs
291
401
  args: ["--feature-dir", "{{feature_dir}}"]
292
402
  outputs:
293
- - { name: checklist_file, from: "$.checklist_file" }
294
- - { name: quality_target, from: "$.target" } # → spec.md
295
-
296
- # Delta twin: a DELTA-SHAPED checklist (ADDED/MODIFIED/REMOVED completeness, merge
297
- # readiness) written INSIDE changes/<cr-id>/, grading the delta-spec. The master
298
- # checklist's items assume a full spec and would mis-fire here, which is exactly why
299
- # this is a separate checklist rather than the same one re-pointed.
300
- - id: create_delta_checklist
301
- condition: "{{spec_exists}} == true"
403
+ - { name: covered_csv, from: "$.covered_csv" }
404
+
405
+ # 6b-bis. WORD THE PREFERENCE FOR THE CASE AT HAND. The rule below is an argument for
406
+ # preferring an ADDITION over an amendment, and it only makes sense while somebody is
407
+ # actually building. Interpolating covered_csv straight into it left a spec with no
408
+ # backlog reading an eight-line case for adding requirements under a blank list — the
409
+ # logic was vacuous ("one of those" names nothing) but the rhetoric was not, and the
410
+ # first extend of a spec nobody has stories against is exactly where amending in place
411
+ # is the honest thing to do. Two ordered sets rather than a compound condition, the
412
+ # same idiom as readdit_verdict_na/_real: the no-backlog wording is the default, and
413
+ # the preference replaces it only when there is open work to protect.
414
+ # Safe on the bootstrap path too — ext_story_context is skipped there, so covered_csv
415
+ # is empty and the second step cannot fire.
416
+ - id: ext_covered_none
417
+ action: set
418
+ vars:
419
+ covered_note: >
420
+ NOBODY HAS OPEN WORK AGAINST ANY REQUIREMENT IN THIS SPEC. No story cites any id in
421
+ this document, so nothing you do here can strand work already under way. Amend an
422
+ existing requirement where the change genuinely belongs to it, and add a new one
423
+ where the change is genuinely new — let the contract read the way it should read,
424
+ without inventing a companion requirement to avoid touching anything.
425
+
426
+ - id: ext_covered_some
427
+ condition: "{{covered_csv}} != ''"
428
+ action: set
429
+ vars:
430
+ covered_note: >
431
+ THESE REQUIREMENTS ARE BEING BUILT RIGHT NOW: {{covered_csv}}. Where the change can
432
+ be expressed EITHER as an amendment to one of those or as a new requirement
433
+ alongside it, prefer the new requirement — an amendment moves ground someone is
434
+ standing on, and a requirement that gains a companion leaves their work correct as
435
+ written. This is a preference, not a prohibition: when the change genuinely
436
+ contradicts what one of them says, amend it. A contract that states two
437
+ incompatible things to avoid disturbing someone is worse than the disturbance.
438
+ Requirements NOT in that list have no open work against them: amend those in place
439
+ wherever the change genuinely belongs to them.
440
+
441
+ - id: ext_ctx
442
+ condition: "{{needs_bootstrap}} == false"
443
+ action: build_context
444
+ context:
445
+ - "{{spec_file}}" # the living spec — edited in place
446
+ - .appweaver/memory/constitution.domain.md # same domain frame as the bootstrap path
447
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
448
+
449
+ - id: ext_generate
450
+ condition: "{{needs_bootstrap}} == false"
451
+ action: agent_author
452
+ output_target: { type: file, path: "{{spec_file}}" }
453
+ task: |
454
+ Update the specification in context — the living contract for this aggregate —
455
+ to carry this change:
456
+
457
+ {{USER_INPUT}}
458
+
459
+ Return the COMPLETE updated document. Rules:
460
+ - EDIT IN PLACE. This is the living spec, not a change document. Add what the
461
+ input describes; amend what it changes; leave everything else EXACTLY as it is,
462
+ character for character. Do not restate, reword, reorder or "improve" a
463
+ requirement the input did not touch — an unrelated edit here reads downstream
464
+ as a contract change and pulls stories into rework for nothing.
465
+ - NEW requirements take the NEXT FREE id in their class. Ids are permanent and
466
+ are NEVER re-issued: if FR-004 was removed at some point, the next functional
467
+ requirement is still FR-006 and not FR-004. Stories, designs and code cite
468
+ these ids, so a reused id silently re-points work that has already shipped.
469
+ - NEVER RENUMBER an existing requirement. If a requirement is genuinely obsolete,
470
+ remove it and say so — do not renumber the ones after it to close the gap.
471
+ - AMEND, don't duplicate. When the input changes existing behaviour, edit that
472
+ requirement where it stands. Adding a second requirement that contradicts the
473
+ first leaves the aggregate with two answers and no way to tell which holds.
474
+ - {{covered_note}}
475
+ - THIS DOCUMENT HOLDS NO USER STORIES. Do not add an "As a … I want …" block, a
476
+ Given/When/Then acceptance scenario, or a section for either. Stories live in
477
+ stories/ beside this file and are written by /appweaver update-stories.
478
+ - DO NOT TOUCH the "**Version**" header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is. Both are
479
+ written by the kit after this step, from a diff of what you actually changed —
480
+ editing them here produces a duplicate row and a version nothing agrees on.
481
+ - Keep every section, in order. A section with nothing to say keeps its "None"
482
+ text; never delete one, and never write "N/A".
483
+ - Everything the bootstrap rules require of this document still holds: EARS form
484
+ for functional requirements, business-facing language, no implementation
485
+ mechanics, the domain lift into CN-/DC-/IC- and the experience lift into UX-,
486
+ stable ids throughout, and Success Criteria present and measurable.
487
+ - Where the change genuinely leaves an open question, mark it
488
+ "[NEEDS CLARIFICATION: <question>]" (at most 3, scope/UX/security only).
489
+
490
+ # ══════ CONVERGE — checklist + clarify + validate, over spec.md, both paths ══════
491
+ # One target now that the delta document is gone, so the checklist, the clarify loop
492
+ # and the validation loop all read and rewrite the same file the run authored. The
493
+ # loops may touch any part of the spec; that is deliberate and safe, because the
494
+ # Change History row is computed from a diff taken AFTER they finish rather than from
495
+ # a declaration made before they start.
496
+ #
497
+ # 6c. DROP THE AUTHORING ENVELOPE. build_context sets a STICKY context that every later
498
+ # model step carries in front of its own, so the two constitutions the author needed
499
+ # were re-sent with all ~10 converge calls — a checklist grading pass paying for the
500
+ # domain rules on every iteration. The converge steps each declare what they read, so
501
+ # emptying the envelope here costs nothing; the two that genuinely judge the domain
502
+ # lift (validate and its patch) name the constitutions themselves.
503
+ - id: converge_ctx
504
+ action: build_context
505
+ context: []
506
+
507
+ # 7. Quality checklist (shared script — static content, NO model). Idempotent in content.
508
+ - id: create_checklist
302
509
  action: execute_script
303
510
  script: { ref: actions }
304
511
  subcommand: create-checklist
305
- args: ["--feature-dir", "{{feature_dir}}", "--kind", "delta", "--change-dir", "{{cr_dir}}"]
512
+ args: ["--feature-dir", "{{feature_dir}}"]
306
513
  outputs:
307
- - { name: checklist_file, from: "$.checklist_file" }
308
- - { name: quality_target, from: "$.target" } # → changes/<cr-id>/delta-spec.md
514
+ - { name: checklist_file, from: "$.checklist_file" }
309
515
 
310
- # 6. Resolve [NEEDS CLARIFICATION] markers interactively (≤3). Non-interactive
516
+ # 8. Resolve [NEEDS CLARIFICATION] markers interactively (≤3). Non-interactive
311
517
  # runs answer with the first/recommended option (a documented default).
518
+ #
519
+ # Counted by a script before the model is asked. Most runs leave no marker, and asking
520
+ # a model to confirm that costs a round trip per run to be told "none" — the model's
521
+ # real work is turning a marker into a question with answerable options, which is only
522
+ # worth paying for once there is a marker to turn.
523
+ - id: spec_markers
524
+ action: execute_script
525
+ script: { ref: actions }
526
+ subcommand: spec-markers
527
+ args: ["--feature-dir", "{{feature_dir}}"]
528
+ outputs:
529
+ - { name: marker_count, from: "$.marker_count" }
530
+
312
531
  - id: extract_clarifications
532
+ condition: "{{marker_count}} != 0"
313
533
  action: agent_query
314
- context: ["{{quality_target}}"]
534
+ context: ["{{spec_file}}"]
315
535
  include_standards: false
316
536
  task: |
317
- Extract up to 3 unresolved "[NEEDS CLARIFICATION: <question>]" markers from
318
- the document in context (a feature spec, or one change's delta-spec), most
319
- scope/security/UX-impacting first. Return {"clarifications":[]} if there are none.
537
+ Extract up to 3 unresolved "[NEEDS CLARIFICATION: <question>]" markers from the
538
+ feature spec in context, most scope/security/UX-impacting first. Return
539
+ {"clarifications":[]} if there are none.
320
540
  schema: { clarifications: [ { id: c1, question: "<the question>", options: ["<answer A>", "<answer B>", "<answer C>"] } ] }
321
541
  mock: { clarifications: [] }
322
542
  outputs:
323
543
  - { name: clarifications, from: "$.clarifications" }
324
544
 
325
545
  - id: clarify_loop
546
+ condition: "{{marker_count}} != 0"
326
547
  action: loop
327
548
  over: "{{clarifications}}"
328
549
  as: c
@@ -336,18 +557,18 @@ workflow:
336
557
  answer_var: c_answer
337
558
  - id: resolve_clar
338
559
  action: agent_author
339
- context: ["{{quality_target}}"]
340
- include_standards: false # rewrite-in-place: the graded document only
560
+ context: ["{{spec_file}}"]
561
+ include_standards: false # rewrite-in-place: the spec only
341
562
  task: |
342
- Replace the matching "[NEEDS CLARIFICATION]" marker in the document in
343
- context with this answer and update the affected requirement/section
344
- accordingly. Preserve structure and headings. If it is a delta-spec, stay
345
- within the delta — keep every item's ADDED / MODIFIED / REMOVED placement.
563
+ Replace the matching "[NEEDS CLARIFICATION]" marker in the spec in context
564
+ with this answer and update the affected requirement/section accordingly.
565
+ Preserve structure, headings and every requirement id. Do not touch the
566
+ "**Version**" header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is.
346
567
  Question: {{c.question}}
347
568
  Answer: {{c_answer}}
348
- output_target: { type: file, path: "{{quality_target}}" }
569
+ output_target: { type: file, path: "{{spec_file}}" }
349
570
 
350
- # 7. Spec-quality validation loop — evaluate the spec against the checklist,
571
+ # 9. Spec-quality validation loop — evaluate the spec against the checklist,
351
572
  # fix failing items, re-check (max 3), then score the checklist file.
352
573
  - id: checklist_read
353
574
  action: execute_script
@@ -364,15 +585,22 @@ workflow:
364
585
  steps:
365
586
  - id: validate
366
587
  action: agent_query
367
- context: ["{{checklist_file}}", "{{quality_target}}"]
588
+ # The domain and UI constitutions ride along HERE and nowhere else in the converge
589
+ # block: the Domain Lift and Experience Lift items ask whether the spec carries what
590
+ # those files impose, and that is not judgeable from the spec alone. Optional, not
591
+ # required — ext_ctx/boot_ctx already announce a missing domain constitution once,
592
+ # and repeating that announcement per iteration is noise, not signal.
593
+ context:
594
+ - "{{checklist_file}}"
595
+ - "{{spec_file}}"
596
+ - { path: .appweaver/memory/constitution.domain.md, optional: true }
597
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
368
598
  include_standards: false
369
599
  task: |
370
- Evaluate the document under review (in context) against each checklist item
371
- (in context, 1-based in order). The checklist's own title states which kind
372
- of document it grades — a feature spec, or one change's delta-spec; judge it
373
- as that kind and nothing more. An item passes when the document satisfies it.
374
- (An item about remaining [NEEDS CLARIFICATION] markers passes once none
375
- remain.) In "failing", quote the offending/absent part per failed item.
600
+ Evaluate the feature spec (in context) against each checklist item (in
601
+ context, 1-based in order). An item passes when the spec satisfies it. (An
602
+ item about remaining [NEEDS CLARIFICATION] markers passes once none remain.)
603
+ In "failing", quote the offending/absent part per failed item.
376
604
  schema: { all_pass: false, results: { "1": true, "2": false }, failing: [ { n: 2, issue: "<quote>" } ] }
377
605
  mock: { all_pass: true, results: {}, failing: [] }
378
606
  outputs:
@@ -382,41 +610,552 @@ workflow:
382
610
  - id: pass_break
383
611
  action: break
384
612
  condition: "{{spec_all_pass}} == true"
613
+ # Only while another pass remains. The loop is validate -> break-if-pass -> patch, so
614
+ # hitting max_iterations always landed on a PATCH nobody re-read: the document shipped
615
+ # with ungraded text and score_checklist wrote the pre-patch verdict into the checklist
616
+ # file. Skipping the last patch costs one model call less and makes the last thing that
617
+ # happened to the document a grading.
385
618
  - id: patch_spec
619
+ condition: "{{iteration}} != 2"
386
620
  action: agent_author
387
- context: ["{{quality_target}}"]
621
+ context:
622
+ - "{{spec_file}}"
623
+ - { path: .appweaver/memory/constitution.domain.md, optional: true }
624
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
388
625
  include_standards: false
389
626
  task: |
390
- Fix these quality failures in the document in context, preserving its
391
- section order and headings. Do NOT add implementation detail; keep it
392
- business-facing and testable.
393
- If the document is a DELTA-SPEC: it describes one change, not the whole
394
- feature. Fix it AS a delta — keep the ADDED / MODIFIED / REMOVED structure,
395
- never expand it into a full specification, never copy in unchanged master
396
- content to satisfy an item, and never edit the master spec.
627
+ Fix these quality failures in the spec in context, preserving its section
628
+ order, headings and every requirement id. Do NOT add implementation detail;
629
+ keep it business-facing and testable. Do NOT add user stories or acceptance
630
+ scenarios — this document holds neither. Do NOT touch the "**Version**"
631
+ header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is.
397
632
  Failures: {{spec_failing}}
398
- output_target: { type: file, path: "{{quality_target}}" }
633
+ output_target: { type: file, path: "{{spec_file}}" }
399
634
 
400
635
  - id: score_checklist
401
636
  action: execute_script
402
637
  condition: "{{checklist_total}} != 0"
403
638
  script: { ref: actions }
404
639
  subcommand: checklist-apply
405
- args: ["--file", "{{checklist_file}}", "--results", "{{spec_results}}"]
640
+ # --all-pass because the per-item map is the model's and a pass routinely comes back
641
+ # with a partial map or none at all, which scored a passing spec as "0/25 items passing".
642
+ args: ["--file", "{{checklist_file}}", "--results", "{{spec_results}}", "--all-pass", "{{spec_all_pass}}"]
406
643
  outputs:
407
644
  - { name: checklist_passed, from: "$.after_checked" }
408
645
 
409
- # 8. Read the current version for the report (both paths).
410
- - id: version_read
646
+ # 9b. THE SPEC STILL FAILS. The loop grades, patches, and grades again; when it runs out of
647
+ # iterations the document is left failing and every step after here would have carried
648
+ # on as if it had passed — appending a version row for a spec the run itself judged red,
649
+ # with a checklist score beside it that need not agree.
650
+ #
651
+ # Asked, not decided, and RECORD IS FIRST on purpose. spec.md is already written by this
652
+ # point: a run that refuses the ledger row leaves a document with no row, which is the
653
+ # `unledgered_edit` state the next run refuses outright. Recording a red spec is honest
654
+ # and recoverable — the row names what moved, so update-stories still works, and the
655
+ # failures are in the checklist file. Stopping is the safer-feeling option that costs
656
+ # more, so the prompt says what it costs and how to undo it.
657
+ - id: quality_ask
658
+ condition: "{{spec_all_pass}} != true"
659
+ action: ask
660
+ prompt: |
661
+ The spec still fails checklist items after the quality loop gave up
662
+ ({{checklist_passed}}/{{checklist_total}} passing). The failures are listed in
663
+ {{checklist_file}}.
664
+ Outstanding: {{spec_failing}}
665
+ spec.md is ALREADY WRITTEN either way — the only question is whether this run records
666
+ what it changed.
667
+ options:
668
+ - "Record it — the version row is what keeps the stories in step; fix the spec next"
669
+ - "Stop — write no version row, and repair spec.md by hand first"
670
+ answer_key: quality_accept
671
+ answer_var: quality_accept
672
+
673
+ - id: quality_gate
674
+ condition: "{{spec_all_pass}} != true"
675
+ action: verify_context
676
+ require_value: quality_accept
677
+ equals: "Record it — the version row is what keeps the stories in step; fix the spec next"
678
+ on_mismatch: stop
679
+ on_mismatch_message: >
680
+ Stopped at your request with no version row written. The quality failures are listed in
681
+ {{checklist_file}}.
682
+ On a FIRST spec nothing else is needed: the bootstrap is still marked unfinished, so the
683
+ next specify re-authors it rather than extending it.
684
+ On an EXISTING spec, spec.md HAS been rewritten and the next specify will refuse it as an
685
+ unledgered edit until one of two things is true — add a Change History row naming the ids
686
+ you changed and re-run spec-stamp, or revert the document from the copy taken before this
687
+ run, which is beside it at {{snapshot}}.
688
+
689
+ # ═══════════════ THE FIRST LEDGER ROW — bootstrap only ═════════════════════
690
+ # 9b. The v1.0 row, OBSERVED. Every other version row is computed from a before/after
691
+ # diff for the reason this file opens with: a row the model DECLARED can omit an id it
692
+ # moved, and an omitted id is silent. v1.0 was the one exception — written by the
693
+ # author from a template whose placeholder reads "FR-001…FR-00n", so nothing
694
+ # downstream could trust it and update-stories had to be told to enumerate from the
695
+ # document instead. Diffing against no snapshot yields every id the finished spec
696
+ # carries, which is exactly the v1.0 row's content, so the first row is now derived
697
+ # the same way as the rest and the special case downstream goes away.
698
+ #
699
+ # on_error: continue — spec.md is already written, and a spec so empty that the diff
700
+ # found no id at all must not be stranded on disk with no ledger row. It keeps what
701
+ # the author wrote and says so in the run log.
702
+ - id: boot_diff
703
+ condition: "{{needs_bootstrap}} == true"
704
+ action: execute_script
705
+ script: { ref: actions }
706
+ subcommand: spec-diff
707
+ args: ["--feature-dir", "{{feature_dir}}", "--snapshot", ""]
708
+ outputs:
709
+ - { name: changed_csv, from: "$.changed_csv" }
710
+ - { name: story_ids_csv, from: "$.story_ids_csv" }
711
+ - { name: change_count, from: "$.change_count" }
712
+
713
+ # The hold is the other half of that on_error. spec-stamp runs on every path and
714
+ # re-baselines the hash, so a soft-failed ledger-ids used to leave a first draft with
715
+ # NO version row and a hash saying the document was in step — the next run's tripwire
716
+ # reads `clean` and every tool that trusts the hash believes the spec is finished.
717
+ # Held here and released only by a row that actually landed, so a bootstrap that did
718
+ # not complete stays unstamped AND still marked pending: an honest re-author.
719
+ - id: boot_stamp_hold
720
+ condition: "{{needs_bootstrap}} == true"
721
+ action: set
722
+ vars: { stamp_hold: "true", version: "v1.0 (draft — no version row yet)" }
723
+
724
+ - id: boot_ledger_ids
725
+ condition: "{{needs_bootstrap}} == true"
726
+ action: execute_script
727
+ on_error: continue
728
+ script: { ref: actions }
729
+ subcommand: ledger-ids
730
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{changed_csv}}"]
731
+ outputs:
732
+ - { name: boot_row_version, from: "$.version" }
733
+
734
+ - id: boot_stamp_release
735
+ condition: "{{boot_row_version}} != ''"
736
+ action: set
737
+ vars: { stamp_hold: "false" }
738
+
739
+ # ═════════════════════════ THE LEDGER ROW — extend only ════════════════════
740
+ # 10. What changed, observed. Diffing the finished document against the snapshot taken
741
+ # before authoring gives the requirement ids this run added, amended or removed —
742
+ # including any the converge loop touched on its way past, which is the whole
743
+ # reason the diff is taken here and not before the loop.
744
+ - id: ext_diff
745
+ condition: "{{needs_bootstrap}} == false"
746
+ action: execute_script
747
+ script: { ref: actions }
748
+ subcommand: spec-diff
749
+ args: ["--feature-dir", "{{feature_dir}}", "--snapshot", "{{snapshot}}"]
750
+ outputs:
751
+ - { name: changed_csv, from: "$.changed_csv" }
752
+ - { name: story_ids_csv, from: "$.story_ids_csv" }
753
+ - { name: removed_csv, from: "$.removed_csv" }
754
+ - { name: modified_csv, from: "$.modified_csv" }
755
+ - { name: disturbing_csv, from: "$.disturbing_csv" }
756
+ - { name: reissued_csv, from: "$.reissued_csv" }
757
+ - { name: change_count, from: "$.change_count" }
758
+
759
+ # 10b. DID THIS MOVE GROUND SOMEONE IS STANDING ON? The diff is fact, so this is the
760
+ # detector the pre-authoring preference lacked: of the requirements this run
761
+ # MODIFIED or REMOVED, which have open stories against them. An ADDED requirement
762
+ # is never in this set — no story cites an id that did not exist a moment ago.
763
+ # NO `on_error: continue` here, unlike the pre-authoring scan above. That one is a
764
+ # preference and degrades honestly to "no preference"; this one is the only thing
765
+ # standing between an amendment and the people building against it. Continuing past a
766
+ # failure bound disturbed_any to nothing, which is falsy, which SKIPS the ask — the
767
+ # check designed to stop a silent amendment failing open into a silent amendment.
768
+ - id: ext_disturbed
769
+ condition: "{{change_count}} != 0"
770
+ action: execute_script
771
+ script: { ref: actions }
772
+ subcommand: story-refs
773
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{disturbing_csv}}"]
774
+ outputs:
775
+ - { name: disturbed_any, from: "$.any" }
776
+ - { name: disturbed_csv, from: "$.affected_csv" }
777
+ - { name: disturbed_ids, from: "$.affected_ids" }
778
+ - { name: disturbed_stories, from: "$.story_csv" }
779
+ - { name: disturbed_flying, from: "$.in_flight_csv" }
780
+
781
+ # 10c. THE ASK. Changing a requirement someone is building against is a decision with a
782
+ # cost the machine cannot weigh: it may be the only honest way to state the new
783
+ # contract, or it may be an amendment that could equally have been an addition. The
784
+ # two outcomes differ sharply for the people downstream —
785
+ #
786
+ # amend the contract moves. Every open story citing it goes stale, and
787
+ # update-stories reconciles them: edited in place if no story-spec
788
+ # exists yet, carried by a follow-on if one does (§5.5).
789
+ # add the amended requirements go back to how they read, and the change is
790
+ # expressed as new requirements alongside them. Nobody's work goes
791
+ # stale, because nothing they cite has moved, and the new requirements
792
+ # become new stories.
793
+ #
794
+ # Asked rather than decided, for the same reason §5.4 asks on indirect impact:
795
+ # there is no oracle for "should this have been additive", and a wrong silent
796
+ # answer either strands work or splits one contract across two requirements.
797
+ # Amend is first, so a non-interactive run keeps what the author actually wrote.
798
+ - id: ask_disturb
799
+ condition: "{{disturbed_any}} == true"
800
+ action: ask
801
+ multi: true
802
+ prompt: |
803
+ This change MODIFIED or REMOVED requirements that people have open stories against.
804
+ Requirements: {{disturbed_csv}}
805
+ Open stories: {{disturbed_stories}}
806
+ Already in flight (story-spec exists): {{disturbed_flying}}
807
+ Pick the ones this change MAY alter. Each one you pick moves the contract: its open
808
+ stories go stale, and update-stories reconciles them — editing a story in place, or
809
+ minting a follow-on where a story-spec already exists. Each one you LEAVE OUT is put
810
+ back exactly as it read, and the change it was carrying is expressed as a new
811
+ requirement alongside it, so the work already under way stays correct.
812
+ Which may be changed?
813
+ options_from: "{{disturbed_ids}}"
814
+ answer_key: may_change
815
+ answer_var: may_change
816
+
817
+ # 10d. The complement: disturbed minus approved is what must go back. Computed by a
818
+ # script because the orchestrator binds values and never computes them.
819
+ - id: ext_preserve
820
+ condition: "{{disturbed_any}} == true"
821
+ action: execute_script
822
+ script: { ref: actions }
823
+ subcommand: spec-preserve
824
+ args: ["--disturbing", "{{disturbed_csv}}", "--approved", "{{may_change}}"]
825
+ outputs:
826
+ - { name: preserve_csv, from: "$.preserve_csv" }
827
+ - { name: preserve_count, from: "$.preserve_count" }
828
+ - { name: approved_csv, from: "$.approved_csv" }
829
+
830
+ # 10e. Restore what was not approved. The snapshot is still on disk, so both versions
831
+ # are in context: put those requirements back exactly as they read, and carry what
832
+ # they were expressing in new requirements instead. Requirements the author DID
833
+ # approve stay amended — this is per requirement, not all-or-nothing.
834
+ - id: ext_readdit
835
+ condition: "{{preserve_count}} != 0"
836
+ action: agent_author
837
+ # The constitutions its own grader and patcher already read. Minting requirements
838
+ # without them meant the step that WRITES the new contract was the only one in the
839
+ # chain flying blind, and the two steps after it then graded and patched what it
840
+ # produced against rules it had never seen.
841
+ context:
842
+ - "{{spec_file}}"
843
+ - "{{snapshot}}"
844
+ - { path: .appweaver/memory/constitution.domain.md, optional: true }
845
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
846
+ include_standards: false
847
+ output_target: { type: file, path: "{{spec_file}}" }
848
+ task: |
849
+ Two versions of this specification are in context: the CURRENT one at
850
+ {{spec_file}}, and the SNAPSHOT of it taken before this run edited it, at
851
+ {{snapshot}}. Some of the requirements this run changed
852
+ were NOT approved for change, because people have open stories against them.
853
+
854
+ Restore, and re-express additively: {{preserve_csv}}
855
+ Leave amended as they now stand: {{approved_csv}}
856
+
857
+ Return the COMPLETE document. Rules:
858
+ - Restore each requirement in the FIRST list to EXACTLY its snapshot wording. If one
859
+ was removed, put it back where it was, with its original id.
860
+ - Carry whatever those restored requirements were expressing in NEW requirements
861
+ instead, each taking the next free id in its class. Where a new requirement
862
+ narrows, extends or supersedes a restored one, say so in its text and name the
863
+ id — the pair has to read as one coherent contract, not two rules that contradict
864
+ each other.
865
+ - Requirements in the SECOND list were approved for change. Leave them exactly as
866
+ the current document has them; do not restore them and do not re-express them.
867
+ - Where a restored requirement's change genuinely cannot be expressed without
868
+ contradicting it, leave that ONE amended as it now stands and add a note naming
869
+ it. A contract that states two incompatible things is worse than an amendment
870
+ somebody has to reconcile.
871
+ - Every other part of the document stays exactly as it currently reads. Do not
872
+ touch the "**Version**" header or the "## Change History" table, and leave everything between the AW:COVERAGE markers exactly as it is.
873
+
874
+ # 10f. GRADE WHAT THE RESTORE WROTE. ext_readdit runs after the converge loop has
875
+ # finished, so the requirements it mints to carry the change would otherwise be the
876
+ # only ones in the document never checked for EARS form, testability or coherence —
877
+ # the additive path would produce weaker requirements than the ordinary one, which
878
+ # is precisely backwards, since it is the path taken when people are already
879
+ # building against this contract.
880
+ #
881
+ # Graded over the WHOLE document rather than only the new ids, because the point of
882
+ # a restored requirement plus its new companion is that the pair reads as ONE
883
+ # contract; only a whole-document pass can see that they do. Two iterations, and
884
+ # the patch is forbidden from touching either the restored or the approved
885
+ # requirements — otherwise the fix could re-disturb what the author just protected.
886
+ - id: readdit_check_loop
887
+ condition: "{{preserve_count}} != 0"
888
+ action: loop
889
+ while: "{{readdit_pass}} != true"
890
+ max_iterations: 2
891
+ steps:
892
+ - id: readdit_validate
893
+ action: agent_query
894
+ # The same sources the main validate gets. Without them this pass could not judge the
895
+ # Domain Lift or Experience Lift items at all, so the requirements minted by the
896
+ # ADDITIVE path — the path taken precisely when people are already building against
897
+ # this contract — were graded more thinly than the ones the ordinary path writes.
898
+ context:
899
+ - "{{checklist_file}}"
900
+ - "{{spec_file}}"
901
+ - { path: .appweaver/memory/constitution.domain.md, optional: true }
902
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
903
+ include_standards: false
904
+ task: |
905
+ The spec in context was just re-authored: some requirements were restored to
906
+ their earlier wording, and NEW requirements were added to carry the change they
907
+ were expressing. That happened after the quality pass, so the new requirements
908
+ have never been graded.
909
+ Restored (must read exactly as they did): {{preserve_csv}}
910
+ Amended with approval: {{approved_csv}}
911
+ Evaluate the document against each checklist item (in context, 1-based in
912
+ order), paying particular attention to the requirements added by the
913
+ re-authoring: EARS form, atomic and testable, stable ids, no implementation
914
+ mechanics. Judge also whether each restored requirement and the new one carrying
915
+ its change read as ONE coherent contract rather than two rules that contradict
916
+ each other. In "failing", quote the offending part per failed item.
917
+ schema: { all_pass: false, failing: [ { n: 2, issue: "<quote>" } ] }
918
+ mock: { all_pass: true, failing: [] }
919
+ outputs:
920
+ - { name: readdit_pass, from: "$.all_pass" }
921
+ - { name: readdit_failing, from: "$.failing" }
922
+ - id: readdit_break
923
+ action: break
924
+ condition: "{{readdit_pass}} == true"
925
+ # Same reason as patch_spec: max_iterations is 2, so iteration 1 grades and stops.
926
+ - id: readdit_patch
927
+ condition: "{{iteration}} != 1"
928
+ action: agent_author
929
+ context:
930
+ - "{{spec_file}}"
931
+ - { path: .appweaver/memory/constitution.domain.md, optional: true }
932
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
933
+ include_standards: false
934
+ task: |
935
+ Fix these quality failures in the spec in context.
936
+ Failures: {{readdit_failing}}
937
+ Constraints that override the failures if they ever conflict:
938
+ - Do NOT modify these restored requirements — their wording is deliberate and
939
+ people are building against it: {{preserve_csv}}
940
+ - Do NOT modify these approved requirements either: {{approved_csv}}
941
+ - Fix the NEW requirements instead: put them in EARS form, make them atomic and
942
+ testable, and keep each one's stated relationship to the requirement whose
943
+ change it carries.
944
+ - Preserve section order, headings and every requirement id. Do not touch the
945
+ "**Version**" header or the "## Change History" table, and leave everything
946
+ between the AW:COVERAGE markers exactly as it is.
947
+ output_target: { type: file, path: "{{spec_file}}" }
948
+
949
+ # 10f-bis. THE RESTORE'S SPEC STILL FAILS. Same shape, and the same stakes, as the ask
950
+ # after the main quality loop — with one difference that makes it matter more: this
951
+ # is the path taken when people are ALREADY BUILDING against these requirements, and
952
+ # what the loop grades is the brand-new text minted to carry a change the author
953
+ # refused to make in place. Without this, a red restore appended a version row and
954
+ # told those authors their work was reconciled.
955
+ #
956
+ # Two ordered sets rather than a compound condition: the verdict is "nothing to
957
+ # grade" by default, and becomes the loop's own answer only on the path where the
958
+ # loop actually ran. Record is first for the reason it is first above — spec.md is
959
+ # written by now, and a run that refuses the row leaves the unledgered_edit state.
960
+ - id: readdit_verdict_na
961
+ action: set
962
+ vars: { readdit_verdict: "true" }
963
+ - id: readdit_verdict_real
964
+ condition: "{{preserve_count}} != 0"
965
+ action: set
966
+ vars: { readdit_verdict: "{{readdit_pass}}" }
967
+
968
+ - id: readdit_ask
969
+ condition: "{{readdit_verdict}} != true"
970
+ action: ask
971
+ prompt: |
972
+ The re-authored spec still fails its quality pass. These requirements were restored
973
+ to their earlier wording and NEW ones were minted to carry the change instead, and
974
+ the grader is not satisfied with the result:
975
+ Restored: {{preserve_csv}}
976
+ Failing: {{readdit_failing}}
977
+ spec.md is ALREADY WRITTEN either way — the only question is whether this run records
978
+ what it changed.
979
+ options:
980
+ - "Record it — the version row is what keeps the stories in step; fix the spec next"
981
+ - "Stop — write no version row, and repair spec.md by hand first"
982
+ answer_key: readdit_accept
983
+ answer_var: readdit_accept
984
+
985
+ - id: readdit_gate
986
+ condition: "{{readdit_verdict}} != true"
987
+ action: verify_context
988
+ require_value: readdit_accept
989
+ equals: "Record it — the version row is what keeps the stories in step; fix the spec next"
990
+ on_mismatch: stop
991
+ on_mismatch_message: >
992
+ Stopped at your request with no version row written. What the grader found is above,
993
+ and the checklist is at {{checklist_file}}.
994
+ spec.md HAS been rewritten, so the next specify will refuse it as an unledgered edit
995
+ until one of two things is true — add a Change History row naming the ids you changed
996
+ and re-run spec-stamp, or revert the document from the copy taken before this run,
997
+ which is beside it at {{snapshot}}.
998
+
999
+ # 10g. Re-diff after re-authoring AND its quality pass, so the ledger row records what
1000
+ # finally landed rather than what the first pass wrote. Skipped when everything was
1001
+ # approved. Must run after the gate above: a patch there can add or reword a
1002
+ # requirement, and a row computed before it would miss that.
1003
+ - id: ext_rediff
1004
+ condition: "{{preserve_count}} != 0"
1005
+ action: execute_script
1006
+ script: { ref: actions }
1007
+ subcommand: spec-diff
1008
+ args: ["--feature-dir", "{{feature_dir}}", "--snapshot", "{{snapshot}}"]
1009
+ outputs:
1010
+ - { name: changed_csv, from: "$.changed_csv" }
1011
+ - { name: story_ids_csv, from: "$.story_ids_csv" }
1012
+ - { name: removed_csv, from: "$.removed_csv" }
1013
+ - { name: modified_csv, from: "$.modified_csv" }
1014
+ - { name: disturbing_csv, from: "$.disturbing_csv" }
1015
+ - { name: reissued_csv, from: "$.reissued_csv" }
1016
+ - { name: change_count, from: "$.change_count" }
1017
+
1018
+ # 10g-bis. DID THE RESTORE HOLD. ext_readdit is a model asked to put requirements back to
1019
+ # their earlier wording, and until now nothing checked that it did. The re-diff is the
1020
+ # proof: a requirement the author refused to move must have dropped out of the
1021
+ # disturbing set. One still in it was amended WITHOUT approval — and the run would
1022
+ # otherwise close by telling that author, in the same sentence, that it was left alone.
1023
+ #
1024
+ # It announces rather than stops. spec.md is already written by this point, so a stop
1025
+ # here leaves a document on disk with no ledger row, which is the state the next run's
1026
+ # tripwire refuses. Announcing keeps the ledger row honest — the violated ids ARE in
1027
+ # it, so update-stories still reconciles their stories — and moves them out of the
1028
+ # "preserved" column of the report into a column that says what actually happened.
1029
+ - id: ext_restore_check
1030
+ condition: "{{preserve_count}} != 0"
1031
+ action: execute_script
1032
+ script: { ref: actions }
1033
+ subcommand: spec-preserve
1034
+ args: ["--verify", "--preserved", "{{preserve_csv}}", "--disturbing", "{{disturbing_csv}}"]
1035
+ outputs:
1036
+ - { name: held_csv, from: "$.held_csv" }
1037
+ - { name: violated_csv, from: "$.violated_csv" }
1038
+ - { name: restore_ok, from: "$.restore_ok" }
1039
+
1040
+ - id: ext_restore_gate
1041
+ condition: "{{preserve_count}} != 0"
1042
+ action: verify_context
1043
+ require_true: restore_ok
1044
+ on_false: continue
1045
+ on_false_message: >
1046
+ The re-authoring did NOT restore {{violated_csv}} — you declined to change these and
1047
+ they moved anyway. They are recorded in this version's Change History row, so
1048
+ /appweaver update-stories will reconcile the stories citing them rather than leaving
1049
+ them silently stale; check that the wording it landed on is one you can live with.
1050
+
1051
+ # 10h. Re-read the affected stories from the FINAL document. After a restore, the
1052
+ # requirements that were put back disturb nobody any more, and their stories must
1053
+ # drop out of the report — otherwise the run tells people to go and reconcile work
1054
+ # that this run deliberately left alone. Same call as 10b, over what actually landed.
1055
+ # Keyed on disturbed_any, not change_count: a restore that put everything back can leave
1056
+ # the run with nothing changed at all, and on that path a change_count gate skipped this
1057
+ # step and left the report naming the stories from BEFORE the restore — telling people to
1058
+ # go and reconcile work this run deliberately left alone.
1059
+ - id: ext_final_refs
1060
+ condition: "{{disturbed_any}} == true"
1061
+ action: execute_script
1062
+ on_error: continue
1063
+ script: { ref: actions }
1064
+ subcommand: story-refs
1065
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{disturbing_csv}}"]
1066
+ outputs:
1067
+ - { name: disturbed_stories, from: "$.story_csv" }
1068
+
1069
+ # 10i. AN ID THE LEDGER HAS ALREADY SPENT. Ids are permanent: re-issuing a retired one
1070
+ # silently re-points stories, designs and code that have already shipped, which is
1071
+ # why every authoring prompt forbids it — and until now nothing checked. Announced,
1072
+ # not blocked, for the same reason as the restore gate: the document is written by
1073
+ # the time this is visible, and refusing here would strand it. Deciding WHAT to do
1074
+ # about a re-issued id is still the author's, and is not yet automated.
1075
+ - id: ext_reissue_gate
1076
+ condition: "{{change_count}} != 0"
1077
+ action: verify_context
1078
+ require_value: reissued_csv
1079
+ equals: ""
1080
+ on_mismatch: continue
1081
+ on_mismatch_message: >
1082
+ {{reissued_csv}} was introduced by this run, but the Change History has already
1083
+ spent it. An id is permanent — whatever cited it before now points at something
1084
+ else. Renumber the new requirement to the next free id in its class before anyone
1085
+ builds against it.
1086
+
1087
+ # 11. One call writes the row, bumps the version, stamps the header and re-hashes, so
1088
+ # a row cannot exist without the hash that proves the body it describes. Skipped
1089
+ # when the run changed no requirement — a version that names no ids is a bump
1090
+ # update-stories can do nothing with.
1091
+ - id: ext_ledger_append
1092
+ condition: "{{change_count}} != 0"
1093
+ action: execute_script
1094
+ script: { ref: actions }
1095
+ subcommand: ledger-append
1096
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{changed_csv}}", "--summary", "{{USER_INPUT}}"]
1097
+ outputs:
1098
+ - { name: ledger_version, from: "$.version" }
1099
+
1100
+ # ═══════════════ STORY COVERAGE — both paths, before the baseline ══════════
1101
+ # 11b. Rewrite the spec's AW:COVERAGE block: every FR-/BR- in the document and the
1102
+ # stories delivering it, read out of the stories/ frontmatter. The relation is
1103
+ # stored once, in the story; this is that relation the other way round, which is
1104
+ # why the block is regenerated wholesale and hand edits to it are discarded.
1105
+ #
1106
+ # Deliberately OUTSIDE the ledger. The hash guards the CONTRACT, and coverage is a
1107
+ # projection of the backlog — spec-check, spec-diff and ledger-append all cut this
1108
+ # block out before they look. That is what lets user-story and update-stories write
1109
+ # it too without a story creation reading as an unledgered spec edit, or a story
1110
+ # raised against FR-003 reading as FR-003 having changed.
1111
+ #
1112
+ # Run on the bootstrap path as well, where it lists every requirement as uncovered:
1113
+ # a new spec that nobody is building yet is exactly what it should say.
1114
+ - id: coverage_refresh
1115
+ action: execute_script
1116
+ on_error: continue
1117
+ script: { ref: actions }
1118
+ subcommand: spec-coverage
1119
+ args: ["--feature-dir", "{{feature_dir}}"]
1120
+ outputs:
1121
+ - { name: uncovered_csv, from: "$.uncovered_csv" }
1122
+
1123
+ # ═══════════════════ RE-BASELINE — both paths, always last ═════════════════
1124
+ # 12. Record the finished document's hash. Everything above may have rewritten spec.md
1125
+ # after the ledger row was appended (the converge loop on the bootstrap path, and
1126
+ # nothing on the extend path — but the order is what guarantees that), so the hash
1127
+ # is taken here, once, over the document as it will be read next. Without this the
1128
+ # next run's entry tripwire would see a changed body and refuse a spec that is
1129
+ # perfectly in step.
1130
+ # Skipped when the bootstrap's v1.0 row never landed: a hash is a claim that the
1131
+ # document on disk is the one the ledger describes, and there is no ledger row to
1132
+ # describe it. Leaving it unstamped keeps that honest and costs nothing — the
1133
+ # bootstrap marker is still up, so the next run re-authors rather than extends.
1134
+ - id: spec_stamp
1135
+ condition: "{{stamp_hold}} != true"
411
1136
  action: execute_script
412
1137
  script: { ref: actions }
413
- subcommand: version-read
1138
+ subcommand: spec-stamp
414
1139
  args: ["--feature-dir", "{{feature_dir}}"]
415
1140
  outputs:
416
- - { name: version, from: "$.version" }
417
- - { name: active_change, from: "$.active_change" }
1141
+ - { name: version, from: "$.version" }
1142
+
1143
+ # 13. Post-execution hooks: resolve (script) then dispatch (invoking agent).
1144
+ # 12b. Say so when it was held. Everything downstream reads `version`, and a first draft
1145
+ # with no row and no hash is not a baseline anything should build on.
1146
+ - id: boot_incomplete_notice
1147
+ condition: "{{stamp_hold}} == true"
1148
+ action: verify_context
1149
+ require_value: stamp_hold
1150
+ equals: "false"
1151
+ on_mismatch: continue
1152
+ on_mismatch_message: >
1153
+ The first draft was written to {{spec_file}}, but its v1.0 Change History row could not
1154
+ be added — so the document is deliberately left UNSTAMPED and still marked an unfinished
1155
+ bootstrap. Do not treat it as a baseline. Re-run /appweaver specify with the same input
1156
+ (it re-authors the draft rather than extending it), or add the v1.0 row by hand naming
1157
+ the requirement ids and re-run spec-stamp.
418
1158
 
419
- # 9. Post-execution hooks: resolve (script) then dispatch (invoking agent).
420
1159
  - id: resolve_post_hooks
421
1160
  action: execute_script
422
1161
  script: { ref: actions }
@@ -432,8 +1171,16 @@ outputs:
432
1171
  feature_directory: "{{feature_dir}}"
433
1172
  spec_file: "{{spec_file}}"
434
1173
  version: "{{version}}"
435
- change_id: "{{cr_id}}"
436
- authored_document: "{{quality_target}}" # spec.md (bootstrap) | delta-spec.md (delta)
1174
+ requirements_changed: "{{changed_csv}}" # every class — what the ledger row records
1175
+ requirements_for_stories: "{{story_ids_csv}}" # the FR-/BR- subset — what update-stories acts on
1176
+ requirements_removed: "{{removed_csv}}"
1177
+ requirements_amended: "{{modified_csv}}" # what finally stands amended, ask or no ask
1178
+ requirements_preserved: "{{held_csv}}" # the ones the restore actually put back
1179
+ requirements_not_restored: "{{violated_csv}}" # declined, but moved anyway
1180
+ requirements_reissued: "{{reissued_csv}}" # ids the ledger had already spent
1181
+ open_change: "{{open_change}}" # a delta change open on this feature, if any
1182
+ requirements_uncovered: "{{uncovered_csv}}"
1183
+ stories_disturbed: "{{disturbed_stories}}"
437
1184
  checklist_file: "{{checklist_file}}"
438
1185
  checklist_quality: "{{checklist_passed}}/{{checklist_total}} items passing"
439
- next: "/appweaver design — BOOTSTRAP authors design.md from the spec; a DELTA (this run opened change {{cr_id}}) authors the delta-design for it. A change then flows: analyze-delta-design → implement-delta → build → finalize-delta → analyze-slice → close-delta (finalize-delta merges the delta into spec.md + design.md but leaves the change open; analyze-slice verifies the slice against the merged master; close-delta archives the change and bumps the version)."
1186
+ next: "spec.md is at {{version}}. Run /appweaver update-stories — it raises or refreshes the backlog for the requirements this run touched that a story can cite ({{story_ids_csv}}), and nothing else keeps the contract and its stories in step. That is the FR-/BR- subset of requirements_changed on purpose: the ledger row records every class this run moved, but only these are slices of work. Then /appweaver design authors design.md from the spec. READ THE FIELDS ABOVE BEFORE YOU DO: requirements_removed orphans every story citing them and needs deciding by hand; requirements_not_restored moved despite you declining them; requirements_reissued point at something other than what cited them before; open_change will be merged into this document by finalize-delta; and checklist_quality is what the spec actually scored."