aw-spec-cli 0.1.16 → 0.1.18

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