aw-spec-cli 0.1.15 → 0.1.17

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