aw-spec-cli 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +18 -18
  2. package/assets/.appweaver/ORCH-DIALECT.md +1 -3
  3. package/assets/.appweaver/RULE-PARTITION.md +29 -13
  4. package/assets/.appweaver/STACK-PROFILES.md +10 -9
  5. package/assets/.appweaver/orchestrator/.state/smoke.state.json +32 -0
  6. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +4 -1
  7. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +251 -0
  8. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +104 -35
  9. package/assets/.appweaver/orchestrator/orchestrator.py +5 -4
  10. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +94 -36
  11. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +1 -1
  12. package/assets/.appweaver/orchestrator/rev-templates/domain-model-template.skeleton.md +115 -0
  13. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +2 -9
  14. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +16 -40
  15. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +38 -17
  16. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +8 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +10 -11
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +673 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +535 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +643 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +84 -701
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +7 -7
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +16 -14
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +2 -2
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +243 -144
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +877 -578
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -4
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +32 -91
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +836 -514
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +2 -2
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +132 -0
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +38 -5
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +3 -3
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +271 -979
  37. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +1085 -3385
  38. package/assets/.appweaver/scripts/bash/aw-xref.sh +1 -2
  39. package/assets/.appweaver/scripts/bash/common.sh +47 -0
  40. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +914 -3220
  41. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +1 -2
  42. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -3
  43. package/assets/.appweaver/scripts/python/aw-stack.py +6 -2
  44. package/assets/.appweaver/scripts/python/aw-xref.py +86 -350
  45. package/assets/.appweaver/templates/constitution-ui-template.md +1 -1
  46. package/assets/.appweaver/templates/delta-design-template.md +120 -30
  47. package/assets/.appweaver/templates/delta-domain-model-template.md +81 -0
  48. package/assets/.appweaver/templates/delta-spec-template.md +43 -17
  49. package/assets/.appweaver/templates/design-template.md +68 -98
  50. package/assets/.appweaver/templates/domain-model-template.md +130 -0
  51. package/assets/.appweaver/templates/okf-concept-template.md +2 -2
  52. package/assets/.appweaver/templates/source-map-template.md +3 -3
  53. package/assets/.appweaver/templates/spec-template.md +100 -122
  54. package/assets/.appweaver/templates/ui-delta-design-template.md +159 -0
  55. package/assets/.appweaver/templates/ui-design-template.md +46 -75
  56. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +7 -18
  57. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +34 -39
  58. package/assets/guardrails/requirement-spec.txt +2 -2
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  60. package/package.json +1 -1
  61. package/assets/.appweaver/orchestrator/modules/delta-design.orch.yaml +0 -105
  62. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +0 -87
  63. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -251
  64. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -813
  65. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -253
  66. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +0 -663
  67. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +0 -600
  68. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +0 -1147
  69. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +0 -1121
  70. package/assets/.appweaver/templates/story-template.md +0 -178
@@ -1,30 +1,45 @@
1
1
  # =============================================================================
2
2
  # aw-spec-finalize-delta (orchestrator) — MERGE the delta into the master docs
3
3
  #
4
- # The fourth of the five CR stages. Fold the open change's delta docs into the feature's
5
- # masters (doc-first merge) and refresh the cross-slice edges from the now-merged design.
6
- # After this the master docs describe the shipped-plus-change state — but THE CHANGE
7
- # REMAINS OPEN.
4
+ # The fourth of the five CR stages. Fold the open change's delta into the two documents that
5
+ # DEFINE the slice — the feature's spec.md and the bounded context's domain-model.md — then
6
+ # refresh the OKF knowledge layer. After this the masters describe the shipped-plus-change
7
+ # state, but THE CHANGE REMAINS OPEN.
8
8
  #
9
- # HOW MANY MASTERS. Two always — spec.md + design.md. THREE where the feature holds a UI
10
- # half (design.ui.md). This stage is the ONLY writer of the masters, so every document the
11
- # change can move has to move here: a UI delta left in the change dir is lost the moment
12
- # close-delta archives it, and a UX- merged into the spec that reaches no design is a
13
- # standard nothing implements.
9
+ # TWO MERGE TARGETS, ONE TRANSACTION:
10
+ # • specs/<Feature>/spec.md ← changes/<id>/delta-spec.md
11
+ # • specs/<Context>/domain-model.md ← specs/<Context>/domain-model.<id>.delta.md
12
+ # Behaviour and model land together or neither lands. A requirement merged citing a BR- the
13
+ # model never gained references an invariant that does not exist; an invariant merged that no
14
+ # requirement reached is a rule nothing asks for. ONE agent step writes both, so there is no
15
+ # window in which one is in and the other is not.
14
16
  #
15
- # THE UI LANE HAS ITS OWN COMMANDS — ⌛ design-ui / delta-design-ui author design.ui.md and
16
- # delta-design.ui.md, exactly as design / delta-design author the service pair. This stage
17
- # does not author either one; it MERGES whichever of them the feature has. The two UI paths
18
- # it must handle are therefore different, and both are normal:
19
- # • MASTER + DELTA — the change has an experience half. Merge it, then append the row.
20
- # • MASTER, NO DELTA — the change never reached the UI, or ⌛ delta-design-ui has not run.
21
- # Append the Change History row and touch nothing else. The row is not bookkeeping
22
- # for its own sake: close-delta stamps design.ui.md to the new version alongside its
23
- # siblings (version-stamp covers all three), so a header carrying a version that no
24
- # row explains is indistinguishable from a merge that silently dropped one.
25
- # A feature with no design.ui.md at all has no UI half and merges two documents, unchanged.
17
+ # THE MODEL MERGES ONE `## Aggregate:` SECTION AT A TIME — the section is the merge unit, and
18
+ # only the sections the delta's "Aggregates touched" header names are opened. Two open deltas
19
+ # on DIFFERENT aggregates therefore never collide; two on the SAME one are a real semantic
20
+ # conflict, which analyze-plan-delta's overlap finding surfaces before the merge rather than
21
+ # leaving it to be discovered here.
26
22
  #
27
- # specify → design → analyze-delta-design → implement-delta → build
23
+ # THE LANE PLANS ARE NOT MERGED HERE. backend-plan.md and frontend-plan.md are DERIVED from
24
+ # the spec and the model — that is what the lane split made them — so they are re-derived from
25
+ # the merged masters, not patched from a delta. Merging a delta plan would fork the
26
+ # derivation: the master would carry an edit no run of `plan` produced, and the next `plan`
27
+ # would silently overwrite it. The delta plans stay in changes/<id>/, where `implement`
28
+ # generated from them, and close-delta archives them as the record of how the change was
29
+ # built. A UX-
30
+ # item merged into the spec is carried by the spec until the frontend plan is re-derived from
31
+ # it — not dropped, re-derived.
32
+ #
33
+ # ⌛ THE RE-DERIVATION IS NOT BUILT YET, and this stage is where that shows. `plan`
34
+ # self-detects on plan-EXISTS, so a run against a merged master authors another DELTA plan
35
+ # rather than refreshing the master ("to regenerate a master, delete it first"). Until a
36
+ # refresh mode lands, the master plans stand at their bootstrap content while spec.md and
37
+ # domain-model.md move ahead of them — visible, because analyze-plan gates the masters against
38
+ # exactly these two documents and reports the drift. What this stage must NOT do is hide that
39
+ # by merging a delta plan: the plan would then agree with the spec without any run of `plan`
40
+ # ever having derived it.
41
+ #
42
+ # specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta
28
43
  # → finalize-delta ← you are here
29
44
  # → analyze-slice → close-delta
30
45
  #
@@ -39,17 +54,24 @@
39
54
  # The downstream fence below still guards the MERGE (a change that breaks a dependent
40
55
  # must not reach the master docs); close-delta guards nothing — it is the bookkeeping.
41
56
  #
42
- # Prereqs: run /appweaver implement-delta then build first (the delta's code is
43
- # generated and compiles), so after the merge the master docs and the code agree.
44
- # finalize-delta edits ONLY the master docs; it does not generate code.
57
+ # Prereqs: run /appweaver implement (answering it with THIS change), then build, then
58
+ # /appweaver analyze-slice-delta — the delta's code is generated, compiles, and has been
59
+ # VERIFIED to contain what the delta plans said it would, so after the merge the masters
60
+ # and the code agree. That last gate is the one that matters here: this stage writes the
61
+ # change into spec.md and domain-model.md, and merging a change that was never fully
62
+ # implemented is how a master ends up asserting behaviour no code has. Nothing MECHANICALLY
63
+ # proves it ran — the stage boundary is operator-driven, as everywhere else in this
64
+ # pipeline — but it is the difference between a master that describes the system and one
65
+ # that describes an intention.
66
+ # finalize-delta edits ONLY spec.md and domain-model.md; it generates no code.
45
67
  #
46
68
  # Launch: /appweaver finalize-delta [strategy]
47
69
  # Prereq: an OPEN change whose delta was implemented.
48
70
  # =============================================================================
49
71
 
50
72
  name: aw-spec-finalize-delta
51
- description: Merge the open change's delta-spec into spec.md, delta-design into design.md and — where the feature has a UI half — delta-design.ui.md into design.ui.md (doc-first), append the Change History to each in lockstep, and refresh the cross-slice edges from the merged design. Gated first by a SCOPED downstream fence over the transitive blast radius (fence_green), which writes a triage report and asks Abort/Override on red. Leaves the change OPEN — analyze-slice runs next against the merged master, and close-delta archives the change and bumps the version.
52
- version: 1.3.0
73
+ description: Merge the open change's delta-spec into the feature's spec.md and its model delta into the bounded context's domain-model.md — one aggregate section at a time, only the aggregates the delta names — in a single transaction, and append the spec's Change History row. The lane plans are NOT merged — backend-plan.md and frontend-plan.md are derived from these two documents and are re-derived by /appweaver plan main. Gated first by a SCOPED downstream fence over the transitive blast radius (fence_green), which writes a triage report and asks Abort/Override on red. Leaves the change OPEN — analyze-slice runs next against the merged masters, and close-delta archives the change and bumps the version.
74
+ version: 2.0.0
53
75
  agent_agnostic: true
54
76
  dialect: aw-spec.orchestrator/v1
55
77
 
@@ -73,10 +95,14 @@ vars:
73
95
  downstream_csv: "" # the blast-radius names, fed to the scoped fence's --also and shown to the human
74
96
  fence_ok: "true" # default green so the gate is a no-op when nothing downstream
75
97
  gate_decision: "clean" # audit: 'clean' unless a RED fence was deliberately overridden
76
- ui_design_exists: "false" # the feature holds a design.ui.md master ⇒ this merge has a THIRD target.
77
- # feature_paths runs on_error:continue, so this default is what a failed
78
- # readback falls back to: the two-master merge, unchanged.
79
- ui_design_doc: "" # specs/<Feature>/design.ui.md — the UI merge target
98
+ domain_model: "" # specs/<Context>/domain-model.md — the second merge target
99
+ domain_model_exists: "false"
100
+ delta_model: "" # specs/<Context>/domain-model.<id>.delta.md — the model half of the change
101
+ delta_model_exists: "false" # false ⇒ a behaviour-only change: the spec merge is the whole merge
102
+ model_orphan: "false" # a model delta whose context has no master to merge it into
103
+ model_master_ok: "true" # lowered by model_orphan_raise; the gate reads only this
104
+ backend_plan: "" # READ, never written here — the OKF edges are parsed out of it
105
+ backend_plan_exists: "false"
80
106
 
81
107
  workflow:
82
108
 
@@ -87,11 +113,14 @@ workflow:
87
113
  script: { ref: actions }
88
114
  subcommand: feature-paths
89
115
  outputs:
116
+ - { name: feature, from: "$.feature" }
90
117
  - { name: feature_dir, from: "$.feature_dir" }
91
118
  - { name: spec_file, from: "$.spec_file" }
92
119
  - { name: spec_exists, from: "$.spec_exists" }
93
- - { name: ui_design_exists, from: "$.ui_design_exists" } # ⇒ a third master to merge into
94
- - { name: ui_design_doc, from: "$.ui_design_file" }
120
+ # The backend plan is a READER's input here, not a merge target: the OKF cross-slice
121
+ # edges are parsed out of it, so this stage needs its path and whether it exists.
122
+ - { name: backend_plan, from: "$.backend_plan" }
123
+ - { name: backend_plan_exists, from: "$.backend_plan_exists" }
95
124
 
96
125
  - id: require_feature
97
126
  action: verify_context
@@ -103,7 +132,7 @@ workflow:
103
132
  action: verify_context
104
133
  require_true: spec_exists
105
134
  on_false: stop
106
- on_false_message: "No spec.md for the current feature. Run /appweaver specify + design first."
135
+ on_false_message: "No spec.md for the current feature — there is no master to merge into. Run /appweaver specify first."
107
136
 
108
137
  # 1b. Read the OPEN change (opened by cr). finalize-delta needs a change to close.
109
138
  - id: read_change
@@ -121,7 +150,59 @@ workflow:
121
150
  action: verify_context
122
151
  require_true: has_change
123
152
  on_false: stop
124
- on_false_message: "No open change on this feature — nothing to finalize. Run /appweaver specify → design → analyze-delta-design → implement-delta first."
153
+ on_false_message: "No open change on this feature — nothing to finalize. Run /appweaver specify → plan → analyze-plan-delta → implement first."
154
+
155
+ # 1b-ii. The change's own documents, from the ONE resolver that knows the layout: the
156
+ # delta-spec sits at FEATURE level (changes/<id>/) and the model delta at CONTEXT
157
+ # level (specs/<Context>/domain-model.<id>.delta.md), beside the master it merges
158
+ # into. Both merge targets and both sources come from here, so this stage never
159
+ # rebuilds a path by hand.
160
+ - id: cr_paths
161
+ action: execute_script
162
+ script: { ref: actions }
163
+ subcommand: cr-paths
164
+ args: ["--cr-id", "{{active_change}}", "--feature-dir", "{{feature_dir}}"]
165
+ outputs:
166
+ - { name: cr_dir, from: "$.cr_dir" }
167
+ - { name: delta_spec, from: "$.delta_spec" }
168
+ - { name: delta_spec_exists, from: "$.delta_spec_exists" }
169
+ - { name: delta_model, from: "$.delta_model" }
170
+ - { name: delta_model_exists, from: "$.delta_model_exists" }
171
+ - { name: delta_backend_plan, from: "$.delta_backend_plan" }
172
+ - { name: domain_model, from: "$.domain_model" }
173
+ - { name: domain_model_exists, from: "$.domain_model_exists" }
174
+
175
+ # The delta-spec is the merge's source of truth. A missing one would still reach the
176
+ # agent step as an announced-missing context file rather than a stop, and merging from
177
+ # nothing is how a master acquires content no change ever asked for.
178
+ - id: require_delta_spec
179
+ action: verify_context
180
+ require_true: delta_spec_exists
181
+ on_false: stop
182
+ on_false_message: "Change {{active_change}} has no delta-spec.md at {{delta_spec}} — nothing to merge into spec.md. Run /appweaver specify to author the change, or /appweaver recall-delta to withdraw it."
183
+
184
+ # A model delta with no master to merge it into. specify creates domain-model.md the
185
+ # first time it models a context, so this is a context whose master was moved or
186
+ # deleted after the change was opened. Stop rather than merge the spec half alone:
187
+ # close-delta archives the model delta, so a silently skipped model change is a
188
+ # change that is gone. Two ordered assignments, because the engine takes no compound
189
+ # condition: the probe runs only where the master is ABSENT and carries the delta's
190
+ # own presence, so the flag it raises means "a model delta with nothing to merge into".
191
+ # A feature outside any bounded context has neither file and passes both, which is
192
+ # the legacy layout and not an error.
193
+ - id: model_orphan_probe
194
+ condition: "{{domain_model_exists}} == false"
195
+ action: set
196
+ vars: { model_orphan: "{{delta_model_exists}}" }
197
+ - id: model_orphan_raise
198
+ condition: "{{model_orphan}} == true"
199
+ action: set
200
+ vars: { model_master_ok: "false" }
201
+ - id: require_model_master
202
+ action: verify_context
203
+ require_true: model_master_ok
204
+ on_false: stop
205
+ on_false_message: "Change {{active_change}} carries a model delta ({{delta_model}}) but its bounded context has no domain-model.md master at {{domain_model}}. Spec and model merge in one transaction, so this stage will not merge the spec half alone. Restore the context's domain-model.md (it is what specify creates when it first models the context) and re-run /appweaver finalize-delta."
125
206
 
126
207
  # 1c. DOWNSTREAM GATE (hard, with a REVIEWED override). Who depends on this aggregate —
127
208
  # its OKF Cross-References, TRANSITIVELY (C→B→A all count) and placeholder-filtered
@@ -175,7 +256,10 @@ workflow:
175
256
  fence_log: "{{fence_log}}"
176
257
  context:
177
258
  - { path: "{{spec_file}}" }
178
- - { path: "{{feature_dir}}/changes/{{active_change}}/delta-design.md" }
259
+ - { path: "{{delta_spec}}" }
260
+ # The delta plan is how the change was BUILT — the most direct account of what could
261
+ # have broken a dependent. Optional: a frontend-only change never has one.
262
+ - { path: "{{delta_backend_plan}}", optional: true }
179
263
  - id: write_broken_downstream
180
264
  action: agent_author
181
265
  condition: "{{downstream_count}} != 0"
@@ -251,16 +335,7 @@ workflow:
251
335
  on_mismatch: stop
252
336
  on_mismatch_message: "finalize-delta ABORTED — downstream fence RED after change {{active_change}} and the override was declined. {{downstream_count}} slice(s) depend on this aggregate ({{downstream_csv}}): the change would break a slice that USES this one. Triage: {{feature_dir}}/changes/{{active_change}}/broken-downstream.md. Fix the downstream break; or, if the behaviour change is intended, open a CR on the affected slice to re-baseline its golden-master. Then re-run /appweaver finalize-delta. Last log: {{fence_log}}"
253
337
 
254
- # 2. Ensure the master design.md exists (idempotent) — the second merge target.
255
- - id: design_scaffold
256
- action: execute_script
257
- script: { ref: actions }
258
- subcommand: design-scaffold
259
- args: ["--feature-dir", "{{feature_dir}}"]
260
- outputs:
261
- - { name: design_doc, from: "$.design_doc" }
262
-
263
- # 3. Pre-execution hooks.
338
+ # 2. Pre-execution hooks.
264
339
  - id: resolve_pre_hooks
265
340
  action: execute_script
266
341
  script: { ref: actions }
@@ -272,43 +347,53 @@ workflow:
272
347
  action: dispatch_hooks
273
348
  hooks_from_variable: before_hooks
274
349
 
275
- # 4. DOC-FIRST merge: fold the two delta docs into the two master docs in place.
350
+ # 3. THE MERGE: the change's two halves into the two masters, in place.
276
351
  # Context is fed via build_context — agent_generate reads the ENVELOPE, not a
277
352
  # step-level context: key (unlike agent_author/agent_query which read both).
353
+ # FOUR documents at most, and NO PLAN AMONG THEM. A plan in context is a plan the
354
+ # merge could quote from, and everything it would quote is a derivation of these
355
+ # two masters rather than a source for them — the delta plan's wording is how the
356
+ # change was BUILT, not what was agreed. The two model documents are independently
357
+ # optional — the gate above has already refused the one combination that would be a
358
+ # mistake — so a behaviour-only change still gets the master model in context, which
359
+ # is what lets the merge check that the BR- ids the spec cites actually exist.
278
360
  - id: ctx_merge
279
361
  action: build_context
280
362
  context:
281
- - { path: "{{feature_dir}}/changes/{{active_change}}/delta-spec.md" }
282
- - { path: "{{feature_dir}}/changes/{{active_change}}/delta-design.md" }
363
+ - { path: "{{delta_spec}}" }
283
364
  - { path: "{{spec_file}}" }
284
- - { path: "{{design_doc}}" }
285
- # THE UI LANE's pair, INDEPENDENTLY optional — the merge task reads which of them
286
- # arrived as its instruction. Neither: a backend-only feature, and a two-master
287
- # merge is the complete outcome. Master only: the change did not reach the UI, or
288
- # ⌛ delta-design-ui has not run — Change History row only. Both: a full third merge.
289
- # Optional rather than gated because a missing UI document is an ordinary state
290
- # here, not a misconfiguration worth stopping the merge over.
291
- - { path: "{{ui_design_doc}}", optional: true }
292
- - { path: "{{feature_dir}}/changes/{{active_change}}/delta-design.ui.md", optional: true }
365
+ - { path: "{{delta_model}}", optional: true }
366
+ - { path: "{{domain_model}}", optional: true }
293
367
  - id: merge_delta
294
368
  action: agent_generate
295
369
  tools: "Read,Write,Edit"
296
370
  permission_mode: acceptEdits
297
371
  task: |
298
- Merge the open change {{active_change}} into this feature's MASTER docs (all in
299
- context), DOC-FIRST. The delta docs (in context) are the source of the change;
300
- apply them and change nothing else.
301
- HOW MANY MASTERS: spec.md and design.md always. design.ui.md — the FRONTEND lane's
302
- master — only where it is in context (ui_design_exists={{ui_design_exists}}); its
303
- delta, delta-design.ui.md, may or may not be there beside it. Absent both, the
304
- feature has no UI half and merging two documents is the COMPLETE outcome, not a
305
- step you skipped.
372
+ Merge the open change {{active_change}} into the two MASTER documents in context.
373
+ The delta documents are the source of the change; apply them and change nothing else.
374
+ THE MASTERS: spec.md (this feature's behaviour) and, where it is in context,
375
+ domain-model.md (the bounded context's model — delta_model_exists={{delta_model_exists}}).
376
+ A change with no model delta merges the spec alone and that is the COMPLETE outcome,
377
+ not a step you skipped.
378
+ NO LANE PLAN IS A TARGET HERE. backend-plan.md and frontend-plan.md are DERIVED from
379
+ these two documents and are re-derived by /appweaver plan main after this merge; neither is
380
+ in context and neither is to be opened, read or edited. Do not restate a plan's
381
+ wording in a master: the plan is how the change was built, not what was agreed.
382
+ BOTH MASTERS MOVE TOGETHER. Where there is a model delta, edit spec.md and
383
+ domain-model.md in the same pass — a merged requirement citing a BR- the model never
384
+ gained, or a merged invariant no requirement reached, is exactly the split state this
385
+ stage exists to prevent.
306
386
  To spec.md:
307
387
  - Add each ADDED user story as a US-n tagged `Added: {{active_change}}`, and fold
308
- the ADDED / MODIFIED / REMOVED FR / BR / CN- / DC- / IC- / UX- items into their
309
- sections using the master's vocabulary (EARS FR, BR invariants, etc.). Every class
310
- the delta authored is merged, including UX- into Experience Standards: that section
311
- is the UI lane's only carrier, so a UX- item left behind reaches no UI phase.
388
+ the ADDED / MODIFIED / REMOVED FR / CN- / DC- / IC- / UX- items into their
389
+ sections using the master's vocabulary (EARS FR, etc.). Every class the delta
390
+ authored is merged, including UX- into Experience Standards: that section is what
391
+ the frontend plan is re-derived from, so a UX- item left behind reaches no UI.
392
+ - A BR- in the delta-spec is a CITATION, never a definition — the invariant itself
393
+ belongs to the model. Merge the citations (an FR's "Exercises", the
394
+ "Relies on invariants" line, a story's exercised ids) and merge the RULE only into
395
+ domain-model.md. A BR- whose text lands in spec.md is the restatement the model
396
+ exists to prevent, and it drifts the moment the model moves.
312
397
  - Where a lift section (Constraints & NFRs, Data Classification, Integration
313
398
  Contracts, Experience Standards) still reads "None", its first merged item REPLACES
314
399
  that placeholder; a section the delta does not touch keeps its "None" verbatim.
@@ -316,52 +401,45 @@ workflow:
316
401
  REMOVED item by removing it. Keep the rest of spec.md VERBATIM.
317
402
  - Append a Change History row for {{active_change}} (date + one-line summary).
318
403
  Its Version cell is {{next_version}} — the version this change ships as.
319
- To design.md:
320
- - ADD each new design element in its own structural section; apply each MODIFIED
321
- element as an in-place edit of the existing element; remove each REMOVED element.
322
- - Fold the delta's new DA-n / WA-n into the design's two deferral ledgers, and keep
323
- the Traceability section in sync — every new FR / BR / CN- / DC- / IC- / US- / SC-
324
- gets a design home. UX- is NOT among them: the service design realises no
325
- experience standard, so a Traceability row for one is a fabrication. Keep the rest
326
- of design.md VERBATIM.
327
- - Append a Change History row for {{active_change}} (date + one-line summary),
328
- Version cell {{next_version}}, in lockstep with the spec's row.
329
- To design.ui.md — ONLY where it is in context. TWO CASES, decided by whether
330
- delta-design.ui.md came with it:
331
- - NO delta-design.ui.md in context (or one whose Change summary reads "No UI
332
- consequence", its ADDED / MODIFIED / REMOVED sections all None): merge NOTHING
333
- into the body. The change was server-side, or the UI lane's own delta command
334
- has not run for it. Append ONLY a Change History row for {{active_change}} (date,
335
- Version cell {{next_version}}, summary stating the change carried no UI content)
336
- and change nothing else — close-delta stamps this document to {{next_version}} alongside
337
- its siblings, so a header carrying a version that no row explains is
338
- indistinguishable from a merge that dropped one. Do NOT invent UI content to
339
- justify the row, and do NOT infer the experience change from the service delta:
340
- design.md is not this document's source.
341
- With delta-design.ui.md in context:
342
- - ADD each new screen, component, state, interaction or contract binding into the
343
- master's OWN section — the delta's "Section (§ in design.ui.md)" column names it.
344
- Apply each MODIFIED element as an in-place edit of that element and remove each
345
- REMOVED one. A shared component or shared state container is edited ONCE, where
346
- the master defines it, never duplicated into a second place.
347
- - Fold the delta's interaction states into the states the master already records
348
- for those interactions, and its new DA-n / WA-n into the master's two ledgers.
349
- - Keep the Traceability section — the lane's spine — in sync: every UX- the delta
350
- realises and every new US- it serves gets a row naming its UI home. UX- BELONGS
351
- HERE AND ONLY HERE: design.md must never carry a UX- row (the service design
352
- realises no experience standard) and design.ui.md is the sole document that
353
- does, so a UX- merged into the spec and into neither design has been dropped.
354
- - Where a mandatory ledger still reads "None (as of vX.Y)", its first merged item
355
- REPLACES that placeholder; a ledger the delta does not touch keeps it verbatim.
356
- - Append a Change History row for {{active_change}} (date + one-line summary),
357
- Version cell {{next_version}}, in lockstep with the other two.
358
- Leave the `**Version**:` HEADER of EVERY document in context exactly as it is — it
359
- still reads {{version}} and does not move here. close-delta stamps the headers to
360
- {{next_version}} at the moment it bumps active_version.json, so every carrier of
361
- the number changes in one step by one mechanism. Do not invent a version anywhere.
362
- Preserve every unrelated section of EVERY doc byte-for-byte. Do NOT touch the
363
- delta docs (close-delta archives them later) and do NOT touch any source code.
364
- Report the files you changed.
404
+ To domain-model.md — ONLY where it and a model delta are both in context:
405
+ - THE MERGE UNIT IS ONE `## Aggregate: <Name>` SECTION, and the ONLY sections you may
406
+ open are the ones the model delta's "Aggregates touched" header names. Every other
407
+ aggregate section stays byte-for-byte, whatever the delta says elsewhere — another
408
+ open change may be merging into one of them, and the section boundary is what keeps
409
+ the two merges from colliding.
410
+ - ADDED: put each new entity, value object, domain event, invariant (BR-n) or
411
+ cross-aggregate invariant (XBR-n) into the sub-section it belongs to (### Entities,
412
+ ### Value objects, ### Domain events, ### Invariants) of the aggregate the delta
413
+ names for it. A BR-n is added with its RULE TEXT — this document is where the rule
414
+ lives. An XBR-n goes in `## Cross-Aggregate Invariants`, not inside an aggregate.
415
+ - A wholly NEW aggregate gets a whole new `## Aggregate: <Root>` section in the
416
+ master's own shape (Serving specs, Last changed, ### Boundary, ### Entities,
417
+ ### Value objects, ### Domain events, ### Invariants), placed after the existing
418
+ aggregate sections and before `## Cross-Aggregate Invariants`. Its
419
+ **Serving specs** is `{{feature}}`.
420
+ - MODIFIED: the delta states a BEFORE and an AFTER — verify the BEFORE is what the
421
+ master actually says, then replace it with the AFTER in place. If the master does
422
+ not say the BEFORE, do NOT guess: leave it and report the mismatch, because the
423
+ master has moved since the delta was authored and the change needs a rebase.
424
+ - REMOVED: remove the named element. Where the delta says what replaces it, the
425
+ replacement is in ADDED and merges there.
426
+ - Fold new terms into `## Ubiquitous Language` and new context relationships into
427
+ `## Context Map`, both only where the delta introduces them.
428
+ - On each aggregate section you touched, set **Last changed** to
429
+ `<the version token already on that line> · {{active_change}}` — replace the CR half
430
+ only. The version half is a CONTEXT version, stamped by script, and this stage is
431
+ not its author.
432
+ - Do NOT append a Change History row to domain-model.md and do NOT touch its
433
+ `**Context version**:` header. That ledger is close-delta's, written with the
434
+ version it bumps to — the model's row and its version land together or the row
435
+ carries a number nothing else agrees with.
436
+ Leave the `**Version**:` HEADER of spec.md exactly as it is — it still reads
437
+ {{version}} and does not move here. close-delta stamps it to {{next_version}} at the
438
+ moment it bumps active_version.json, so every carrier of the number changes in one
439
+ step by one mechanism. Do not invent a version anywhere.
440
+ Preserve every unrelated section of BOTH masters byte-for-byte. Do NOT touch the
441
+ delta documents (close-delta archives them later), do NOT open or edit any lane plan,
442
+ and do NOT touch any source code. Report the files you changed.
365
443
  outputs:
366
444
  - { name: merged_docs, from: "$.files" }
367
445
 
@@ -369,47 +447,68 @@ workflow:
369
447
  action: verify_context
370
448
  require_non_empty: merged_docs
371
449
  on_empty: stop
372
- on_empty_message: "The merge produced no changes — expected edits to spec.md and design.md (and design.ui.md where the feature has a UI half). Ensure the delta docs are filled (not the empty template) and re-run."
450
+ on_empty_message: "The merge produced no changes — expected an edit to spec.md, and to domain-model.md where the change carries a model delta. Ensure the delta documents are filled (not the empty template) and re-run."
373
451
 
374
- # 4b. Refresh the cross-slice EDGES from the NOW-MERGED master design.md — the delta's
375
- # added / removed references have just landed, so this is the first point they are
376
- # authoritative. Reconcile-on-write: new deps appear in the used slices'
377
- # Cross-References, dropped deps are pruned. `--origin auto` keeps the slice's
378
- # provenance (greenfield vs reverse-engineered) from its design.md, so this works
379
- # identically for a greenfield CR and a brownfield CR. Best-effort (never blocks close).
452
+ # 4b. Reconcile the cross-slice EDGES. They are parsed out of the master backend-plan.md
453
+ # (§1's References-by-id + its External integrations table) — the plan is not a merge
454
+ # target here, so this run neither adds nor prunes an edge on its own account; it
455
+ # re-asserts the index against the plan as it stands, which is what keeps a
456
+ # concept stub created by another slice's merge from lingering. The delta's own new
457
+ # dependencies reach the index when /appweaver plan main re-derives the plan from the
458
+ # masters this stage just merged. Skipped where the feature has no plan yet: parsing
459
+ # an absent document yields zero edges, and reconcile-on-write would read that as
460
+ # "every dependency was dropped" and prune the slice's real edges.
461
+ # `--origin auto` keeps the slice's provenance (greenfield vs reverse-engineered)
462
+ # from the plan itself. Best-effort (never blocks the merge).
380
463
  - id: xref_record
464
+ condition: "{{backend_plan_exists}} == true"
381
465
  action: execute_script
382
466
  on_error: continue
383
467
  script: { ref: xref }
384
468
  subcommand: record
385
- args: ["--feature-dir", "{{feature_dir}}", "--design", "{{design_doc}}", "--origin", "auto"]
469
+ args: ["--feature-dir", "{{feature_dir}}", "--design", "{{backend_plan}}", "--origin", "auto"]
386
470
  outputs:
387
471
  - { name: xref_edge_count, from: "$.edge_count" }
388
472
  - { name: xref_origin, from: "$.origin" }
389
473
 
390
- # 4b-ii. Re-author the concept's Overview from the NOW-MERGED master design.md — the
391
- # change may have moved the aggregate's boundary or invariants, and this is the
392
- # first point the master states them. Section-scoped splice (the Cross-References
393
- # rows other slices own are untouched). Best-effort; an empty answer is a no-op.
474
+ # 4b-ii. Re-author the concept's Overview from the NOW-MERGED domain-model.md. What the
475
+ # Overview states — the root, its invariants, its transaction boundary — is the
476
+ # model's own content, and the model is what this stage just moved, so this is both
477
+ # the authoritative source and the first point the change is in it. Section-scoped
478
+ # splice (the Cross-References rows other slices own are untouched). Skipped where
479
+ # the context has no model: there would be nothing in context to distil, and a
480
+ # query with no source invents one. Best-effort; an empty answer is a no-op.
481
+ # Re-scope the envelope first. build_context REPLACES it and it PERSISTS across the
482
+ # steps that follow, so without this the Overview query would still be holding the
483
+ # merge's payload — the delta-spec and the model delta among it — and would distil the
484
+ # concept's prose from a pending change as readily as from the merged master.
485
+ - id: ctx_overview
486
+ condition: "{{domain_model_exists}} == true"
487
+ action: build_context
488
+ context:
489
+ - { path: "{{domain_model}}" }
394
490
  - id: okf_overview
491
+ condition: "{{domain_model_exists}} == true"
395
492
  action: agent_query
396
- context: ["{{design_doc}}"]
397
493
  include_standards: false
398
494
  schema: { overview: "<2-4 sentences of prose>" }
399
495
  mock: { overview: "" }
400
496
  task: |
401
- Write the OKF knowledge-concept Overview for the aggregate that the merged
402
- design.md (in context) describes. 2-4 sentences of PROSE, distilled from §1
403
- Domain Model: name the aggregate ROOT and what the aggregate IS; state the
404
- INVARIANTS it guards; state its TRANSACTION BOUNDARY — what it OWNS inside the
405
- boundary vs what it only REFERENCES by id. Plain prose only: no headings, no
406
- bullets, no code fences, no file paths, and no restating of the dependency edges
407
- (those live in the Depends-On / Cross-References tables). Describe only what the
408
- design states — invent nothing.
497
+ Write the OKF knowledge-concept Overview for the aggregate `{{feature}}`, from the
498
+ merged domain-model.md in context. That document models the WHOLE bounded context —
499
+ read ONLY the `## Aggregate: {{feature}}` section (and any `## Cross-Aggregate
500
+ Invariants` naming it); the other aggregates belong to other concepts.
501
+ 2-4 sentences of PROSE: name the aggregate ROOT and what the aggregate IS; state the
502
+ INVARIANTS it guards (its BR-n rules); state its TRANSACTION BOUNDARY — what it OWNS
503
+ inside the boundary vs what it only REFERENCES by id. Plain prose only: no headings,
504
+ no bullets, no code fences, no file paths, and no restating of the dependency edges
505
+ (those live in the Depends-On / Cross-References tables). Describe only what the model
506
+ states — invent nothing. If the section is absent, answer with an empty string.
409
507
  outputs:
410
508
  - { name: okf_overview, from: "$.overview" }
411
509
 
412
510
  - id: okf_overview_write
511
+ condition: "{{domain_model_exists}} == true"
413
512
  action: execute_script
414
513
  on_error: continue
415
514
  script: { ref: xref }
@@ -424,7 +523,7 @@ workflow:
424
523
  on_error: continue
425
524
  script: { ref: xref }
426
525
  subcommand: log
427
- args: ["--note", "CR {{active_change}} merged (downstream gate: {{gate_decision}}) — OKF cross-references refreshed"]
526
+ args: ["--note", "CR {{active_change}} merged into spec.md + domain-model.md (downstream gate: {{gate_decision}}) — OKF cross-references refreshed"]
428
527
  outputs:
429
528
  - { name: xref_log, from: "$.log" }
430
529
 
@@ -432,8 +531,8 @@ workflow:
432
531
  # analyze-slice — see the header. The change stays OPEN past this stage on purpose:
433
532
  # it is what keeps a red analyze-slice recoverable ("fix and re-run") instead of
434
533
  # landing on an already-shipped version. The delta docs therefore remain in
435
- # changes/{{active_change}}/ even though their content is now merged; close-delta
436
- # moves them to changes/archive/.
534
+ # changes/{{active_change}}/ and the model delta beside domain-model.md even though
535
+ # their content is now merged; close-delta moves both to changes/archive/.
437
536
 
438
537
  # 6. Post-execution hooks.
439
538
  - id: resolve_post_hooks
@@ -455,8 +554,8 @@ outputs:
455
554
  downstream_count: "{{downstream_count}}"
456
555
  gate_decision: "{{gate_decision}}"
457
556
  xref_edge_count: "{{xref_edge_count}}"
458
- ui_design_updated: "{{ui_design_exists}}" # true ⇒ design.ui.md was a target of this stage. It reports
459
- # the MASTER's presence, not how much moved: a change with no
460
- # UI delta still takes its Change History row, because
461
- # close-delta stamps the header either way.
462
- next: "Delta MERGED into spec.md + design.md, and into design.ui.md where the feature has a UI half (ui_design_updated={{ui_design_exists}} — with a UI delta present that is a full merge, without one it is the Change History row that keeps the header close-delta stamps explainable); OKF edges refreshed ({{xref_edge_count}} dep(s)), {{downstream_count}} downstream slice(s) fenced (gate: {{gate_decision}}). Change {{active_change}} is STILL OPEN and the version is still {{version}}. Next: /appweaver analyze-slice — it now reads a master spec that carries this change, and a red result is still recoverable because nothing has been archived. Then /appweaver close-delta archives the change and bumps the version."
557
+ domain_model: "{{domain_model}}"
558
+ model_merged: "{{delta_model_exists}}" # true ⇒ domain-model.md was the second target of this merge.
559
+ # false = a behaviour-only change: the spec merge was the
560
+ # whole merge, not a half of one.
561
+ next: "Delta MERGED into spec.md, and into the context's domain-model.md one aggregate section at a time where the change carried a model delta (model_merged={{delta_model_exists}} — false means a behaviour-only change, so the spec merge was the whole merge). The lane plans were NOT touched — they are derived from these two masters, and ⌛ re-deriving a MASTER plan is not built yet (plan self-detects on plan-exists and would author another delta), so backend-plan.md / frontend-plan.md stand at their last authored content and analyze-plan will report the drift against the merged spec and model. OKF edges reconciled ({{xref_edge_count}} dep(s)), {{downstream_count}} downstream slice(s) fenced (gate: {{gate_decision}}). Change {{active_change}} is STILL OPEN and the version is still {{version}}. Next: /appweaver analyze-slice — it now reads masters that carry this change, and a red result is still recoverable because nothing has been archived. Then /appweaver close-delta archives the change and bumps the version."