aw-spec-cli 0.1.16 → 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 (70) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +18 -18
  2. package/assets/.appweaver/ORCH-DIALECT.md +1 -3
  3. package/assets/.appweaver/RULE-PARTITION.md +29 -13
  4. package/assets/.appweaver/STACK-PROFILES.md +10 -9
  5. package/assets/.appweaver/orchestrator/.state/smoke.state.json +32 -0
  6. package/assets/.appweaver/orchestrator/AGENT-PROFILE.md +4 -1
  7. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +251 -0
  8. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +104 -35
  9. package/assets/.appweaver/orchestrator/orchestrator.py +5 -4
  10. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +94 -36
  11. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +1 -1
  12. package/assets/.appweaver/orchestrator/rev-templates/domain-model-template.skeleton.md +115 -0
  13. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +2 -9
  14. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +16 -40
  15. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +38 -17
  16. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +8 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +10 -11
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +673 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +535 -0
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +643 -0
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +84 -701
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +4 -4
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-characterize.orch.yaml +7 -7
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +16 -14
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +2 -2
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +243 -144
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +877 -578
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-inventory.orch.yaml +4 -4
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +32 -91
  30. package/assets/.appweaver/orchestrator/workflows/aw-spec-parity.orch.yaml +2 -2
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +836 -514
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-run.orch.yaml +2 -2
  33. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-bounded-context.orch.yaml +132 -0
  34. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +38 -5
  35. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +3 -3
  36. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +271 -979
  37. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +1085 -3385
  38. package/assets/.appweaver/scripts/bash/aw-xref.sh +1 -2
  39. package/assets/.appweaver/scripts/bash/common.sh +47 -0
  40. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +914 -3220
  41. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +1 -2
  42. package/assets/.appweaver/scripts/python/aw-payload-parity.py +19 -3
  43. package/assets/.appweaver/scripts/python/aw-stack.py +6 -2
  44. package/assets/.appweaver/scripts/python/aw-xref.py +86 -350
  45. package/assets/.appweaver/templates/constitution-ui-template.md +1 -1
  46. package/assets/.appweaver/templates/delta-design-template.md +120 -30
  47. package/assets/.appweaver/templates/delta-domain-model-template.md +81 -0
  48. package/assets/.appweaver/templates/delta-spec-template.md +43 -17
  49. package/assets/.appweaver/templates/design-template.md +68 -98
  50. package/assets/.appweaver/templates/domain-model-template.md +130 -0
  51. package/assets/.appweaver/templates/okf-concept-template.md +2 -2
  52. package/assets/.appweaver/templates/source-map-template.md +3 -3
  53. package/assets/.appweaver/templates/spec-template.md +100 -122
  54. package/assets/.appweaver/templates/ui-delta-design-template.md +159 -0
  55. package/assets/.appweaver/templates/ui-design-template.md +46 -75
  56. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +7 -18
  57. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +34 -39
  58. package/assets/guardrails/requirement-spec.txt +2 -2
  59. package/assets/guardrails/technology/dotnet-webapi/testing.md +1 -1
  60. package/package.json +1 -1
  61. package/assets/.appweaver/orchestrator/modules/delta-design.orch.yaml +0 -105
  62. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +0 -87
  63. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -251
  64. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -813
  65. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -253
  66. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +0 -663
  67. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +0 -600
  68. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +0 -1147
  69. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +0 -1121
  70. package/assets/.appweaver/templates/story-template.md +0 -178
@@ -1,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,519 +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
- # UNASKED IS NOT UNMATCHED. detect-project answers true whenever no --ecosystem was
98
- # given, and the gate below only reads it on the repo-scope fallback — so the default
99
- # has to be permissive or a probe that could not run would refuse a scaffolded lane.
100
- ecosystem_match: "true"
101
- project_scope: ""
102
- project_markers: ""
103
- current_version: ""
104
- stale_csv: ""
105
- stale_note: ""
106
- story_dir: ""
107
- adr_path: ""
108
- if_path: ""
109
- # the story's SERVICE design
110
- design_doc: ""
111
- design_exists: "false"
112
- design_pass: "0"
113
- design_spec_version: ""
114
- design_stale: "false"
115
- # the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe that
116
- # could not run fails CLOSED, and the message default carries a route for the same reason.
117
- pass_plan_hash: ""
118
- gate_status: ""
119
- gate_certified: "false"
120
- gate_aborted: "false"
121
- gate_gap_refs: ""
122
- gate_report: ""
123
- gate_lane_sha: ""
124
- gate_msg: "The analyze-design verdict could not be read, so this run cannot tell whether the feature plans were ever certified. Run /appweaver analyze-design, then re-run."
125
- # the feature's current code, per lane (OKF)
126
- okf_concept: ""
127
- coverage_digest: ""
128
- be_source_csv: ""
129
- be_source_count: "0"
130
- fe_source_count: "0"
131
- # generation + enrichment
132
- files: ""
133
- xref_wrote: ""
134
- xref_edge_count: "0"
135
- okf_overview: ""
136
- accept_ok: "true"
137
- accept_gaps: ""
138
- 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"
139
180
 
140
181
  workflow:
141
182
 
142
- # 0. TECHNICAL rule files → sticky standards (configuration, not hardcoded).
143
- # No blocking gate: an unconfigured project soft-continues, because
144
- # constitution.technical.md still carries the technology base.
145
- - id: tech_select
146
- action: include
147
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
148
- with: { command: aw-spec-implement }
149
-
150
- # 1. The active feature. spec.md and backend-plan.md are resolved here but neither is
151
- # 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.
152
230
  - id: feature_paths
153
231
  action: execute_script
154
232
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
155
233
  script: { ref: actions }
156
234
  subcommand: feature-paths
157
235
  outputs:
158
- - { name: feature_dir, from: "$.feature_dir" }
159
- - { name: spec_file, from: "$.spec_file" }
160
- - { name: be_plan, from: "$.backend_plan_file" }
161
- - { 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
+
162
243
  - id: require_feature
163
244
  action: verify_context
164
245
  require_non_empty: feature_dir
165
246
  on_empty: stop
166
247
  on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement."
167
248
 
168
- # 1b. THE LEDGER FENCE. Generation is design-only by contract, but this run reads the
169
- # spec twice all the same: stale_csv below is computed against the ledger, and the
170
- # acceptance pass at the end reads spec.md as the wording the code is judged
171
- # against. Both are worthless if the version does not name the contents.
172
- - id: spec_state_guard
173
- action: include
174
- module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
175
- with: { command: "/appweaver implement" }
176
-
177
- # 2. WHICH STORY. Same resolution story-plan uses: an explicit id in the input wins,
178
- # anything else (nothing named, an unknown id, several) becomes a pick from the open
179
- # backlog. One scan returns every fact the gates below read.
180
- - 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
181
254
  action: execute_script
255
+ on_error: continue
182
256
  script: { ref: actions }
183
- subcommand: story-plan-check
184
- args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
257
+ subcommand: context-paths
185
258
  outputs:
186
- - { name: any_open, from: "$.any_open" }
187
- - { name: open_ids, from: "$.open_ids" }
188
- - { name: digest, from: "$.digest" }
189
- - { name: resolved, from: "$.resolved" }
190
- - { name: needs_pick, from: "$.needs_pick" }
191
- - { name: pick_reason, from: "$.pick_reason" }
192
- - { name: story, from: "$.story" }
193
- - { name: story_path, from: "$.story_path" }
194
- - { name: story_open, from: "$.story_open" }
195
- - { name: status_msg, from: "$.status_msg" }
196
- - { name: story_title, from: "$.story_title" }
197
- - { name: exercises_csv, from: "$.exercises_csv" }
198
- - { name: constraints_csv, from: "$.constraints_csv" }
199
- - { name: current_version, from: "$.current_version" }
200
- - { name: stale_csv, from: "$.stale_csv" }
201
- - { name: stale_note, from: "$.stale_note" }
202
- - { name: story_dir, from: "$.story_dir" }
203
- - { name: adr_path, from: "$.adr_path" }
204
- - { name: if_path, from: "$.if_path" }
205
- - { name: design_doc, from: "$.be_doc_path" }
206
- - { name: design_exists, from: "$.be_doc_exists" }
207
- - { name: design_pass, from: "$.be_last_pass" }
208
- - { name: design_spec_version, from: "$.be_doc_spec_version" }
209
- - { name: design_stale, from: "$.be_doc_stale" }
210
- - { name: pass_plan_hash, from: "$.be_pass_plan_hash" }
211
- - id: require_open_backlog
212
- action: verify_context
213
- require_true: any_open
214
- on_false: stop
215
- 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)."
216
- - id: ask_story
217
- condition: "{{needs_pick}} == true"
218
- action: ask
219
- prompt: |
220
- Which story is this implementation for? ({{pick_reason}})
221
-
222
- {{digest}}
223
- options_from: "{{open_ids}}"
224
- answer_key: picked_story
225
- answer_var: picked_story
226
- - id: story_pick_check
227
- 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
228
269
  action: execute_script
229
270
  script: { ref: actions }
230
- subcommand: story-plan-check
231
- args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
271
+ subcommand: cr-list
272
+ args: ["--feature-dir", "{{feature_dir}}", "--preselect", "{{USER_INPUT}}"]
232
273
  outputs:
233
- - { name: resolved, from: "$.resolved" }
234
- - { name: story, from: "$.story" }
235
- - { name: story_path, from: "$.story_path" }
236
- - { name: story_open, from: "$.story_open" }
237
- - { name: status_msg, from: "$.status_msg" }
238
- - { name: story_title, from: "$.story_title" }
239
- - { name: exercises_csv, from: "$.exercises_csv" }
240
- - { name: constraints_csv, from: "$.constraints_csv" }
241
- - { name: stale_csv, from: "$.stale_csv" }
242
- - { name: stale_note, from: "$.stale_note" }
243
- - { name: story_dir, from: "$.story_dir" }
244
- - { name: design_doc, from: "$.be_doc_path" }
245
- - { name: design_exists, from: "$.be_doc_exists" }
246
- - { name: design_pass, from: "$.be_last_pass" }
247
- - { name: design_spec_version, from: "$.be_doc_spec_version" }
248
- - { name: design_stale, from: "$.be_doc_stale" }
249
- - { name: pass_plan_hash, from: "$.be_pass_plan_hash" }
250
- - id: require_story
251
- action: verify_context
252
- require_true: resolved
253
- on_false: stop
254
- on_false_message: |
255
- Could not resolve a story from '{{picked_story}}'. Open stories:
256
- {{digest}}
257
- Re-run implement naming one of them.
258
- - id: require_open_story
259
- action: verify_context
260
- require_true: story_open
261
- on_false: stop
262
- 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" }
263
278
 
264
- # 3. THE STORY MUST HAVE A SERVICE DESIGN. This phase generates from a design pass and
265
- # from nothing else, so an undesigned story has no input — and the fix is one
266
- # command, not a judgement call.
267
- - id: require_design
268
- action: verify_context
269
- require_true: design_exists
270
- on_false: stop
271
- 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."
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 }
272
297
 
273
- # 3a. A design behind the spec is ANNOUNCED, not refused. The story is legitimately in
274
- # flight and the spec may have moved on ids it does not carry; stale_csv below is
275
- # the sharper signal, because it names only the ids THIS story actually carries.
276
- - id: design_stale_notice
277
- condition: "{{design_stale}} == true"
278
- action: verify_context
279
- require_value: design_stale
280
- equals: "false"
281
- on_mismatch: continue
282
- 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."
283
- # stale_csv is a STRING, so require_value/equals compares cleanly and needs no
284
- # condition. design_stale above is a JSON boolean, which require_value stringifies as
285
- # 'True' — it can never equal 'false', so that one is gated by `condition:` and the
286
- # value gate is only the vehicle that prints (law 43).
287
- - id: story_stale_notice
288
- action: verify_context
289
- require_value: stale_csv
290
- equals: ""
291
- on_mismatch: continue
292
- 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."
293
-
294
- # ═══════ 3a-bis. LAW 40, THE LAST HOP — the plan this pass was designed against ═══════
295
- # THREE HASHES, not two. story-plan proved the feature plan was certified in the shape it
296
- # was in when this pass was designed, and story-plan-doc stamped that certified hash into
297
- # the pass header. But a pass is written once and read forever: comparing only the CURRENT
298
- # plan to the CURRENT verdict would let a pass designed against plan H1 still generate
299
- # after the plan was edited to H2 and re-gated — which is precisely the case law 40 names,
300
- # one level below where it was being checked.
301
- # pass hash what this pass was designed against (the story pass header)
302
- # verdict hash what analyze-design last certified (the gate verdict)
303
- # A MISMATCH IS A STOP, at the same severity as never-gated: continuing ships code built
304
- # to an architecture nobody approved. Per lane, and only for the lane this command builds.
305
- - id: gate_verdict
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" }
309
+
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"
306
314
  action: execute_script
307
- on_error: continue
308
315
  script: { ref: actions }
309
- subcommand: gate-read
310
- args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
316
+ subcommand: cr-paths
317
+ args: ["--cr-id", "{{cr_choice}}", "--feature-dir", "{{feature_dir}}"]
311
318
  outputs:
312
- - { name: gate_status, from: "$.status" }
313
- - { name: gate_certified, from: "$.certified" }
314
- - { name: gate_aborted, from: "$.aborted" }
315
- - { name: gate_gap_refs, from: "$.gap_refs_csv" }
316
- - { name: gate_report, from: "$.report" }
317
- - { name: gate_lane_sha, from: "$.backend_sha256" }
318
-
319
- # Three reasons, three routes; ordered sets, last match wins. Aborted is last because it
320
- # also carries status "fail" — the red gate_result_open writes BEFORE any analysis — and
321
- # sending someone to read gaps that were never found is the one wrong answer here.
322
- - id: gate_msg_never
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"
323
338
  action: set
324
- vars: { gate_msg: "The feature plans for this feature have never been certified: there is no analyze-design verdict beside them. Generation builds from a story pass that sits under an unapproved architecture. Run /appweaver analyze-design, then /appweaver story-plan {{story}}, then re-run /appweaver implement." }
325
- - id: gate_msg_red
326
- condition: "{{gate_status}} == fail"
339
+ vars: { be_plan: "{{backend_plan}}", be_plan_ok: "{{backend_plan_exists}}" }
340
+ - id: be_plan_delta
341
+ condition: "{{mode}} == delta"
327
342
  action: set
328
- vars: { gate_msg: "The feature plans do NOT conform to the spec: analyze-design's last verdict is FAIL on {{gate_gap_refs}} (report: {{gate_report}}). Close those gaps before generating code beneath them, then re-run /appweaver implement." }
329
- - id: gate_msg_aborted
330
- condition: "{{gate_aborted}} == true"
343
+ vars: { be_plan: "{{delta_backend_plan}}", be_plan_ok: "{{delta_backend_plan_exists}}" }
344
+ - id: fe_plan_bootstrap
345
+ condition: "{{mode}} == bootstrap"
331
346
  action: set
332
- vars: { gate_msg: "The last analyze-design run STARTED and never reached a verdict — the red on disk is the one it writes before any analysis, so nothing was found and there are no gaps to read. The plans are UNCERTIFIED, not rejected. Run /appweaver analyze-design to completion, then re-run /appweaver implement." }
333
-
334
- - id: gate_certified_guard
335
- action: verify_context
336
- require_true: gate_certified
337
- on_false: stop
338
- on_false_message: "{{gate_msg}}"
339
-
340
- # An EMPTY pass hash fails this, and that is correct: a pass written before the hash was
341
- # stamped cannot say which plan shape it was designed against, so nothing can prove it was
342
- # designed against the approved one. The route is the same either way — re-design.
343
- - id: pass_plan_shape_guard
344
- action: verify_context
345
- require_value: pass_plan_hash
346
- equals: "{{gate_lane_sha}}"
347
- on_mismatch: stop
348
- on_mismatch_message: "Design pass {{design_pass}} for story {{story}} was designed against a backend plan that is NOT the one analyze-design currently certifies — the plan has been edited and re-gated since, or this pass predates plan-hash stamping and cannot say what it was designed against. Generating from it would build to an architecture nobody approved for this shape of the plan. Re-run /appweaver story-plan {{story}} to design against the currently certified plan, then re-run /appweaver implement."
349
-
350
- # 4. THE SERVICE LANE and its source path. This phase writes code, and the lane's root
351
- # is the only subtree it may write into — so this runs before the project gate,
352
- # which searches that subtree.
353
- - id: lane_gate
354
- action: include
355
- module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
356
- with: { require: backend, command: "/appweaver implement" }
357
-
358
- # 5. Require the PROJECT — a scaffolded, compiling tree. Implement GENERATES into it;
359
- # with no project there is nowhere to place the files. Deterministic structure gate:
360
- # searches the LANE'S OWN subtree for an ecosystem manifest, falling back to the
361
- # whole repo for the layout that keeps its manifest beside the lane root rather than
362
- # inside it. How the project got there is not asked.
363
- # THE FALLBACK IS DELIBERATE AND IT NEEDED A DISCRIMINATOR. Searching the whole repo
364
- # when the lane's own subtree has no manifest is right — a .NET .sln legitimately sits
365
- # ABOVE the src/ it builds — but it answered with whatever manifest it found first, so
366
- # in a two-lane repo where only the frontend is scaffolded, the SERVICE lane's gate
367
- # passed on the frontend's package.json. Which is exactly the failure implement-ui's
368
- # `project_scope == lane` rule was written to prevent, one lane over.
369
- # `--ecosystem` is the fix, and NOT implement-ui's rule: this passes the value
370
- # lane-gate has already bound from stacks.json (that include runs above), so the repo
371
- # -scope hit is accepted only when the manifest is one this lane's ecosystem knows.
372
- # Nothing here re-derives it — a second guess is a second thing to keep in step.
373
- - id: detect_foundation
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"
374
377
  action: execute_script
375
378
  script: { ref: actions }
376
379
  subcommand: detect-project
377
- args: ["--root", "{{lane_source_path}}", "--ecosystem", "{{lane_ecosystem}}"]
380
+ args: ["--root", "{{fe_source_path}}"]
378
381
  outputs:
379
- - { name: foundation_present, from: "$.scaffolded" }
380
- - { name: project_markers, from: "$.markers" }
381
- - { name: project_scope, from: "$.scope" }
382
- - { name: ecosystem_match, from: "$.ecosystem_match" }
383
- - 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"
384
395
  action: verify_context
385
- require_true: foundation_present
386
- on_false: stop
387
- 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."
388
-
389
- # 5b. THE GATE PASSED ON SOMEBODY ELSE'S PROJECT. A repo-scope hit whose manifests are
390
- # none of this lane's ecosystem means the search fell back past the lane root and
391
- # found the OTHER lane's project. STOPS rather than warns: the next thing this run
392
- # does is generate {{lane_language}} code, and it has just been told there is nowhere
393
- # of that kind to put it — carrying on writes a service tree inside a frontend, or
394
- # into the repo root. Scoped to the fallback, so the deliberate manifest-above-the-lane
395
- # layout (a .sln over src/) still passes: that hit IS this lane's ecosystem.
396
- - id: foundation_ecosystem_gate
397
- condition: "{{project_scope}} == repo"
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
398
471
  action: verify_context
399
- require_true: ecosystem_match
472
+ require_true: generate_any
400
473
  on_false: stop
401
- on_false_message: "The {{lane_id}} lane's source path `{{lane_source_path}}` holds no project, and the repo-wide fallback found only manifests that are not {{lane_ecosystem}}: {{project_markers}}. That is the OTHER lane's project — this gate passing on it would send {{lane_language}} code into a tree built for something else. Scaffold this lane: /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{lane_profile}}, or create the project with the stack's own tooling under `{{lane_source_path}}`. (A manifest deliberately kept ABOVE the lane root — a .sln over src/ — is accepted, because it is this lane's ecosystem.)"
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."
402
475
 
403
- # 6. WHAT ALREADY EXISTS, from the feature's OKF concept, split by lane. This is what
404
- # makes the run incremental: the pass names the files it changes, and this names the
405
- # files that are there to change. Advisory — the first story of a greenfield feature
406
- # has no concept and generates into a clean tree.
407
- - id: coverage
408
- action: execute_script
409
- on_error: continue
410
- script: { ref: xref }
411
- subcommand: concept-coverage
412
- args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
413
- outputs:
414
- - { name: okf_concept, from: "$.concept" }
415
- - { name: coverage_digest, from: "$.digest" }
416
- - { name: be_source_csv, from: "$.backend_csv" }
417
- - { name: be_source_count, from: "$.backend_count" }
418
- - { name: fe_source_count, from: "$.frontend_count" }
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}}" }
498
+
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 }
419
504
 
420
- # 7. Pre-execution hooks.
505
+ # 10. Pre-execution hooks — see the hook-phase vars above.
421
506
  - id: resolve_pre_hooks
422
507
  action: execute_script
423
508
  script: { ref: actions }
424
509
  subcommand: resolve-hooks
425
- args: ["--phase", "before_implement"]
510
+ args: ["--phase", "before_implement", "--phase", "{{pre_ui_phase}}", "--phase", "{{pre_cr_phase}}"]
426
511
  outputs:
427
512
  - { name: before_hooks, from: "$.hooks" }
428
513
  - id: dispatch_pre_hooks
429
514
  action: dispatch_hooks
430
515
  hooks_from_variable: before_hooks
431
516
 
432
- # ═══════════════════ GENERATION — one story's slice, from one pass ═══════════
433
- # The pass constrains WHAT to build; the constitution, the lane and the loaded
434
- # standards fix HOW. spec.md is deliberately absent: the design is the single source
435
- # here, and the acceptance check below is where the spec gets its say.
436
- - 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"
437
524
  action: build_context
438
- vars:
439
- story_id: "{{story}}"
440
- story_title: "{{story_title}}"
441
- exercises_csv: "{{exercises_csv}}"
442
- constraints_csv: "{{constraints_csv}}"
443
- pass_number: "{{design_pass}}"
444
- coverage_digest: "{{coverage_digest}}"
445
- source_files_csv: "{{be_source_csv}}"
446
- source_file_count: "{{be_source_count}}"
447
- stale_note: "{{stale_note}}"
448
525
  context:
449
- - { path: "{{design_doc}}" } # the story's SERVICE design — the LATEST pass is the input
450
- - { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
451
- - { path: "{{if_path}}", optional: true } # the PUBLISHED surface this code must match
452
- - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
453
- - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
454
- - 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"
455
531
  action: agent_generate
456
- tools: "Read,Write,Edit" # the existing code arrives as {{source_files_csv}} paths, not inlined
532
+ tools: "Read,Write,Edit"
457
533
  permission_mode: acceptEdits
458
534
  task: |
459
- Implement story {{story_id}} — {{story_title}} — in the service lane, from
460
- "## Design pass {{pass_number}}" of the story design in context. Guidance: {{USER_INPUT}}
461
-
462
- THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
463
- document; every earlier pass is superseded history, and any of them may carry
464
- hand edits that were deliberate. Read the last one and build exactly it. Do NOT
465
- consult or infer from spec.md — it is not in context on purpose: the design
466
- already went through the gates that conform it, and generating from the spec
467
- routes around all of them.
468
-
469
- THIS IS ONE SLICE OF AN EXISTING FEATURE, NOT THE WHOLE FEATURE.
470
- - The pass's "### Changes" section names, file by file, what to create and what
471
- to modify. Create ONLY the files it names as new; modify the named existing
472
- files IN PLACE. Every other file in this feature belongs to work that is
473
- already done — do not regenerate, reformat or "tidy" it.
474
- - The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}} — the
475
- complete set of bounds the spec declares for this aggregate, stamped into the
476
- story's frontmatter by the kit rather than transcribed by anyone. Satisfy the
477
- CN-, DC- and IC- among them. IGNORE the UX-: those are what the UI lane realises,
478
- and a service-side obligation is stated as a CN- by design. Anything outside those
479
- ids is another story's work, even if you can see it is
480
- missing. If the slice genuinely cannot be built without touching shared
481
- structure, make the smallest change that works and SAY SO in your summary.
482
- - The code that already exists for this feature, per lane, is below. The service
483
- files ({{source_file_count}} on disk) are yours to read before you edit; the UI
484
- list is there so you do not change a surface that already has a client.
485
-
486
- {{coverage_digest}}
487
-
488
- {{source_files_csv}}
489
-
490
- BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
491
- {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
492
- compiled with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
493
- language and framework and no other, using its idioms, its project layout and its
494
- test framework. Take persistence, messaging and the remaining technology choices
495
- from the constitution's technology base + the loaded standards; the design is
496
- technology-agnostic, so do not re-decide anything already fixed there. If the
497
- loaded standards describe a DIFFERENT stack than the one above, the lane wins on
498
- language and layout — follow the standards' INTENT and STOP to surface the
499
- mismatch in your summary.
500
-
501
- WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
502
- declares. That subtree is this lane's; a file outside it belongs to another lane
503
- or to no lane at all. Do not create a parallel source tree because the layout
504
- inside it is unfamiliar; if the pass needs a place this subtree has no home for,
505
- STOP and surface it.
506
-
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}}
507
540
  Rules:
508
- - THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and framework/code
509
- standards — and NO domain rule files. Introduce NO domain rule, identifier,
510
- terminology or behaviour the pass does not state. If a domain specific seems
511
- 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.
512
573
  - Follow the project's folder/layer convention + the loaded coding-standards,
513
- dependency-policy and testing standards for HOW to structure, name and place
514
- files WITHIN that subtree — the pass says WHAT to build; these say HOW.
515
- - Realise the pass's "### Approach" and "### Changes" exactly, changing nothing
516
- about WHAT they specify, and preserve every ubiquitous-language name verbatim.
517
- - INTERFACES: where the pass's "### Interfaces" describes a published surface,
518
- the entry in interfaces.md (in context) is the contract — match its route,
519
- shape and semantics. Where the log and the pass disagree, the pass is the
520
- newer word and the log entry it supersedes is named in it; if neither says
521
- which, STOP and surface it rather than picking.
522
- - COMPONENTS: where the pass depends on a framework component (e.g.
523
- `IEmailService`, `IJobScheduler`), code against that component's ABSTRACTION /
524
- port as the pass declares — do NOT hand-roll it. Installing the component into
525
- the project (packages, provider wiring, config) is a SEPARATE step:
526
- /appweaver add-component <Component>. If the port is not present yet, code to
527
- it and note add-component as the prerequisite.
528
- - ADRs are DECIDED — follow them. The pass may propose a superseding one; a
529
- proposal is not yet ratified, so follow the pass and change nothing else.
530
- - Author a paired test for every acceptance criterion in the story and every
531
- behaviour the pass's "### Tests" section names, and cite the requirement id
532
- ({{exercises_csv}}) each test exists for. Include the constraint tests the
533
- pass names ({{constraints_csv}}) — e.g. an audit record is emitted, a
534
- restricted field is NOT exposed on the public surface.
535
- - Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
536
- the pass looks wrong or incomplete, STOP and surface it — it is fixed by
537
- re-running /appweaver story-plan, never compensated for here from the spec or
538
- from memory.
539
- - This feature OWNS its files; never regenerate a file another feature owns.
540
- {{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.
541
602
  outputs:
542
- - { name: files, from: "$.files" }
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" }
543
693
 
544
- - id: verify_generated
694
+ - id: verify_be_files
695
+ condition: "{{be_go}} == true"
545
696
  action: verify_context
546
- require_non_empty: files
697
+ require_non_empty: be_files
547
698
  on_empty: stop
548
- 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."
549
-
550
- # 8. OKF — MERGE, never replace. This run wrote ONE STORY's files; the feature's
551
- # other stories wrote the rest, and a replace would erase them the moment a second
552
- # story implements. --lane backend puts them in `source_files`, the service lane's
553
- # key, leaving `ui_source_files` for implement-ui.
554
- # Known limit: a file the slice DELETED stays in the list; concept-coverage reports
555
- # it as declared-but-missing drift rather than silently carrying it as real.
556
- - id: xref_concept
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"
557
709
  action: execute_script
710
+ on_error: continue
558
711
  script: { ref: xref }
559
712
  subcommand: concept-upsert
560
- # --ids + --slice record WHICH REQUIREMENT this run's files realise, as Requirement
561
- # Anchors rows in the concept. Both halves were already here and thrown away: the ids
562
- # the story exercises, and the files just generated. Only `adopt` writes a
563
- # source-map.md, so without this every id on a greenfield feature came back unanchored
564
- # at analyze-slice and the trace pass had to read the whole tree to find out what
565
- # implements what. Reconcile-on-write, keyed by slice + lane.
566
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "backend", "--origin", "greenfield", "--ids", "{{exercises_csv}}", "--slice", "{{story}}"]
713
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{be_files}}", "--merge", "--origin", "greenfield"]
567
714
  outputs:
568
- - { name: okf_concept, from: "$.concept" }
569
- - { name: okf_anchors, from: "$.anchors_written" }
570
-
571
- # 8a. Cross-slice edges are a FEATURE property, not a story's: which sibling
572
- # aggregates this one references is stated by the feature plan's domain model,
573
- # and no single slice can answer it. So this reads backend-plan.md — the one
574
- # derived fact it takes from the plan — and reconciles on write, so a dropped
575
- # dependency self-heals out of the used aggregate's Cross-References. Skipped
576
- # with no plan on disk: there is then nothing to derive edges from.
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.
806
+ outputs:
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" }
885
+
886
+ - id: verify_fe_files
887
+ condition: "{{fe_go}} == true"
888
+ action: verify_context
889
+ require_non_empty: fe_files
890
+ on_empty: stop
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"
895
+ action: execute_script
896
+ on_error: continue
897
+ script: { ref: xref }
898
+ subcommand: concept-upsert
899
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fe_files}}", "--merge", "--origin", "greenfield"]
900
+ outputs:
901
+ - { name: okf_concept, from: "$.concept" }
902
+
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}}" }
577
915
  - id: xref_record
578
- condition: "{{be_plan_exists}} == true"
916
+ condition: "{{record_edges}} == true"
579
917
  action: execute_script
580
918
  on_error: continue
581
919
  script: { ref: xref }
@@ -585,118 +923,72 @@ workflow:
585
923
  - { name: xref_wrote, from: "$.wrote" }
586
924
  - { name: xref_edge_count, from: "$.edge_count" }
587
925
 
588
- # 8b. The concept's Overview — the one part a deterministic script cannot produce.
589
- # It describes the AGGREGATE, which is the feature plan's subject: distilling it
590
- # from one story's slice would name whatever that slice happened to touch. The
591
- # model authors prose only; concept-overview splices it into `## Overview` alone,
592
- # so the Cross-References rows other slices own are structurally out of reach.
593
- # build_context, not a step-level `context:`: the engine REPLACES the envelope on
594
- # build_context and only ADDS to it on a step-level list, so naming the plan alone
595
- # there would have left generation's whole payload — this story's slice included —
596
- # 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
+
597
949
  - id: ctx_overview
598
- condition: "{{be_plan_exists}} == true"
950
+ condition: "{{overview_ok}} == true"
599
951
  action: build_context
600
952
  context:
601
- - { path: "{{be_plan}}" }
953
+ - { path: "{{domain_model}}", optional: true }
954
+ - { path: "{{backend_plan}}", optional: true }
602
955
  - id: okf_overview
603
- condition: "{{be_plan_exists}} == true"
956
+ condition: "{{overview_ok}} == true"
604
957
  action: agent_query
605
958
  include_standards: false
606
959
  schema: { overview: "<2-4 sentences of prose>" }
607
960
  mock: { overview: "" }
608
961
  task: |
609
- Write the OKF knowledge-concept Overview for the aggregate that the backend plan
610
- (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:
611
964
  - name the aggregate ROOT and what the aggregate IS;
612
- - state the INVARIANTS it guards;
613
- - state its TRANSACTION BOUNDARY — what it OWNS inside the boundary vs what it
614
- 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.
615
972
  Plain prose only: no headings, no bullets, no code fences, no file paths, and no
616
973
  restating of the dependency edges (those live in the Depends-On / Cross-References
617
- tables). Describe only what the plan states — invent nothing, and describe the
618
- whole aggregate rather than the one story that happens to be building now.
974
+ tables). Describe only what the document states — invent nothing.
619
975
  outputs:
620
976
  - { name: okf_overview, from: "$.overview" }
977
+
621
978
  - id: okf_overview_write
622
- condition: "{{be_plan_exists}} == true"
979
+ condition: "{{overview_ok}} == true"
623
980
  action: execute_script
981
+ on_error: continue
624
982
  script: { ref: xref }
625
983
  subcommand: concept-overview
626
984
  args: ["--feature-dir", "{{feature_dir}}", "--text", "{{okf_overview}}"]
627
985
 
628
- # ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
629
- # Generation is design-only by contract; this is the check that the design was not
630
- # the wrong design. READ-ONLY and REPORT-ONLY: it never edits code, never edits the
631
- # pass, and never stops the run. A gap here means one of two documents is wrong, and
632
- # which one is a human decision — the message names both routes.
633
- - id: ctx_accept
634
- action: build_context
635
- vars:
636
- story_id: "{{story}}"
637
- exercises_csv: "{{exercises_csv}}"
638
- constraints_csv: "{{constraints_csv}}"
639
- files_csv: "{{files}}"
640
- context:
641
- - { path: "{{story_path}}" } # the acceptance criteria
642
- - { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
643
- - { path: "{{design_doc}}" } # what was designed
644
- - id: accept_check
645
- action: agent_query
646
- tools: Read # the generated files arrive as paths, not inlined
647
- include_standards: false
648
- schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
649
- mock: { ok: "true", gaps: "" }
650
- task: |
651
- Story {{story_id}} was just implemented. Judge ONLY this: does the code that now
652
- exists satisfy the requirements the story delivers, AS THE SPEC WORDS THEM?
653
-
654
- In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold).
655
- Resolve each id's wording from spec.md in context — that wording, not the design's
656
- restatement of it, is what the code is measured against.
657
-
658
- Read the files this run wrote:
659
- {{files_csv}}
660
- plus anything they call that you need to follow. Do not modify a single file.
661
-
662
- Report a gap ONLY where the code fails to deliver an in-scope id as the spec words
663
- it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
664
- noise that hides the real ones:
665
- - a requirement no story has claimed yet, or one another story delivers;
666
- - style, naming, structure or test-coverage opinions — other gates own those;
667
- - anything the design pass deliberately deferred and named under its risks.
668
-
669
- One line per gap: the id, what the spec requires, and what the code does instead.
670
- Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
671
- about an in-scope id, that IS a gap — say which document you believe is wrong and
672
- why, because the two routes out of it are different commands.
673
- outputs:
674
- - { name: accept_ok, from: "$.ok" }
675
- - { name: accept_gaps, from: "$.gaps" }
676
- - id: accept_notice
677
- condition: "{{accept_ok}} != true"
678
- action: verify_context
679
- require_value: accept_ok
680
- equals: "true"
681
- on_mismatch: continue
682
- on_mismatch_message: |
683
- ACCEPTANCE — story {{story}} does not yet satisfy the spec wording of the ids it delivers:
684
-
685
- {{accept_gaps}}
686
-
687
- Nothing was changed to fix this: the code follows design pass {{design_pass}}, so a gap
688
- here means either the design was wrong or the spec has moved. Route by which it is —
689
- the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
690
- the spec is wrong: /appweaver sync-spec (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md), then sync-stories, then story-plan.
691
- Run /appweaver build either way — a design that is right and code that does not compile
692
- is a third thing, and build is where that one surfaces.
693
-
694
- # 9. Post-execution hooks.
986
+ # 13. Post-execution hooks — the same phase set as the pre-hooks above.
695
987
  - id: resolve_post_hooks
696
988
  action: execute_script
697
989
  script: { ref: actions }
698
990
  subcommand: resolve-hooks
699
- args: ["--phase", "after_implement"]
991
+ args: ["--phase", "after_implement", "--phase", "{{post_ui_phase}}", "--phase", "{{post_cr_phase}}"]
700
992
  outputs:
701
993
  - { name: after_hooks, from: "$.hooks" }
702
994
  - id: dispatch_post_hooks
@@ -704,12 +996,19 @@ workflow:
704
996
  hooks_from_variable: after_hooks
705
997
 
706
998
  outputs:
707
- story: "{{story}}"
708
- built_from: "{{design_doc}} — design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
709
- requirements: "{{exercises_csv}}"
710
- files: "{{files}}"
711
- lane: "{{lane_id}} → {{lane_source_path}}"
712
- okf_concept: "{{okf_concept}} (this run's files MERGED into source_files; {{xref_edge_count}} cross-slice edge(s) recorded into sibling aggregates)"
713
- acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
714
- stale_ids: "{{stale_csv}}"
715
- 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: /appweaver close-story — which would ratify its ADRs, flip its interface entries live and archive the passes — is ⌛ NOT BUILT YET, so there is no transition out of in-flight and no last gate before merge."
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."