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
@@ -1,57 +1,104 @@
1
1
  # =============================================================================
2
- # aw-spec-implement (orchestrator) — code generation from ONE STORY'S DESIGN
2
+ # aw-spec-implement (orchestrator) — code generation from the LANE PLANS, BOTH LANES
3
3
  #
4
- # The executable end of the story loop: GENERATE the service lane's source files and
5
- # paired tests for ONE STORY, strictly from the LATEST design pass in that story's own
6
- # service design — specs/<F>/stories/<ID>/story-backend-plan.md.
4
+ # GENERATE the source files + paired tests for the aggregate, STRICTLY from its lane
5
+ # plans — the mediators between the spec and the code:
6
+ # • specs/<Context>/<Feature>/backend-plan.md when a BACKEND lane is declared
7
+ # • specs/<Context>/<Feature>/frontend-plan.md when a FRONTEND lane is declared
7
8
  #
8
- # IT BUILDS A SLICE, NOT A FEATURE. The old contract generated the whole aggregate from
9
- # the feature design every run, which is only correct once: the second run rewrites the
10
- # first story's code from a document that has since moved. A story design names the
11
- # files it changes, so this phase edits an existing tree — creating only what the pass
12
- # says is new — and its OKF write is a MERGE, never a replace.
9
+ # THE LANES COME FROM stacks.json, NOT FROM A FLAG — select-stack is the sole writer of
10
+ # the declaration, and this phase implements whichever lanes are there: backend only,
11
+ # frontend only, or both. It absorbs the former `implement-ui` and `implement-delta`
12
+ # commands, exactly as `plan` absorbed `design-ui` and the delta-design module.
13
13
  #
14
- # DESIGN-ONLY, AND THE DESIGN IS THE STORY'S. spec.md is deliberately NOT a generation
15
- # input: generating from the spec routes around every gate between them and makes the
16
- # design advisory without anyone deciding that it should. The spec appears once, AFTER
17
- # the code exists, in a read-only acceptance check that reports and never repairs.
14
+ # SELF-DETECTING, off the CHANGES ON DISK (symmetric with how `plan` keys off the plan
15
+ # documents' own existence):
16
+ # • BOOTSTRAP (no change open) → generate from the MASTER plans.
17
+ # Next: /appweaver build → analyze-slice.
18
+ # • DELTA (one or more changes/<CR-id>/ open) → ASK WHICH ONE, activate it, and
19
+ # generate ONLY what that change's delta plans mark ADDED / MODIFIED, into the
20
+ # tree that is already there. The masters are the BASE, read and never written.
21
+ # A bare CR id in the argument selects without a prompt; a feature with exactly one
22
+ # open change auto-confirms. Next: build → analyze-slice-delta (which verifies that
23
+ # what was planned was actually built, before the merge) → finalize-delta →
24
+ # analyze-slice → close-delta.
25
+ # "The most recently opened" is a guess that is wrong the moment two people work in
26
+ # parallel, so the change is CHOSEN and then ACTIVATED (cr-activate) — every stage after
27
+ # this one still resolves "the" change through active_version.json.active_change.
18
28
  #
19
- # THE LATEST PASS WINS, AND A HAND EDIT TO IT IS HONOURED. Passes append; earlier ones
20
- # are superseded history. The pass this run builds from is named in the prompt and in
21
- # the outputs, so "which design produced this code" has one answer.
29
+ # EACH LANE IS GATED ON ITS OWN, AND A LANE'S GAP IS NOT THE RUN'S. A declared lane needs
30
+ # two things before it can generate: the plan for THIS run (its master plan, or this
31
+ # change's delta plan) and a scaffolded project under the source path it declares. A lane
32
+ # that is missing either is REPORTED with the fix named and SKIPPED — the other lane still
33
+ # runs. Failing the whole command for the frontend's sake is how a backend that was ready
34
+ # goes unbuilt; failing silently is how a frontend that was never generated reports green.
35
+ # Only when NO lane can run does the phase stop, naming every reason it collected.
22
36
  #
23
- # INPUT CHANNELS, each with one job:
24
- # story-backend-plan.md the LATEST pass = the complete WHAT for this slice
25
- # stories/<ID>.md the story's acceptance criteria — what "done" means
26
- # current code (OKF) what EXISTS, per lane, so this run edits rather than recreates
27
- # interfaces.md the PUBLISHED surface the generated code must match
28
- # adr.md what was DECIDED — binding unless a pass supersedes it
29
- # constitution + rules HOW to build it (stack, layout, standards)
30
- # backend-plan.md is NOT a generation input. The pass was designed inside it and is the
31
- # concrete form of it; re-reading the plan here would let code drift from the pass back
32
- # toward the architecture, with no gate between the two. It is read for two DERIVED
33
- # facts only — the aggregate's cross-slice edges and its Overview prose — both of which
34
- # are feature-level properties no single story can state.
37
+ # IT ASKS BEFORE IT WRITES. Generation is the expensive, side-effecting step of the
38
+ # pipeline, so each runnable lane confirms first, naming its stack, its plan and the
39
+ # subtree it will write into. Answering no to one lane leaves the other free to run.
35
40
  #
36
- # GATES (all loud, all before any write): the service lane declared with its source path;
37
- # a scaffolded project under that path; an active feature; an OPEN story; and that story
38
- # having a service design at all. A design behind the current spec, or a story the spec
39
- # has moved past, is ANNOUNCED — never refused: the story is legitimately in flight, and
40
- # the spec may have moved on ids it does not carry. The route out of both is named.
41
+ # THE PLAN SAYS WHAT TO BUILD, THE MODEL SAYS WHAT THE DOMAIN IS. This phase does NOT read
42
+ # spec.md — analyze-plan already conformed the plans to it, so the plan is the complete,
43
+ # authoritative source for WHICH elements exist, where each lives and what surface they
44
+ # publish, and anything missing from it is fixed in `plan`, never compensated for here.
45
+ # But since the model split a plan CITES an invariant by id and never restates it, so the
46
+ # bounded context's domain-model.md is in context too, as the authority on the entity
47
+ # names, the aggregate boundary, the lifecycle states and the TEXT of every BR-nnn the plan
48
+ # cites. The plan names the PLACE a rule is enforced; the model states the RULE.
49
+ # ON THE DELTA PATH THE MODEL ARRIVES IN TWO HALVES — the master, plus that change's
50
+ # domain-model.<CR-id>.delta.md — and the effective model is the two read together, the
51
+ # delta winning where they differ. It has to be, because finalize-delta merges the model
52
+ # AFTER this phase: at generation time the master does not yet carry the change, and a
53
+ # generator reading only the master would build the change's code against the pre-change
54
+ # rules. No OTHER change's pending delta is loaded: a predecessor was already accounted
55
+ # for when the delta plan was authored, and reaching for one here would implement a
56
+ # change that has not landed.
57
+ # THE MODEL IS READ, NEVER MINED FOR WORK. It describes the whole bounded context while a
58
+ # run builds one feature, so an entity or invariant in it that no plan element realises is
59
+ # a PLANNING gap to surface — not work to invent here.
41
60
  #
42
- # Launch: /appweaver implement [<story-id>] [guidance] (asks when ambiguous)
43
- # Prereq: /appweaver story-plan (the story's design) · Next: /appweaver build
61
+ # THE STACK BINDS HERE. The plans are technology-agnostic — a component is a
62
+ # responsibility, a contract is a set of field shapes. The language, framework, router,
63
+ # state library, component library and styling approach come from the lane declaration +
64
+ # the constitution halves + this phase's rule sets. Each lane takes its rules from its own
65
+ # heading of tech-rule-bindings, and the frontend set is loaded only when that lane runs,
66
+ # so a backend-only project sees exactly the rules it did before:
67
+ # • BACKEND — `## /aw-spec-implement` on the bootstrap path, `## /aw-spec-implement-delta`
68
+ # on the delta path. Keyed by MODE, which is what the two commands did separately, so
69
+ # no project's rule set changes by being merged into one command.
70
+ # • FRONTEND — `## /aw-spec-implement-ui`, both paths. There was never a delta heading
71
+ # for this lane because the delta half of it was never built; it is built here.
72
+ #
73
+ # ONE SUBTREE PER LANE. Each lane owns the source path stacks.json declares for it and
74
+ # writes nothing outside it. A file written elsewhere belongs to the other lane or to no
75
+ # lane at all, and the project gate is deliberately stricter for the frontend: a manifest
76
+ # found only elsewhere in the repository is the service lane's, and accepting it is how a
77
+ # frontend that was never scaffolded is generated into anyway and reports green.
78
+ #
79
+ # WHAT IT DOES NOT DO. It never writes spec.md, a master plan or the domain model, and it
80
+ # never closes a change — finalize-delta merges the change's spec and model halves,
81
+ # analyze-slice verifies the slice, and close-delta archives it and bumps the version.
82
+ #
83
+ # Prereqs: /appweaver plan (+ analyze-plan, or analyze-plan-delta for a change), and a
84
+ # project scaffolded under each lane's source path — this phase GENERATES into that tree,
85
+ # so with no project there is nowhere to place the files. /appweaver foundation unpacks one
86
+ # where a bundle ships for the stack; a stack with no bundle scaffolds with its own tooling
87
+ # instead, and the gate accepts either, since it tests for a project rather than a bundle.
88
+ #
89
+ # Launch: /appweaver implement [guidance]
90
+ # /appweaver implement CR-20260910-… (implement THAT open change, no prompt)
44
91
  # =============================================================================
45
92
 
46
93
  name: aw-spec-implement
47
- description: "Generate the service lane's source files and paired tests for ONE STORY, strictly from the LATEST design pass in stories/<ID>/story-backend-plan.md, bound to the declared backend stack and written only under that lane's source path. Model-driven and design-only — the spec is never a generation input; it appears once afterwards in a read-only acceptance check that reports and never repairs. Incremental by contract: it edits the feature's existing tree, creating only what the pass names as new, and MERGES the files it wrote into the feature's OKF concept rather than replacing the list."
94
+ description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own plan and its own scaffolded project, and a lane missing either is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
48
95
  version: 3.0.0
49
96
  agent_agnostic: true
50
97
  dialect: aw-spec.orchestrator/v1
51
98
 
52
99
  inputs:
53
100
  user_input:
54
- description: Optional story id and/or implementation guidance (e.g. "Order-001 keep the mapper hand-written").
101
+ description: "Optional implementation guidance (focus area, stack hints). On the delta path a bare CR id selects which open change to implement."
55
102
  required: false
56
103
  bind: USER_INPUT
57
104
 
@@ -63,400 +110,810 @@ scripts:
63
110
  sh: .appweaver/scripts/bash/aw-xref.sh
64
111
  ps1: .appweaver/scripts/powershell/aw-xref.ps1
65
112
 
66
- # Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). The FRAMEWORK /
67
- # code-generation rule files are CONFIGURATION, loaded per-command from
68
- # guardrails/projects/<project>/tech-rule-bindings.md under "## /aw-spec-implement" by
69
- # the tech-select module. Per the rule-partition this phase loads NO domain rule file —
70
- # the domain reached it through spec → feature plan → the story's design pass. The
71
- # architecture principles arrive via constitution.technical.md (referred to directly,
72
- # as-is — NOT mapped). Component rule docs are NOT loaded here: implement codes against
73
- # a component's PORT, and installing the component is /appweaver add-component.
113
+ # Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). Each lane's
114
+ # FRAMEWORK / code-generation rule files are CONFIGURATION, loaded per-lane from
115
+ # guardrails/projects/<project>/tech-rule-bindings.md by the tech-select module (see the
116
+ # tech_select steps below). Per the rule-partition this phase loads NO domain rule file —
117
+ # everything domain reached the code through spec → plan. The architecture and UX bases
118
+ # arrive via constitution.technical.md / constitution.ui.md (referred to directly, as-is —
119
+ # NOT mapped); the structure via the lane plans. Component rule docs are NOT loaded here:
120
+ # this phase codes against a component's PORT, and installing the component itself is a
121
+ # separate step, /appweaver add-component. (missing files skipped at assembly time)
74
122
  standards:
75
123
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
76
124
 
77
125
  vars:
78
- feature_dir: ""
79
- spec_file: ""
126
+ # WHICH RUN THIS IS. bootstrap = generate from the masters; delta = generate one open
127
+ # change's delta plans. Decided by the changes on disk, not by a flag.
128
+ mode: "bootstrap"
129
+ has_changes: "false"
130
+ cr_choice: ""
131
+ active_change: ""
132
+ cr_dir: ""
133
+ delta_spec: ""
134
+ delta_model: ""
135
+ delta_model_exists: "false"
136
+ delta_backend_plan: ""
137
+ delta_backend_plan_exists: "false"
138
+ delta_frontend_plan: ""
139
+ delta_frontend_plan_exists: "false"
140
+ wave: ""
141
+ # PER-LANE STATE. Each lane resolves its own plan, its own project and its own answer;
142
+ # a lane that is not declared never resolves any of them and its steps never run.
143
+ # The engine has no compound conditions, so each AND is an ordered assignment: a step
144
+ # conditioned on one fact assigning the value of the other.
80
145
  be_plan: ""
81
- be_plan_exists: "false"
82
- # story resolution
83
- any_open: "false"
84
- open_ids: "[]"
85
- digest: ""
86
- resolved: "false"
87
- needs_pick: "false"
88
- pick_reason: ""
89
- picked_story: ""
90
- story: ""
91
- story_path: ""
92
- story_open: "false"
93
- status_msg: ""
94
- story_title: ""
95
- exercises_csv: ""
96
- constraints_csv: ""
97
- current_version: ""
98
- stale_csv: ""
99
- stale_note: ""
100
- story_dir: ""
101
- adr_path: ""
102
- if_path: ""
103
- # the story's SERVICE design
104
- design_doc: ""
105
- design_exists: "false"
106
- design_pass: "0"
107
- design_spec_version: ""
108
- design_stale: "false"
109
- # the feature's current code, per lane (OKF)
110
- okf_concept: ""
111
- coverage_digest: ""
112
- be_source_csv: ""
113
- be_source_count: "0"
114
- fe_source_count: "0"
115
- # generation + enrichment
116
- files: ""
117
- xref_wrote: ""
118
- xref_edge_count: "0"
119
- okf_overview: ""
120
- accept_ok: "true"
121
- accept_gaps: ""
122
- accept_note: ""
146
+ be_plan_ok: "false"
147
+ be_project_ok: "false"
148
+ be_project_scope: ""
149
+ be_runnable: "false"
150
+ be_answer: ""
151
+ be_go: "false"
152
+ be_mode: "skip" # skip | bootstrap | delta
153
+ be_files: []
154
+ fe_plan: ""
155
+ fe_plan_ok: "false"
156
+ fe_project_ok: "false"
157
+ fe_project_scope: ""
158
+ fe_runnable: "false"
159
+ fe_answer: ""
160
+ fe_go: "false"
161
+ fe_mode: "skip" # skip | bootstrap | delta
162
+ fe_files: []
163
+ generate_any: "false"
164
+ record_edges: "false"
165
+ overview_ok: "false"
166
+ domain_model: ""
167
+ domain_model_exists: "false"
168
+ # The backend lane's rule-set heading, keyed by MODE — see the header. The frontend
169
+ # lane's is the same on both paths, so it is written inline at its include.
170
+ be_tech_command: "aw-spec-implement"
171
+ # HOOK PHASES. The merged command fires the phase of every branch it actually takes, so
172
+ # an extensions.yml wired to any of the three former commands keeps working:
173
+ # before/after_implement always, the _ui pair when the frontend lane generates, the _cr
174
+ # pair on the delta path. A branch not taken passes the base phase again, and
175
+ # resolve-hooks merges its phases and dedups by command, so the repeat adds nothing.
176
+ pre_ui_phase: "before_implement"
177
+ pre_cr_phase: "before_implement"
178
+ post_ui_phase: "after_implement"
179
+ post_cr_phase: "after_implement"
123
180
 
124
181
  workflow:
125
182
 
126
- # 0. TECHNICAL rule files → sticky standards (configuration, not hardcoded).
127
- # No blocking gate: an unconfigured project soft-continues, because
128
- # constitution.technical.md still carries the technology base.
129
- - id: tech_select
130
- action: include
131
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
132
- with: { command: aw-spec-implement }
133
-
134
- # 1. The active feature. spec.md and backend-plan.md are resolved here but neither is
135
- # a generation input — see the header for what each is actually used for.
183
+ # 0. THE LANE MODEL. lane-verify with NO --require, because this phase requires neither
184
+ # kind: a backend-only and a frontend-only project are both correct states, and which
185
+ # lanes exist is exactly what it is here to find out. It still reports every DECLARED
186
+ # lane whose source path is empty, missing or overlapping another's — a stack named
187
+ # against a path that is not there is a mistake in a phase that writes code — and it
188
+ # always exits 0, so the reason reaches the gate below as text.
189
+ - id: lane_verify
190
+ action: execute_script
191
+ on_error: continue
192
+ script: { ref: actions }
193
+ subcommand: lane-verify
194
+ args: ["--command", "/appweaver implement"]
195
+ outputs:
196
+ - { name: lane_ok, from: "$.ok" }
197
+ - { name: lane_issues, from: "$.message" }
198
+ - { name: be_declared, from: "$.backend_declared" }
199
+ - { name: be_lane_id, from: "$.backend_lane_id" }
200
+ - { name: be_source_path, from: "$.backend_source_path" }
201
+ - { name: be_profile, from: "$.backend_profile" }
202
+ - { name: be_title, from: "$.backend_title" }
203
+ - { name: be_language, from: "$.backend_language" }
204
+ - { name: be_ecosystem, from: "$.backend_ecosystem" }
205
+ - { name: be_build, from: "$.backend_build" }
206
+ - { name: be_test, from: "$.backend_test" }
207
+ - { name: fe_declared, from: "$.frontend_declared" }
208
+ - { name: fe_lane_id, from: "$.frontend_lane_id" }
209
+ - { name: fe_source_path, from: "$.frontend_source_path" }
210
+ - { name: fe_profile, from: "$.frontend_profile" }
211
+ - { name: fe_title, from: "$.frontend_title" }
212
+ - { name: fe_language, from: "$.frontend_language" }
213
+ - { name: fe_ecosystem, from: "$.frontend_ecosystem" }
214
+ - { name: fe_build, from: "$.frontend_build" }
215
+ - { name: fe_test, from: "$.frontend_test" }
216
+
217
+ - id: require_lanes
218
+ action: verify_context
219
+ require_true: lane_ok
220
+ on_false: stop
221
+ on_false_message: >
222
+ /appweaver implement cannot run — {{lane_issues}}
223
+ The lane model in .appweaver/stacks.json is what tells this phase which subtree it
224
+ owns, what language to generate and what to compile. Declaring it is the first step
225
+ of both the greenfield and the brownfield sequence.
226
+
227
+ # 1. The current feature and its MASTER documents. The plans resolve under their legacy
228
+ # names too (design.md / design.ui.md), so a feature authored before the lane split
229
+ # is implemented from the document that is actually on disk.
136
230
  - id: feature_paths
137
231
  action: execute_script
138
232
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
139
233
  script: { ref: actions }
140
234
  subcommand: feature-paths
141
235
  outputs:
142
- - { name: feature_dir, from: "$.feature_dir" }
143
- - { name: spec_file, from: "$.spec_file" }
144
- - { name: be_plan, from: "$.backend_plan_file" }
145
- - { name: be_plan_exists, from: "$.backend_plan_exists" }
236
+ - { name: feature, from: "$.feature" }
237
+ - { name: feature_dir, from: "$.feature_dir" }
238
+ - { name: backend_plan, from: "$.backend_plan" }
239
+ - { name: backend_plan_exists, from: "$.backend_plan_exists" }
240
+ - { name: frontend_plan, from: "$.frontend_plan" }
241
+ - { name: frontend_plan_exists, from: "$.frontend_plan_exists" }
242
+
146
243
  - id: require_feature
147
244
  action: verify_context
148
245
  require_non_empty: feature_dir
149
246
  on_empty: stop
150
247
  on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement."
151
248
 
152
- # 2. WHICH STORY. Same resolution story-plan uses: an explicit id in the input wins,
153
- # anything else (nothing named, an unknown id, several) becomes a pick from the open
154
- # backlog. One scan returns every fact the gates below read.
155
- - id: story_scan
249
+ # The bounded context's DOMAIN MODEL — in every generating lane's payload as the authority
250
+ # on what the plan's cited BR-nnn actually require and on the ubiquitous language, and the
251
+ # source of the OKF concept Overview at the end. On the delta path it is joined by that
252
+ # change's own model delta, which cr-paths resolves. See the header.
253
+ - id: context_paths
156
254
  action: execute_script
255
+ on_error: continue
157
256
  script: { ref: actions }
158
- subcommand: story-plan-check
159
- args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
257
+ subcommand: context-paths
160
258
  outputs:
161
- - { name: any_open, from: "$.any_open" }
162
- - { name: open_ids, from: "$.open_ids" }
163
- - { name: digest, from: "$.digest" }
164
- - { name: resolved, from: "$.resolved" }
165
- - { name: needs_pick, from: "$.needs_pick" }
166
- - { name: pick_reason, from: "$.pick_reason" }
167
- - { name: story, from: "$.story" }
168
- - { name: story_path, from: "$.story_path" }
169
- - { name: story_open, from: "$.story_open" }
170
- - { name: status_msg, from: "$.status_msg" }
171
- - { name: story_title, from: "$.story_title" }
172
- - { name: exercises_csv, from: "$.exercises_csv" }
173
- - { name: constraints_csv, from: "$.constraints_csv" }
174
- - { name: current_version, from: "$.current_version" }
175
- - { name: stale_csv, from: "$.stale_csv" }
176
- - { name: stale_note, from: "$.stale_note" }
177
- - { name: story_dir, from: "$.story_dir" }
178
- - { name: adr_path, from: "$.adr_path" }
179
- - { name: if_path, from: "$.if_path" }
180
- - { name: design_doc, from: "$.be_doc_path" }
181
- - { name: design_exists, from: "$.be_doc_exists" }
182
- - { name: design_pass, from: "$.be_last_pass" }
183
- - { name: design_spec_version, from: "$.be_doc_spec_version" }
184
- - { name: design_stale, from: "$.be_doc_stale" }
185
- - id: require_open_backlog
186
- action: verify_context
187
- require_true: any_open
188
- on_false: stop
189
- on_false_message: "No open stories for this feature — there is nothing to implement. Stories are minted by /appweaver specify (bootstrap) and /appweaver sync-stories (when the spec moves)."
190
- - id: ask_story
191
- condition: "{{needs_pick}} == true"
192
- action: ask
193
- prompt: |
194
- Which story is this implementation for? ({{pick_reason}})
195
-
196
- {{digest}}
197
- options_from: "{{open_ids}}"
198
- answer_key: picked_story
199
- answer_var: picked_story
200
- - id: story_pick_check
201
- condition: "{{needs_pick}} == true"
259
+ - { name: context, from: "$.context" }
260
+ - { name: domain_model, from: "$.domain_model" }
261
+ - { name: domain_model_exists, from: "$.domain_model_exists" }
262
+
263
+ # 2. WHICH CHANGES ARE OPEN — the evidence is changes/<CR-id>/ on disk; a closed change
264
+ # has moved to changes/archive/. Any open change makes this run a DELTA run.
265
+ # `matched_label` resolves a CR id passed as the argument to the whole option string,
266
+ # so the pick below can auto-select it (the engine matches a preselection against the
267
+ # option text exactly).
268
+ - id: cr_list
202
269
  action: execute_script
203
270
  script: { ref: actions }
204
- subcommand: story-plan-check
205
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
271
+ subcommand: cr-list
272
+ args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
206
273
  outputs:
207
- - { name: resolved, from: "$.resolved" }
208
- - { name: story, from: "$.story" }
209
- - { name: story_path, from: "$.story_path" }
210
- - { name: story_open, from: "$.story_open" }
211
- - { name: status_msg, from: "$.status_msg" }
212
- - { name: story_title, from: "$.story_title" }
213
- - { name: exercises_csv, from: "$.exercises_csv" }
214
- - { name: constraints_csv, from: "$.constraints_csv" }
215
- - { name: stale_csv, from: "$.stale_csv" }
216
- - { name: stale_note, from: "$.stale_note" }
217
- - { name: story_dir, from: "$.story_dir" }
218
- - { name: design_doc, from: "$.be_doc_path" }
219
- - { name: design_exists, from: "$.be_doc_exists" }
220
- - { name: design_pass, from: "$.be_last_pass" }
221
- - { name: design_spec_version, from: "$.be_doc_spec_version" }
222
- - { name: design_stale, from: "$.be_doc_stale" }
223
- - id: require_story
224
- action: verify_context
225
- require_true: resolved
226
- on_false: stop
227
- on_false_message: |
228
- Could not resolve a story from '{{picked_story}}'. Open stories:
229
- {{digest}}
230
- Re-run implement naming one of them.
231
- - id: require_open_story
232
- action: verify_context
233
- require_true: story_open
234
- on_false: stop
235
- on_false_message: "{{status_msg}}"
274
+ - { name: change_options, from: "$.select_options" }
275
+ - { name: matched_label, from: "$.matched_label" }
276
+ - { name: has_changes, from: "$.has_changes" }
277
+ - { name: change_count, from: "$.count" }
278
+
279
+ - id: mode_delta
280
+ condition: "{{has_changes}} == true"
281
+ action: set
282
+ vars:
283
+ mode: "delta"
284
+ pre_cr_phase: "before_implement_cr"
285
+ post_cr_phase: "after_implement_cr"
286
+ be_tech_command: "aw-spec-implement-delta"
287
+
288
+ - id: pick_change
289
+ condition: "{{mode}} == delta"
290
+ action: user_select
291
+ prompt: "Which open change is being implemented? Deltas sharing a wave are independent; a higher wave is built on top of the lower ones, so implement those first."
292
+ options_from_variable: change_options
293
+ preselect_from: matched_label
294
+ single_option_confirm: true
295
+ outputs:
296
+ - { name: cr_choice, from: selection }
236
297
 
237
- # 3. THE STORY MUST HAVE A SERVICE DESIGN. This phase generates from a design pass and
238
- # from nothing else, so an undesigned story has no input — and the fix is one
239
- # command, not a judgement call.
240
- - id: require_design
241
- action: verify_context
242
- require_true: design_exists
243
- on_false: stop
244
- on_false_message: "Story {{story}} has no service design — {{story_dir}}/story-backend-plan.md does not exist. Run /appweaver story-plan {{story}} first: it designs the slice against the feature's backend plan, the current code and the published interfaces. implement generates from that pass and has no other input."
298
+ # 2a. Make the chosen change the ACTIVE one. build, finalize-delta, analyze-slice and
299
+ # close-delta all still resolve the change through active_version.json.active_change,
300
+ # so without this the code generated for CR-B would be merged and closed as CR-C.
301
+ - id: activate
302
+ condition: "{{mode}} == delta"
303
+ action: execute_script
304
+ script: { ref: actions }
305
+ subcommand: cr-activate
306
+ args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
307
+ outputs:
308
+ - { name: active_change, from: "$.active_change" }
245
309
 
246
- # 3a. A design behind the spec is ANNOUNCED, not refused. The story is legitimately in
247
- # flight and the spec may have moved on ids it does not carry; stale_csv below is
248
- # the sharper signal, because it names only the ids THIS story actually carries.
249
- - id: design_stale_notice
250
- condition: "{{design_stale}} == true"
251
- action: verify_context
252
- require_value: design_stale
253
- equals: "false"
254
- on_mismatch: continue
255
- on_mismatch_message: "Design pass {{design_pass}} was written against spec {{design_spec_version}} and the spec is now at {{current_version}}. Generating from it is not wrong — the move may not touch this story — but if it does, the code inherits a contract that no longer holds. Re-run /appweaver story-plan {{story}} to design against the current spec, or check the ids named below."
256
- # stale_csv is a STRING, so require_value/equals compares cleanly and needs no
257
- # condition. design_stale above is a JSON boolean, which require_value stringifies as
258
- # 'True' — it can never equal 'false', so that one is gated by `condition:` and the
259
- # value gate is only the vehicle that prints (law 43).
260
- - id: story_stale_notice
261
- action: verify_context
262
- require_value: stale_csv
263
- equals: ""
264
- on_mismatch: continue
265
- on_mismatch_message: "The spec has moved on ids THIS story carries: {{stale_csv}}. Whatever this run generates for them realises superseded wording. Consider /appweaver sync-stories to bring the backlog back in step, then /appweaver story-plan {{story}}, before building."
266
-
267
- # 4. THE SERVICE LANE and its source path. This phase writes code, and the lane's root
268
- # is the only subtree it may write into — so this runs before the project gate,
269
- # which searches that subtree.
270
- - id: lane_gate
271
- action: include
272
- module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
273
- with: { require: backend, command: "/appweaver implement" }
274
-
275
- # 5. Require the PROJECT — a scaffolded, compiling tree. Implement GENERATES into it;
276
- # with no project there is nowhere to place the files. Deterministic structure gate:
277
- # searches the LANE'S OWN subtree for an ecosystem manifest, falling back to the
278
- # whole repo for the layout that keeps its manifest beside the lane root rather than
279
- # inside it. How the project got there is not asked.
280
- - id: detect_foundation
310
+ # 2b. That change's documents. The picked value is a LABEL ("CR-… · wave 2 · title");
311
+ # the scripts take its first token, and cr-paths reports the clean id back.
312
+ - id: cr_paths
313
+ condition: "{{mode}} == delta"
314
+ action: execute_script
315
+ script: { ref: actions }
316
+ subcommand: cr-paths
317
+ args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
318
+ outputs:
319
+ - { name: cr_id, from: "$.cr_id" }
320
+ - { name: cr_dir, from: "$.cr_dir" }
321
+ - { name: delta_spec, from: "$.delta_spec" }
322
+ # THIS CHANGE'S MODEL HALF — the structure the delta plans realise. It is context for
323
+ # every generating lane, exactly as the master model is: the delta plan cites the
324
+ # invariants this change ADDS or CHANGES by id and never restates them either.
325
+ - { name: delta_model, from: "$.delta_model" }
326
+ - { name: delta_model_exists, from: "$.delta_model_exists" }
327
+ - { name: delta_backend_plan, from: "$.delta_backend_plan" }
328
+ - { name: delta_backend_plan_exists, from: "$.delta_backend_plan_exists" }
329
+ - { name: delta_frontend_plan, from: "$.delta_frontend_plan" }
330
+ - { name: delta_frontend_plan_exists, from: "$.delta_frontend_plan_exists" }
331
+ - { name: wave, from: "$.wave" }
332
+
333
+ # 3. WHICH PLAN EACH LANE GENERATES FROM, resolved once per lane from the mode — the
334
+ # master on the bootstrap path, this change's delta plan on the delta path. Every gate
335
+ # and prompt below reads be_plan / fe_plan, so neither has to re-branch on the mode.
336
+ - id: be_plan_bootstrap
337
+ condition: "{{mode}} == bootstrap"
338
+ action: set
339
+ vars: { be_plan: "{{backend_plan}}", be_plan_ok: "{{backend_plan_exists}}" }
340
+ - id: be_plan_delta
341
+ condition: "{{mode}} == delta"
342
+ action: set
343
+ vars: { be_plan: "{{delta_backend_plan}}", be_plan_ok: "{{delta_backend_plan_exists}}" }
344
+ - id: fe_plan_bootstrap
345
+ condition: "{{mode}} == bootstrap"
346
+ action: set
347
+ vars: { fe_plan: "{{frontend_plan}}", fe_plan_ok: "{{frontend_plan_exists}}" }
348
+ - id: fe_plan_delta
349
+ condition: "{{mode}} == delta"
350
+ action: set
351
+ vars: { fe_plan: "{{delta_frontend_plan}}", fe_plan_ok: "{{delta_frontend_plan_exists}}" }
352
+
353
+ # 4. THE PROJECT each declared lane generates INTO. Deterministic structure gate:
354
+ # detect-project searches the LANE'S OWN subtree for an ecosystem manifest (.sln/
355
+ # .csproj, pom.xml, build.gradle, package.json, go.mod, Cargo.toml, pyproject.toml),
356
+ # falls back to the whole repo, and reports which scope answered. How the project got
357
+ # there is not asked — `foundation` unpacked it, or the team scaffolded it with the
358
+ # stack's own tooling.
359
+ - id: be_detect_project
360
+ condition: "{{be_declared}} == true"
361
+ action: execute_script
362
+ script: { ref: actions }
363
+ subcommand: detect-project
364
+ args: ["--root", "{{be_source_path}}"]
365
+ outputs:
366
+ - { name: be_project_ok, from: "$.scaffolded" }
367
+ - { name: be_project_scope, from: "$.scope" }
368
+
369
+ # THE SCOPE DISTINCTION IS THE GATE FOR THE FRONTEND, and only for it. A repo-scope hit
370
+ # in a two-lane project is the service lane's manifest, and generating this lane's code
371
+ # against it is exactly the failure the lane model exists to prevent — a frontend that
372
+ # was never scaffolded, generated into anyway, reporting green. The service lane accepts
373
+ # either scope, because the layout that keeps a manifest beside the lane root rather than
374
+ # inside it is a normal one for it.
375
+ - id: fe_detect_project
376
+ condition: "{{fe_declared}} == true"
281
377
  action: execute_script
282
378
  script: { ref: actions }
283
379
  subcommand: detect-project
284
- args: ["--root", "{{lane_source_path}}"]
380
+ args: ["--root", "{{fe_source_path}}"]
285
381
  outputs:
286
- - { name: foundation_present, from: "$.scaffolded" }
287
- - { name: project_markers, from: "$.markers" }
288
- - { name: project_scope, from: "$.scope" }
289
- - id: foundation_gate
382
+ - { name: fe_scaffolded, from: "$.scaffolded" }
383
+ - { name: fe_project_scope, from: "$.scope" }
384
+ - id: fe_project_flag
385
+ condition: "{{fe_project_scope}} == lane"
386
+ action: set
387
+ vars: { fe_project_ok: "true" }
388
+
389
+ # 5. REPORT EACH DECLARED LANE'S GAPS AND CARRY ON. `on_false: continue` is the whole
390
+ # point: the message is the deliverable, not the stop. A lane that cannot run is named
391
+ # here with the command that fixes it, and the run continues so the other lane still
392
+ # generates. If NOTHING can run, the gate at step 7 stops with that fact.
393
+ - id: warn_be_plan
394
+ condition: "{{be_declared}} == true"
290
395
  action: verify_context
291
- require_true: foundation_present
396
+ require_true: be_plan_ok
397
+ on_false: continue
398
+ on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{be_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
399
+ - id: warn_be_project
400
+ condition: "{{be_declared}} == true"
401
+ action: verify_context
402
+ require_true: be_project_ok
403
+ on_false: continue
404
+ on_false_message: "BACKEND LANE SKIPPED — no project found under the {{be_lane_id}} lane's source path `{{be_source_path}}`, nor anywhere else in this repository. This phase GENERATES into an existing project tree, so one has to be scaffolded first: run /appweaver foundation <FoundationName> <AppName> where a foundation bundle ships for {{be_profile}}, or create the project with the stack's own tooling under that path. Any ecosystem manifest marks it — .sln/.csproj, pom.xml, build.gradle(.kts), package.json, go.mod, Cargo.toml or pyproject.toml."
405
+ - id: warn_fe_plan
406
+ condition: "{{fe_declared}} == true"
407
+ action: verify_context
408
+ require_true: fe_plan_ok
409
+ on_false: continue
410
+ on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{fe_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
411
+ - id: warn_fe_project
412
+ condition: "{{fe_declared}} == true"
413
+ action: verify_context
414
+ require_true: fe_project_ok
415
+ on_false: continue
416
+ on_false_message: "FRONTEND LANE SKIPPED — no frontend project found under the {{fe_lane_id}} lane's source path `{{fe_source_path}}`. detect-project searched that subtree and then the whole repository, and answered at scope `{{fe_project_scope}}`; `lane` is the only scope this lane accepts. Scaffold a project there — /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{fe_profile}}, or the stack's own tooling — and re-run implement. A manifest found elsewhere in the repository does NOT count: that one belongs to another lane, and generating this lane's code against it is how a frontend that was never built reports green."
417
+
418
+ # 6. RUNNABLE = declared AND its plan is there AND its project is there, as an ordered
419
+ # assignment plus a knockdown, since the engine has no compound conditions. An
420
+ # undeclared lane stays false either way — its `declared` value is what the raise
421
+ # assigns.
422
+ - id: be_runnable_raise
423
+ condition: "{{be_plan_ok}} == true"
424
+ action: set
425
+ vars: { be_runnable: "{{be_declared}}" }
426
+ - id: be_runnable_clear
427
+ condition: "{{be_project_ok}} == false"
428
+ action: set
429
+ vars: { be_runnable: "false" }
430
+ - id: fe_runnable_raise
431
+ condition: "{{fe_plan_ok}} == true"
432
+ action: set
433
+ vars: { fe_runnable: "{{fe_declared}}" }
434
+ - id: fe_runnable_clear
435
+ condition: "{{fe_project_ok}} == false"
436
+ action: set
437
+ vars: { fe_runnable: "false" }
438
+
439
+ # 7. CONFIRM EACH RUNNABLE LANE. Generation writes into the repository and costs a model
440
+ # pass per lane, so the operator says which lanes this run is for, with the stack, the
441
+ # plan and the target subtree in front of them. Declining one lane leaves the other
442
+ # free to run. A lane that is not runnable is never asked about — its reason was
443
+ # already reported above.
444
+ - id: ask_backend
445
+ condition: "{{be_runnable}} == true"
446
+ action: ask
447
+ prompt: "Implement the BACKEND lane now? Lane `{{be_lane_id}}` — {{be_title}}, {{be_language}} ({{be_ecosystem}}) — generating from {{be_plan}} into {{be_source_path}}."
448
+ options: ["yes", "no"]
449
+ answer_var: be_answer
450
+ - id: be_go_set
451
+ condition: "{{be_answer}} == yes"
452
+ action: set
453
+ vars: { be_go: "{{be_runnable}}", generate_any: "true" }
454
+
455
+ - id: ask_frontend
456
+ condition: "{{fe_runnable}} == true"
457
+ action: ask
458
+ prompt: "Implement the FRONTEND lane now? Lane `{{fe_lane_id}}` — {{fe_title}}, {{fe_language}} ({{fe_ecosystem}}) — generating from {{fe_plan}} into {{fe_source_path}}."
459
+ options: ["yes", "no"]
460
+ answer_var: fe_answer
461
+ - id: fe_go_set
462
+ condition: "{{fe_answer}} == yes"
463
+ action: set
464
+ vars:
465
+ fe_go: "{{fe_runnable}}"
466
+ generate_any: "true"
467
+ pre_ui_phase: "before_implement_ui"
468
+ post_ui_phase: "after_implement_ui"
469
+
470
+ - id: require_a_lane
471
+ action: verify_context
472
+ require_true: generate_any
292
473
  on_false: stop
293
- on_false_message: "No project found under the {{lane_id}} lane's source path `{{lane_source_path}}`, nor anywhere else in this repository. Implement GENERATES this story's code into an existing project tree, so one has to be scaffolded first: run /appweaver foundation <FoundationName> <AppName> where a foundation bundle ships for this stack, or create the project with the stack's own tooling under that path. Any ecosystem manifest marks it — .sln/.csproj, pom.xml, build.gradle(.kts), package.json, go.mod, Cargo.toml or pyproject.toml."
474
+ on_false_message: "Nothing to implement — no declared lane is both ready and confirmed. Either every declared lane was skipped for a reason printed above (its plan or its project is missing), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, ready {{fe_runnable}}. Fix the reasons named above, or re-run and confirm a lane."
475
+
476
+ # 8. EACH GENERATING LANE'S authoring MODE — the run's mode where the lane is going, and
477
+ # `skip` where it is not, which silences both of that lane's branches without a
478
+ # compound condition.
479
+ - id: be_mode_set
480
+ condition: "{{be_go}} == true"
481
+ action: set
482
+ vars: { be_mode: "{{mode}}" }
483
+ - id: fe_mode_set
484
+ condition: "{{fe_go}} == true"
485
+ action: set
486
+ vars: { fe_mode: "{{mode}}" }
487
+
488
+ # 9. Each generating lane's code-generation rule files → sticky standards (configuration,
489
+ # not hardcoded). Loaded only for a lane that is actually going to generate, and the
490
+ # backend's heading is keyed by mode (see the header). No blocking gate: an
491
+ # unconfigured project soft-continues, since the constitution halves still carry each
492
+ # lane's ratified base.
493
+ - id: tech_select
494
+ condition: "{{be_go}} == true"
495
+ action: include
496
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
497
+ with: { command: "{{be_tech_command}}" }
294
498
 
295
- # 6. WHAT ALREADY EXISTS, from the feature's OKF concept, split by lane. This is what
296
- # makes the run incremental: the pass names the files it changes, and this names the
297
- # files that are there to change. Advisory — the first story of a greenfield feature
298
- # has no concept and generates into a clean tree.
299
- - id: coverage
300
- action: execute_script
301
- on_error: continue
302
- script: { ref: xref }
303
- subcommand: concept-coverage
304
- args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
305
- outputs:
306
- - { name: okf_concept, from: "$.concept" }
307
- - { name: coverage_digest, from: "$.digest" }
308
- - { name: be_source_csv, from: "$.backend_csv" }
309
- - { name: be_source_count, from: "$.backend_count" }
310
- - { name: fe_source_count, from: "$.frontend_count" }
499
+ - id: tech_select_ui
500
+ condition: "{{fe_go}} == true"
501
+ action: include
502
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
503
+ with: { command: aw-spec-implement-ui }
311
504
 
312
- # 7. Pre-execution hooks.
505
+ # 10. Pre-execution hooks — see the hook-phase vars above.
313
506
  - id: resolve_pre_hooks
314
507
  action: execute_script
315
508
  script: { ref: actions }
316
509
  subcommand: resolve-hooks
317
- args: ["--phase", "before_implement"]
510
+ args: ["--phase", "before_implement", "--phase", "{{pre_ui_phase}}", "--phase", "{{pre_cr_phase}}"]
318
511
  outputs:
319
512
  - { name: before_hooks, from: "$.hooks" }
320
513
  - id: dispatch_pre_hooks
321
514
  action: dispatch_hooks
322
515
  hooks_from_variable: before_hooks
323
516
 
324
- # ═══════════════════ GENERATION — one story's slice, from one pass ═══════════
325
- # The pass constrains WHAT to build; the constitution, the lane and the loaded
326
- # standards fix HOW. spec.md is deliberately absent: the design is the single source
327
- # here, and the acceptance check below is where the spec gets its say.
328
- - id: ctx_generate
517
+ # ═══════════ BACKEND LANE — bootstrap, generate from the MASTER plan ═════════
518
+ # The plan constrains WHAT to build; the constitution + the lane fix HOW. The spec is
519
+ # deliberately NOT in context — the plan is the single source here. build_context
520
+ # REPLACES the per-request envelope and PERSISTS, so each generating branch scopes its
521
+ # own; sticky standards stay prepended.
522
+ - id: ctx_backend
523
+ condition: "{{be_mode}} == bootstrap"
329
524
  action: build_context
330
- vars:
331
- story_id: "{{story}}"
332
- story_title: "{{story_title}}"
333
- exercises_csv: "{{exercises_csv}}"
334
- constraints_csv: "{{constraints_csv}}"
335
- pass_number: "{{design_pass}}"
336
- coverage_digest: "{{coverage_digest}}"
337
- source_files_csv: "{{be_source_csv}}"
338
- source_file_count: "{{be_source_count}}"
339
- stale_note: "{{stale_note}}"
340
525
  context:
341
- - { path: "{{design_doc}}" } # the story's SERVICE design — the LATEST pass is the input
342
- - { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
343
- - { path: "{{if_path}}", optional: true } # the PUBLISHED surface this code must match
344
- - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
345
- - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
346
- - id: generate
526
+ - { path: "{{be_plan}}" }
527
+ - { path: "{{domain_model}}", optional: true } # the CONTEXT'S canonical model — what each cited BR-nnn REQUIRES
528
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain rules
529
+ - id: gen_backend
530
+ condition: "{{be_mode}} == bootstrap"
347
531
  action: agent_generate
348
- tools: "Read,Write,Edit" # the existing code arrives as {{source_files_csv}} paths, not inlined
532
+ tools: "Read,Write,Edit"
349
533
  permission_mode: acceptEdits
350
534
  task: |
351
- Implement story {{story_id}} — {{story_title}} — in the service lane, from
352
- "## Design pass {{pass_number}}" of the story design in context. Guidance: {{USER_INPUT}}
353
-
354
- THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
355
- document; every earlier pass is superseded history, and any of them may carry
356
- hand edits that were deliberate. Read the last one and build exactly it. Do NOT
357
- consult or infer from spec.md — it is not in context on purpose: the design
358
- already went through the gates that conform it, and generating from the spec
359
- routes around all of them.
360
-
361
- THIS IS ONE SLICE OF AN EXISTING FEATURE, NOT THE WHOLE FEATURE.
362
- - The pass's "### Changes" section names, file by file, what to create and what
363
- to modify. Create ONLY the files it names as new; modify the named existing
364
- files IN PLACE. Every other file in this feature belongs to work that is
365
- already done — do not regenerate, reformat or "tidy" it.
366
- - The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}}.
367
- Anything outside those ids is another story's work, even if you can see it is
368
- missing. If the slice genuinely cannot be built without touching shared
369
- structure, make the smallest change that works and SAY SO in your summary.
370
- - The code that already exists for this feature, per lane, is below. The service
371
- files ({{source_file_count}} on disk) are yours to read before you edit; the UI
372
- list is there so you do not change a surface that already has a client.
373
-
374
- {{coverage_digest}}
375
-
376
- {{source_files_csv}}
377
-
378
- BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
379
- {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
380
- compiled with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
381
- language and framework and no other, using its idioms, its project layout and its
382
- test framework. Take persistence, messaging and the remaining technology choices
383
- from the constitution's technology base + the loaded standards; the design is
384
- technology-agnostic, so do not re-decide anything already fixed there. If the
385
- loaded standards describe a DIFFERENT stack than the one above, the lane wins on
386
- language and layout — follow the standards' INTENT and STOP to surface the
387
- mismatch in your summary.
388
-
389
- WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
390
- declares. That subtree is this lane's; a file outside it belongs to another lane
391
- or to no lane at all. Do not create a parallel source tree because the layout
392
- inside it is unfamiliar; if the pass needs a place this subtree has no home for,
393
- STOP and surface it.
394
-
535
+ Generate the production code AND paired tests for this aggregate STRICTLY from its
536
+ backend-plan.md (in context). The plan is the complete, authoritative source of WHAT
537
+ to build — do NOT consult or infer from the spec; analyze-plan already conformed the
538
+ plan to it. This phase is the HOME of the technology-specific, solution-wide
539
+ code-generation rules. Guidance: {{USER_INPUT}}
395
540
  Rules:
396
- - THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and framework/code
397
- standards — and NO domain rule files. Introduce NO domain rule, identifier,
398
- terminology or behaviour the pass does not state. If a domain specific seems
399
- missing, STOP and surface it; never invent it from memory.
541
+ - TWO DOMAIN INPUTS, WITH DIFFERENT JOBS. The PLAN is the only authority on WHAT TO
542
+ BUILD — which elements exist, where each one lives, what surface they publish. The
543
+ bounded context's DOMAIN MODEL (domain-model.md, in context) is the authority on
544
+ WHAT THE DOMAIN IS — the entity and value-object NAMES, the aggregate boundary,
545
+ the lifecycle states, and the TEXT of every invariant the plan cites by id. The
546
+ plan records WHERE a BR-nnn is enforced and deliberately never restates it, so the
547
+ model is where you read what it requires; enforce the rule the model states, in the
548
+ place the plan names. You have NO domain rule files and NO spec — introduce nothing
549
+ neither document states, and if a domain specific is in neither, STOP and surface
550
+ it rather than inferring it from a name, from surrounding code or from memory.
551
+ - THE MODEL IS READ, NEVER MINED FOR WORK. It describes the WHOLE bounded context,
552
+ while this run builds one feature. Realise only what the PLAN calls for: an entity,
553
+ event or invariant in the model that no plan element realises is a PLANNING gap —
554
+ surface it, do not implement it. `{{feature}}` is the aggregate this feature owns;
555
+ any other `## Aggregate:` section belongs to another slice, and you read it only to
556
+ honour a reference the plan makes, never to generate its code.
557
+ - BIND THE STACK: this lane is `{{be_lane_id}}` — {{be_title}}, language
558
+ {{be_language}} ({{be_ecosystem}}), declared in .appweaver/stacks.json and compiled
559
+ with `{{be_build}}`, tested with `{{be_test}}`. Generate in THAT language and
560
+ framework and no other, using its idioms, its project layout and its test
561
+ framework. Take persistence, messaging and the remaining technology choices from
562
+ the constitution's technology base + the loaded standards; the plan is
563
+ technology-agnostic, so do not re-decide anything already fixed there. If the
564
+ loaded standards describe a DIFFERENT stack than the one above, the lane wins on
565
+ language and layout — the standards' stack-specific mechanics do not apply, so
566
+ follow their INTENT and STOP to surface the mismatch in your summary.
567
+ - WRITE ONLY UNDER `{{be_source_path}}` — the source path the `{{be_lane_id}}` lane
568
+ declares in .appweaver/stacks.json. That subtree is this lane's, and a file outside
569
+ it belongs to another lane or to no lane at all. NEVER write into the frontend
570
+ lane's subtree, not even to consume a contract. Do not create a parallel source
571
+ tree because the layout inside it is unfamiliar; if the plan needs a place this
572
+ subtree has no home for, STOP and surface it.
400
573
  - Follow the project's folder/layer convention + the loaded coding-standards,
401
- dependency-policy and testing standards for HOW to structure, name and place
402
- files WITHIN that subtree — the pass says WHAT to build; these say HOW.
403
- - Realise the pass's "### Approach" and "### Changes" exactly, changing nothing
404
- about WHAT they specify, and preserve every ubiquitous-language name verbatim.
405
- - INTERFACES: where the pass's "### Interfaces" describes a published surface,
406
- the entry in interfaces.md (in context) is the contract — match its route,
407
- shape and semantics. Where the log and the pass disagree, the pass is the
408
- newer word and the log entry it supersedes is named in it; if neither says
409
- which, STOP and surface it rather than picking.
410
- - COMPONENTS: where the pass depends on a framework component (e.g.
411
- `IEmailService`, `IJobScheduler`), code against that component's ABSTRACTION /
412
- port as the pass declares — do NOT hand-roll it. Installing the component into
413
- the project (packages, provider wiring, config) is a SEPARATE step:
414
- /appweaver add-component <Component>. If the port is not present yet, code to
415
- it and note add-component as the prerequisite.
416
- - ADRs are DECIDED — follow them. The pass may propose a superseding one; a
417
- proposal is not yet ratified, so follow the pass and change nothing else.
418
- - Author a paired test for every acceptance criterion in the story and every
419
- behaviour the pass's "### Tests" section names, and cite the requirement id
420
- ({{exercises_csv}}) each test exists for. Include the constraint tests the
421
- pass names ({{constraints_csv}}) — e.g. an audit record is emitted, a
422
- restricted field is NOT exposed on the public surface.
423
- - Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
424
- the pass looks wrong or incomplete, STOP and surface it — it is fixed by
425
- re-running /appweaver story-plan, never compensated for here from the spec or
426
- from memory.
427
- - This feature OWNS its files; never regenerate a file another feature owns.
428
- {{stale_note}}
574
+ dependency-policy and testing standards for HOW to structure, name and place files
575
+ WITHIN that subtree — the plan says WHAT to build; these standards (identical for
576
+ every feature) say HOW.
577
+ - COMPONENTS: when the plan's External Integrations table depends on a framework
578
+ component (e.g. `IEmailService`, `IJobScheduler`), code against that component's
579
+ ABSTRACTION / port as the plan declares — do NOT hand-roll it. Installing the
580
+ component into the project (packages, provider wiring, config) is a SEPARATE step:
581
+ /appweaver add-component <Component>. If the port isn't present yet, code to it and
582
+ note add-component as the prerequisite; do not load component rule docs here.
583
+ - Realise EVERY plan element as code, changing nothing about WHAT it specifies: one
584
+ file per Domain type / Application use-case / API endpoint / Infrastructure
585
+ adapter, placed by layer. Preserve the ubiquitous-language names verbatim.
586
+ - Enforce EXACTLY as the plan states: every Domain invariant, Application handler
587
+ orchestration and API error contract; the cross-cutting constraints in the
588
+ Cross-Cutting Concerns table (audit / retention / data-exposure — the CN-/DC-
589
+ realisations); and each integration contract (IC-) in the External Integrations
590
+ table. Implement every port the plan declares.
591
+ - Author a paired test for every plan behaviour / use-case / endpoint; derive the
592
+ cases from the plan's pre/post/invariant contracts and cite the spec id (FR / BR /
593
+ CN / DC / IC / US) from its Traceability section. Include tests for the constraints
594
+ — e.g. an audit record is emitted, a DC-restricted field is NOT exposed on the
595
+ public surface, an IC- projection round-trips.
596
+ - Do NOT add behaviour the plan does not state, and do NOT omit any it does. If the
597
+ plan looks wrong or incomplete, STOP and surface it (it is fixed in plan /
598
+ analyze-plan) — never compensate from the spec or from memory.
599
+ - This aggregate OWNS its files; never regenerate a file another feature owns.
600
+ - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
601
+ it with a comment citing that id so it is easy to revisit later.
602
+ outputs:
603
+ - { name: be_files, from: "$.files" }
604
+
605
+ # ═══════════ BACKEND LANE — delta, generate ONLY what the change adds ════════
606
+ # The delta plan is the authoritative source of WHAT changed — analyze-plan-delta already
607
+ # conformed it to the delta spec. The MASTER plan is the BASE (the unchanged elements the
608
+ # delta depends on), and the delta spec is here ONLY so the paired tests can cite
609
+ # traceability ids.
610
+ - id: ctx_backend_delta
611
+ condition: "{{be_mode}} == delta"
612
+ action: build_context
613
+ context:
614
+ - { path: "{{be_plan}}" } # this change's delta-backend-plan.md
615
+ - { path: "{{delta_spec}}", optional: true }
616
+ - { path: "{{delta_model}}", optional: true } # this change's MODEL half — what it ADDS to / CHANGES in the model
617
+ - { path: "{{backend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
618
+ - { path: "{{domain_model}}", optional: true } # the master model — the domain as it stands BEFORE this change
619
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain rules
620
+ - id: gen_backend_delta
621
+ condition: "{{be_mode}} == delta"
622
+ action: agent_generate
623
+ tools: "Read,Write,Edit"
624
+ permission_mode: acceptEdits
625
+ task: |
626
+ Implement ONLY change {{cr_id}} (wave {{wave}}) into the existing project tree,
627
+ STRICTLY from its delta-backend-plan.md (in context). The delta plan is the
628
+ authoritative source of WHAT changed — analyze-plan-delta already conformed it to the
629
+ delta spec. The master backend-plan.md (in context) is the BASE: the unchanged
630
+ elements the delta depends on; do NOT regenerate them. Guidance: {{USER_INPUT}}
631
+ Rules:
632
+ - SCOPE = THE DELTA ONLY. Realise exactly the elements the delta plan marks ADDED
633
+ (create the new file) or MODIFIED (edit the existing file in place). Touch NOTHING
634
+ the delta does not list, except the regression re-tests below.
635
+ - TWO DOMAIN INPUTS, WITH DIFFERENT JOBS. The DELTA PLAN is the only authority on
636
+ WHAT TO BUILD in this change — which elements are added or edited, where each one
637
+ lives, what surface they publish. The DOMAIN MODEL is the authority on WHAT THE
638
+ DOMAIN IS, and it reaches you in TWO halves, both in context: the master
639
+ domain-model.md is the domain as it stands BEFORE this change, and this change's
640
+ model delta (domain-model.{{cr_id}}.delta.md) is what the change ADDS to, MODIFIES
641
+ in or REMOVES from it. THE EFFECTIVE MODEL IS THE TWO READ TOGETHER, the delta
642
+ winning wherever they differ — it is the newer statement, and the master has not
643
+ been merged yet (finalize-delta does that after this phase). Read there what every
644
+ BR-nnn the delta plan cites actually requires; the plan records WHERE each is
645
+ enforced and never restates it. You have NO domain rule files and NO spec beyond
646
+ the delta-spec's ids — if a domain specific is in none of them, STOP and surface it
647
+ (it is fixed in specify / plan / analyze-plan-delta), never invent it from memory.
648
+ - THE MODEL IS READ, NEVER MINED FOR WORK. Realise only what the DELTA PLAN calls
649
+ for: an item in the model delta that no delta-plan element realises is a PLANNING
650
+ gap — surface it, do not implement it. Everything in the master model that this
651
+ change does not touch is ALREADY BUILT; do not regenerate it. `{{feature}}` is the
652
+ aggregate this feature owns, and another `## Aggregate:` section belongs to another
653
+ slice — read it only to honour a reference the delta plan makes.
654
+ - NO OTHER CHANGE'S MODEL DELTA IS IN CONTEXT, deliberately. A predecessor this
655
+ change builds on was already accounted for when the delta plan was authored and
656
+ recorded under its "Depends on"; implement it from the PLAN, not by reaching for
657
+ another change's pending files.
658
+ - BIND THE STACK: this lane is `{{be_lane_id}}` — {{be_title}}, language
659
+ {{be_language}} ({{be_ecosystem}}), declared in .appweaver/stacks.json and compiled
660
+ with `{{be_build}}`, tested with `{{be_test}}`. Generate in THAT language and
661
+ framework and no other. Take persistence, messaging and the remaining technology
662
+ choices from the constitution's technology base + the loaded standards; the plan is
663
+ technology-agnostic, so do not re-decide anything already fixed there. If the
664
+ loaded standards describe a DIFFERENT stack than the one above, the lane wins on
665
+ language and layout — follow the standards' INTENT and STOP to surface the
666
+ mismatch in your summary.
667
+ - WRITE ONLY UNDER `{{be_source_path}}` — the source path the `{{be_lane_id}}` lane
668
+ declares. That subtree is this lane's, and a file outside it belongs to another
669
+ lane or to no lane at all. NEVER write into the frontend lane's subtree.
670
+ - Follow the project's EXISTING folder/layer convention + the loaded
671
+ coding-standards, dependency-policy and testing standards for HOW to structure,
672
+ name and place files WITHIN that subtree. Match the surrounding code already there.
673
+ - Realise EVERY ADDED/MODIFIED delta element as code: one file per Domain type /
674
+ Application use-case / API endpoint / Infrastructure adapter, placed by layer.
675
+ Preserve the ubiquitous-language names verbatim. Enforce each new/changed Domain
676
+ invariant, handler orchestration, API error contract, the Cross-Cutting Concerns
677
+ realisations (audit / retention / data-exposure — CN-/DC-) and the External
678
+ Integrations table's contracts (IC-) exactly as the delta plan states.
679
+ - COMPONENTS: if a delta element depends on a framework component, code against its
680
+ ABSTRACTION / port as the delta plan declares — do NOT hand-roll it. Installing the
681
+ component (packages, wiring, config) is /appweaver add-component <Component>; if
682
+ the port isn't present, code to it and note that as the prerequisite.
683
+ - Author a paired test for every ADDED/MODIFIED behaviour / use-case / endpoint;
684
+ derive the cases from the delta plan's pre/post/invariant contracts and cite the
685
+ delta-spec id (FR / BR / CN / DC / IC / US).
686
+ - REGRESSION: for each element in the delta plan's "Regression targets", verify (add
687
+ or adjust a test where needed) that the existing behaviour still holds after a
688
+ shared element changed — but do NOT rewrite that existing behaviour.
689
+ - Where generated code depends on a delta Assumption (DA-n) or Workaround (WA-n),
690
+ mark it with a comment citing that id.
691
+ outputs:
692
+ - { name: be_files, from: "$.files" }
693
+
694
+ - id: verify_be_files
695
+ condition: "{{be_go}} == true"
696
+ action: verify_context
697
+ require_non_empty: be_files
698
+ on_empty: stop
699
+ on_empty_message: "The backend lane generated no files. Ensure {{be_plan}} is a FILLED plan (not the empty template) and that it passed its gate — /appweaver analyze-plan for a master, /appweaver analyze-plan-delta for a change — then re-run implement."
700
+
701
+ # OKF knowledge — the feature has ONE concept across both lanes and both modes, so every
702
+ # lane's files JOIN it rather than replace it (--merge unions them with what is already
703
+ # recorded). Without the union a backend run would erase the frontend lane's source_files
704
+ # and vice-versa, and either lane may have run first. ⌛ The trade-off is that a file this
705
+ # lane DELETED stays listed until something prunes it; a lane-scoped replace needs
706
+ # concept-upsert to learn about lane roots, which is a script change and not built.
707
+ - id: xref_concept_backend
708
+ condition: "{{be_go}} == true"
709
+ action: execute_script
710
+ on_error: continue
711
+ script: { ref: xref }
712
+ subcommand: concept-upsert
713
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{be_files}}", "--merge", "--origin", "greenfield"]
714
+ outputs:
715
+ - { name: okf_concept, from: "$.concept" }
716
+
717
+ # ═══════════ FRONTEND LANE — bootstrap, generate from the MASTER plan ════════
718
+ # The experience twin of the service generation, one lane over. Its own build_context,
719
+ # because the envelope persists across steps and the two lanes must not see each other's
720
+ # payload — the frontend binds to the service's PUBLISHED surface, never its internals.
721
+ - id: ctx_frontend
722
+ condition: "{{fe_mode}} == bootstrap"
723
+ action: build_context
724
+ context:
725
+ - { path: "{{fe_plan}}" }
726
+ - { path: "{{domain_model}}", optional: true } # the UBIQUITOUS LANGUAGE this lane's labels, states and messages use — and what a cited BR-nnn requires
727
+ - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — conventions, forbidden patterns, gates
728
+ - id: gen_frontend
729
+ condition: "{{fe_mode}} == bootstrap"
730
+ action: agent_generate
731
+ tools: "Read,Write,Edit"
732
+ permission_mode: acceptEdits
733
+ task: |
734
+ Generate the production UI code AND paired tests for this feature STRICTLY from its
735
+ frontend-plan.md (in context). The plan is the complete, authoritative source of WHAT
736
+ to build — do NOT consult or infer from the spec; analyze-plan already conformed the
737
+ plan to it. This phase is the HOME of the framework-specific, lane-wide
738
+ code-generation rules. Guidance: {{USER_INPUT}}
739
+ Rules:
740
+ - TWO DOMAIN INPUTS, WITH DIFFERENT JOBS. The PLAN is the only authority on WHAT TO
741
+ BUILD — the screens, the state, the components, the bindings. The bounded context's
742
+ DOMAIN MODEL (domain-model.md, in context) is the authority on WHAT THE DOMAIN IS:
743
+ use its UBIQUITOUS LANGUAGE exactly — the same terms in labels, states, empty
744
+ copy and messages, no synonyms — and read there what a BR-nnn the plan cites
745
+ actually requires, since the plan records where each is enforced and never restates
746
+ it. You have NO domain rule files and NO spec: introduce nothing neither document
747
+ states, and if a domain specific is in neither, STOP and surface it.
748
+ - THE MODEL IS READ, NEVER MINED FOR WORK, and reading a rule is not permission to
749
+ move it. Realise only what the PLAN calls for. A client-side check restates a
750
+ server-enforced invariant for FEEDBACK — the message says what the model says, in
751
+ the model's words — and it never becomes the place the rule lives. An entity or
752
+ invariant in the model that no plan element realises is a PLANNING gap; surface it.
753
+ `{{feature}}` is this feature's aggregate; another `## Aggregate:` section belongs
754
+ to another slice.
755
+ - BIND THE STACK: this lane is `{{fe_lane_id}}` — {{fe_title}}, language
756
+ {{fe_language}} ({{fe_ecosystem}}), declared in .appweaver/stacks.json and built
757
+ with `{{fe_build}}`, tested with `{{fe_test}}`. Generate in THAT framework and no
758
+ other, using its idioms, its project layout and its test framework. Take the
759
+ router, state approach, component library, design system and styling from the UI
760
+ constitution + the loaded standards; the plan is technology-agnostic, so do not
761
+ re-decide anything already fixed there. If the loaded standards describe a
762
+ DIFFERENT stack than the one above, the lane wins on framework and layout — follow
763
+ the standards' INTENT and STOP to surface the mismatch in your summary.
764
+ - WRITE ONLY UNDER `{{fe_source_path}}` — the source path the `{{fe_lane_id}}` lane
765
+ declares. That subtree is this lane's; a file outside it belongs to the service
766
+ lane or to no lane. NEVER touch the service lane's code, not even to add a field
767
+ the UI wants: a gap in the service surface is a contract delta raised back to that
768
+ lane. Do not create a parallel source tree because the layout inside this one is
769
+ unfamiliar; if the plan needs a place this subtree has no home for, STOP and
770
+ surface it.
771
+ - Follow the UI constitution's folder structure, naming conventions and forbidden
772
+ patterns, plus the loaded standards, for HOW to structure, name and place files
773
+ WITHIN that subtree — the plan says WHAT to build; these say HOW.
774
+ - REALISE EVERY PLAN ELEMENT, changing nothing about WHAT it specifies:
775
+ • §1 — one route/screen per row, with the navigation shape and the access
776
+ conditions the row states.
777
+ • §2 — the client state exactly as owned and scoped there, including its
778
+ invalidation triggers. Anything the plan lists under "what must NOT be held"
779
+ NEVER reaches client state, storage, the URL or telemetry.
780
+ • §3 — one component per row, with the responsibility, the input field shapes and
781
+ the events the plan gives it. Compose the shared components the plan says it
782
+ reuses; do not re-implement them.
783
+ • §4 — EVERY state in EVERY interaction row: loading, empty, error,
784
+ permission-denied and in-flight/disabled, plus the validation timing, the focus
785
+ and keyboard behaviour and the concurrency handling stated there. These are
786
+ requirements, not polish, and they are the first thing a generator drops.
787
+ • §5 — the service binding, through the lane's own client layer, with the write
788
+ semantics and rollback each row states.
789
+ • §6 — the cross-cutting decisions as code: what is hidden vs disabled,
790
+ accessibility obligations, localization, performance and offline behaviour.
791
+ - THE CLIENT IS NOT AN AUTHORITY. A client-side validation or permission check
792
+ restates a server rule for feedback; it never replaces it. Generate no server-side
793
+ behaviour, no persistence and no business rule evaluation here.
794
+ - Author a paired test for every screen, interaction and state the plan names —
795
+ including the unwanted-behaviour states from §4 and an accessibility assertion per
796
+ interactive surface — and cite the spec id (UX-/US-/FR-/CN-/DC-/IC-/SC-) from the
797
+ plan's §7 Traceability in each test. A snapshot alone is not a test.
798
+ - Do NOT add behaviour the plan does not state, and do NOT omit any it does. If the
799
+ plan looks wrong or incomplete, STOP and surface it (it is fixed in plan /
800
+ analyze-plan) — never compensate from the spec or from memory.
801
+ - This feature OWNS its files; never regenerate a file another feature owns, and do
802
+ not modify the app shell, global layout or design system — they belong to no
803
+ feature. If one of them has to change, STOP and surface it.
804
+ - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
805
+ it with a comment citing that id so it is easy to revisit later.
429
806
  outputs:
430
- - { name: files, from: "$.files" }
807
+ - { name: fe_files, from: "$.files" }
808
+
809
+ # ═══════════ FRONTEND LANE — delta, generate ONLY what the change adds ═══════
810
+ # The UI half of the CR loop, which never existed as its own command: the ⌛
811
+ # `implement-ui-delta` phase is BUILT HERE by absorption, exactly as the delta UI plan
812
+ # shipped as the frontend half of the delta-plan module.
813
+ - id: ctx_frontend_delta
814
+ condition: "{{fe_mode}} == delta"
815
+ action: build_context
816
+ context:
817
+ - { path: "{{fe_plan}}" } # this change's delta-frontend-plan.md
818
+ - { path: "{{delta_spec}}", optional: true }
819
+ - { path: "{{delta_model}}", optional: true } # this change's MODEL half — new terms, states and rules the UI must speak
820
+ - { path: "{{frontend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
821
+ - { path: "{{domain_model}}", optional: true } # the master model — the ubiquitous language as it stands BEFORE this change
822
+ - { path: .appweaver/memory/constitution.ui.md, optional: true }
823
+ - id: gen_frontend_delta
824
+ condition: "{{fe_mode}} == delta"
825
+ action: agent_generate
826
+ tools: "Read,Write,Edit"
827
+ permission_mode: acceptEdits
828
+ task: |
829
+ Implement ONLY change {{cr_id}} (wave {{wave}}) into the existing frontend tree,
830
+ STRICTLY from its delta-frontend-plan.md (in context). The delta plan is the
831
+ authoritative source of WHAT changed — analyze-plan-delta already conformed it to the
832
+ delta spec. The master frontend-plan.md (in context) is the BASE: the unchanged
833
+ screens, state and components the delta depends on; do NOT regenerate them.
834
+ Guidance: {{USER_INPUT}}
835
+ Rules:
836
+ - SCOPE = THE DELTA ONLY. Realise exactly the screens, components, state and bindings
837
+ the delta plan marks ADDED (create the new file) or MODIFIED (edit the existing
838
+ file in place). A shared component or a shared state container is MODIFIED in
839
+ place, never copied into the change. Touch NOTHING the delta does not list, except
840
+ the regression re-tests below.
841
+ - TWO DOMAIN INPUTS, WITH DIFFERENT JOBS. The DELTA PLAN is the only authority on
842
+ WHAT TO BUILD in this change. The DOMAIN MODEL is the authority on WHAT THE DOMAIN
843
+ IS, and it reaches you in TWO halves, both in context: the master domain-model.md
844
+ is the language and the rules as they stand BEFORE this change, and this change's
845
+ model delta is what it ADDS, MODIFIES or REMOVES. THE EFFECTIVE MODEL IS THE TWO
846
+ READ TOGETHER, the delta winning wherever they differ — the master has not been
847
+ merged yet. A TERM THIS CHANGE INTRODUCES OR RENAMES IS IN THE DELTA, and the UI
848
+ must speak it exactly as the delta spells it, in labels, states and messages. Read
849
+ there what any BR-nnn the delta plan cites requires. NO domain rule files, and no
850
+ spec beyond the delta-spec's ids; if a domain specific is in none of them, STOP and
851
+ surface it.
852
+ - THE MODEL IS READ, NEVER MINED FOR WORK, and reading a rule is not permission to
853
+ move it. Realise only what the DELTA PLAN calls for — an item in the model delta
854
+ that no delta-plan element realises is a PLANNING gap, and a client-side check
855
+ restates a server-enforced rule for FEEDBACK and never replaces it.
856
+ - BIND THE STACK: this lane is `{{fe_lane_id}}` — {{fe_title}}, language
857
+ {{fe_language}} ({{fe_ecosystem}}), built with `{{fe_build}}`, tested with
858
+ `{{fe_test}}`. Generate in THAT framework and no other. Take the router, state
859
+ approach, component library, design system and styling from the UI constitution +
860
+ the loaded standards, and MATCH the code already in the tree.
861
+ - WRITE ONLY UNDER `{{fe_source_path}}`. NEVER touch the service lane's code, not
862
+ even to add a field the UI wants — a gap in the service surface is a contract delta
863
+ raised back to that lane, never worked around in the client.
864
+ - STATES ARE PART OF THE CHANGE. Every interaction this delta adds or alters ships
865
+ its FULL state set — loading, empty, error, permission-denied, in-flight — plus the
866
+ validation timing, focus placement and keyboard reachability the delta plan states.
867
+ A new interaction without them is an incomplete implementation, not a later
868
+ refinement.
869
+ - BIND AGAINST THE PUBLISHED SURFACE ONLY: the delta plan's binding rows and the
870
+ service delta's "Contract deltas for the frontend lane" table as the delta plan
871
+ cites them. Generate no server-side behaviour, no persistence and no business rule
872
+ evaluation.
873
+ - Author a paired test for every ADDED/MODIFIED screen, interaction and state,
874
+ including the unwanted-behaviour states and an accessibility assertion per
875
+ interactive surface, citing the delta-spec id (UX-/US-/FR-/CN-/DC-/IC-/SC-).
876
+ - REGRESSION: for each element in the delta plan's regression targets, verify (add or
877
+ adjust a test where needed) that the existing behaviour still holds after a shared
878
+ component or state container changed — but do NOT rewrite that behaviour.
879
+ - Do not modify the app shell, global layout or design system — they belong to no
880
+ feature. If one of them has to change, STOP and surface it.
881
+ - Where generated code depends on a delta Assumption (DA-n) or Workaround (WA-n),
882
+ mark it with a comment citing that id.
883
+ outputs:
884
+ - { name: fe_files, from: "$.files" }
431
885
 
432
- - id: verify_generated
886
+ - id: verify_fe_files
887
+ condition: "{{fe_go}} == true"
433
888
  action: verify_context
434
- require_non_empty: files
889
+ require_non_empty: fe_files
435
890
  on_empty: stop
436
- on_empty_message: "No files generated for story {{story}}. Check that design pass {{design_pass}} in {{design_doc}} actually describes changes (a pass whose '### Changes' section is empty has nothing to build), then re-run — or re-run /appweaver story-plan {{story}} to design the slice properly first."
437
-
438
- # 8. OKF — MERGE, never replace. This run wrote ONE STORY's files; the feature's
439
- # other stories wrote the rest, and a replace would erase them the moment a second
440
- # story implements. --lane backend puts them in `source_files`, the service lane's
441
- # key, leaving `ui_source_files` for implement-ui.
442
- # Known limit: a file the slice DELETED stays in the list; concept-coverage reports
443
- # it as declared-but-missing drift rather than silently carrying it as real.
444
- - id: xref_concept
891
+ on_empty_message: "The frontend lane generated no files. Ensure {{fe_plan}} is a FILLED plan (not the empty template) and that it passed its gate — /appweaver analyze-plan for a master, /appweaver analyze-plan-delta for a change — then re-run implement."
892
+
893
+ - id: xref_concept_frontend
894
+ condition: "{{fe_go}} == true"
445
895
  action: execute_script
896
+ on_error: continue
446
897
  script: { ref: xref }
447
898
  subcommand: concept-upsert
448
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "backend", "--origin", "greenfield"]
899
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fe_files}}", "--merge", "--origin", "greenfield"]
449
900
  outputs:
450
901
  - { name: okf_concept, from: "$.concept" }
451
902
 
452
- # 8a. Cross-slice edges are a FEATURE property, not a story's: which sibling
453
- # aggregates this one references is stated by the feature plan's domain model,
454
- # and no single slice can answer it. So this reads backend-plan.md — the one
455
- # derived fact it takes from the plan — and reconciles on write, so a dropped
456
- # dependency self-heals out of the used aggregate's Cross-References. Skipped
457
- # with no plan on disk: there is then nothing to derive edges from.
903
+ # 11. CROSS-SLICE EDGES — a Cross-References row written into every SIBLING aggregate
904
+ # this one uses, read off the master backend plan (§1 References-by-id + its External
905
+ # integrations table), reconcile-on-write so a dropped dependency self-heals and a
906
+ # used sibling with no concept yet is stubbed.
907
+ # BOOTSTRAP ONLY. On the delta path the master plan still holds the PRE-change
908
+ # dependencies, so recording edges now would publish stale references — finalize-delta
909
+ # records them after the change merges. Enrichment downstream of a successful
910
+ # generation; it never re-decides WHAT was built.
911
+ - id: record_edges_flag
912
+ condition: "{{mode}} == bootstrap"
913
+ action: set
914
+ vars: { record_edges: "{{be_go}}" }
458
915
  - id: xref_record
459
- condition: "{{be_plan_exists}} == true"
916
+ condition: "{{record_edges}} == true"
460
917
  action: execute_script
461
918
  on_error: continue
462
919
  script: { ref: xref }
@@ -466,118 +923,72 @@ workflow:
466
923
  - { name: xref_wrote, from: "$.wrote" }
467
924
  - { name: xref_edge_count, from: "$.edge_count" }
468
925
 
469
- # 8b. The concept's Overview — the one part a deterministic script cannot produce.
470
- # It describes the AGGREGATE, which is the feature plan's subject: distilling it
471
- # from one story's slice would name whatever that slice happened to touch. The
472
- # model authors prose only; concept-overview splices it into `## Overview` alone,
473
- # so the Cross-References rows other slices own are structurally out of reach.
474
- # build_context, not a step-level `context:`: the engine REPLACES the envelope on
475
- # build_context and only ADDS to it on a step-level list, so naming the plan alone
476
- # there would have left generation's whole payload — this story's slice included —
477
- # underneath a prompt that says "describe the whole aggregate, not one story".
926
+ # 12. The concept's Overview — the ONE part of the OKF concept a deterministic script
927
+ # cannot produce. It describes the aggregate ROOT, the invariants it guards and its
928
+ # transaction boundary, and since the model split those are literally the CONTEXT'S
929
+ # DOMAIN MODEL's content: the plan only records where each invariant is enforced. So
930
+ # the model is the source where there is one, and the backend plan is the fallback
931
+ # for a feature that predates the split. Its own build_context — the envelope
932
+ # persists, and distilling the concept prose from a generation payload would describe
933
+ # the code rather than the aggregate.
934
+ # BOOTSTRAP ONLY: on the delta path finalize-delta re-authors it from the merged
935
+ # model, after the change has actually landed in it.
936
+ - id: overview_from_model
937
+ condition: "{{domain_model_exists}} == true"
938
+ action: set
939
+ vars: { overview_ok: "true" }
940
+ - id: overview_from_plan
941
+ condition: "{{be_go}} == true"
942
+ action: set
943
+ vars: { overview_ok: "true" }
944
+ - id: overview_delta_off
945
+ condition: "{{mode}} == delta"
946
+ action: set
947
+ vars: { overview_ok: "false" }
948
+
478
949
  - id: ctx_overview
479
- condition: "{{be_plan_exists}} == true"
950
+ condition: "{{overview_ok}} == true"
480
951
  action: build_context
481
952
  context:
482
- - { path: "{{be_plan}}" }
953
+ - { path: "{{domain_model}}", optional: true }
954
+ - { path: "{{backend_plan}}", optional: true }
483
955
  - id: okf_overview
484
- condition: "{{be_plan_exists}} == true"
956
+ condition: "{{overview_ok}} == true"
485
957
  action: agent_query
486
958
  include_standards: false
487
959
  schema: { overview: "<2-4 sentences of prose>" }
488
960
  mock: { overview: "" }
489
961
  task: |
490
- Write the OKF knowledge-concept Overview for the aggregate that the backend plan
491
- (in context) describes. 2-4 sentences of PROSE, distilled from §1 Domain Model:
962
+ Write the OKF knowledge-concept Overview for the aggregate `{{feature}}`. 2-4
963
+ sentences of PROSE:
492
964
  - name the aggregate ROOT and what the aggregate IS;
493
- - state the INVARIANTS it guards;
494
- - state its TRANSACTION BOUNDARY — what it OWNS inside the boundary vs what it
495
- only REFERENCES by id.
965
+ - state the INVARIANTS it guards (its BR-nnn — what they require, not their ids);
966
+ - state its TRANSACTION BOUNDARY — what it OWNS inside the boundary vs what it only
967
+ REFERENCES by id.
968
+ The bounded context's domain-model.md is the source where it is in context: read
969
+ ONLY its `## Aggregate: {{feature}}` section — the other aggregates are other
970
+ slices' concepts. Where it is not in context, distil the same three things from
971
+ backend-plan.md's domain section instead.
496
972
  Plain prose only: no headings, no bullets, no code fences, no file paths, and no
497
973
  restating of the dependency edges (those live in the Depends-On / Cross-References
498
- tables). Describe only what the plan states — invent nothing, and describe the
499
- whole aggregate rather than the one story that happens to be building now.
974
+ tables). Describe only what the document states — invent nothing.
500
975
  outputs:
501
976
  - { name: okf_overview, from: "$.overview" }
977
+
502
978
  - id: okf_overview_write
503
- condition: "{{be_plan_exists}} == true"
979
+ condition: "{{overview_ok}} == true"
504
980
  action: execute_script
981
+ on_error: continue
505
982
  script: { ref: xref }
506
983
  subcommand: concept-overview
507
984
  args: ["--feature-dir", "{{feature_dir}}", "--text", "{{okf_overview}}"]
508
985
 
509
- # ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
510
- # Generation is design-only by contract; this is the check that the design was not
511
- # the wrong design. READ-ONLY and REPORT-ONLY: it never edits code, never edits the
512
- # pass, and never stops the run. A gap here means one of two documents is wrong, and
513
- # which one is a human decision — the message names both routes.
514
- - id: ctx_accept
515
- action: build_context
516
- vars:
517
- story_id: "{{story}}"
518
- exercises_csv: "{{exercises_csv}}"
519
- constraints_csv: "{{constraints_csv}}"
520
- files_csv: "{{files}}"
521
- context:
522
- - { path: "{{story_path}}" } # the acceptance criteria
523
- - { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
524
- - { path: "{{design_doc}}" } # what was designed
525
- - id: accept_check
526
- action: agent_query
527
- tools: Read # the generated files arrive as paths, not inlined
528
- include_standards: false
529
- schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
530
- mock: { ok: "true", gaps: "" }
531
- task: |
532
- Story {{story_id}} was just implemented. Judge ONLY this: does the code that now
533
- exists satisfy the requirements the story delivers, AS THE SPEC WORDS THEM?
534
-
535
- In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold).
536
- Resolve each id's wording from spec.md in context — that wording, not the design's
537
- restatement of it, is what the code is measured against.
538
-
539
- Read the files this run wrote:
540
- {{files_csv}}
541
- plus anything they call that you need to follow. Do not modify a single file.
542
-
543
- Report a gap ONLY where the code fails to deliver an in-scope id as the spec words
544
- it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
545
- noise that hides the real ones:
546
- - a requirement no story has claimed yet, or one another story delivers;
547
- - style, naming, structure or test-coverage opinions — other gates own those;
548
- - anything the design pass deliberately deferred and named under its risks.
549
-
550
- One line per gap: the id, what the spec requires, and what the code does instead.
551
- Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
552
- about an in-scope id, that IS a gap — say which document you believe is wrong and
553
- why, because the two routes out of it are different commands.
554
- outputs:
555
- - { name: accept_ok, from: "$.ok" }
556
- - { name: accept_gaps, from: "$.gaps" }
557
- - id: accept_notice
558
- condition: "{{accept_ok}} != true"
559
- action: verify_context
560
- require_value: accept_ok
561
- equals: "true"
562
- on_mismatch: continue
563
- on_mismatch_message: |
564
- ACCEPTANCE — story {{story}} does not yet satisfy the spec wording of the ids it delivers:
565
-
566
- {{accept_gaps}}
567
-
568
- Nothing was changed to fix this: the code follows design pass {{design_pass}}, so a gap
569
- here means either the design was wrong or the spec has moved. Route by which it is —
570
- the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
571
- the spec is wrong: /appweaver sync-spec, then sync-stories, then story-plan.
572
- Run /appweaver build either way — a design that is right and code that does not compile
573
- is a third thing, and build is where that one surfaces.
574
-
575
- # 9. Post-execution hooks.
986
+ # 13. Post-execution hooks — the same phase set as the pre-hooks above.
576
987
  - id: resolve_post_hooks
577
988
  action: execute_script
578
989
  script: { ref: actions }
579
990
  subcommand: resolve-hooks
580
- args: ["--phase", "after_implement"]
991
+ args: ["--phase", "after_implement", "--phase", "{{post_ui_phase}}", "--phase", "{{post_cr_phase}}"]
581
992
  outputs:
582
993
  - { name: after_hooks, from: "$.hooks" }
583
994
  - id: dispatch_post_hooks
@@ -585,12 +996,19 @@ workflow:
585
996
  hooks_from_variable: after_hooks
586
997
 
587
998
  outputs:
588
- story: "{{story}}"
589
- built_from: "{{design_doc}} — design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
590
- requirements: "{{exercises_csv}}"
591
- files: "{{files}}"
592
- lane: "{{lane_id}} → {{lane_source_path}}"
593
- okf_concept: "{{okf_concept}} (this run's files MERGED into source_files; {{xref_edge_count}} cross-slice edge(s) recorded into sibling aggregates)"
594
- acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
595
- stale_ids: "{{stale_csv}}"
596
- next: "/appweaver build — it compiles every declared lane. Then /appweaver analyze-slice (spec ↔ code, through the design), and /appweaver implement-ui if this story has a UI design. The story stays OPEN until /appweaver close-story ratifies its ADRs, flips its interface entries live and archives the passes."
999
+ feature_dir: "{{feature_dir}}"
1000
+ bounded_context: "{{context}}"
1001
+ mode: "{{mode}}"
1002
+ cr_id: "{{active_change}}"
1003
+ backend_implemented: "{{be_go}}"
1004
+ backend_plan: "{{be_plan}}" # the document the backend lane generated FROM
1005
+ backend_files: "{{be_files}}"
1006
+ frontend_implemented: "{{fe_go}}"
1007
+ frontend_plan: "{{fe_plan}}" # the document the frontend lane generated FROM
1008
+ frontend_files: "{{fe_files}}"
1009
+ domain_model: "{{domain_model}}" # the master model every generating lane read
1010
+ delta_model: "{{delta_model}}" # its second half on the delta path — empty on the bootstrap path
1011
+ okf_concept: "{{okf_concept}}"
1012
+ xref_edge_count: "{{xref_edge_count}}"
1013
+ note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else."
1014
+ next: "This run was mode `{{mode}}`, change `{{active_change}}` (empty on the bootstrap path). BOOTSTRAP → /appweaver build → analyze-slice → run. DELTA → /appweaver build → analyze-slice-delta {{active_change}} → finalize-delta → analyze-slice → close-delta, with the change still OPEN throughout — compile first, then analyze-slice-delta verifies that every element the delta plans marked ADDED or MODIFIED is actually in the code with a paired test (the gate that catches a lane you declined at the prompt above), then finalize-delta merges it into spec.md + the context's domain-model.md while leaving it open, analyze-slice verifies the slice against those merged masters while a red result is still recoverable, and close-delta LAST archives the change and bumps the version."