aw-spec-cli 0.1.14 → 0.1.16

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 (32) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +1 -1
  2. package/assets/.appweaver/orchestrator/modules/spec-state-gate.orch.yaml +87 -0
  3. package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
  4. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
  5. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +672 -124
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +700 -72
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +531 -135
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +573 -145
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +600 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +721 -0
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +119 -32
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +1147 -0
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1121 -0
  18. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2564 -38
  19. package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
  20. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +2458 -22
  21. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
  22. package/assets/.appweaver/scripts/python/aw-payload-parity.py +158 -0
  23. package/assets/.appweaver/scripts/python/aw-xref.py +431 -48
  24. package/assets/.appweaver/templates/design-template.md +88 -54
  25. package/assets/.appweaver/templates/spec-template.md +12 -10
  26. package/assets/.appweaver/templates/story-template.md +83 -12
  27. package/assets/.appweaver/templates/ui-design-template.md +71 -39
  28. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
  29. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
  30. package/package.json +1 -1
  31. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
  32. package/assets/.appweaver/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
@@ -1,52 +1,58 @@
1
1
  # =============================================================================
2
- # aw-spec-implement-ui (orchestrator) — model-driven UI generation from design.ui.md
2
+ # aw-spec-implement-ui (orchestrator) — UI generation from ONE STORY'S DESIGN
3
3
  #
4
- # The frontend lane's Implement phase: GENERATE the screens, components, client state
5
- # and paired tests for the aggregate STRICTLY from its design.ui.md — the mediator
6
- # between the spec and the lane's code. It is the twin of `implement`, and it keeps the
7
- # same contract, one lane over:
4
+ # The frontend lane's executable end of the story loop: GENERATE the screens,
5
+ # components, client state and paired tests for ONE STORY, strictly from the LATEST
6
+ # design pass in that story's own UI design —
7
+ # specs/<F>/stories/<ID>/story-frontend-plan.md. It is the twin of `implement`, one
8
+ # lane over, and keeps the same contract:
8
9
  #
9
- # • DESIGN-ONLY. implement-ui does NOT read spec.md. The design is the complete,
10
- # authoritative source for WHAT to build; anything missing from it is fixed in
11
- # design-ui, never compensated for here from the spec or from memory.
12
- # • THE FRAMEWORK BINDS HERE. design.ui.md is technology-agnostic — a "component" is
13
- # a responsibility, its inputs are field shapes, its states are named. The actual
14
- # framework, router, state library, component library and styling approach come
15
- # from constitution.ui.md + the lane declaration + this command's rule set.
16
- # • ONE SUBTREE. The lane owns the source path .appweaver/stacks.json declares for it,
17
- # and nothing outside it. A file written elsewhere belongs to the service lane or
18
- # to no lane at all.
10
+ # • ONE SLICE, NOT THE FEATURE. The pass names the files it changes; this phase edits
11
+ # an existing tree, creating only what the pass says is new, and MERGES its files
12
+ # into the feature's OKF concept rather than replacing the list.
13
+ # • DESIGN-ONLY. spec.md is not a generation input. It is read once, AFTER the code
14
+ # exists, by a read-only acceptance check that reports and never repairs.
15
+ # • THE FRAMEWORK BINDS HERE. The design is technology-agnostic — a "component" is a
16
+ # responsibility, its inputs are field shapes, its states are named. The framework,
17
+ # router, state library, component library and styling approach come from
18
+ # constitution.ui.md + the lane declaration + this command's rule set.
19
+ # • ONE SUBTREE. The lane owns the source path .appweaver/stacks.json declares for it
20
+ # and nothing outside it. A file written elsewhere belongs to the service lane.
21
+ #
22
+ # THE UI LANE CONSUMES THE CONTRACT; IT NEVER DEFINES ONE. interfaces.md is the
23
+ # published surface and the pass binds to it by IF- id. A contract this slice needs that
24
+ # the log does not carry is a DEFECT to surface — never an endpoint to invent, and never
25
+ # a reason to reach into the service lane's code. Where the SAME story has a service
26
+ # design it is in context too, as the newer word on what that surface will be.
19
27
  #
20
28
  # WHY A SEPARATE COMMAND, NOT A UI BRANCH INSIDE `implement`. The two lanes take their
21
29
  # code-generation rules from different map headings and write into disjoint subtrees.
22
30
  # Keying the rule maps on `## /aw-spec-implement-ui` gives the lane its own rule set for
23
31
  # zero mechanism change; a UI branch inside `implement` would generate frontend code
24
- # under the service lane's standards.
25
- #
26
- # Prereqs: /appweaver design-ui (the UI design), and a frontend project scaffolded under
27
- # the lane's own source path — implement-ui GENERATES into that tree, so with no project
28
- # there is nowhere to place the files. The project gate is deliberately LANE-SCOPED: a
29
- # manifest found only elsewhere in the repository is the service lane's, and accepting
30
- # it is how a frontend that was never built reports green.
32
+ # under the service lane's standards. (`story-plan` designs BOTH lanes in one run
33
+ # because everything ahead of its authoring step is shared; here nothing is — the
34
+ # lane, the subtree, the rules and the OKF key all differ.)
31
35
  #
32
- # ⌛ /appweaver analyze-design-ui — the spec↔design.ui.md conformance gate that would
33
- # run between design-ui and this phase — is not built yet. Until it is, the two-way
34
- # check that no UX-/US- fell between the two designs is design.ui.md §7 read by hand.
36
+ # GATES (all loud, all before any write): the frontend lane declared with its source
37
+ # path; a project scaffolded under THAT path specifically — a manifest found only
38
+ # elsewhere is the service lane's, and accepting it is how a frontend that was never
39
+ # built reports green; an active feature; an OPEN story; and that story having a UI
40
+ # design at all. A design behind the current spec, or a story the spec has moved past,
41
+ # is ANNOUNCED, never refused; the route out of both is named.
35
42
  #
36
- # Next: /appweaver build (compiles every declared lane, this one included).
37
- #
38
- # Launch: /appweaver implement-ui [strategy] [guidance]
43
+ # Launch: /appweaver implement-ui [<story-id>] [guidance] (asks when ambiguous)
44
+ # Prereq: /appweaver story-plan (the story's UI design) · Next: /appweaver build
39
45
  # =============================================================================
40
46
 
41
47
  name: aw-spec-implement-ui
42
- description: Generate the frontend lane's screens, components, client state and paired tests STRICTLY from the aggregate's design.ui.md, bound to the declared UI stack and written only under that lane's source path. Model-driven and design-only — it does not read the spec, the same contract implement keeps for the service lane. The framework, router, state library and styling approach bind here from constitution.ui.md and the lane declaration; the design stays technology-agnostic. Merges the generated files into the feature's existing OKF concept rather than replacing the service lane's.
43
- version: 1.0.0
48
+ description: "Generate the frontend lane's screens, components, client state and paired tests for ONE STORY, strictly from the LATEST design pass in stories/<ID>/story-frontend-plan.md, bound to the declared UI 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. Consumes interfaces.md by IF- id and never publishes into it. Incremental by contract: it edits the lane's existing tree and MERGES its files into the feature's OKF concept under ui_source_files, so the service lane's list is untouched."
49
+ version: 2.0.0
44
50
  agent_agnostic: true
45
51
  dialect: aw-spec.orchestrator/v1
46
52
 
47
53
  inputs:
48
54
  user_input:
49
- description: Optional implementation guidance (focus area, screen subset, framework hints).
55
+ description: Optional story id and/or implementation guidance (e.g. "Order-001 reuse the shared date picker").
50
56
  required: false
51
57
  bind: USER_INPUT
52
58
 
@@ -60,14 +66,73 @@ scripts:
60
66
 
61
67
  # Sticky standards seed = the UNIVERSAL always-on rule only (ai-rules). The lane's
62
68
  # FRONTEND code-generation rule files are CONFIGURATION, loaded per-command from
63
- # guardrails/projects/<project>/tech-rule-bindings.md under `## /aw-spec-implement-ui` by
64
- # the tech-select module. Per the rule-partition this phase loads NO domain rule file —
65
- # the domain reached the lane through spec → design.ui.md. The lane's ratified base
66
- # arrives via constitution.ui.md (referred to directly, as-is — NOT mapped).
67
- # (missing files skipped at assembly time)
69
+ # guardrails/projects/<project>/tech-rule-bindings.md under `## /aw-spec-implement-ui`
70
+ # by the tech-select module. Per the rule-partition this phase loads NO domain rule
71
+ # file — the domain reached the lane through spec → frontend-plan.md → the story's
72
+ # design pass. The lane's ratified base arrives via constitution.ui.md (referred to
73
+ # directly, as-is — NOT mapped).
68
74
  standards:
69
75
  - { path: .appweaver/ai-rules.md } # kit-resident: the universal operating rules, every flow
70
76
 
77
+ vars:
78
+ feature_dir: ""
79
+ spec_file: ""
80
+ # story resolution
81
+ any_open: "false"
82
+ open_ids: "[]"
83
+ digest: ""
84
+ resolved: "false"
85
+ needs_pick: "false"
86
+ pick_reason: ""
87
+ picked_story: ""
88
+ story: ""
89
+ story_path: ""
90
+ story_open: "false"
91
+ status_msg: ""
92
+ story_title: ""
93
+ exercises_csv: ""
94
+ constraints_csv: ""
95
+ current_version: ""
96
+ stale_csv: ""
97
+ stale_note: ""
98
+ story_dir: ""
99
+ adr_path: ""
100
+ if_path: ""
101
+ # the story's UI design (+ the SERVICE design of the same story, for the contract)
102
+ design_doc: ""
103
+ design_exists: "false"
104
+ design_pass: "0"
105
+ design_spec_version: ""
106
+ design_stale: "false"
107
+ # WHICH LANES story-plan designed and which it dropped. Absent on a story designed before
108
+ # the keys existed, which is why the default message below keeps both readings.
109
+ designed_lanes: ""
110
+ dropped_lanes: ""
111
+ lane_keys_present: "false"
112
+ fe_lane_dropped: "false"
113
+ no_design_msg: ""
114
+ # the analyze-design verdict — law 40's evidence. certified defaults FALSE so a probe that
115
+ # could not run fails CLOSED, and the message default carries a route for the same reason.
116
+ pass_plan_hash: ""
117
+ gate_status: ""
118
+ gate_certified: "false"
119
+ gate_aborted: "false"
120
+ gate_gap_refs: ""
121
+ gate_report: ""
122
+ gate_lane_sha: ""
123
+ 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."
124
+ service_design_doc: ""
125
+ # the feature's current code, per lane (OKF)
126
+ okf_concept: ""
127
+ coverage_digest: ""
128
+ fe_source_csv: ""
129
+ fe_source_count: "0"
130
+ be_source_count: "0"
131
+ # generation + enrichment
132
+ files: ""
133
+ accept_ok: "true"
134
+ accept_gaps: ""
135
+
71
136
  workflow:
72
137
 
73
138
  # 0. FRONTEND code-generation rule files → sticky standards (configuration, not
@@ -78,45 +143,242 @@ workflow:
78
143
  module: .appweaver/orchestrator/modules/tech-select.orch.yaml
79
144
  with: { command: aw-spec-implement-ui }
80
145
 
81
- # 1. Resolve the current feature + its UI design; require the DESIGN (this phase's
82
- # only input). No spec — the design is the authoritative source for WHAT to build.
146
+ # 1. The active feature. spec.md is resolved here for the acceptance check ONLY —
147
+ # it is not a generation input.
83
148
  - id: feature_paths
84
149
  action: execute_script
85
150
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
86
151
  script: { ref: actions }
87
152
  subcommand: feature-paths
88
153
  outputs:
89
- - { name: feature_dir, from: "$.feature_dir" }
90
- - { name: ui_design_doc, from: "$.ui_design_file" }
91
- - { name: ui_design_exists, from: "$.ui_design_exists" }
92
-
154
+ - { name: feature_dir, from: "$.feature_dir" }
155
+ - { name: spec_file, from: "$.spec_file" }
93
156
  - id: require_feature
94
157
  action: verify_context
95
158
  require_non_empty: feature_dir
96
159
  on_empty: stop
97
160
  on_empty_message: "No active feature. Run /appweaver select-feature first (pick or create the aggregate, e.g. Order), then re-run implement-ui."
98
161
 
99
- - id: verify_ui_design
162
+ # 1b. THE LEDGER FENCE — the same one implement carries, for the same two readings:
163
+ # the staleness computation below and the acceptance pass at the end.
164
+ - id: spec_state_guard
165
+ action: include
166
+ module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
167
+ with: { command: "/appweaver implement-ui" }
168
+
169
+ # 2. WHICH STORY — the same resolution story-plan and implement use.
170
+ - id: story_scan
171
+ action: execute_script
172
+ script: { ref: actions }
173
+ subcommand: story-plan-check
174
+ args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
175
+ outputs:
176
+ - { name: any_open, from: "$.any_open" }
177
+ - { name: open_ids, from: "$.open_ids" }
178
+ - { name: digest, from: "$.digest" }
179
+ - { name: resolved, from: "$.resolved" }
180
+ - { name: needs_pick, from: "$.needs_pick" }
181
+ - { name: pick_reason, from: "$.pick_reason" }
182
+ - { name: story, from: "$.story" }
183
+ - { name: story_path, from: "$.story_path" }
184
+ - { name: story_open, from: "$.story_open" }
185
+ - { name: status_msg, from: "$.status_msg" }
186
+ - { name: story_title, from: "$.story_title" }
187
+ - { name: exercises_csv, from: "$.exercises_csv" }
188
+ - { name: constraints_csv, from: "$.constraints_csv" }
189
+ - { name: current_version, from: "$.current_version" }
190
+ - { name: stale_csv, from: "$.stale_csv" }
191
+ - { name: stale_note, from: "$.stale_note" }
192
+ - { name: story_dir, from: "$.story_dir" }
193
+ - { name: adr_path, from: "$.adr_path" }
194
+ - { name: if_path, from: "$.if_path" }
195
+ - { name: design_doc, from: "$.fe_doc_path" }
196
+ - { name: design_exists, from: "$.fe_doc_exists" }
197
+ - { name: design_pass, from: "$.fe_last_pass" }
198
+ - { name: design_spec_version, from: "$.fe_doc_spec_version" }
199
+ - { name: design_stale, from: "$.fe_doc_stale" }
200
+ - { name: pass_plan_hash, from: "$.fe_pass_plan_hash" }
201
+ - { name: designed_lanes, from: "$.designed_lanes_csv" }
202
+ - { name: dropped_lanes, from: "$.dropped_lanes_csv" }
203
+ - { name: lane_keys_present, from: "$.lane_keys_present" }
204
+ - { name: fe_lane_dropped, from: "$.fe_lane_dropped" }
205
+ - { name: service_design_doc, from: "$.be_doc_path" }
206
+ - id: require_open_backlog
100
207
  action: verify_context
101
- require_true: ui_design_exists
208
+ require_true: any_open
102
209
  on_false: stop
103
- on_false_message: "No design.ui.md for the current feature. Run /appweaver design-ui first — implement-ui generates STRICTLY from the UI design, and there is nothing else it may read. Review its §7 Traceability before generating: ⌛ /appweaver analyze-design-ui, the spec↔design.ui.md gate, is not built yet."
210
+ 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)."
211
+ - id: ask_story
212
+ condition: "{{needs_pick}} == true"
213
+ action: ask
214
+ prompt: |
215
+ Which story is this UI implementation for? ({{pick_reason}})
104
216
 
105
- # 1a. Require the FRONTEND LANE and its source path. This phase writes code, and the
106
- # lane's root is the only subtree it may write into — so this runs before the
107
- # project gate, which searches that subtree.
217
+ {{digest}}
218
+ options_from: "{{open_ids}}"
219
+ answer_key: picked_story
220
+ answer_var: picked_story
221
+ - id: story_pick_check
222
+ condition: "{{needs_pick}} == true"
223
+ action: execute_script
224
+ script: { ref: actions }
225
+ subcommand: story-plan-check
226
+ args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
227
+ outputs:
228
+ - { name: resolved, from: "$.resolved" }
229
+ - { name: story, from: "$.story" }
230
+ - { name: story_path, from: "$.story_path" }
231
+ - { name: story_open, from: "$.story_open" }
232
+ - { name: status_msg, from: "$.status_msg" }
233
+ - { name: story_title, from: "$.story_title" }
234
+ - { name: exercises_csv, from: "$.exercises_csv" }
235
+ - { name: constraints_csv, from: "$.constraints_csv" }
236
+ - { name: stale_csv, from: "$.stale_csv" }
237
+ - { name: stale_note, from: "$.stale_note" }
238
+ - { name: story_dir, from: "$.story_dir" }
239
+ - { name: design_doc, from: "$.fe_doc_path" }
240
+ - { name: design_exists, from: "$.fe_doc_exists" }
241
+ - { name: design_pass, from: "$.fe_last_pass" }
242
+ - { name: design_spec_version, from: "$.fe_doc_spec_version" }
243
+ - { name: design_stale, from: "$.fe_doc_stale" }
244
+ - { name: pass_plan_hash, from: "$.fe_pass_plan_hash" }
245
+ - { name: designed_lanes, from: "$.designed_lanes_csv" }
246
+ - { name: dropped_lanes, from: "$.dropped_lanes_csv" }
247
+ - { name: lane_keys_present, from: "$.lane_keys_present" }
248
+ - { name: fe_lane_dropped, from: "$.fe_lane_dropped" }
249
+ - { name: service_design_doc, from: "$.be_doc_path" }
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-ui 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}}"
263
+
264
+ # 3. THE STORY MUST HAVE A UI DESIGN. A story with a service design and no UI one is
265
+ # not a failure — it is a slice with no user-facing surface, and the message says
266
+ # so rather than implying something is broken.
267
+ # WHICH OF THE TWO READINGS IT IS, where the story says. analyze-design STOPS on a lane
268
+ # whose plan is behind; story-plan DROPS it and designs the other — so one command later
269
+ # "this slice has no user-facing surface" and "the UI lane was out of scope when this was
270
+ # designed" are the same absence on disk, and the message below could only offer both.
271
+ # story-plan now stamps `designed_lanes:` and `dropped_lanes:`, so the answer is recorded.
272
+ # ORDERED SETS, most specific last. The default is the two-reading message, which stays
273
+ # exactly right for a story designed BEFORE those keys existed: it has neither, and a
274
+ # missing key must never be read as "no user-facing surface".
275
+ - id: no_design_msg_default
276
+ action: set
277
+ vars:
278
+ no_design_msg: "Story {{story}} has no UI design — {{story_dir}}/story-frontend-plan.md does not exist, and the story records no designed/dropped lanes (it was designed before the kit stamped them). Either this slice has no user-facing surface (nothing to do here; run /appweaver implement for the service half), or the UI lane was out of scope when it was designed — a frontend lane that is undeclared, or a frontend-plan.md that is missing or behind the spec, drops that lane from a story-plan run and says so. Fix whichever it was and re-run /appweaver story-plan {{story}}."
279
+ - id: no_design_msg_no_surface
280
+ condition: "{{lane_keys_present}} == true"
281
+ action: set
282
+ vars:
283
+ no_design_msg: "Story {{story}} has no UI design and its UI lane was NOT dropped — the story records designed lanes [{{designed_lanes}}] and dropped lanes [{{dropped_lanes}}], so this slice genuinely has no user-facing surface. There is nothing for implement-ui to do here; run /appweaver implement for the service half. If it SHOULD have a UI surface, declare the frontend lane and re-run /appweaver story-plan {{story}}."
284
+ - id: no_design_msg_dropped
285
+ condition: "{{fe_lane_dropped}} == true"
286
+ action: set
287
+ vars:
288
+ no_design_msg: "Story {{story}} has no UI design because the UI LANE WAS DROPPED when it was designed — the story records dropped lanes [{{dropped_lanes}}]. This is not a slice without a user-facing surface; it is one whose UI half was never designed, because the frontend lane was undeclared or frontend-plan.md was missing or behind the spec. Fix that lane (/appweaver plan-ui, or declare it with /appweaver select-stack), then re-run /appweaver story-plan {{story}} and this command."
289
+
290
+ - id: require_design
291
+ action: verify_context
292
+ require_true: design_exists
293
+ on_false: stop
294
+ on_false_message: "{{no_design_msg}}"
295
+
296
+ # 3a. Announced, never refused — the same contract implement keeps. design_stale is a
297
+ # JSON boolean, so `condition:` does the deciding and the value gate is only the
298
+ # vehicle that prints (law 43); stale_csv is a string and compares directly.
299
+ - id: design_stale_notice
300
+ condition: "{{design_stale}} == true"
301
+ action: verify_context
302
+ require_value: design_stale
303
+ equals: "false"
304
+ on_mismatch: continue
305
+ on_mismatch_message: "UI 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."
306
+ - id: story_stale_notice
307
+ action: verify_context
308
+ require_value: stale_csv
309
+ equals: ""
310
+ on_mismatch: continue
311
+ 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."
312
+
313
+ # ═══════ 3a-bis. LAW 40, THE LAST HOP — the plan this pass was designed against ═══════
314
+ # THREE HASHES, not two. story-plan proved the feature plan was certified in the shape it
315
+ # was in when this pass was designed, and story-plan-doc stamped that certified hash into
316
+ # the pass header. But a pass is written once and read forever: comparing only the CURRENT
317
+ # plan to the CURRENT verdict would let a pass designed against plan H1 still generate
318
+ # after the plan was edited to H2 and re-gated — which is precisely the case law 40 names,
319
+ # one level below where it was being checked.
320
+ # pass hash what this pass was designed against (the story pass header)
321
+ # verdict hash what analyze-design last certified (the gate verdict)
322
+ # A MISMATCH IS A STOP, at the same severity as never-gated: continuing ships code built
323
+ # to an architecture nobody approved. Per lane, and only for the lane this command builds.
324
+ - id: gate_verdict
325
+ action: execute_script
326
+ on_error: continue
327
+ script: { ref: actions }
328
+ subcommand: gate-read
329
+ args: ["--feature-dir", "{{feature_dir}}", "--gate", "analyze-design"]
330
+ outputs:
331
+ - { name: gate_status, from: "$.status" }
332
+ - { name: gate_certified, from: "$.certified" }
333
+ - { name: gate_aborted, from: "$.aborted" }
334
+ - { name: gate_gap_refs, from: "$.gap_refs_csv" }
335
+ - { name: gate_report, from: "$.report" }
336
+ - { name: gate_lane_sha, from: "$.frontend_sha256" }
337
+
338
+ # Three reasons, three routes; ordered sets, last match wins. Aborted is last because it
339
+ # also carries status "fail" — the red gate_result_open writes BEFORE any analysis — and
340
+ # sending someone to read gaps that were never found is the one wrong answer here.
341
+ - id: gate_msg_never
342
+ action: set
343
+ 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-ui." }
344
+ - id: gate_msg_red
345
+ condition: "{{gate_status}} == fail"
346
+ action: set
347
+ 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-ui." }
348
+ - id: gate_msg_aborted
349
+ condition: "{{gate_aborted}} == true"
350
+ action: set
351
+ 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-ui." }
352
+
353
+ - id: gate_certified_guard
354
+ action: verify_context
355
+ require_true: gate_certified
356
+ on_false: stop
357
+ on_false_message: "{{gate_msg}}"
358
+
359
+ # An EMPTY pass hash fails this, and that is correct: a pass written before the hash was
360
+ # stamped cannot say which plan shape it was designed against, so nothing can prove it was
361
+ # designed against the approved one. The route is the same either way — re-design.
362
+ - id: pass_plan_shape_guard
363
+ action: verify_context
364
+ require_value: pass_plan_hash
365
+ equals: "{{gate_lane_sha}}"
366
+ on_mismatch: stop
367
+ on_mismatch_message: "Design pass {{design_pass}} for story {{story}} was designed against a frontend 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-ui."
368
+
369
+ # 4. THE FRONTEND LANE and its source path — the only subtree this phase may write
370
+ # into, resolved before the project gate that searches it.
108
371
  - id: lane_gate
109
372
  action: include
110
373
  module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
111
374
  with: { require: frontend, command: "/appweaver implement-ui" }
112
375
 
113
- # 1b. Require a frontend PROJECT under the LANE'S OWN path. Deterministic structure
114
- # gate: detect-project searches the lane subtree for an ecosystem manifest and
115
- # falls back to the whole repository when it finds none, reporting which scope
116
- # answered. THAT DISTINCTION IS THE GATE HERE. A repo-scope hit in a two-lane
117
- # project is the service lane's manifest, and accepting it is exactly the failure
118
- # the lane model exists to prevent — a frontend that was never scaffolded,
119
- # generated into anyway, reporting green.
376
+ # 5. Require a frontend PROJECT under the LANE'S OWN path. detect-project searches the
377
+ # lane subtree first and falls back to the whole repository, reporting which scope
378
+ # answered. THAT DISTINCTION IS THE GATE HERE. A repo-scope hit in a two-lane
379
+ # project is the service lane's manifest, and accepting it is exactly the failure
380
+ # the lane model exists to prevent — a frontend that was never scaffolded,
381
+ # generated into anyway, reporting green.
120
382
  - id: detect_ui_project
121
383
  action: execute_script
122
384
  script: { ref: actions }
@@ -126,15 +388,31 @@ workflow:
126
388
  - { name: ui_project_present, from: "$.scaffolded" }
127
389
  - { name: project_markers, from: "$.markers" }
128
390
  - { name: project_scope, from: "$.scope" }
129
-
130
391
  - id: ui_project_gate
131
392
  action: verify_context
132
393
  require_value: project_scope
133
394
  equals: "lane"
134
395
  on_mismatch: stop
135
- on_mismatch_message: "No frontend project found under the {{lane_id}} lane's source path `{{lane_source_path}}`. implement-ui GENERATES this feature's UI into an existing project tree, so one has to be scaffolded there first: run /appweaver foundation <FoundationName> <AppName> where a foundation bundle ships for {{lane_profile}}, or create the project with the stack's own tooling under that path. Any ecosystem manifest inside the lane's path marks it — package.json, go.mod, pyproject.toml, Cargo.toml, pom.xml, build.gradle(.kts) or .sln/.csproj. 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."
396
+ on_mismatch_message: "No frontend project found under the {{lane_id}} lane's source path `{{lane_source_path}}`. implement-ui GENERATES this story's UI into an existing project tree, so one has to be scaffolded there 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 inside the lane's path marks it — package.json, go.mod, pyproject.toml, Cargo.toml, pom.xml, build.gradle(.kts) or .sln/.csproj. 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."
136
397
 
137
- # 2. Pre-execution hooks.
398
+ # 6. WHAT ALREADY EXISTS, per lane, from the feature's OKF concept. The UI list is
399
+ # what this run edits; the service count tells the pass whether the contract it
400
+ # binds to has been built yet. Advisory — a first UI story generates into a clean
401
+ # tree and a missing bundle must never block generation.
402
+ - id: coverage
403
+ action: execute_script
404
+ on_error: continue
405
+ script: { ref: xref }
406
+ subcommand: concept-coverage
407
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
408
+ outputs:
409
+ - { name: okf_concept, from: "$.concept" }
410
+ - { name: coverage_digest, from: "$.digest" }
411
+ - { name: fe_source_csv, from: "$.frontend_csv" }
412
+ - { name: fe_source_count, from: "$.frontend_count" }
413
+ - { name: be_source_count, from: "$.backend_count" }
414
+
415
+ # 7. Pre-execution hooks.
138
416
  - id: resolve_pre_hooks
139
417
  action: execute_script
140
418
  script: { ref: actions }
@@ -146,81 +424,122 @@ workflow:
146
424
  action: dispatch_hooks
147
425
  hooks_from_variable: before_hooks
148
426
 
149
- # 3. Model-driven generation: design.ui.md → screens, components, state + tests.
150
- # The design constrains WHAT to build; constitution.ui.md + the lane fix HOW.
151
- # The spec is deliberately NOT in context — the design is the single source here.
427
+ # ═══════════════════ GENERATION — one story's UI slice, one pass ═════════════
428
+ # The pass constrains WHAT to build; constitution.ui.md, the lane and the loaded
429
+ # standards fix HOW. spec.md is deliberately absent — the acceptance check below is
430
+ # where it gets its say.
152
431
  - id: ctx_generate
153
432
  action: build_context
433
+ vars:
434
+ story_id: "{{story}}"
435
+ story_title: "{{story_title}}"
436
+ exercises_csv: "{{exercises_csv}}"
437
+ constraints_csv: "{{constraints_csv}}"
438
+ pass_number: "{{design_pass}}"
439
+ coverage_digest: "{{coverage_digest}}"
440
+ ui_source_files_csv: "{{fe_source_csv}}"
441
+ ui_source_file_count: "{{fe_source_count}}"
442
+ stale_note: "{{stale_note}}"
154
443
  context:
155
- - { path: "{{ui_design_doc}}" }
444
+ - { path: "{{design_doc}}" } # the story's UI design — the LATEST pass is the input
445
+ - { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
446
+ - { path: "{{if_path}}", optional: true } # the PUBLISHED surface this slice binds to, by IF- id
447
+ - { path: "{{service_design_doc}}", optional: true } # the SAME story's service design — the newer word on that surface
448
+ - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
156
449
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — conventions, forbidden patterns, gates
157
450
  - id: generate
158
451
  action: agent_generate
159
- tools: "Read,Write,Edit"
452
+ tools: "Read,Write,Edit" # the existing UI code arrives as {{ui_source_files_csv}} paths, not inlined
160
453
  permission_mode: acceptEdits
161
454
  task: |
162
- Generate the production UI code AND paired tests for this feature STRICTLY from
163
- its design.ui.md (in context). The design is the complete, authoritative source of
164
- WHAT to build — do NOT consult or infer from the spec. This phase is the HOME of
165
- the framework-specific, lane-wide code-generation rules. Guidance: {{USER_INPUT}}
455
+ Implement story {{story_id}} — {{story_title}} — in the frontend lane, from
456
+ "## Design pass {{pass_number}}" of the UI story design in context.
457
+ Guidance: {{USER_INPUT}}
458
+
459
+ THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
460
+ document; every earlier pass is superseded history, and any of them may carry
461
+ hand edits that were deliberate. Read the last one and build exactly it. Do NOT
462
+ consult or infer from spec.md — it is not in context on purpose.
463
+
464
+ THIS IS ONE SLICE OF AN EXISTING FRONTEND, NOT THE WHOLE FEATURE.
465
+ - The pass's "### Screens & components" section names, file by file, what to
466
+ create and what to modify. Create ONLY the files it names as new; modify the
467
+ named existing files IN PLACE. Every other file belongs to work already done —
468
+ do not regenerate, reformat or "tidy" it.
469
+ - The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}} — the
470
+ complete set of bounds the spec declares for this aggregate, stamped into the
471
+ story's frontmatter by the kit rather than transcribed by anyone. ALL of them
472
+ apply here: the UX- are yours to realise, and the CN-/DC-/IC- still bind what this
473
+ surface may show, send and store. Anything outside those ids is another story's
474
+ work, even if you can see it is missing.
475
+ - The code that already exists for this feature, per lane, is below. The UI files
476
+ ({{ui_source_file_count}} on disk) are yours to read before you edit; the
477
+ service list is there so you can see what the contract is actually backed by.
478
+
479
+ {{coverage_digest}}
480
+
481
+ {{ui_source_files_csv}}
482
+
483
+ BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
484
+ {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
485
+ built with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
486
+ framework and no other, using its idioms, its project layout and its test
487
+ framework. Take the router, state approach, component library, design system and
488
+ styling from the UI constitution + the loaded standards; the design is
489
+ technology-agnostic, so do not re-decide anything already fixed there. If the
490
+ loaded standards describe a DIFFERENT stack than the one above, the lane wins on
491
+ framework and layout — follow the standards' INTENT and STOP to surface the
492
+ mismatch in your summary.
493
+
494
+ WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
495
+ declares. NEVER touch the service lane's code, not even to add a field the UI
496
+ wants: a gap in the service surface is a contract delta raised back to that lane,
497
+ not a file you edit. Do not create a parallel source tree because the layout
498
+ inside this one is unfamiliar; if the pass needs a place this subtree has no home
499
+ for, STOP and surface it.
500
+
166
501
  Rules:
167
- - THE DESIGN IS THE ONLY DOMAIN INPUT. You have design.ui.md + the lane's
168
- frontend standards, and NO domain rule files. Introduce NO domain rule,
169
- identifier, terminology or behaviour the design does not state — everything
170
- domain already passed through spec → design.ui.md. If a domain specific seems
171
- missing, STOP and surface it; never invent it from memory.
172
- - BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
173
- {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
174
- built with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
175
- framework and no other, using its idioms, its project layout and its test
176
- framework. Take the router, state approach, component library, design system and
177
- styling from the UI constitution + the loaded standards; the design is
178
- technology-agnostic, so do not re-decide anything already fixed there. If the
179
- loaded standards describe a DIFFERENT stack than the one above, the lane wins on
180
- framework and layout — follow the standards' INTENT and STOP to surface the
181
- mismatch in your summary.
182
- - WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
183
- declares. That subtree is this lane's; a file outside it belongs to the service
184
- lane or to no lane. NEVER touch the service lane's code, not even to add a field
185
- the UI wants: a gap in the service surface is a contract delta raised back to
186
- that lane. Do not create a parallel source tree because the layout inside this
187
- one is unfamiliar; if the design needs a place this subtree has no home for,
188
- STOP and surface it.
502
+ - THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and the lane's
503
+ frontend standards — and NO domain rule files. Introduce NO domain rule,
504
+ identifier, terminology or behaviour the pass does not state. If a domain
505
+ specific seems missing, STOP and surface it; never invent it from memory.
189
506
  - Follow the UI constitution's folder structure, naming conventions and forbidden
190
507
  patterns, plus the loaded standards, for HOW to structure, name and place files
191
- WITHIN that subtree — the design says WHAT to build; these say HOW.
192
- - REALISE EVERY DESIGN ELEMENT, changing nothing about WHAT it specifies:
193
- • §1 — one route/screen per row, with the navigation shape and the access
194
- conditions the row states.
195
- • §2 — the client state exactly as owned and scoped there, including its
196
- invalidation triggers. Anything the design lists under "what must NOT be held"
197
- NEVER reaches client state, storage, the URL or telemetry.
198
- • §3 — one component per row, with the responsibility, the input field shapes
199
- and the events the design gives it. Compose the shared components the design
200
- says it reuses; do not re-implement them.
201
- • §4 — EVERY state in EVERY interaction row: loading, empty, error,
202
- permission-denied and in-flight/disabled, plus the validation timing, the focus
203
- and keyboard behaviour and the concurrency handling stated there. These are
204
- requirements, not polish, and they are the first thing a generator drops.
205
- • §5 — the service binding, through the lane's own client layer, with the write
206
- semantics and rollback each row states.
207
- • §6 — the cross-cutting decisions as code: what is hidden vs disabled,
208
- accessibility obligations, localization, performance and offline behaviour.
508
+ WITHIN that subtree — the pass says WHAT to build; these say HOW.
509
+ - REALISE THE PASS EXACTLY, changing nothing about WHAT it specifies:
510
+ • its "### Approach" — the routes and screens it touches, and where this
511
+ slice's state lives. Anything the design says must NOT be held never reaches
512
+ client state, storage, the URL or telemetry.
513
+ • its "### Screens & components" — each component with the responsibility, the
514
+ input shapes and the events the pass gives it. Compose the shared components
515
+ it says it reuses; do not re-implement them.
516
+ • its "### Contract binding" — call each interface BY ITS IF- id as the pass
517
+ names it, with the request it states, and render EVERY state it lists:
518
+ in-flight, empty, error and the failure behaviour. These are requirements,
519
+ not polish, and they are the first thing a generator drops.
520
+ - THE CONTRACT IS NOT YOURS TO INVENT. interfaces.md (in context) is the
521
+ published surface; bind to the latest entry per id. If the pass names an
522
+ interface the log does not carry, STOP and surface it — do not invent an
523
+ endpoint, and do not reach into the service lane to add one. Where the SAME
524
+ story's service design is in context, it is the newer word on what that surface
525
+ will be; bind to it and say so in your summary.
209
526
  - THE CLIENT IS NOT AN AUTHORITY. A client-side validation or permission check
210
527
  restates a server rule for feedback; it never replaces it. Generate no
211
- server-side behaviour, no persistence and no business rule evaluation here.
212
- - Author a paired test for every screen, interaction and state the design names —
213
- including the unwanted-behaviour states from §4 and an accessibility assertion
214
- per interactive surface — and cite the spec id (UX-/US-/FR-/CN-/DC-/IC-/SC-)
215
- from the design's §7 Traceability in each test. A snapshot alone is not a test.
216
- - Do NOT add behaviour the design does not state, and do NOT omit any it does. If
217
- the design looks wrong or incomplete, STOP and surface it (it is fixed in
218
- design-ui) — never compensate from the spec or from memory.
528
+ server-side behaviour, no persistence and no business-rule evaluation here.
529
+ - ADRs are DECIDED — follow them. The pass may propose a superseding one; a
530
+ proposal is not yet ratified, so follow the pass and change nothing else.
531
+ - Author a paired test for every acceptance criterion in the story and every
532
+ behaviour the pass's "### Tests" section names — including the unwanted-state
533
+ cases from its contract binding and an accessibility assertion per interactive
534
+ surface — and cite the requirement id ({{exercises_csv}}, {{constraints_csv}})
535
+ each test exists for. A snapshot alone is not a test.
536
+ - Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
537
+ the pass looks wrong or incomplete, STOP and surface it — it is fixed by
538
+ re-running /appweaver story-plan, never compensated for here.
219
539
  - This feature OWNS its files; never regenerate a file another feature owns, and
220
540
  do not modify the app shell, global layout or design system — they belong to no
221
541
  feature. If one of them has to change, STOP and surface it.
222
- - Where generated code depends on a design Assumption (DA-n) or Workaround (WA-n),
223
- mark it with a comment citing that id so it is easy to revisit later.
542
+ {{stale_note}}
224
543
  outputs:
225
544
  - { name: files, from: "$.files" }
226
545
 
@@ -228,24 +547,99 @@ workflow:
228
547
  action: verify_context
229
548
  require_non_empty: files
230
549
  on_empty: stop
231
- on_empty_message: "No files generated. Ensure /appweaver design-ui produced a filled design.ui.md (not the empty template), then re-run."
232
-
233
- # 3b. OKF knowledge — the feature has ONE concept across both lanes, so the UI files
234
- # JOIN it rather than replace it. --merge unions them with whatever is already
235
- # recorded; without it this run would erase the service lane's source_files, and
236
- # `implement` may have run before or after this one. The concept's Overview is
237
- # NOT re-authored here: it describes the aggregate and its invariants, which is
238
- # the service design's subject, and rewriting it from the UI design would trade a
239
- # domain description for an experience one.
550
+ on_empty_message: "No files generated for story {{story}}. Check that UI design pass {{design_pass}} in {{design_doc}} actually describes changes (a pass whose '### Screens & components' section is empty has nothing to build), then re-run — or re-run /appweaver story-plan {{story}} to design the slice properly first."
551
+
552
+ # 8. OKF — MERGE, into the UI LANE'S KEY. The feature has ONE concept across both
553
+ # lanes: `--lane frontend` writes `ui_source_files`, so the service lane's
554
+ # `source_files` is untouched whichever lane implements first, and `--merge` unions
555
+ # this story's files with what the feature's earlier UI stories wrote. The
556
+ # concept's Overview is NOT re-authored here: it describes the aggregate and its
557
+ # invariants, which is the service design's subject, and rewriting it from a UI
558
+ # slice would trade a domain description for an experience one.
240
559
  - id: xref_concept
241
560
  action: execute_script
242
561
  script: { ref: xref }
243
562
  subcommand: concept-upsert
244
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--origin", "greenfield"]
563
+ # --ids + --slice record WHICH REQUIREMENT this run's files realise, as Requirement
564
+ # Anchors rows in the concept. Both halves were already here and thrown away: the ids
565
+ # the story exercises, and the files just generated. Only `adopt` writes a
566
+ # source-map.md, so without this every id on a greenfield feature came back unanchored
567
+ # at analyze-slice and the trace pass had to read the whole tree to find out what
568
+ # implements what. Reconcile-on-write, keyed by slice + lane.
569
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "frontend", "--origin", "greenfield", "--ids", "{{exercises_csv}}", "--slice", "{{story}}"]
245
570
  outputs:
246
- - { name: okf_concept, from: "$.concept" }
571
+ - { name: okf_concept, from: "$.concept" }
572
+ - { name: okf_anchors, from: "$.anchors_written" }
573
+
574
+ # ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
575
+ # READ-ONLY and REPORT-ONLY: it never edits code, never edits the pass, and never
576
+ # stops the run. A gap means one of two documents is wrong, and which one is a human
577
+ # decision — the message names both routes.
578
+ - id: ctx_accept
579
+ action: build_context
580
+ vars:
581
+ story_id: "{{story}}"
582
+ exercises_csv: "{{exercises_csv}}"
583
+ constraints_csv: "{{constraints_csv}}"
584
+ files_csv: "{{files}}"
585
+ context:
586
+ - { path: "{{story_path}}" } # the acceptance criteria
587
+ - { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
588
+ - { path: "{{design_doc}}" } # what was designed
589
+ - id: accept_check
590
+ action: agent_query
591
+ tools: Read # the generated files arrive as paths, not inlined
592
+ include_standards: false
593
+ schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
594
+ mock: { ok: "true", gaps: "" }
595
+ task: |
596
+ Story {{story_id}} was just implemented in the frontend lane. Judge ONLY this:
597
+ does the UI that now exists satisfy the requirements the story delivers, AS THE
598
+ SPEC WORDS THEM?
599
+
600
+ In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold) — the
601
+ UX- ids among them are this lane's to realise. Resolve each id's wording from
602
+ spec.md in context; that wording, not the design's restatement of it, is what the
603
+ code is measured against.
604
+
605
+ Read the files this run wrote:
606
+ {{files_csv}}
607
+ plus anything they import that you need to follow. Do not modify a single file.
608
+
609
+ Report a gap ONLY where the UI fails to deliver an in-scope id as the spec words
610
+ it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
611
+ noise that hides the real ones:
612
+ - server-side behaviour — this lane cannot implement it and must not;
613
+ - a requirement no story has claimed yet, or one another story delivers;
614
+ - style, naming, structure or test-coverage opinions — other gates own those;
615
+ - anything the design pass deliberately deferred and named under its risks.
616
+
617
+ One line per gap: the id, what the spec requires, and what the UI does instead.
618
+ Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
619
+ about an in-scope id, that IS a gap — say which document you believe is wrong and
620
+ why, because the two routes out of it are different commands.
621
+ outputs:
622
+ - { name: accept_ok, from: "$.ok" }
623
+ - { name: accept_gaps, from: "$.gaps" }
624
+ - id: accept_notice
625
+ condition: "{{accept_ok}} != true"
626
+ action: verify_context
627
+ require_value: accept_ok
628
+ equals: "true"
629
+ on_mismatch: continue
630
+ on_mismatch_message: |
631
+ ACCEPTANCE — story {{story}}'s UI does not yet satisfy the spec wording of the ids it delivers:
632
+
633
+ {{accept_gaps}}
634
+
635
+ Nothing was changed to fix this: the code follows UI design pass {{design_pass}}, so a gap
636
+ here means either the design was wrong or the spec has moved. Route by which it is —
637
+ the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
638
+ 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.
639
+ Run /appweaver build either way — a design that is right and code that does not compile
640
+ is a third thing, and build is where that one surfaces.
247
641
 
248
- # 4. Post-execution hooks.
642
+ # 9. Post-execution hooks.
249
643
  - id: resolve_post_hooks
250
644
  action: execute_script
251
645
  script: { ref: actions }
@@ -258,10 +652,12 @@ workflow:
258
652
  hooks_from_variable: after_hooks
259
653
 
260
654
  outputs:
261
- feature_dir: "{{feature_dir}}"
262
- ui_design_doc: "{{ui_design_doc}}"
263
- lane_id: "{{lane_id}}"
264
- lane_source_path: "{{lane_source_path}}"
655
+ story: "{{story}}"
656
+ built_from: "{{design_doc}} — UI design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
657
+ requirements: "{{exercises_csv}}"
265
658
  files: "{{files}}"
266
- okf_concept: "{{okf_concept}}"
267
- next: "/appweaver build (generated the {{lane_id}} lane's UI under {{lane_source_path}} and merged the files into the feature's OKF concept at {{okf_concept}}; build compiles every declared lane, so a break in either one surfaces there)"
659
+ lane: "{{lane_id}} → {{lane_source_path}}"
660
+ okf_concept: "{{okf_concept}} (this run's files MERGED into ui_source_files; the service lane's source_files is untouched)"
661
+ acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
662
+ stale_ids: "{{stale_csv}}"
663
+ next: "/appweaver build — it compiles every declared lane, so a break in either surfaces there. Then /appweaver analyze-slice. 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."