aw-spec-cli 0.1.13 → 0.1.15

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 (30) hide show
  1. package/assets/.appweaver/STACK-PROFILES.md +1 -1
  2. package/assets/.appweaver/orchestrator/orchestrator.py +5 -0
  3. package/assets/.appweaver/orchestrator/rev-templates/domain-rule-bindings.skeleton.md +10 -3
  4. package/assets/.appweaver/orchestrator/rev-templates/tech-rule-bindings.skeleton.md +38 -11
  5. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +2 -1
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +607 -123
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +625 -66
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement-ui.orch.yaml +407 -134
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +452 -143
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +78 -14
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan-ui.orch.yaml +597 -0
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +688 -0
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-feature.orch.yaml +3 -3
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +23 -23
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-story-plan.orch.yaml +945 -0
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-sync-stories.orch.yaml +1040 -0
  17. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +2069 -28
  18. package/assets/.appweaver/scripts/bash/aw-xref.sh +2 -1
  19. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +1973 -13
  20. package/assets/.appweaver/scripts/powershell/aw-xref.ps1 +2 -1
  21. package/assets/.appweaver/scripts/python/aw-xref.py +246 -20
  22. package/assets/.appweaver/templates/design-template.md +88 -54
  23. package/assets/.appweaver/templates/spec-template.md +12 -10
  24. package/assets/.appweaver/templates/story-template.md +48 -8
  25. package/assets/.appweaver/templates/ui-design-template.md +71 -39
  26. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +19 -7
  27. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +35 -26
  28. package/package.json +1 -1
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-design-ui.orch.yaml +0 -386
  30. 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.
31
- #
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.
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.)
35
35
  #
36
- # Next: /appweaver build (compiles every declared lane, this one included).
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.
37
42
  #
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,56 @@ 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
+ service_design_doc: ""
108
+ # the feature's current code, per lane (OKF)
109
+ okf_concept: ""
110
+ coverage_digest: ""
111
+ fe_source_csv: ""
112
+ fe_source_count: "0"
113
+ be_source_count: "0"
114
+ # generation + enrichment
115
+ files: ""
116
+ accept_ok: "true"
117
+ accept_gaps: ""
118
+
71
119
  workflow:
72
120
 
73
121
  # 0. FRONTEND code-generation rule files → sticky standards (configuration, not
@@ -78,45 +126,146 @@ workflow:
78
126
  module: .appweaver/orchestrator/modules/tech-select.orch.yaml
79
127
  with: { command: aw-spec-implement-ui }
80
128
 
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.
129
+ # 1. The active feature. spec.md is resolved here for the acceptance check ONLY —
130
+ # it is not a generation input.
83
131
  - id: feature_paths
84
132
  action: execute_script
85
133
  on_error: continue # never crash raw on 'no feature set' — the gate below explains it
86
134
  script: { ref: actions }
87
135
  subcommand: feature-paths
88
136
  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
-
137
+ - { name: feature_dir, from: "$.feature_dir" }
138
+ - { name: spec_file, from: "$.spec_file" }
93
139
  - id: require_feature
94
140
  action: verify_context
95
141
  require_non_empty: feature_dir
96
142
  on_empty: stop
97
143
  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
144
 
99
- - id: verify_ui_design
145
+ # 2. WHICH STORY — the same resolution story-plan and implement use.
146
+ - id: story_scan
147
+ action: execute_script
148
+ script: { ref: actions }
149
+ subcommand: story-plan-check
150
+ args: ["--feature-dir", "{{feature_dir}}", "--input", "{{USER_INPUT}}"]
151
+ outputs:
152
+ - { name: any_open, from: "$.any_open" }
153
+ - { name: open_ids, from: "$.open_ids" }
154
+ - { name: digest, from: "$.digest" }
155
+ - { name: resolved, from: "$.resolved" }
156
+ - { name: needs_pick, from: "$.needs_pick" }
157
+ - { name: pick_reason, from: "$.pick_reason" }
158
+ - { name: story, from: "$.story" }
159
+ - { name: story_path, from: "$.story_path" }
160
+ - { name: story_open, from: "$.story_open" }
161
+ - { name: status_msg, from: "$.status_msg" }
162
+ - { name: story_title, from: "$.story_title" }
163
+ - { name: exercises_csv, from: "$.exercises_csv" }
164
+ - { name: constraints_csv, from: "$.constraints_csv" }
165
+ - { name: current_version, from: "$.current_version" }
166
+ - { name: stale_csv, from: "$.stale_csv" }
167
+ - { name: stale_note, from: "$.stale_note" }
168
+ - { name: story_dir, from: "$.story_dir" }
169
+ - { name: adr_path, from: "$.adr_path" }
170
+ - { name: if_path, from: "$.if_path" }
171
+ - { name: design_doc, from: "$.fe_doc_path" }
172
+ - { name: design_exists, from: "$.fe_doc_exists" }
173
+ - { name: design_pass, from: "$.fe_last_pass" }
174
+ - { name: design_spec_version, from: "$.fe_doc_spec_version" }
175
+ - { name: design_stale, from: "$.fe_doc_stale" }
176
+ - { name: service_design_doc, from: "$.be_doc_path" }
177
+ - id: require_open_backlog
178
+ action: verify_context
179
+ require_true: any_open
180
+ on_false: stop
181
+ 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)."
182
+ - id: ask_story
183
+ condition: "{{needs_pick}} == true"
184
+ action: ask
185
+ prompt: |
186
+ Which story is this UI implementation for? ({{pick_reason}})
187
+
188
+ {{digest}}
189
+ options_from: "{{open_ids}}"
190
+ answer_key: picked_story
191
+ answer_var: picked_story
192
+ - id: story_pick_check
193
+ condition: "{{needs_pick}} == true"
194
+ action: execute_script
195
+ script: { ref: actions }
196
+ subcommand: story-plan-check
197
+ args: ["--feature-dir", "{{feature_dir}}", "--story", "{{picked_story}}"]
198
+ outputs:
199
+ - { name: resolved, from: "$.resolved" }
200
+ - { name: story, from: "$.story" }
201
+ - { name: story_path, from: "$.story_path" }
202
+ - { name: story_open, from: "$.story_open" }
203
+ - { name: status_msg, from: "$.status_msg" }
204
+ - { name: story_title, from: "$.story_title" }
205
+ - { name: exercises_csv, from: "$.exercises_csv" }
206
+ - { name: constraints_csv, from: "$.constraints_csv" }
207
+ - { name: stale_csv, from: "$.stale_csv" }
208
+ - { name: stale_note, from: "$.stale_note" }
209
+ - { name: story_dir, from: "$.story_dir" }
210
+ - { name: design_doc, from: "$.fe_doc_path" }
211
+ - { name: design_exists, from: "$.fe_doc_exists" }
212
+ - { name: design_pass, from: "$.fe_last_pass" }
213
+ - { name: design_spec_version, from: "$.fe_doc_spec_version" }
214
+ - { name: design_stale, from: "$.fe_doc_stale" }
215
+ - { name: service_design_doc, from: "$.be_doc_path" }
216
+ - id: require_story
217
+ action: verify_context
218
+ require_true: resolved
219
+ on_false: stop
220
+ on_false_message: |
221
+ Could not resolve a story from '{{picked_story}}'. Open stories:
222
+ {{digest}}
223
+ Re-run implement-ui naming one of them.
224
+ - id: require_open_story
225
+ action: verify_context
226
+ require_true: story_open
227
+ on_false: stop
228
+ on_false_message: "{{status_msg}}"
229
+
230
+ # 3. THE STORY MUST HAVE A UI DESIGN. A story with a service design and no UI one is
231
+ # not a failure — it is a slice with no user-facing surface, and the message says
232
+ # so rather than implying something is broken.
233
+ - id: require_design
100
234
  action: verify_context
101
- require_true: ui_design_exists
235
+ require_true: design_exists
102
236
  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."
237
+ on_false_message: "Story {{story}} has no UI design — {{story_dir}}/story-frontend-plan.md does not exist. 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}}."
238
+
239
+ # 3a. Announced, never refused — the same contract implement keeps. design_stale is a
240
+ # JSON boolean, so `condition:` does the deciding and the value gate is only the
241
+ # vehicle that prints (law 43); stale_csv is a string and compares directly.
242
+ - id: design_stale_notice
243
+ condition: "{{design_stale}} == true"
244
+ action: verify_context
245
+ require_value: design_stale
246
+ equals: "false"
247
+ on_mismatch: continue
248
+ 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."
249
+ - id: story_stale_notice
250
+ action: verify_context
251
+ require_value: stale_csv
252
+ equals: ""
253
+ on_mismatch: continue
254
+ 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."
104
255
 
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.
256
+ # 4. THE FRONTEND LANE and its source path — the only subtree this phase may write
257
+ # into, resolved before the project gate that searches it.
108
258
  - id: lane_gate
109
259
  action: include
110
260
  module: .appweaver/orchestrator/modules/lane-gate.orch.yaml
111
261
  with: { require: frontend, command: "/appweaver implement-ui" }
112
262
 
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.
263
+ # 5. Require a frontend PROJECT under the LANE'S OWN path. detect-project searches the
264
+ # lane subtree first and falls back to the whole repository, reporting which scope
265
+ # answered. THAT DISTINCTION IS THE GATE HERE. A repo-scope hit in a two-lane
266
+ # project is the service lane's manifest, and accepting it is exactly the failure
267
+ # the lane model exists to prevent — a frontend that was never scaffolded,
268
+ # generated into anyway, reporting green.
120
269
  - id: detect_ui_project
121
270
  action: execute_script
122
271
  script: { ref: actions }
@@ -126,15 +275,31 @@ workflow:
126
275
  - { name: ui_project_present, from: "$.scaffolded" }
127
276
  - { name: project_markers, from: "$.markers" }
128
277
  - { name: project_scope, from: "$.scope" }
129
-
130
278
  - id: ui_project_gate
131
279
  action: verify_context
132
280
  require_value: project_scope
133
281
  equals: "lane"
134
282
  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."
283
+ 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
284
 
137
- # 2. Pre-execution hooks.
285
+ # 6. WHAT ALREADY EXISTS, per lane, from the feature's OKF concept. The UI list is
286
+ # what this run edits; the service count tells the pass whether the contract it
287
+ # binds to has been built yet. Advisory — a first UI story generates into a clean
288
+ # tree and a missing bundle must never block generation.
289
+ - id: coverage
290
+ action: execute_script
291
+ on_error: continue
292
+ script: { ref: xref }
293
+ subcommand: concept-coverage
294
+ args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
295
+ outputs:
296
+ - { name: okf_concept, from: "$.concept" }
297
+ - { name: coverage_digest, from: "$.digest" }
298
+ - { name: fe_source_csv, from: "$.frontend_csv" }
299
+ - { name: fe_source_count, from: "$.frontend_count" }
300
+ - { name: be_source_count, from: "$.backend_count" }
301
+
302
+ # 7. Pre-execution hooks.
138
303
  - id: resolve_pre_hooks
139
304
  action: execute_script
140
305
  script: { ref: actions }
@@ -146,81 +311,119 @@ workflow:
146
311
  action: dispatch_hooks
147
312
  hooks_from_variable: before_hooks
148
313
 
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.
314
+ # ═══════════════════ GENERATION — one story's UI slice, one pass ═════════════
315
+ # The pass constrains WHAT to build; constitution.ui.md, the lane and the loaded
316
+ # standards fix HOW. spec.md is deliberately absent — the acceptance check below is
317
+ # where it gets its say.
152
318
  - id: ctx_generate
153
319
  action: build_context
320
+ vars:
321
+ story_id: "{{story}}"
322
+ story_title: "{{story_title}}"
323
+ exercises_csv: "{{exercises_csv}}"
324
+ constraints_csv: "{{constraints_csv}}"
325
+ pass_number: "{{design_pass}}"
326
+ coverage_digest: "{{coverage_digest}}"
327
+ ui_source_files_csv: "{{fe_source_csv}}"
328
+ ui_source_file_count: "{{fe_source_count}}"
329
+ stale_note: "{{stale_note}}"
154
330
  context:
155
- - { path: "{{ui_design_doc}}" }
331
+ - { path: "{{design_doc}}" } # the story's UI design — the LATEST pass is the input
332
+ - { path: "{{story_path}}" } # the story's acceptance criteria — what "done" means
333
+ - { path: "{{if_path}}", optional: true } # the PUBLISHED surface this slice binds to, by IF- id
334
+ - { path: "{{service_design_doc}}", optional: true } # the SAME story's service design — the newer word on that surface
335
+ - { path: "{{adr_path}}", optional: true } # ratified decisions — binding unless a pass supersedes them
156
336
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — conventions, forbidden patterns, gates
157
337
  - id: generate
158
338
  action: agent_generate
159
- tools: "Read,Write,Edit"
339
+ tools: "Read,Write,Edit" # the existing UI code arrives as {{ui_source_files_csv}} paths, not inlined
160
340
  permission_mode: acceptEdits
161
341
  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}}
342
+ Implement story {{story_id}} — {{story_title}} — in the frontend lane, from
343
+ "## Design pass {{pass_number}}" of the UI story design in context.
344
+ Guidance: {{USER_INPUT}}
345
+
346
+ THAT PASS IS THE ONLY SOURCE OF WHAT TO BUILD. It is the LAST pass in the
347
+ document; every earlier pass is superseded history, and any of them may carry
348
+ hand edits that were deliberate. Read the last one and build exactly it. Do NOT
349
+ consult or infer from spec.md — it is not in context on purpose.
350
+
351
+ THIS IS ONE SLICE OF AN EXISTING FRONTEND, NOT THE WHOLE FEATURE.
352
+ - The pass's "### Screens & components" section names, file by file, what to
353
+ create and what to modify. Create ONLY the files it names as new; modify the
354
+ named existing files IN PLACE. Every other file belongs to work already done —
355
+ do not regenerate, reformat or "tidy" it.
356
+ - The story delivers {{exercises_csv}} and must satisfy {{constraints_csv}}, of
357
+ which the UX- constraints are yours to realise. Anything outside those ids is
358
+ another story's work, even if you can see it is missing.
359
+ - The code that already exists for this feature, per lane, is below. The UI files
360
+ ({{ui_source_file_count}} on disk) are yours to read before you edit; the
361
+ service list is there so you can see what the contract is actually backed by.
362
+
363
+ {{coverage_digest}}
364
+
365
+ {{ui_source_files_csv}}
366
+
367
+ BIND THE STACK: this lane is `{{lane_id}}` — {{lane_title}}, language
368
+ {{lane_language}} ({{lane_ecosystem}}), declared in .appweaver/stacks.json and
369
+ built with `{{lane_build}}`, tested with `{{lane_test}}`. Generate in THAT
370
+ framework and no other, using its idioms, its project layout and its test
371
+ framework. Take the router, state approach, component library, design system and
372
+ styling from the UI constitution + the loaded standards; the design is
373
+ technology-agnostic, so do not re-decide anything already fixed there. If the
374
+ loaded standards describe a DIFFERENT stack than the one above, the lane wins on
375
+ framework and layout — follow the standards' INTENT and STOP to surface the
376
+ mismatch in your summary.
377
+
378
+ WRITE ONLY UNDER `{{lane_source_path}}` — the source path the `{{lane_id}}` lane
379
+ declares. NEVER touch the service lane's code, not even to add a field the UI
380
+ wants: a gap in the service surface is a contract delta raised back to that lane,
381
+ not a file you edit. Do not create a parallel source tree because the layout
382
+ inside this one is unfamiliar; if the pass needs a place this subtree has no home
383
+ for, STOP and surface it.
384
+
166
385
  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.
386
+ - THE PASS IS THE ONLY DOMAIN INPUT. You have it, the story, and the lane's
387
+ frontend standards — and NO domain rule files. Introduce NO domain rule,
388
+ identifier, terminology or behaviour the pass does not state. If a domain
389
+ specific seems missing, STOP and surface it; never invent it from memory.
189
390
  - Follow the UI constitution's folder structure, naming conventions and forbidden
190
391
  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.
392
+ WITHIN that subtree — the pass says WHAT to build; these say HOW.
393
+ - REALISE THE PASS EXACTLY, changing nothing about WHAT it specifies:
394
+ • its "### Approach" — the routes and screens it touches, and where this
395
+ slice's state lives. Anything the design says must NOT be held never reaches
396
+ client state, storage, the URL or telemetry.
397
+ • its "### Screens & components" — each component with the responsibility, the
398
+ input shapes and the events the pass gives it. Compose the shared components
399
+ it says it reuses; do not re-implement them.
400
+ • its "### Contract binding" — call each interface BY ITS IF- id as the pass
401
+ names it, with the request it states, and render EVERY state it lists:
402
+ in-flight, empty, error and the failure behaviour. These are requirements,
403
+ not polish, and they are the first thing a generator drops.
404
+ - THE CONTRACT IS NOT YOURS TO INVENT. interfaces.md (in context) is the
405
+ published surface; bind to the latest entry per id. If the pass names an
406
+ interface the log does not carry, STOP and surface it — do not invent an
407
+ endpoint, and do not reach into the service lane to add one. Where the SAME
408
+ story's service design is in context, it is the newer word on what that surface
409
+ will be; bind to it and say so in your summary.
209
410
  - THE CLIENT IS NOT AN AUTHORITY. A client-side validation or permission check
210
411
  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.
412
+ server-side behaviour, no persistence and no business-rule evaluation here.
413
+ - ADRs are DECIDED — follow them. The pass may propose a superseding one; a
414
+ proposal is not yet ratified, so follow the pass and change nothing else.
415
+ - Author a paired test for every acceptance criterion in the story and every
416
+ behaviour the pass's "### Tests" section names — including the unwanted-state
417
+ cases from its contract binding and an accessibility assertion per interactive
418
+ surface — and cite the requirement id ({{exercises_csv}}, {{constraints_csv}})
419
+ each test exists for. A snapshot alone is not a test.
420
+ - Do NOT add behaviour the pass does not state, and do NOT omit any it does. If
421
+ the pass looks wrong or incomplete, STOP and surface it — it is fixed by
422
+ re-running /appweaver story-plan, never compensated for here.
219
423
  - This feature OWNS its files; never regenerate a file another feature owns, and
220
424
  do not modify the app shell, global layout or design system — they belong to no
221
425
  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.
426
+ {{stale_note}}
224
427
  outputs:
225
428
  - { name: files, from: "$.files" }
226
429
 
@@ -228,24 +431,92 @@ workflow:
228
431
  action: verify_context
229
432
  require_non_empty: files
230
433
  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.
434
+ 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."
435
+
436
+ # 8. OKF — MERGE, into the UI LANE'S KEY. The feature has ONE concept across both
437
+ # lanes: `--lane frontend` writes `ui_source_files`, so the service lane's
438
+ # `source_files` is untouched whichever lane implements first, and `--merge` unions
439
+ # this story's files with what the feature's earlier UI stories wrote. The
440
+ # concept's Overview is NOT re-authored here: it describes the aggregate and its
441
+ # invariants, which is the service design's subject, and rewriting it from a UI
442
+ # slice would trade a domain description for an experience one.
240
443
  - id: xref_concept
241
444
  action: execute_script
242
445
  script: { ref: xref }
243
446
  subcommand: concept-upsert
244
- args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--origin", "greenfield"]
447
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{files}}", "--merge", "--lane", "frontend", "--origin", "greenfield"]
245
448
  outputs:
246
449
  - { name: okf_concept, from: "$.concept" }
247
450
 
248
- # 4. Post-execution hooks.
451
+ # ═══════════════ ACCEPTANCE — the ONE place the spec is read ═════════════════
452
+ # READ-ONLY and REPORT-ONLY: it never edits code, never edits the pass, and never
453
+ # stops the run. A gap means one of two documents is wrong, and which one is a human
454
+ # decision — the message names both routes.
455
+ - id: ctx_accept
456
+ action: build_context
457
+ vars:
458
+ story_id: "{{story}}"
459
+ exercises_csv: "{{exercises_csv}}"
460
+ constraints_csv: "{{constraints_csv}}"
461
+ files_csv: "{{files}}"
462
+ context:
463
+ - { path: "{{story_path}}" } # the acceptance criteria
464
+ - { path: "{{spec_file}}" } # the contract — the ONLY step in this workflow that reads it
465
+ - { path: "{{design_doc}}" } # what was designed
466
+ - id: accept_check
467
+ action: agent_query
468
+ tools: Read # the generated files arrive as paths, not inlined
469
+ include_standards: false
470
+ schema: { ok: "<true|false>", gaps: "<one line per gap, or empty>" }
471
+ mock: { ok: "true", gaps: "" }
472
+ task: |
473
+ Story {{story_id}} was just implemented in the frontend lane. Judge ONLY this:
474
+ does the UI that now exists satisfy the requirements the story delivers, AS THE
475
+ SPEC WORDS THEM?
476
+
477
+ In scope: {{exercises_csv}} (delivered) and {{constraints_csv}} (must hold) — the
478
+ UX- ids among them are this lane's to realise. Resolve each id's wording from
479
+ spec.md in context; that wording, not the design's restatement of it, is what the
480
+ code is measured against.
481
+
482
+ Read the files this run wrote:
483
+ {{files_csv}}
484
+ plus anything they import that you need to follow. Do not modify a single file.
485
+
486
+ Report a gap ONLY where the UI fails to deliver an in-scope id as the spec words
487
+ it, or breaks an in-scope constraint. These are NOT gaps, and reporting them is
488
+ noise that hides the real ones:
489
+ - server-side behaviour — this lane cannot implement it and must not;
490
+ - a requirement no story has claimed yet, or one another story delivers;
491
+ - style, naming, structure or test-coverage opinions — other gates own those;
492
+ - anything the design pass deliberately deferred and named under its risks.
493
+
494
+ One line per gap: the id, what the spec requires, and what the UI does instead.
495
+ Set ok=false only when you found at least one. If the SPEC and the DESIGN disagree
496
+ about an in-scope id, that IS a gap — say which document you believe is wrong and
497
+ why, because the two routes out of it are different commands.
498
+ outputs:
499
+ - { name: accept_ok, from: "$.ok" }
500
+ - { name: accept_gaps, from: "$.gaps" }
501
+ - id: accept_notice
502
+ condition: "{{accept_ok}} != true"
503
+ action: verify_context
504
+ require_value: accept_ok
505
+ equals: "true"
506
+ on_mismatch: continue
507
+ on_mismatch_message: |
508
+ ACCEPTANCE — story {{story}}'s UI does not yet satisfy the spec wording of the ids it delivers:
509
+
510
+ {{accept_gaps}}
511
+
512
+ Nothing was changed to fix this: the code follows UI design pass {{design_pass}}, so a gap
513
+ here means either the design was wrong or the spec has moved. Route by which it is —
514
+ the design is wrong: /appweaver story-plan {{story}} (a new pass supersedes this one);
515
+ the spec is wrong: /appweaver sync-spec, then sync-stories, then story-plan.
516
+ Run /appweaver build either way — a design that is right and code that does not compile
517
+ is a third thing, and build is where that one surfaces.
518
+
519
+ # 9. Post-execution hooks.
249
520
  - id: resolve_post_hooks
250
521
  action: execute_script
251
522
  script: { ref: actions }
@@ -258,10 +529,12 @@ workflow:
258
529
  hooks_from_variable: after_hooks
259
530
 
260
531
  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}}"
532
+ story: "{{story}}"
533
+ built_from: "{{design_doc}} — UI design pass {{design_pass}} (designed against spec {{design_spec_version}}; spec now {{current_version}})"
534
+ requirements: "{{exercises_csv}}"
265
535
  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)"
536
+ lane: "{{lane_id}} → {{lane_source_path}}"
537
+ okf_concept: "{{okf_concept}} (this run's files MERGED into ui_source_files; the service lane's source_files is untouched)"
538
+ acceptance: "ok={{accept_ok}} · gaps: {{accept_gaps}}"
539
+ stale_ids: "{{stale_csv}}"
540
+ next: "/appweaver build — it compiles every declared lane, so a break in either surfaces there. Then /appweaver analyze-slice. The story stays OPEN until /appweaver close-story ratifies its ADRs, flips its interface entries live and archives the passes."